Introduction
I think Evaluating MongoDB-like JSON Queries in PHP has given all the Information you need. all you need is to be creative with the solution and you achieve what you want
The Array
Lets assume we have the follow json
converted to array
$json = '[{
"name":"Mongo",
"type":"db",
"release":{
"arch":"x86",
"version":22,
"year":2012
}
},
{
"name":"Mongo",
"type":"db",
"release":{
"arch":"x64",
"version":21,
"year":2012
}
},
{
"name":"Mongo",
"type":"db",
"release":{
"arch":"x86",
"version":23,
"year":2013
}
},
{
"key":"Diffrent",
"value":"cool",
"children":{
"tech":"json",
"lang":"php",
"year":2013
}
}
]';
$array = json_decode($json, true);
Example 1
check if key
- Different
would be as simple as
echo new ArrayCollection($array, array("key" => "Diffrent"));
Output
{"3":{"key":"Diffrent","value":"cool","children":{"tech":"json","lang":"php","year":2013}}}
Example 2
Check if release year
is 2013
echo new ArrayCollection($array, array("release.year" => 2013));
Output
{"2":{"name":"Mongo","type":"db","release":{"arch":"x86","version":23,"year":2013}}}
Example 3
Count where Year
is 2012
$c = new ArrayCollection($array, array("release.year" => 2012));
echo count($c); // output 2
Example 4
Lets take from your example where you want to check version
is grater than 22
$c = new ArrayCollection($array, array("release.version" => array('$gt'=>22)));
echo $c;
Output
{"2":{"name":"Mongo","type":"db","release":{"arch":"x86","version":23,"year":2013}}}
Example 5
Check if release.arch
value is IN
a set such as [x86,x100]
(Example)
$c = new ArrayCollection($array, array("release.arch" => array('$in'=>array("x86","x100"))));
foreach($c as $var)
{
print_r($var);
}
Output
Array
(
[name] => Mongo
[type] => db
[release] => Array
(
[arch] => x86
[version] => 22
[year] => 2012
)
)
Array
(
[name] => Mongo
[type] => db
[release] => Array
(
[arch] => x86
[version] => 23
[year] => 2013
)
)
Example 6
Using Callable
$year = 2013;
$expression = array("release.year" => array('$func' => function ($value) use($year) {
return $value === 2013;
}));
$c = new ArrayCollection($array, $expression);
foreach ( $c as $var ) {
print_r($var);
}
Output
Array
(
[name] => Mongo
[type] => db
[release] => Array
(
[arch] => x86
[version] => 23
[year] => 2013
)
)
Example 7
Register your own expression name
$c = new ArrayCollection($array, array("release.year" => array('$baba' => 3)), false);
$c->register('$baba', function ($a, $b) {
return substr($a, - 1) == $b;
});
$c->parse();
echo $c;
Output
{"2":{"name":"Mongo","type":"db","release":{"arch":"x86","version":23,"year":2013}}}
Class Used
class ArrayCollection implements IteratorAggregate, Countable, JsonSerializable {
private $array;
private $found = array();
private $log;
private $expression;
private $register;
function __construct(array $array, array $expression, $parse = true) {
$this->array = $array;
$this->expression = $expression;
$this->registerDefault();
$parse === true and $this->parse();
}
public function __toString() {
return $this->jsonSerialize();
}
public function jsonSerialize() {
return json_encode($this->found);
}
public function getIterator() {
return new ArrayIterator($this->found);
}
public function count() {
return count($this->found);
}
public function getLog() {
return $this->log;
}
public function register($offset, $value) {
if (strpos($offset, '$') !== 0)
throw new InvalidArgumentException('Expresiion name must always start with "$" sign');
if (isset($this->register[$offset]))
throw new InvalidArgumentException(sprintf('Expression %s already registred .. Please unregister It first'));
if (! is_callable($value)) {
throw new InvalidArgumentException(sprintf('Only callable value can be registred'));
}
$this->register[$offset] = $value;
}
public function unRegister($offset) {
unset($this->register[$offset]);
}
public function parse() {
$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($this->array));
foreach ( $it as $k => $items ) {
if ($this->evaluate($this->getPath($it), $items)) {
$this->found[$it->getSubIterator(0)->key()] = $this->array[$it->getSubIterator(0)->key()];
}
}
}
private function registerDefault() {
$this->register['$eq'] = array($this,"evaluateEqal");
$this->register['$not'] = array($this,"evaluateNotEqual");
$this->register['$gte'] = array($this,"evaluateGreater");
$this->register['$gt'] = array($this,"evaluateGreater");
$this->register['$lte'] = array($this,"evaluateLess");
$this->register['$lt'] = array($this,"evaluateLess");
$this->register['$in'] = array($this,"evalueateInset");
$this->register['$func'] = array($this,"evalueateFunction");
$this->register['$fn'] = array($this,"evalueateFunction");
$this->register['$f'] = array($this,"evalueateFunction");
}
private function log($log) {
$this->log[] = $log;
}
private function getPath(RecursiveIteratorIterator $it) {
$keyPath = array();
foreach ( range(1, $it->getDepth()) as $depth ) {
$keyPath[] = $it->getSubIterator($depth)->key();
}
return implode(".", $keyPath);
}
private function checkType($a, $b) {
if (gettype($a) != gettype($b)) {
$this->log(sprintf("%s - %s is not same type of %s - %s", json_encode($a), gettype($a), json_encode($b), gettype($b)));
return false;
}
return true;
}
private function evaluate($key, $value) {
$o = $r = 0; // Obigation & Requirement
foreach ( $this->expression as $k => $options ) {
if ($k !== $key)
continue;
if (is_array($options)) {
foreach ( $options as $eK => $eValue ) {
if (strpos($eK, '$') === 0) {
$r ++;
$callable = $this->register[$eK];
$callable($value, $eValue) and $o ++;
} else {
throw new InvalidArgumentException('Missing "$" in expession key');
}
}
} else {
$r ++;
$this->evaluateEqal($value, $options) and $o ++;
}
}
return $r > 0 && $o === $r;
}
private function evaluateEqal($a, $b) {
return $a == $b;
}
private function evaluateNotEqual($a, $b) {
return $a != $b;
}
private function evaluateLess($a, $b) {
return $this->checkType($a, $b) and $a < $b;
}
private function evaluateGreater($a, $b) {
return $this->checkType($a, $b) and $a > $b;
}
private function evalueateInset($a, array $b) {
return in_array($a, $b);
}
private function evalueateFunction($a, callable $b) {
return $b($a);
}
}
Summary
It may cover not all the advanced features, and should have extensible architecture
The above class shows a typical example of what you want .. you can easy decouple
it , extend it to support compound expressions like $and
and $or
MongoDB-like query expression objects are easy for understanding and usage, providing ability to write clean, self-explaining code, because both query and objects to search in, are associative arrays.
Why not just write the array to a MongoDB
database rather than working it arrays ?? It more efficient and it would save you a lot of troubles
I must also mention that use the best tool for the best job ... What you want is basically a function of a Database
Basically talking its a convenient function to extract information from php arrays. Knowing the array structure(the arrayPath), it will allow to perform operations on multidimensional arrays data, without the need for multiple nested loops.
The example shows how using a path to search for value but you are still dependent on loading the array to memory and your class performing multiple recursion ans loops which is not as efficient as a database .
I appreciate architecture tips, related or similar code, which may be a good practice example for building php "if..else" expressions on the fly.
Do you really mean you want all those just in here ???