实现类似goMongoDB的Query表达式对象评估 [英] Implementing goMongoDB-like Query expression object evaluation

查看:134
本文介绍了实现类似goMongoDB的Query表达式对象评估的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在寻找 MongoDb-like (略微修改了样本ui加载器(用?profile = FILE_TO_PROFILE调用)

 <!doctype html> 
< html>
< head>
< style>
body {margin:0px}
< / style>
< / head>
< body>
< div class =forp>< / div>
<?php
register_shutdown_function(
function(){
//下一代码可以在开发模式下附加到PHP脚本
?>
< 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:固定
})
})(forp);
< / script>
<?php
}
);

// start forp
forp_start();

//我们的PHP脚本分析
include($ _ GET ['profile']);

//停止forp
forp_end();
?>
< / body>
< / html>


解决方案

最新更新




@baba给出了一个很好的原始PHP版本的类,实现类似MongoDB的查询表达式对象评估,但输出结构略有不同,我的意思是嵌套中的点符号数组输出([release.arch] => x86),而不是常规数组([release] => Array([arch] => x86))。我将非常感谢您的提示如何使该类与mongoDB完全兼容,因为它似乎严格依赖于原始的PHP类实现。


============================================== =========================



答案:



您想要的只是非常简单,您需要的是当前代码输入和输出循环中的 2更正会得到你的新格式。



我的意思是什么?



A。已更改

  foreach($ array as $ part){
$ this-> ; flatten [] = $ this-> convert($ part);
}

  foreach($ array as $ k => $ part){
$ this-> flatten [$ k] = $ this-> convert($ part);
}

B。已更改

  foreach($ this-> flatten as $ data){
$ this-> check($ find,$ data,$ type) )和$ f [] = $ data;
}

收件人:

  foreach($ this-> flatten as $ k => $ data){
$ this-> check($ find,$ data,$ type)和$ f [] = $ this-> array [$ k];
}

休息的新阵列

  $ json ='[
{
name:Mongo,
release:{
arch:x86,
版本:22,
年:2012
},
类型:db
},
{
name:Mongo,
release:{
arch:x64,
version: 21,
年:2012
},
类型:db
},
{
名称:Mongo ,
发布:{
arch:x86,
版本:23,
年:2013
},
type:db
},
{
name:MongoBuster,
release:{
arch:[
x86,
x64
],
版本:23,
年:2013
},
输入:db
},
{
children:{
dance:[
one,
two ,
{
三:{
a:apple,
b:700000,
c:8.8
}
}
],
lang:php,
tech:json,
年:2013
},
关键: 不同,
价值:酷
}
]';

$ array = json_decode($ json,true);

简单测试

  $ s = new ArrayStandard($ array); 
print_r($ s-> find(array(release.arch=>x86)));

输出

 数组

[0] =>数组

[名称] => Mongo
[类型] => db
[release] =>数组

[arch] => x86
[版本] => 22
[年] => 2012




[1] =>数组

[name] => Mongo
[type] = > db
[release] =>数组

[arch] => x86
[版本] => 23
[年] => ; 2013





如果您还想保留原始的数组键位置,您可以

  foreach($ this-> flatten as $ k => $ data){
$ this-> check($ find,$ data,$ type)和$ f [$ k] = $ this-> array [$ k];
}

仅限娱乐部分



A。支持正则表达式



为了好玩,我添加了对 $ regex 的支持,别名为 $ preg $ match 这意味着你可以拥有

  print_r($ s-> find(array(release.arch) => array('$ regex'=>/ 4 $ /))));; 

  print_r($ s-> find(array(release.arch=> array('$ regex'=>/ 4 $ /)))); 

输出

 数组

[1] =>数组

[name] => Mongo
[type] => db
[release] =>数组

[arch] => x64
[版本] => 21
[年] => 2012





