I've been looking for a MongoDb-like ( http://docs.mongodb.org/manual/applications/read/#find, docs.mongodb.org/manual/reference/operators/ ) query expression object evaluation function implementation or a class. It may cover not all the advanced features, and should have extensible architecture.
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.
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.
If you are not familiar with MongoDb, take a look at a given expression object and array to search in.
I wrote it as JSON string for simplicity. The object contents makes no sense, just showng the MongoDb query syntax.
MongoDb-like query expression object
{
"name": "Mongo",
"type": "db",
"arch": {
"$in": [
"x86",
"x64"
]
},
"version": {
"$gte": 22
},
"released": {
"$or": {
"$lt": 2013,
"$gt": 2012
}
}
}
The array to search in
[
{
"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
}
}
]
Find using Mongo-like query expressions
So, with the help of the function, we should be able to issue the following query to the target array.
$found=findLikeMongo($array, $queryExpr); //resulting in a $array[0] value;
//@return found array
Get array path using Mongo-like query expressions
$arrayPath=getPathFromMongo($array, $queryExpr);// resulting in array("0")
//@return array path, represented as an array where entries are consecutive keys.
Homework
I found that goessner.net/articles/JsonPath/ could possibly cover my needs(not being an exact match because it uses Xpath-like expressions), the caveat is, that it heavily relies on regular expressions and string parsing, what will definitely slow it down compared to array only(JSON like) implementation.
Also I've found a similar question here, @stackoverflow Evaluating MongoDB-like JSON Queries in PHP. The resulting answer was to use some SPL functions, which I am used to avoid most of the time.
Wonder if the author had came up with function, he had been trying to develop.The possible arrayPath implementation was found on thereisamoduleforthat.com/content/dealing-deep-arrays-php, thus the lack of this implementation, is that it relies on pointers.
I know its not a trivial question with a oneliner answer, that's why I'm asking it before starting the actual development of my own class.
I appreciate architecture tips, related or similar code, which may be a good practice example for building php "if..else" expressions on the fly.emphasized text
How to write a non-SPL version?
@Baba provided an excellent class, which is written with the use of SPL. I wonder how to rewrite this code without SPL.
There are two reasons for this
- calling the class multiple times will give function overhead, that can be avoided rewriting it in raw PHP.
- it would be easily portable to raw Javascript where SPL is not available, leading to easier code maintenance on both platforms.
Results
The created ArrayQuery class is published on Github, consider checking-out the repository for updates.
SPL, raw PHP version and Chequer2 FORP profiler output
In brief-
- the raw PHP version performs 10x faster than the SPL one, consuming 20% less memory.
- Chequer2 class performs 40% slower than PHP SPL class, and almost 20x slower than raw PHP version.
- MongoDb is the fastest(10x faster than raw PHP implementation and consumes 5x less memory), do not use these classes unless you are sure you want to avoid interaction with MongoDb.
MongoDb version
SPL version
Raw PHP(latest ArrayQuery class) version
Chequer2 version
MongoDb reference test profiling code
$m = new MongoClient(); // connect
$db = $m->testmongo; // select a database
$collection = $db->data;
$loops=100;
for ($i=0; $i<$loops; $i++) {
$d = $collection->find(array("release.year" => 2013));
}
print_r( iterator_to_array($d) );
PHP with SPL class profiling code
include('data.php');
include('phpmongo-spl.php');
$s = new ArrayCollection($array, array("release.year" => 2013),false);
$loops=100;
for ($i=0; $i<$loops; $i++) {
$d = $s->parse();
}
print_r( $d );
The SPL class parse() function has been slightly modified to return the value after execution, it could be also be modified to accept expression, but it's not essential for profiling purposes as the expression is being reevaluated every time.
raw PHP(latest ArrayQuery class) profiling code
include('data.php');
include('phpmongo-raw.php');
$s = new ArrayStandard($array);
$loops=100;
for ($i=0; $i<$loops; $i++) {
$d = $s->find(array("release.year" => 2013));
}
print_r( $d );
chequer2 PHP profiling code
<?php
include('data.php');
include('../chequer2/Chequer.php');
$query=array("release.year" => 2013);
$loops=100;
for ($i=0; $i<$loops; $i++) {
$result=Chequer::shorthand('(.release.year > 2012) ? (.) : NULL')
->walk($array);
}
print_r($result);
?>
data used(same as @baba provided in his answer)
$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);
the forp-ui slightly modified sample ui loader(to be called with ?profile=FILE_TO_PROFILE)
<!doctype html>
<html>
<head>
<style>
body {margin : 0px}
</style>
</head>
<body>
<div class="forp"></div>
<?php
register_shutdown_function(
function() {
// next code can be append to PHP scripts in dev mode
?>
<script src="../forp-ui/js/forp.min.js"></script>
<script>
(function(f) {
f.find(".forp")
.each(
function(el) {
el.css('margin:50px;height:300px;border:1px solid #333');
}
)
.forp({
stack : <?php echo json_encode(forp_dump()); ?>,
//mode : "fixed"
})
})(forp);
</script>
<?php
}
);
// start forp
forp_start();
// our PHP script to profile
include($_GET['profile']);
// stop forp
forp_end();
?>
</body>
</html>
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 arrayExample 1
check if
key
-Different
would be as simple asOutput
Example 2 Check if
release year
is2013
Output
Example 3
Count where
Year
is2012
Example 4
Lets take from your example where you want to check
version
isgrater than 22
Output
Example 5
Check if
release.arch
value isIN
a set such as[x86,x100]
(Example)Output
Example 6
Using Callable
Output
Example 7
Register your own expression name
Output
Class Used
Summary
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
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 troublesI must also mention that use the best tool for the best job ... What you want is basically a function of a Database
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 .
Do you really mean you want all those just in here ???
Latest Update
=======================================================================
Answer:
What you want is very easy, All you need is
2 corrections
in the current code input and output loop and you would get your new format.What do i mean ?
A. Changed
To
B. Changed
To:
New Array for resting
Simple Test
Output
If you also want to retain original
array key position
you can haveJust for Fun Part
A. Support for
regex
Just for fun i added support for
$regex
with alias$preg
or$match
which means you can haveOr
Output
B. Use Simple array like
queries
$s->convert($queryArray)
has convertedTo
C. Modulus
$mod
D. Count elements with
$size
E. Check if it matches all element in array
$all
Output
F. If you are not sure of the element key name then you ca use
$has
its like theopposite
of$in
=======================================================================
Old Update
=======================================================================
Since you don't want
SPL
and functions .. it took a while but i was able to come up with alternative class that is also flexible and easy to useTo avoid loading the array multiple times you declare it once :
A. Find where
release.year
is2013
Output
B. For the first time you can run complex
$and
or$or
statement like find whererelease.arch
=x86
andrelease.year
=2012
Output
C. Imagine a much more complex query
Output
The new Modified class