B。使用简单数组,如查询

  $ queryArray = array(
release=>数组(
arch=>x86

);
$ d = $ s-> find($ s-> convert($ queryArray));

$ s-> convert($ queryArray)已转换

 数组

[发布] =>数组

[arch] => x86



 数组

[release.arch] => x86

C。模数 $ mod

  print_r($ s-> find(array(
)release.version =>数组(
'$ mod'=>数组(
23 => 0


)));;

//检查release.version%23 == 0;

D。使用 $ size <计算元素/ code>

  print_r($ s-> find(array(
release.arch =>数组(
'$ size'=> 2

)));

//返回count(release.arch)== 2;

E. 检查它是否与数组中的所有元素匹配 $ all

  print_r($ s-> find(array(
) release.arch=>数组(
'$ all'=>数组(
x86,
x64


)));

输出

 数组

[3] =>数组

[名称] => MongoBuster
[发布] =>数组

[arch] =>数组

[0] => x86
[1] => x64


[版本] => 23
[年] => 2013


[类型] => db



F。如果您不确定元素键名称然后你可以使用 $ has 它就像对面 $ in

  print_r($ s-> find(array(
release=> ;数组(
'$ has'=>x86

)));

===================== ==================================================



旧更新




@Baba提供了一个优秀的课程,用使用SPL。我想知道如何在没有SPL的情况下重写这段代码。原因是多次调用这个类会产生函数开销,可以避免在原始PHP中重写它,也可以在最终版本中使用goto语句,以避免递归函数调用。




====================================== =================================



既然你不想要 SPL 和函数..它需要一段时间,但我能够提出灵活且易于使用的替代类



为了避免多次加载数组,你只声明一次:

  $ array = json_decode( $ json,true); 
$ s = new ArrayStandard($ array);

A。查找 release.year 2013

  $ d = $ s - > find(array(
release.year=>2013​​
));
print_r($ d);

输出

 数组

[0] =>数组

[名称] => Mongo
[类型] => db
[release.arch] => x86
[release.version] => 23
[release.year] => 2013



B。这是你第一次运行复杂的 $和 $或 find这样的语句 release.arch = x86 release.year = 2012

  $ d = $ s-> find(array(
release.arch=>x86,
release.year=>2012
),ArrayStandard :: COMPLEX_AND);

print_r($ d);

输出

 数组

[0] =>数组

[名称] => Mongo
[类型] => db
[release.arch] => x86
[release.version] => 22
[release.year] => 2012



C。想象一个更复杂的查询

  $ d = $ s-> find(数组(
release.year=>数组(
' $ in'>数组(
2012,
2013​​

),
release.version=>数组(
'$ gt'=> 22
),
release.arch=>数组(
'$ func'=>函数($ a){
返回$ a ==x86;
}

),ArrayStandard :: COMPLEX_AND);

print_r($ d);

输出

 数组

[0] =>数组

[名称] => Mongo
[类型] => db
[release.arch] => x86
[release.version] => 23
[release.year] => 2013



新修改类

  class ArrayStandard {
const COMPLEX_OR = 1;
const COMPLEX_AND = 2;
private $ array;
私人$令牌;
private $ found;

函数__construct(array $ array){
$ this-> array = $ array;
foreach($ array as $ k => $ item){
$ this-> tokens [$ k] = $ this-> tokenize($ item);
}
}

公共函数getTokens(){
返回$ this-> tokens;
}

公共函数转换($ part){
返回$ this-> tokenize($ part,null,false);
}

公共函数find(array $ find,$ type = 1){
$ f = array();
foreach($ this->令牌为$ k => $ data){
$ this-> check($ find,$ data,$ type)和$ f [$ k] = $这 - >阵列[$ K];
}
返回$ f;
}

私人函数检查($ find,$ data,$ type){
$ o = $ r = 0; // Obigation&要求
foreach($ data as $ key => $ value){
if(isset($ find [$ key])){
$ r ++;
$ options = $ find [$ key];
if(is_array($ options)){
reset($ options);
$ eK = key($ options);
$ eValue =当前($ options);
if(strpos($ eK,'$')=== 0){
$ this-> evaluate($ eK,$ value,$ eValue)和$ o ++;
} else {
抛出新的InvalidArgumentException(在'expession key'中'缺少'$);
}
}其他{
$ this-> evaluate('$ eq',$ value,$ options)和$ o ++;
}
}
}

如果($ o === 0)
返回false;

if($ type == self :: COMPLEX_AND and $ o!== $ r)
return false;

返回true;
}

私有函数getValue(数组$ path){
返回计数($ path)> 1? $ this-> getValue(array_slice($ path,1),$ this-> array [$ path [0]]):$ this-> array [$ path [0]];
}

私有函数tokenize($ array,$ prefix ='',$ addParent = true){
$ paths = array();
$ px =空($前缀)? null:$前缀。 ;
foreach($ array as $ key => $ items){
if(is_array($ items)){
$ addParent&& $ paths [$ px。 $ key] = json_encode($ items);
foreach($ this-> tokenize($ items,$ px。$ key)as $ k => $ path){
$ paths [$ k] = $ path;
}
}其他{
$ paths [$ px。 $ key] = $ items;
}
}
返回$ paths;
}

私人函数评估($ func,$ a,$ b){
$ r = false;

开关($ func){
case'$ eq':
$ r = $ a == $ b;
休息;
case'$ not':
$ r = $ a!= $ b;
休息;
case'$ gte':
case'$ gt':
if($ this-> checkType($ a,$ b)){
$ r = $ a > $ B;
}
休息;

case'$ lte':
case'$ lt':
if($ this-> checkType($ a,$ b)){
$ r = $ a< $ B;
}
休息;
case'$ in':
if(!is_array($ b))
抛出新的InvalidArgumentException('$ in参数的无效参数必须是数组');
$ r = in_array($ a,$ b);
休息;

case'$ has':
if(is_array($ b))
抛出新的InvalidArgumentException('$ array的参数无效,不支持');
$ a = @json_decode($ a,true)? :array();
$ r = in_array($ b,$ a);
休息;

case'$ all':
$ a = @json_decode($ a,true)? :array();
if(!is_array($ b))
抛出新的InvalidArgumentException('$ all选项的无效参数必须是数组');
$ r = count(array_intersect_key($ a,$ b))== count($ b);
休息;

case'$ regex':
case'$ preg':
case'$ match':

$ r =(boolean)preg_match( $ b,$ a,$ match);
休息;

case'$ size':
$ a = @json_decode($ a,true)? :array();
$ r =(int)$ b == count($ a);
休息;

case'$ mod':
if(!is_array($ b))
抛出新的InvalidArgumentException('$ mod选项的无效参数必须是数组');
列表($ x,$ y)=每个($ b);
$ r = $ a%$ x == 0;
休息;

case'$ func':
case'$ fn':
case'$ f':
if(!is_callable($ b))
抛出新的InvalidArgumentException('函数应该是可调用的');
$ r = $ b($ a);
休息;

默认值:
抛出新的ErrorException(条件无效...使用\ $ fn进行自定义操作);
休息;
}

返回$ r;
}

私人功能checkType($ a,$ b){
if(is_numeric($ a)&& is_numeric($ b)){
$ a = filter_var($ a,FILTER_SANITIZE_NUMBER_FLOAT);
$ b = filter_var($ b,FILTER_SANITIZE_NUMBER_FLOAT);
}

if(gettype($ a)!= gettype($ b)){
return false;
}
返回true;
}
}


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-

  1. the raw PHP version performs 10x faster than the SPL one, consuming 20% less memory.
  2. Chequer2 class performs 40% slower than PHP SPL class, and almost 20x slower than raw PHP version.
  3. 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>

解决方案

Latest Update

@baba has given a great raw PHP version of a class implementing MongoDB-like query expression object evaluation, but the output structure differs a bit, I mean the dot notation in the nested array output( [release.arch] => x86 ), instead of regular arrays( [release] => Array([arch] => x86) ). I would appreciate your tip how to make the class fully compatible with mongoDB in this order, as it seems its strictly tied to the raw PHP class implementation.

=======================================================================

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

  foreach ( $array as $part ) {
        $this->flatten[] = $this->convert($part);
    }

To

    foreach ( $array as $k => $part ) {
        $this->flatten[$k] = $this->convert($part);
    }

B. Changed

    foreach ( $this->flatten as $data ) {
        $this->check($find, $data, $type) and $f[] = $data;
    }

To:

    foreach ( $this->flatten as $k => $data ) {
        $this->check($find, $data, $type) and $f[] = $this->array[$k];
    }

New Array for resting 

$json = '[
  {
    "name": "Mongo",
    "release": {
      "arch": "x86",
      "version": 22,
      "year": 2012
    },
    "type": "db"
  },
  {
    "name": "Mongo",
    "release": {
      "arch": "x64",
      "version": 21,
      "year": 2012
    },
    "type": "db"
  },
  {
    "name": "Mongo",
    "release": {
      "arch": "x86",
      "version": 23,
      "year": 2013
    },
    "type": "db"
  },
  {
    "name": "MongoBuster",
    "release": {
      "arch": [
        "x86",
        "x64"
      ],
      "version": 23,
      "year": 2013
    },
    "type": "db"
  },
  {
    "children": {
      "dance": [
        "one",
        "two",
        {
          "three": {
            "a": "apple",
            "b": 700000,
            "c": 8.8
          }
        }
      ],
      "lang": "php",
      "tech": "json",
      "year": 2013
    },
    "key": "Diffrent",
    "value": "cool"
  }
]';

$array = json_decode($json, true);

Simple Test

$s = new ArrayStandard($array);
print_r($s->find(array("release.arch"=>"x86")));

Output

Array
(
    [0] => Array
        (
            [name] => Mongo
            [type] => db
            [release] => Array
                (
                    [arch] => x86
                    [version] => 22
                    [year] => 2012
                )

        )

    [1] => Array
        (
            [name] => Mongo
            [type] => db
            [release] => Array
                (
                    [arch] => x86
                    [version] => 23
                    [year] => 2013
                )

        )

)

If you also want to retain original array key position you can have

    foreach ( $this->flatten as $k => $data ) {
        $this->check($find, $data, $type) and $f[$k] = $this->array[$k];
    }

Just for Fun Part

A. Support for regex

Just for fun i added support for $regex with alias $preg or $match which means you can have

print_r($s->find(array("release.arch" => array('$regex' => "/4$/"))));

Or

print_r($s->find(array("release.arch" => array('$regex' => "/4$/"))));

Output

Array
(
    [1] => Array
        (
            [name] => Mongo
            [type] => db
            [release] => Array
                (
                    [arch] => x64
                    [version] => 21
                    [year] => 2012
                )

        )

)

B. Use Simple array like queries

$queryArray = array(
        "release" => array(
                "arch" => "x86"
        )
);
$d = $s->find($s->convert($queryArray));

$s->convert($queryArray) has converted

Array
(
    [release] => Array
        (
            [arch] => x86
        )

)

To

Array
(
    [release.arch] => x86
)

C. Modulus $mod

print_r($s->find(array(
        "release.version" => array(
                '$mod' => array(
                        23 => 0
                )
        )
)));

 //Checks release.version % 23 == 0 ;

D. Count elements with $size

print_r($s->find(array(
        "release.arch" => array(
                '$size' => 2
        )
)));

// returns count(release.arch) == 2;

E. Check if it matches all element in array $all

print_r($s->find(array(
        "release.arch" => array(
                '$all' => array(
                        "x86",
                        "x64"
                )
        )
)));

Output

Array
(
    [3] => Array
        (
            [name] => MongoBuster
            [release] => Array
                (
                    [arch] => Array
                        (
                            [0] => x86
                            [1] => x64
                        )

                    [version] => 23
                    [year] => 2013
                )

            [type] => db
        )

)

F. If you are not sure of the element key name then you ca use $has its like the opposite of $in

print_r($s->find(array(
        "release" => array(
                '$has' => "x86"
        )
)));

=======================================================================

Old Update

@Baba provided an excellent class, which is written with the use of SPL. I wonder how to rewrite this code without SPL. The reason is that calling this class multiple times will give function overhead, that can be avoided rewriting it in raw PHP, and maybe using goto statement in final version, to avoid recursive function calls.

=======================================================================

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 use

To avoid loading the array multiple times you declare it once :

$array = json_decode($json, true);
$s = new ArrayStandard($array);

A. Find where release.year is 2013

$d = $s->find(array(
        "release.year" => "2013"
));
print_r($d);

Output

Array
(
    [0] => Array
        (
            [name] => Mongo
            [type] => db
            [release.arch] => x86
            [release.version] => 23
            [release.year] => 2013
        )

)

B. For the first time you can run complex $and or $or statement like find where release.arch = x86 and release.year = 2012

$d = $s->find(array(
        "release.arch" => "x86",
        "release.year" => "2012"
), ArrayStandard::COMPLEX_AND);

print_r($d);

Output

Array
(
    [0] => Array
        (
            [name] => Mongo
            [type] => db
            [release.arch] => x86
            [release.version] => 22
            [release.year] => 2012
        )

)

C. Imagine a much more complex query

$d = $s->find(array(
        "release.year" => array(
                '$in' => array(
                        "2012",
                        "2013"
                )
        ),
        "release.version" => array(
                '$gt' => 22
        ),
        "release.arch" => array(
                '$func' => function ($a) {
                    return $a == "x86";
                }
        )
), ArrayStandard::COMPLEX_AND);

print_r($d);

Output

Array
(
    [0] => Array
        (
            [name] => Mongo
            [type] => db
            [release.arch] => x86
            [release.version] => 23
            [release.year] => 2013
        )

)

The new Modified class

class ArrayStandard {
    const COMPLEX_OR = 1;
    const COMPLEX_AND = 2;
    private $array;
    private $tokens;
    private $found;

    function __construct(array $array) {
        $this->array = $array;
        foreach ( $array as $k => $item ) {
            $this->tokens[$k] = $this->tokenize($item);
        }   
    }

    public function getTokens() {
        return $this->tokens;
    }

    public function convert($part) {
        return $this->tokenize($part, null, false);
    }

    public function find(array $find, $type = 1) {
        $f = array();
        foreach ( $this->tokens as $k => $data ) {
            $this->check($find, $data, $type) and $f[$k] = $this->array[$k];
        }
        return $f;
    }

    private function check($find, $data, $type) {
        $o = $r = 0; // Obigation & Requirement
        foreach ( $data as $key => $value ) {
            if (isset($find[$key])) {
                $r ++;
                $options = $find[$key];
                if (is_array($options)) {
                    reset($options);
                    $eK = key($options);
                    $eValue = current($options);
                    if (strpos($eK, '$') === 0) {
                        $this->evaluate($eK, $value, $eValue) and $o ++;
                    } else {
                        throw new InvalidArgumentException('Missing "$" in expession key');
                    }
                } else {
                    $this->evaluate('$eq', $value, $options) and $o ++;
                }
            }
        }

        if ($o === 0)
            return false;

        if ($type == self::COMPLEX_AND and $o !== $r)
            return false;

        return true;
    }

    private function getValue(array $path) {
        return count($path) > 1 ? $this->getValue(array_slice($path, 1), $this->array[$path[0]]) : $this->array[$path[0]];
    }

    private function tokenize($array, $prefix = '', $addParent = true) {
        $paths = array();
        $px = empty($prefix) ? null : $prefix . ".";
        foreach ( $array as $key => $items ) {
            if (is_array($items)) {
                $addParent && $paths[$px . $key] = json_encode($items);
                foreach ( $this->tokenize($items, $px . $key) as $k => $path ) {
                    $paths[$k] = $path;
                }
            } else {
                $paths[$px . $key] = $items;
            }
        }
        return $paths;
    }

    private function evaluate($func, $a, $b) {
        $r = false;

        switch ($func) {
            case '$eq' :
                $r = $a == $b;
                break;
            case '$not' :
                $r = $a != $b;
                break;
            case '$gte' :
            case '$gt' :
                if ($this->checkType($a, $b)) {
                    $r = $a > $b;
                }
                break;

            case '$lte' :
            case '$lt' :
                if ($this->checkType($a, $b)) {
                    $r = $a < $b;
                }
                break;
            case '$in' :
                if (! is_array($b))
                    throw new InvalidArgumentException('Invalid argument for $in option must be array');
                $r = in_array($a, $b);
                break;

            case '$has' :
                if (is_array($b))
                    throw new InvalidArgumentException('Invalid argument for $has array not supported');
                $a = @json_decode($a, true) ?  : array();
                $r = in_array($b, $a);
                break;

            case '$all' :
                $a = @json_decode($a, true) ?  : array();
                if (! is_array($b))
                    throw new InvalidArgumentException('Invalid argument for $all option must be array');
                $r = count(array_intersect_key($a, $b)) == count($b);
                break;

            case '$regex' :
            case '$preg' :
            case '$match' :

                $r = (boolean) preg_match($b, $a, $match);
                break;

            case '$size' :
                $a = @json_decode($a, true) ?  : array();
                $r = (int) $b == count($a);
                break;

            case '$mod' :
                if (! is_array($b))
                    throw new InvalidArgumentException('Invalid argument for $mod option must be array');
                list($x, $y) = each($b);
                $r = $a % $x == 0;
                break;

            case '$func' :
            case '$fn' :
            case '$f' :
                if (! is_callable($b))
                    throw new InvalidArgumentException('Function should be callable');
                $r = $b($a);
                break;

            default :
                throw new ErrorException("Condition not valid ... Use \$fn for custom operations");
                break;
        }

        return $r;
    }

    private function checkType($a, $b) {
        if (is_numeric($a) && is_numeric($b)) {
            $a = filter_var($a, FILTER_SANITIZE_NUMBER_FLOAT);
            $b = filter_var($b, FILTER_SANITIZE_NUMBER_FLOAT);
        }

        if (gettype($a) != gettype($b)) {
            return false;
        }
        return true;
    }
}

这篇关于实现类似goMongoDB的Query表达式对象评估的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