├── .editorconfig
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── composer.lock
├── phpunit.xml
├── src
└── JWadhams
│ └── JsonLogic.php
└── tests
├── JsonLogicTest.php
├── LazyEvaluationTest.php
├── MagicPropertiesTest.php
└── ObjectWithArrayAccessorsTest.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 4
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /tests/*.json
3 | .idea
4 | .phpunit.result.cache
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Jeremy Wadhams
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # json-logic-php
2 |
3 | This parser accepts [JsonLogic](http://jsonlogic.com) rules and executes them in PHP.
4 |
5 | The JsonLogic format is designed to allow you to share rules (logic) between front-end and back-end code (regardless of language difference), even to store logic along with a record in a database. JsonLogic is documented extensively at [JsonLogic.com](http://jsonlogic.com), including examples of every [supported operation](http://jsonlogic.com/operations.html) and a place to [try out rules in your browser](http://jsonlogic.com/play.html).
6 |
7 | The same format can also be executed in JavaScript by the library [json-logic-js](https://github.com/jwadhams/json-logic-js/)
8 |
9 | ## Examples
10 |
11 | ### A note about types
12 |
13 | This is a PHP interpreter of a format designed to be transmitted and stored as JSON. So it makes sense to conceptualize the rules in JSON.
14 |
15 | Expressed in JSON, a JsonLogic rule is always one key, with an array of values.
16 |
17 | ```json
18 | {"==" : ["apples", "apples"]}
19 | ```
20 |
21 | PHP has a way to express associative arrays as literals, and no object equivalent, so all these examples are written as if JsonLogic rules were decoded with [`json_decode`'s `$assoc` parameter set true](http://php.net/manual/en/function.json-decode.php), e.g.
22 | ```php
23 | json_decode('{"==" : ["apples", "apples"]}', true);
24 | // ["==" => ["apples", "apples"]]
25 | ```
26 |
27 | The library will happily accept either associative arrays or objects:
28 | ```php
29 | $rule = '{"==":["apples", "apples"]}';
30 |
31 | //Decode the JSON string to an array, and evaluate it.
32 | JWadhams\JsonLogic::apply( json_decode($rule, true) );
33 | // true
34 |
35 | //Decode the JSON string to an object, and evaluate it.
36 | JWadhams\JsonLogic::apply( json_decode($rule, false) );
37 | // true
38 | ```
39 |
40 |
41 | ### Simple
42 | ```php
43 | JWadhams\JsonLogic::apply( [ "==" => [1, 1] ] );
44 | // true
45 | ```
46 |
47 | This is a simple test, equivalent to `1 == 1`. A few things about the format:
48 |
49 | 1. The operator is always in the "key" position. There is only one key per JsonLogic rule.
50 | 1. The values are typically an array.
51 | 1. Each value can be a string, number, boolean, array, or null
52 |
53 | ### Compound
54 | Here we're beginning to nest rules.
55 |
56 | ```php
57 | JWadhams\JsonLogic::apply(
58 | [ "and" => [
59 | [ ">" => [3,1] ],
60 | [ "<" => [1,3] ]
61 | ] ]
62 | );
63 | // true
64 | ```
65 |
66 | In an infix language (like PHP) this could be written as:
67 |
68 | ```php
69 | ( (3 > 1) and (1 < 3) )
70 | ```
71 |
72 | ### Data-Driven
73 |
74 | Obviously these rules aren't very interesting if they can only take static literal data. Typically `JsonLogic::apply` will be called with a rule object and a data object. You can use the `var` operator to get attributes of the data object:
75 |
76 | ```php
77 | JWadhams\JsonLogic::apply(
78 | [ "var" => ["a"] ], // Rule
79 | [ "a" => 1, "b" => 2 ] // Data
80 | );
81 | // 1
82 | ```
83 |
84 | If you like, we support [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar) on unary operators to skip the array around values:
85 |
86 | ```php
87 | JWadhams\JsonLogic::apply(
88 | [ "var" => "a" ],
89 | [ "a" => 1, "b" => 2 ]
90 | );
91 | // 1
92 | ```
93 |
94 | You can also use the `var` operator to access an array by numeric index:
95 |
96 | ```php
97 | JWadhams\JsonLogic::apply(
98 | [ "var" => 1 ],
99 | [ "apple", "banana", "carrot" ]
100 | );
101 | // "banana"
102 | ```
103 |
104 | Here's a complex rule that mixes literals and data. The pie isn't ready to eat unless it's cooler than 110 degrees, *and* filled with apples.
105 |
106 | ```php
107 | $rules = [ "and" => [
108 | [ "<" => [ [ "var" => "temp" ], 110 ] ],
109 | [ "==" => [ [ "var" => "pie.filling" ], "apple" ] ]
110 | ] ];
111 |
112 | $data = [ "temp" => 100, "pie" => [ "filling" => "apple" ] ];
113 |
114 | JWadhams\JsonLogic::apply($rules, $data);
115 | // true
116 | ```
117 |
118 | ### Always and Never
119 | Sometimes the rule you want to process is "Always" or "Never." If the first parameter passed to `JsonLogic::apply` is a non-object, non-associative-array, it is returned immediately.
120 |
121 | ```php
122 | //Always
123 | JWadhams\JsonLogic::apply(true, $data_will_be_ignored);
124 | // true
125 |
126 | //Never
127 | JWadhams\JsonLogic::apply(false, $i_wasnt_even_supposed_to_be_here);
128 | // false
129 | ```
130 |
131 | ## Installation
132 |
133 | The best way to install this library is via [Composer](https://getcomposer.org/):
134 |
135 | ```bash
136 | composer require jwadhams/json-logic-php
137 | ```
138 |
139 | If that doesn't suit you, and you want to manage updates yourself, the entire library is self-contained in `src/JWadhams/JsonLogic.php` and you can download it straight into your project as you see fit.
140 |
141 | ```bash
142 | curl -O https://raw.githubusercontent.com/jwadhams/json-logic-php/master/src/JWadhams/JsonLogic.php
143 | ```
144 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jwadhams/json-logic-php",
3 | "description": "Build rules with complex comparisons and boolean operators, serialized as JSON, and execute them in PHP",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Jeremy Wadhams",
8 | "email": "jwadhams@dealerinspire.com"
9 | }
10 | ],
11 | "minimum-stability": "dev",
12 | "require": {
13 | "php": ">=7.2.0"
14 | },
15 | "require-dev": {
16 | "phpunit/phpunit": "^9.3.3"
17 | },
18 | "autoload": {
19 | "psr-0": {
20 | "JWadhams": "src/"
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "97f57edf962d0882513ce1b7ee2cdc21",
8 | "packages": [],
9 | "packages-dev": [
10 | {
11 | "name": "doctrine/instantiator",
12 | "version": "2.0.x-dev",
13 | "source": {
14 | "type": "git",
15 | "url": "https://github.com/doctrine/instantiator.git",
16 | "reference": "0dfb69d79d0964b8a80bfa92c07f50e3e8d73542"
17 | },
18 | "dist": {
19 | "type": "zip",
20 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0dfb69d79d0964b8a80bfa92c07f50e3e8d73542",
21 | "reference": "0dfb69d79d0964b8a80bfa92c07f50e3e8d73542",
22 | "shasum": ""
23 | },
24 | "require": {
25 | "php": "^8.1"
26 | },
27 | "require-dev": {
28 | "doctrine/coding-standard": "^12",
29 | "ext-pdo": "*",
30 | "ext-phar": "*",
31 | "phpbench/phpbench": "^1.2",
32 | "phpstan/phpstan": "^1.9.4",
33 | "phpstan/phpstan-phpunit": "^1.3",
34 | "phpunit/phpunit": "^10.5",
35 | "vimeo/psalm": "^5.4"
36 | },
37 | "default-branch": true,
38 | "type": "library",
39 | "autoload": {
40 | "psr-4": {
41 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
42 | }
43 | },
44 | "notification-url": "https://packagist.org/downloads/",
45 | "license": [
46 | "MIT"
47 | ],
48 | "authors": [
49 | {
50 | "name": "Marco Pivetta",
51 | "email": "ocramius@gmail.com",
52 | "homepage": "https://ocramius.github.io/"
53 | }
54 | ],
55 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
56 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
57 | "keywords": [
58 | "constructor",
59 | "instantiate"
60 | ],
61 | "support": {
62 | "issues": "https://github.com/doctrine/instantiator/issues",
63 | "source": "https://github.com/doctrine/instantiator/tree/2.0.x"
64 | },
65 | "funding": [
66 | {
67 | "url": "https://www.doctrine-project.org/sponsorship.html",
68 | "type": "custom"
69 | },
70 | {
71 | "url": "https://www.patreon.com/phpdoctrine",
72 | "type": "patreon"
73 | },
74 | {
75 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
76 | "type": "tidelift"
77 | }
78 | ],
79 | "time": "2024-06-20T19:34:15+00:00"
80 | },
81 | {
82 | "name": "myclabs/deep-copy",
83 | "version": "1.x-dev",
84 | "source": {
85 | "type": "git",
86 | "url": "https://github.com/myclabs/DeepCopy.git",
87 | "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c"
88 | },
89 | "dist": {
90 | "type": "zip",
91 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
92 | "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
93 | "shasum": ""
94 | },
95 | "require": {
96 | "php": "^7.1 || ^8.0"
97 | },
98 | "conflict": {
99 | "doctrine/collections": "<1.6.8",
100 | "doctrine/common": "<2.13.3 || >=3 <3.2.2"
101 | },
102 | "require-dev": {
103 | "doctrine/collections": "^1.6.8",
104 | "doctrine/common": "^2.13.3 || ^3.2.2",
105 | "phpspec/prophecy": "^1.10",
106 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
107 | },
108 | "default-branch": true,
109 | "type": "library",
110 | "autoload": {
111 | "files": [
112 | "src/DeepCopy/deep_copy.php"
113 | ],
114 | "psr-4": {
115 | "DeepCopy\\": "src/DeepCopy/"
116 | }
117 | },
118 | "notification-url": "https://packagist.org/downloads/",
119 | "license": [
120 | "MIT"
121 | ],
122 | "description": "Create deep copies (clones) of your objects",
123 | "keywords": [
124 | "clone",
125 | "copy",
126 | "duplicate",
127 | "object",
128 | "object graph"
129 | ],
130 | "support": {
131 | "issues": "https://github.com/myclabs/DeepCopy/issues",
132 | "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0"
133 | },
134 | "funding": [
135 | {
136 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
137 | "type": "tidelift"
138 | }
139 | ],
140 | "time": "2024-06-12T14:39:25+00:00"
141 | },
142 | {
143 | "name": "nikic/php-parser",
144 | "version": "v5.1.0",
145 | "source": {
146 | "type": "git",
147 | "url": "https://github.com/nikic/PHP-Parser.git",
148 | "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1"
149 | },
150 | "dist": {
151 | "type": "zip",
152 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1",
153 | "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1",
154 | "shasum": ""
155 | },
156 | "require": {
157 | "ext-ctype": "*",
158 | "ext-json": "*",
159 | "ext-tokenizer": "*",
160 | "php": ">=7.4"
161 | },
162 | "require-dev": {
163 | "ircmaxell/php-yacc": "^0.0.7",
164 | "phpunit/phpunit": "^9.0"
165 | },
166 | "bin": [
167 | "bin/php-parse"
168 | ],
169 | "type": "library",
170 | "extra": {
171 | "branch-alias": {
172 | "dev-master": "5.0-dev"
173 | }
174 | },
175 | "autoload": {
176 | "psr-4": {
177 | "PhpParser\\": "lib/PhpParser"
178 | }
179 | },
180 | "notification-url": "https://packagist.org/downloads/",
181 | "license": [
182 | "BSD-3-Clause"
183 | ],
184 | "authors": [
185 | {
186 | "name": "Nikita Popov"
187 | }
188 | ],
189 | "description": "A PHP parser written in PHP",
190 | "keywords": [
191 | "parser",
192 | "php"
193 | ],
194 | "support": {
195 | "issues": "https://github.com/nikic/PHP-Parser/issues",
196 | "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0"
197 | },
198 | "time": "2024-07-01T20:03:41+00:00"
199 | },
200 | {
201 | "name": "phar-io/manifest",
202 | "version": "dev-master",
203 | "source": {
204 | "type": "git",
205 | "url": "https://github.com/phar-io/manifest.git",
206 | "reference": "54750ef60c58e43759730615a392c31c80e23176"
207 | },
208 | "dist": {
209 | "type": "zip",
210 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
211 | "reference": "54750ef60c58e43759730615a392c31c80e23176",
212 | "shasum": ""
213 | },
214 | "require": {
215 | "ext-dom": "*",
216 | "ext-libxml": "*",
217 | "ext-phar": "*",
218 | "ext-xmlwriter": "*",
219 | "phar-io/version": "^3.0.1",
220 | "php": "^7.2 || ^8.0"
221 | },
222 | "default-branch": true,
223 | "type": "library",
224 | "extra": {
225 | "branch-alias": {
226 | "dev-master": "2.0.x-dev"
227 | }
228 | },
229 | "autoload": {
230 | "classmap": [
231 | "src/"
232 | ]
233 | },
234 | "notification-url": "https://packagist.org/downloads/",
235 | "license": [
236 | "BSD-3-Clause"
237 | ],
238 | "authors": [
239 | {
240 | "name": "Arne Blankerts",
241 | "email": "arne@blankerts.de",
242 | "role": "Developer"
243 | },
244 | {
245 | "name": "Sebastian Heuer",
246 | "email": "sebastian@phpeople.de",
247 | "role": "Developer"
248 | },
249 | {
250 | "name": "Sebastian Bergmann",
251 | "email": "sebastian@phpunit.de",
252 | "role": "Developer"
253 | }
254 | ],
255 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
256 | "support": {
257 | "issues": "https://github.com/phar-io/manifest/issues",
258 | "source": "https://github.com/phar-io/manifest/tree/2.0.4"
259 | },
260 | "funding": [
261 | {
262 | "url": "https://github.com/theseer",
263 | "type": "github"
264 | }
265 | ],
266 | "time": "2024-03-03T12:33:53+00:00"
267 | },
268 | {
269 | "name": "phar-io/version",
270 | "version": "3.2.1",
271 | "source": {
272 | "type": "git",
273 | "url": "https://github.com/phar-io/version.git",
274 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
275 | },
276 | "dist": {
277 | "type": "zip",
278 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
279 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
280 | "shasum": ""
281 | },
282 | "require": {
283 | "php": "^7.2 || ^8.0"
284 | },
285 | "type": "library",
286 | "autoload": {
287 | "classmap": [
288 | "src/"
289 | ]
290 | },
291 | "notification-url": "https://packagist.org/downloads/",
292 | "license": [
293 | "BSD-3-Clause"
294 | ],
295 | "authors": [
296 | {
297 | "name": "Arne Blankerts",
298 | "email": "arne@blankerts.de",
299 | "role": "Developer"
300 | },
301 | {
302 | "name": "Sebastian Heuer",
303 | "email": "sebastian@phpeople.de",
304 | "role": "Developer"
305 | },
306 | {
307 | "name": "Sebastian Bergmann",
308 | "email": "sebastian@phpunit.de",
309 | "role": "Developer"
310 | }
311 | ],
312 | "description": "Library for handling version information and constraints",
313 | "support": {
314 | "issues": "https://github.com/phar-io/version/issues",
315 | "source": "https://github.com/phar-io/version/tree/3.2.1"
316 | },
317 | "time": "2022-02-21T01:04:05+00:00"
318 | },
319 | {
320 | "name": "phpunit/php-code-coverage",
321 | "version": "9.2.x-dev",
322 | "source": {
323 | "type": "git",
324 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
325 | "reference": "39d628812d8d83344a6c1b07799e3700d830d355"
326 | },
327 | "dist": {
328 | "type": "zip",
329 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/39d628812d8d83344a6c1b07799e3700d830d355",
330 | "reference": "39d628812d8d83344a6c1b07799e3700d830d355",
331 | "shasum": ""
332 | },
333 | "require": {
334 | "ext-dom": "*",
335 | "ext-libxml": "*",
336 | "ext-xmlwriter": "*",
337 | "nikic/php-parser": "^4.18 || ^5.0",
338 | "php": ">=7.3",
339 | "phpunit/php-file-iterator": "^3.0.3",
340 | "phpunit/php-text-template": "^2.0.2",
341 | "sebastian/code-unit-reverse-lookup": "^2.0.2",
342 | "sebastian/complexity": "^2.0",
343 | "sebastian/environment": "^5.1.2",
344 | "sebastian/lines-of-code": "^1.0.3",
345 | "sebastian/version": "^3.0.1",
346 | "theseer/tokenizer": "^1.2.0"
347 | },
348 | "require-dev": {
349 | "phpunit/phpunit": "^9.6"
350 | },
351 | "suggest": {
352 | "ext-pcov": "PHP extension that provides line coverage",
353 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
354 | },
355 | "type": "library",
356 | "extra": {
357 | "branch-alias": {
358 | "dev-main": "9.2-dev"
359 | }
360 | },
361 | "autoload": {
362 | "classmap": [
363 | "src/"
364 | ]
365 | },
366 | "notification-url": "https://packagist.org/downloads/",
367 | "license": [
368 | "BSD-3-Clause"
369 | ],
370 | "authors": [
371 | {
372 | "name": "Sebastian Bergmann",
373 | "email": "sebastian@phpunit.de",
374 | "role": "lead"
375 | }
376 | ],
377 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
378 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
379 | "keywords": [
380 | "coverage",
381 | "testing",
382 | "xunit"
383 | ],
384 | "support": {
385 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
386 | "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
387 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2"
388 | },
389 | "funding": [
390 | {
391 | "url": "https://github.com/sebastianbergmann",
392 | "type": "github"
393 | }
394 | ],
395 | "time": "2024-06-29T07:23:05+00:00"
396 | },
397 | {
398 | "name": "phpunit/php-file-iterator",
399 | "version": "3.0.x-dev",
400 | "source": {
401 | "type": "git",
402 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
403 | "reference": "38b24367e1b340aa78b96d7cab042942d917bb84"
404 | },
405 | "dist": {
406 | "type": "zip",
407 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/38b24367e1b340aa78b96d7cab042942d917bb84",
408 | "reference": "38b24367e1b340aa78b96d7cab042942d917bb84",
409 | "shasum": ""
410 | },
411 | "require": {
412 | "php": ">=7.3"
413 | },
414 | "require-dev": {
415 | "phpunit/phpunit": "^9.3"
416 | },
417 | "type": "library",
418 | "extra": {
419 | "branch-alias": {
420 | "dev-master": "3.0-dev"
421 | }
422 | },
423 | "autoload": {
424 | "classmap": [
425 | "src/"
426 | ]
427 | },
428 | "notification-url": "https://packagist.org/downloads/",
429 | "license": [
430 | "BSD-3-Clause"
431 | ],
432 | "authors": [
433 | {
434 | "name": "Sebastian Bergmann",
435 | "email": "sebastian@phpunit.de",
436 | "role": "lead"
437 | }
438 | ],
439 | "description": "FilterIterator implementation that filters files based on a list of suffixes.",
440 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
441 | "keywords": [
442 | "filesystem",
443 | "iterator"
444 | ],
445 | "support": {
446 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
447 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0"
448 | },
449 | "funding": [
450 | {
451 | "url": "https://github.com/sebastianbergmann",
452 | "type": "github"
453 | }
454 | ],
455 | "time": "2022-02-11T16:23:04+00:00"
456 | },
457 | {
458 | "name": "phpunit/php-invoker",
459 | "version": "3.1.1",
460 | "source": {
461 | "type": "git",
462 | "url": "https://github.com/sebastianbergmann/php-invoker.git",
463 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
464 | },
465 | "dist": {
466 | "type": "zip",
467 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
468 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
469 | "shasum": ""
470 | },
471 | "require": {
472 | "php": ">=7.3"
473 | },
474 | "require-dev": {
475 | "ext-pcntl": "*",
476 | "phpunit/phpunit": "^9.3"
477 | },
478 | "suggest": {
479 | "ext-pcntl": "*"
480 | },
481 | "type": "library",
482 | "extra": {
483 | "branch-alias": {
484 | "dev-master": "3.1-dev"
485 | }
486 | },
487 | "autoload": {
488 | "classmap": [
489 | "src/"
490 | ]
491 | },
492 | "notification-url": "https://packagist.org/downloads/",
493 | "license": [
494 | "BSD-3-Clause"
495 | ],
496 | "authors": [
497 | {
498 | "name": "Sebastian Bergmann",
499 | "email": "sebastian@phpunit.de",
500 | "role": "lead"
501 | }
502 | ],
503 | "description": "Invoke callables with a timeout",
504 | "homepage": "https://github.com/sebastianbergmann/php-invoker/",
505 | "keywords": [
506 | "process"
507 | ],
508 | "support": {
509 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
510 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
511 | },
512 | "funding": [
513 | {
514 | "url": "https://github.com/sebastianbergmann",
515 | "type": "github"
516 | }
517 | ],
518 | "time": "2020-09-28T05:58:55+00:00"
519 | },
520 | {
521 | "name": "phpunit/php-text-template",
522 | "version": "2.0.4",
523 | "source": {
524 | "type": "git",
525 | "url": "https://github.com/sebastianbergmann/php-text-template.git",
526 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
527 | },
528 | "dist": {
529 | "type": "zip",
530 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
531 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
532 | "shasum": ""
533 | },
534 | "require": {
535 | "php": ">=7.3"
536 | },
537 | "require-dev": {
538 | "phpunit/phpunit": "^9.3"
539 | },
540 | "type": "library",
541 | "extra": {
542 | "branch-alias": {
543 | "dev-master": "2.0-dev"
544 | }
545 | },
546 | "autoload": {
547 | "classmap": [
548 | "src/"
549 | ]
550 | },
551 | "notification-url": "https://packagist.org/downloads/",
552 | "license": [
553 | "BSD-3-Clause"
554 | ],
555 | "authors": [
556 | {
557 | "name": "Sebastian Bergmann",
558 | "email": "sebastian@phpunit.de",
559 | "role": "lead"
560 | }
561 | ],
562 | "description": "Simple template engine.",
563 | "homepage": "https://github.com/sebastianbergmann/php-text-template/",
564 | "keywords": [
565 | "template"
566 | ],
567 | "support": {
568 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
569 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
570 | },
571 | "funding": [
572 | {
573 | "url": "https://github.com/sebastianbergmann",
574 | "type": "github"
575 | }
576 | ],
577 | "time": "2020-10-26T05:33:50+00:00"
578 | },
579 | {
580 | "name": "phpunit/php-timer",
581 | "version": "5.0.3",
582 | "source": {
583 | "type": "git",
584 | "url": "https://github.com/sebastianbergmann/php-timer.git",
585 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
586 | },
587 | "dist": {
588 | "type": "zip",
589 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
590 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
591 | "shasum": ""
592 | },
593 | "require": {
594 | "php": ">=7.3"
595 | },
596 | "require-dev": {
597 | "phpunit/phpunit": "^9.3"
598 | },
599 | "type": "library",
600 | "extra": {
601 | "branch-alias": {
602 | "dev-master": "5.0-dev"
603 | }
604 | },
605 | "autoload": {
606 | "classmap": [
607 | "src/"
608 | ]
609 | },
610 | "notification-url": "https://packagist.org/downloads/",
611 | "license": [
612 | "BSD-3-Clause"
613 | ],
614 | "authors": [
615 | {
616 | "name": "Sebastian Bergmann",
617 | "email": "sebastian@phpunit.de",
618 | "role": "lead"
619 | }
620 | ],
621 | "description": "Utility class for timing",
622 | "homepage": "https://github.com/sebastianbergmann/php-timer/",
623 | "keywords": [
624 | "timer"
625 | ],
626 | "support": {
627 | "issues": "https://github.com/sebastianbergmann/php-timer/issues",
628 | "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
629 | },
630 | "funding": [
631 | {
632 | "url": "https://github.com/sebastianbergmann",
633 | "type": "github"
634 | }
635 | ],
636 | "time": "2020-10-26T13:16:10+00:00"
637 | },
638 | {
639 | "name": "phpunit/phpunit",
640 | "version": "9.6.x-dev",
641 | "source": {
642 | "type": "git",
643 | "url": "https://github.com/sebastianbergmann/phpunit.git",
644 | "reference": "d229f5800280c68f1c5fc28b132bc64ff5ed3dd4"
645 | },
646 | "dist": {
647 | "type": "zip",
648 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d229f5800280c68f1c5fc28b132bc64ff5ed3dd4",
649 | "reference": "d229f5800280c68f1c5fc28b132bc64ff5ed3dd4",
650 | "shasum": ""
651 | },
652 | "require": {
653 | "doctrine/instantiator": "^1.3.1 || ^2",
654 | "ext-dom": "*",
655 | "ext-json": "*",
656 | "ext-libxml": "*",
657 | "ext-mbstring": "*",
658 | "ext-xml": "*",
659 | "ext-xmlwriter": "*",
660 | "myclabs/deep-copy": "^1.10.1",
661 | "phar-io/manifest": "^2.0.3",
662 | "phar-io/version": "^3.0.2",
663 | "php": ">=7.3",
664 | "phpunit/php-code-coverage": "^9.2.28",
665 | "phpunit/php-file-iterator": "^3.0.5",
666 | "phpunit/php-invoker": "^3.1.1",
667 | "phpunit/php-text-template": "^2.0.3",
668 | "phpunit/php-timer": "^5.0.2",
669 | "sebastian/cli-parser": "^1.0.1",
670 | "sebastian/code-unit": "^1.0.6",
671 | "sebastian/comparator": "^4.0.8",
672 | "sebastian/diff": "^4.0.3",
673 | "sebastian/environment": "^5.1.3",
674 | "sebastian/exporter": "^4.0.5",
675 | "sebastian/global-state": "^5.0.1",
676 | "sebastian/object-enumerator": "^4.0.3",
677 | "sebastian/resource-operations": "^3.0.3",
678 | "sebastian/type": "^3.2",
679 | "sebastian/version": "^3.0.2"
680 | },
681 | "suggest": {
682 | "ext-soap": "To be able to generate mocks based on WSDL files",
683 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
684 | },
685 | "bin": [
686 | "phpunit"
687 | ],
688 | "type": "library",
689 | "extra": {
690 | "branch-alias": {
691 | "dev-master": "9.6-dev"
692 | }
693 | },
694 | "autoload": {
695 | "files": [
696 | "src/Framework/Assert/Functions.php"
697 | ],
698 | "classmap": [
699 | "src/"
700 | ]
701 | },
702 | "notification-url": "https://packagist.org/downloads/",
703 | "license": [
704 | "BSD-3-Clause"
705 | ],
706 | "authors": [
707 | {
708 | "name": "Sebastian Bergmann",
709 | "email": "sebastian@phpunit.de",
710 | "role": "lead"
711 | }
712 | ],
713 | "description": "The PHP Unit Testing framework.",
714 | "homepage": "https://phpunit.de/",
715 | "keywords": [
716 | "phpunit",
717 | "testing",
718 | "xunit"
719 | ],
720 | "support": {
721 | "issues": "https://github.com/sebastianbergmann/phpunit/issues",
722 | "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
723 | "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6"
724 | },
725 | "funding": [
726 | {
727 | "url": "https://phpunit.de/sponsors.html",
728 | "type": "custom"
729 | },
730 | {
731 | "url": "https://github.com/sebastianbergmann",
732 | "type": "github"
733 | },
734 | {
735 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
736 | "type": "tidelift"
737 | }
738 | ],
739 | "time": "2024-07-03T05:19:53+00:00"
740 | },
741 | {
742 | "name": "sebastian/cli-parser",
743 | "version": "1.0.x-dev",
744 | "source": {
745 | "type": "git",
746 | "url": "https://github.com/sebastianbergmann/cli-parser.git",
747 | "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b"
748 | },
749 | "dist": {
750 | "type": "zip",
751 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
752 | "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
753 | "shasum": ""
754 | },
755 | "require": {
756 | "php": ">=7.3"
757 | },
758 | "require-dev": {
759 | "phpunit/phpunit": "^9.3"
760 | },
761 | "type": "library",
762 | "extra": {
763 | "branch-alias": {
764 | "dev-master": "1.0-dev"
765 | }
766 | },
767 | "autoload": {
768 | "classmap": [
769 | "src/"
770 | ]
771 | },
772 | "notification-url": "https://packagist.org/downloads/",
773 | "license": [
774 | "BSD-3-Clause"
775 | ],
776 | "authors": [
777 | {
778 | "name": "Sebastian Bergmann",
779 | "email": "sebastian@phpunit.de",
780 | "role": "lead"
781 | }
782 | ],
783 | "description": "Library for parsing CLI options",
784 | "homepage": "https://github.com/sebastianbergmann/cli-parser",
785 | "support": {
786 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
787 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2"
788 | },
789 | "funding": [
790 | {
791 | "url": "https://github.com/sebastianbergmann",
792 | "type": "github"
793 | }
794 | ],
795 | "time": "2024-03-02T06:27:43+00:00"
796 | },
797 | {
798 | "name": "sebastian/code-unit",
799 | "version": "1.0.8",
800 | "source": {
801 | "type": "git",
802 | "url": "https://github.com/sebastianbergmann/code-unit.git",
803 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
804 | },
805 | "dist": {
806 | "type": "zip",
807 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
808 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
809 | "shasum": ""
810 | },
811 | "require": {
812 | "php": ">=7.3"
813 | },
814 | "require-dev": {
815 | "phpunit/phpunit": "^9.3"
816 | },
817 | "type": "library",
818 | "extra": {
819 | "branch-alias": {
820 | "dev-master": "1.0-dev"
821 | }
822 | },
823 | "autoload": {
824 | "classmap": [
825 | "src/"
826 | ]
827 | },
828 | "notification-url": "https://packagist.org/downloads/",
829 | "license": [
830 | "BSD-3-Clause"
831 | ],
832 | "authors": [
833 | {
834 | "name": "Sebastian Bergmann",
835 | "email": "sebastian@phpunit.de",
836 | "role": "lead"
837 | }
838 | ],
839 | "description": "Collection of value objects that represent the PHP code units",
840 | "homepage": "https://github.com/sebastianbergmann/code-unit",
841 | "support": {
842 | "issues": "https://github.com/sebastianbergmann/code-unit/issues",
843 | "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
844 | },
845 | "funding": [
846 | {
847 | "url": "https://github.com/sebastianbergmann",
848 | "type": "github"
849 | }
850 | ],
851 | "time": "2020-10-26T13:08:54+00:00"
852 | },
853 | {
854 | "name": "sebastian/code-unit-reverse-lookup",
855 | "version": "2.0.3",
856 | "source": {
857 | "type": "git",
858 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
859 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
860 | },
861 | "dist": {
862 | "type": "zip",
863 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
864 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
865 | "shasum": ""
866 | },
867 | "require": {
868 | "php": ">=7.3"
869 | },
870 | "require-dev": {
871 | "phpunit/phpunit": "^9.3"
872 | },
873 | "type": "library",
874 | "extra": {
875 | "branch-alias": {
876 | "dev-master": "2.0-dev"
877 | }
878 | },
879 | "autoload": {
880 | "classmap": [
881 | "src/"
882 | ]
883 | },
884 | "notification-url": "https://packagist.org/downloads/",
885 | "license": [
886 | "BSD-3-Clause"
887 | ],
888 | "authors": [
889 | {
890 | "name": "Sebastian Bergmann",
891 | "email": "sebastian@phpunit.de"
892 | }
893 | ],
894 | "description": "Looks up which function or method a line of code belongs to",
895 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
896 | "support": {
897 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
898 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
899 | },
900 | "funding": [
901 | {
902 | "url": "https://github.com/sebastianbergmann",
903 | "type": "github"
904 | }
905 | ],
906 | "time": "2020-09-28T05:30:19+00:00"
907 | },
908 | {
909 | "name": "sebastian/comparator",
910 | "version": "4.0.x-dev",
911 | "source": {
912 | "type": "git",
913 | "url": "https://github.com/sebastianbergmann/comparator.git",
914 | "reference": "b247957a1c8dc81a671770f74b479c0a78a818f1"
915 | },
916 | "dist": {
917 | "type": "zip",
918 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/b247957a1c8dc81a671770f74b479c0a78a818f1",
919 | "reference": "b247957a1c8dc81a671770f74b479c0a78a818f1",
920 | "shasum": ""
921 | },
922 | "require": {
923 | "php": ">=7.3",
924 | "sebastian/diff": "^4.0",
925 | "sebastian/exporter": "^4.0"
926 | },
927 | "require-dev": {
928 | "phpunit/phpunit": "^9.3"
929 | },
930 | "type": "library",
931 | "extra": {
932 | "branch-alias": {
933 | "dev-master": "4.0-dev"
934 | }
935 | },
936 | "autoload": {
937 | "classmap": [
938 | "src/"
939 | ]
940 | },
941 | "notification-url": "https://packagist.org/downloads/",
942 | "license": [
943 | "BSD-3-Clause"
944 | ],
945 | "authors": [
946 | {
947 | "name": "Sebastian Bergmann",
948 | "email": "sebastian@phpunit.de"
949 | },
950 | {
951 | "name": "Jeff Welch",
952 | "email": "whatthejeff@gmail.com"
953 | },
954 | {
955 | "name": "Volker Dusch",
956 | "email": "github@wallbash.com"
957 | },
958 | {
959 | "name": "Bernhard Schussek",
960 | "email": "bschussek@2bepublished.at"
961 | }
962 | ],
963 | "description": "Provides the functionality to compare PHP values for equality",
964 | "homepage": "https://github.com/sebastianbergmann/comparator",
965 | "keywords": [
966 | "comparator",
967 | "compare",
968 | "equality"
969 | ],
970 | "support": {
971 | "issues": "https://github.com/sebastianbergmann/comparator/issues",
972 | "source": "https://github.com/sebastianbergmann/comparator/tree/4.0"
973 | },
974 | "funding": [
975 | {
976 | "url": "https://github.com/sebastianbergmann",
977 | "type": "github"
978 | }
979 | ],
980 | "time": "2022-09-14T12:46:14+00:00"
981 | },
982 | {
983 | "name": "sebastian/complexity",
984 | "version": "2.0.x-dev",
985 | "source": {
986 | "type": "git",
987 | "url": "https://github.com/sebastianbergmann/complexity.git",
988 | "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a"
989 | },
990 | "dist": {
991 | "type": "zip",
992 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a",
993 | "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a",
994 | "shasum": ""
995 | },
996 | "require": {
997 | "nikic/php-parser": "^4.18 || ^5.0",
998 | "php": ">=7.3"
999 | },
1000 | "require-dev": {
1001 | "phpunit/phpunit": "^9.3"
1002 | },
1003 | "type": "library",
1004 | "extra": {
1005 | "branch-alias": {
1006 | "dev-master": "2.0-dev"
1007 | }
1008 | },
1009 | "autoload": {
1010 | "classmap": [
1011 | "src/"
1012 | ]
1013 | },
1014 | "notification-url": "https://packagist.org/downloads/",
1015 | "license": [
1016 | "BSD-3-Clause"
1017 | ],
1018 | "authors": [
1019 | {
1020 | "name": "Sebastian Bergmann",
1021 | "email": "sebastian@phpunit.de",
1022 | "role": "lead"
1023 | }
1024 | ],
1025 | "description": "Library for calculating the complexity of PHP code units",
1026 | "homepage": "https://github.com/sebastianbergmann/complexity",
1027 | "support": {
1028 | "issues": "https://github.com/sebastianbergmann/complexity/issues",
1029 | "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3"
1030 | },
1031 | "funding": [
1032 | {
1033 | "url": "https://github.com/sebastianbergmann",
1034 | "type": "github"
1035 | }
1036 | ],
1037 | "time": "2023-12-22T06:19:30+00:00"
1038 | },
1039 | {
1040 | "name": "sebastian/diff",
1041 | "version": "4.0.x-dev",
1042 | "source": {
1043 | "type": "git",
1044 | "url": "https://github.com/sebastianbergmann/diff.git",
1045 | "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc"
1046 | },
1047 | "dist": {
1048 | "type": "zip",
1049 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc",
1050 | "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc",
1051 | "shasum": ""
1052 | },
1053 | "require": {
1054 | "php": ">=7.3"
1055 | },
1056 | "require-dev": {
1057 | "phpunit/phpunit": "^9.3",
1058 | "symfony/process": "^4.2 || ^5"
1059 | },
1060 | "type": "library",
1061 | "extra": {
1062 | "branch-alias": {
1063 | "dev-master": "4.0-dev"
1064 | }
1065 | },
1066 | "autoload": {
1067 | "classmap": [
1068 | "src/"
1069 | ]
1070 | },
1071 | "notification-url": "https://packagist.org/downloads/",
1072 | "license": [
1073 | "BSD-3-Clause"
1074 | ],
1075 | "authors": [
1076 | {
1077 | "name": "Sebastian Bergmann",
1078 | "email": "sebastian@phpunit.de"
1079 | },
1080 | {
1081 | "name": "Kore Nordmann",
1082 | "email": "mail@kore-nordmann.de"
1083 | }
1084 | ],
1085 | "description": "Diff implementation",
1086 | "homepage": "https://github.com/sebastianbergmann/diff",
1087 | "keywords": [
1088 | "diff",
1089 | "udiff",
1090 | "unidiff",
1091 | "unified diff"
1092 | ],
1093 | "support": {
1094 | "issues": "https://github.com/sebastianbergmann/diff/issues",
1095 | "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6"
1096 | },
1097 | "funding": [
1098 | {
1099 | "url": "https://github.com/sebastianbergmann",
1100 | "type": "github"
1101 | }
1102 | ],
1103 | "time": "2024-03-02T06:30:58+00:00"
1104 | },
1105 | {
1106 | "name": "sebastian/environment",
1107 | "version": "5.1.x-dev",
1108 | "source": {
1109 | "type": "git",
1110 | "url": "https://github.com/sebastianbergmann/environment.git",
1111 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed"
1112 | },
1113 | "dist": {
1114 | "type": "zip",
1115 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
1116 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
1117 | "shasum": ""
1118 | },
1119 | "require": {
1120 | "php": ">=7.3"
1121 | },
1122 | "require-dev": {
1123 | "phpunit/phpunit": "^9.3"
1124 | },
1125 | "suggest": {
1126 | "ext-posix": "*"
1127 | },
1128 | "type": "library",
1129 | "extra": {
1130 | "branch-alias": {
1131 | "dev-master": "5.1-dev"
1132 | }
1133 | },
1134 | "autoload": {
1135 | "classmap": [
1136 | "src/"
1137 | ]
1138 | },
1139 | "notification-url": "https://packagist.org/downloads/",
1140 | "license": [
1141 | "BSD-3-Clause"
1142 | ],
1143 | "authors": [
1144 | {
1145 | "name": "Sebastian Bergmann",
1146 | "email": "sebastian@phpunit.de"
1147 | }
1148 | ],
1149 | "description": "Provides functionality to handle HHVM/PHP environments",
1150 | "homepage": "http://www.github.com/sebastianbergmann/environment",
1151 | "keywords": [
1152 | "Xdebug",
1153 | "environment",
1154 | "hhvm"
1155 | ],
1156 | "support": {
1157 | "issues": "https://github.com/sebastianbergmann/environment/issues",
1158 | "source": "https://github.com/sebastianbergmann/environment/tree/5.1"
1159 | },
1160 | "funding": [
1161 | {
1162 | "url": "https://github.com/sebastianbergmann",
1163 | "type": "github"
1164 | }
1165 | ],
1166 | "time": "2023-02-03T06:03:51+00:00"
1167 | },
1168 | {
1169 | "name": "sebastian/exporter",
1170 | "version": "4.0.x-dev",
1171 | "source": {
1172 | "type": "git",
1173 | "url": "https://github.com/sebastianbergmann/exporter.git",
1174 | "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72"
1175 | },
1176 | "dist": {
1177 | "type": "zip",
1178 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72",
1179 | "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72",
1180 | "shasum": ""
1181 | },
1182 | "require": {
1183 | "php": ">=7.3",
1184 | "sebastian/recursion-context": "^4.0"
1185 | },
1186 | "require-dev": {
1187 | "ext-mbstring": "*",
1188 | "phpunit/phpunit": "^9.3"
1189 | },
1190 | "type": "library",
1191 | "extra": {
1192 | "branch-alias": {
1193 | "dev-master": "4.0-dev"
1194 | }
1195 | },
1196 | "autoload": {
1197 | "classmap": [
1198 | "src/"
1199 | ]
1200 | },
1201 | "notification-url": "https://packagist.org/downloads/",
1202 | "license": [
1203 | "BSD-3-Clause"
1204 | ],
1205 | "authors": [
1206 | {
1207 | "name": "Sebastian Bergmann",
1208 | "email": "sebastian@phpunit.de"
1209 | },
1210 | {
1211 | "name": "Jeff Welch",
1212 | "email": "whatthejeff@gmail.com"
1213 | },
1214 | {
1215 | "name": "Volker Dusch",
1216 | "email": "github@wallbash.com"
1217 | },
1218 | {
1219 | "name": "Adam Harvey",
1220 | "email": "aharvey@php.net"
1221 | },
1222 | {
1223 | "name": "Bernhard Schussek",
1224 | "email": "bschussek@gmail.com"
1225 | }
1226 | ],
1227 | "description": "Provides the functionality to export PHP variables for visualization",
1228 | "homepage": "https://www.github.com/sebastianbergmann/exporter",
1229 | "keywords": [
1230 | "export",
1231 | "exporter"
1232 | ],
1233 | "support": {
1234 | "issues": "https://github.com/sebastianbergmann/exporter/issues",
1235 | "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6"
1236 | },
1237 | "funding": [
1238 | {
1239 | "url": "https://github.com/sebastianbergmann",
1240 | "type": "github"
1241 | }
1242 | ],
1243 | "time": "2024-03-02T06:33:00+00:00"
1244 | },
1245 | {
1246 | "name": "sebastian/global-state",
1247 | "version": "5.0.x-dev",
1248 | "source": {
1249 | "type": "git",
1250 | "url": "https://github.com/sebastianbergmann/global-state.git",
1251 | "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9"
1252 | },
1253 | "dist": {
1254 | "type": "zip",
1255 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9",
1256 | "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9",
1257 | "shasum": ""
1258 | },
1259 | "require": {
1260 | "php": ">=7.3",
1261 | "sebastian/object-reflector": "^2.0",
1262 | "sebastian/recursion-context": "^4.0"
1263 | },
1264 | "require-dev": {
1265 | "ext-dom": "*",
1266 | "phpunit/phpunit": "^9.3"
1267 | },
1268 | "suggest": {
1269 | "ext-uopz": "*"
1270 | },
1271 | "type": "library",
1272 | "extra": {
1273 | "branch-alias": {
1274 | "dev-master": "5.0-dev"
1275 | }
1276 | },
1277 | "autoload": {
1278 | "classmap": [
1279 | "src/"
1280 | ]
1281 | },
1282 | "notification-url": "https://packagist.org/downloads/",
1283 | "license": [
1284 | "BSD-3-Clause"
1285 | ],
1286 | "authors": [
1287 | {
1288 | "name": "Sebastian Bergmann",
1289 | "email": "sebastian@phpunit.de"
1290 | }
1291 | ],
1292 | "description": "Snapshotting of global state",
1293 | "homepage": "http://www.github.com/sebastianbergmann/global-state",
1294 | "keywords": [
1295 | "global state"
1296 | ],
1297 | "support": {
1298 | "issues": "https://github.com/sebastianbergmann/global-state/issues",
1299 | "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7"
1300 | },
1301 | "funding": [
1302 | {
1303 | "url": "https://github.com/sebastianbergmann",
1304 | "type": "github"
1305 | }
1306 | ],
1307 | "time": "2024-03-02T06:35:11+00:00"
1308 | },
1309 | {
1310 | "name": "sebastian/lines-of-code",
1311 | "version": "1.0.x-dev",
1312 | "source": {
1313 | "type": "git",
1314 | "url": "https://github.com/sebastianbergmann/lines-of-code.git",
1315 | "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5"
1316 | },
1317 | "dist": {
1318 | "type": "zip",
1319 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5",
1320 | "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5",
1321 | "shasum": ""
1322 | },
1323 | "require": {
1324 | "nikic/php-parser": "^4.18 || ^5.0",
1325 | "php": ">=7.3"
1326 | },
1327 | "require-dev": {
1328 | "phpunit/phpunit": "^9.3"
1329 | },
1330 | "type": "library",
1331 | "extra": {
1332 | "branch-alias": {
1333 | "dev-master": "1.0-dev"
1334 | }
1335 | },
1336 | "autoload": {
1337 | "classmap": [
1338 | "src/"
1339 | ]
1340 | },
1341 | "notification-url": "https://packagist.org/downloads/",
1342 | "license": [
1343 | "BSD-3-Clause"
1344 | ],
1345 | "authors": [
1346 | {
1347 | "name": "Sebastian Bergmann",
1348 | "email": "sebastian@phpunit.de",
1349 | "role": "lead"
1350 | }
1351 | ],
1352 | "description": "Library for counting the lines of code in PHP source code",
1353 | "homepage": "https://github.com/sebastianbergmann/lines-of-code",
1354 | "support": {
1355 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
1356 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4"
1357 | },
1358 | "funding": [
1359 | {
1360 | "url": "https://github.com/sebastianbergmann",
1361 | "type": "github"
1362 | }
1363 | ],
1364 | "time": "2023-12-22T06:20:34+00:00"
1365 | },
1366 | {
1367 | "name": "sebastian/object-enumerator",
1368 | "version": "4.0.4",
1369 | "source": {
1370 | "type": "git",
1371 | "url": "https://github.com/sebastianbergmann/object-enumerator.git",
1372 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
1373 | },
1374 | "dist": {
1375 | "type": "zip",
1376 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
1377 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
1378 | "shasum": ""
1379 | },
1380 | "require": {
1381 | "php": ">=7.3",
1382 | "sebastian/object-reflector": "^2.0",
1383 | "sebastian/recursion-context": "^4.0"
1384 | },
1385 | "require-dev": {
1386 | "phpunit/phpunit": "^9.3"
1387 | },
1388 | "type": "library",
1389 | "extra": {
1390 | "branch-alias": {
1391 | "dev-master": "4.0-dev"
1392 | }
1393 | },
1394 | "autoload": {
1395 | "classmap": [
1396 | "src/"
1397 | ]
1398 | },
1399 | "notification-url": "https://packagist.org/downloads/",
1400 | "license": [
1401 | "BSD-3-Clause"
1402 | ],
1403 | "authors": [
1404 | {
1405 | "name": "Sebastian Bergmann",
1406 | "email": "sebastian@phpunit.de"
1407 | }
1408 | ],
1409 | "description": "Traverses array structures and object graphs to enumerate all referenced objects",
1410 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
1411 | "support": {
1412 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
1413 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
1414 | },
1415 | "funding": [
1416 | {
1417 | "url": "https://github.com/sebastianbergmann",
1418 | "type": "github"
1419 | }
1420 | ],
1421 | "time": "2020-10-26T13:12:34+00:00"
1422 | },
1423 | {
1424 | "name": "sebastian/object-reflector",
1425 | "version": "2.0.4",
1426 | "source": {
1427 | "type": "git",
1428 | "url": "https://github.com/sebastianbergmann/object-reflector.git",
1429 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
1430 | },
1431 | "dist": {
1432 | "type": "zip",
1433 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
1434 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
1435 | "shasum": ""
1436 | },
1437 | "require": {
1438 | "php": ">=7.3"
1439 | },
1440 | "require-dev": {
1441 | "phpunit/phpunit": "^9.3"
1442 | },
1443 | "type": "library",
1444 | "extra": {
1445 | "branch-alias": {
1446 | "dev-master": "2.0-dev"
1447 | }
1448 | },
1449 | "autoload": {
1450 | "classmap": [
1451 | "src/"
1452 | ]
1453 | },
1454 | "notification-url": "https://packagist.org/downloads/",
1455 | "license": [
1456 | "BSD-3-Clause"
1457 | ],
1458 | "authors": [
1459 | {
1460 | "name": "Sebastian Bergmann",
1461 | "email": "sebastian@phpunit.de"
1462 | }
1463 | ],
1464 | "description": "Allows reflection of object attributes, including inherited and non-public ones",
1465 | "homepage": "https://github.com/sebastianbergmann/object-reflector/",
1466 | "support": {
1467 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
1468 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
1469 | },
1470 | "funding": [
1471 | {
1472 | "url": "https://github.com/sebastianbergmann",
1473 | "type": "github"
1474 | }
1475 | ],
1476 | "time": "2020-10-26T13:14:26+00:00"
1477 | },
1478 | {
1479 | "name": "sebastian/recursion-context",
1480 | "version": "4.0.x-dev",
1481 | "source": {
1482 | "type": "git",
1483 | "url": "https://github.com/sebastianbergmann/recursion-context.git",
1484 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1"
1485 | },
1486 | "dist": {
1487 | "type": "zip",
1488 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
1489 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
1490 | "shasum": ""
1491 | },
1492 | "require": {
1493 | "php": ">=7.3"
1494 | },
1495 | "require-dev": {
1496 | "phpunit/phpunit": "^9.3"
1497 | },
1498 | "type": "library",
1499 | "extra": {
1500 | "branch-alias": {
1501 | "dev-master": "4.0-dev"
1502 | }
1503 | },
1504 | "autoload": {
1505 | "classmap": [
1506 | "src/"
1507 | ]
1508 | },
1509 | "notification-url": "https://packagist.org/downloads/",
1510 | "license": [
1511 | "BSD-3-Clause"
1512 | ],
1513 | "authors": [
1514 | {
1515 | "name": "Sebastian Bergmann",
1516 | "email": "sebastian@phpunit.de"
1517 | },
1518 | {
1519 | "name": "Jeff Welch",
1520 | "email": "whatthejeff@gmail.com"
1521 | },
1522 | {
1523 | "name": "Adam Harvey",
1524 | "email": "aharvey@php.net"
1525 | }
1526 | ],
1527 | "description": "Provides functionality to recursively process PHP variables",
1528 | "homepage": "https://github.com/sebastianbergmann/recursion-context",
1529 | "support": {
1530 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
1531 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5"
1532 | },
1533 | "funding": [
1534 | {
1535 | "url": "https://github.com/sebastianbergmann",
1536 | "type": "github"
1537 | }
1538 | ],
1539 | "time": "2023-02-03T06:07:39+00:00"
1540 | },
1541 | {
1542 | "name": "sebastian/resource-operations",
1543 | "version": "dev-main",
1544 | "source": {
1545 | "type": "git",
1546 | "url": "https://github.com/sebastianbergmann/resource-operations.git",
1547 | "reference": "ff553e7482dcee39fa4acc2b175d6ddeb0f7bc25"
1548 | },
1549 | "dist": {
1550 | "type": "zip",
1551 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ff553e7482dcee39fa4acc2b175d6ddeb0f7bc25",
1552 | "reference": "ff553e7482dcee39fa4acc2b175d6ddeb0f7bc25",
1553 | "shasum": ""
1554 | },
1555 | "require": {
1556 | "php": ">=7.3"
1557 | },
1558 | "require-dev": {
1559 | "phpunit/phpunit": "^9.0"
1560 | },
1561 | "default-branch": true,
1562 | "type": "library",
1563 | "extra": {
1564 | "branch-alias": {
1565 | "dev-main": "3.0-dev"
1566 | }
1567 | },
1568 | "autoload": {
1569 | "classmap": [
1570 | "src/"
1571 | ]
1572 | },
1573 | "notification-url": "https://packagist.org/downloads/",
1574 | "license": [
1575 | "BSD-3-Clause"
1576 | ],
1577 | "authors": [
1578 | {
1579 | "name": "Sebastian Bergmann",
1580 | "email": "sebastian@phpunit.de"
1581 | }
1582 | ],
1583 | "description": "Provides a list of PHP built-in functions that operate on resources",
1584 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
1585 | "support": {
1586 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/main"
1587 | },
1588 | "funding": [
1589 | {
1590 | "url": "https://github.com/sebastianbergmann",
1591 | "type": "github"
1592 | }
1593 | ],
1594 | "time": "2024-03-14T18:47:08+00:00"
1595 | },
1596 | {
1597 | "name": "sebastian/type",
1598 | "version": "3.2.x-dev",
1599 | "source": {
1600 | "type": "git",
1601 | "url": "https://github.com/sebastianbergmann/type.git",
1602 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7"
1603 | },
1604 | "dist": {
1605 | "type": "zip",
1606 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
1607 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
1608 | "shasum": ""
1609 | },
1610 | "require": {
1611 | "php": ">=7.3"
1612 | },
1613 | "require-dev": {
1614 | "phpunit/phpunit": "^9.5"
1615 | },
1616 | "type": "library",
1617 | "extra": {
1618 | "branch-alias": {
1619 | "dev-master": "3.2-dev"
1620 | }
1621 | },
1622 | "autoload": {
1623 | "classmap": [
1624 | "src/"
1625 | ]
1626 | },
1627 | "notification-url": "https://packagist.org/downloads/",
1628 | "license": [
1629 | "BSD-3-Clause"
1630 | ],
1631 | "authors": [
1632 | {
1633 | "name": "Sebastian Bergmann",
1634 | "email": "sebastian@phpunit.de",
1635 | "role": "lead"
1636 | }
1637 | ],
1638 | "description": "Collection of value objects that represent the types of the PHP type system",
1639 | "homepage": "https://github.com/sebastianbergmann/type",
1640 | "support": {
1641 | "issues": "https://github.com/sebastianbergmann/type/issues",
1642 | "source": "https://github.com/sebastianbergmann/type/tree/3.2"
1643 | },
1644 | "funding": [
1645 | {
1646 | "url": "https://github.com/sebastianbergmann",
1647 | "type": "github"
1648 | }
1649 | ],
1650 | "time": "2023-02-03T06:13:03+00:00"
1651 | },
1652 | {
1653 | "name": "sebastian/version",
1654 | "version": "3.0.x-dev",
1655 | "source": {
1656 | "type": "git",
1657 | "url": "https://github.com/sebastianbergmann/version.git",
1658 | "reference": "c6c1022351a901512170118436c764e473f6de8c"
1659 | },
1660 | "dist": {
1661 | "type": "zip",
1662 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
1663 | "reference": "c6c1022351a901512170118436c764e473f6de8c",
1664 | "shasum": ""
1665 | },
1666 | "require": {
1667 | "php": ">=7.3"
1668 | },
1669 | "type": "library",
1670 | "extra": {
1671 | "branch-alias": {
1672 | "dev-master": "3.0-dev"
1673 | }
1674 | },
1675 | "autoload": {
1676 | "classmap": [
1677 | "src/"
1678 | ]
1679 | },
1680 | "notification-url": "https://packagist.org/downloads/",
1681 | "license": [
1682 | "BSD-3-Clause"
1683 | ],
1684 | "authors": [
1685 | {
1686 | "name": "Sebastian Bergmann",
1687 | "email": "sebastian@phpunit.de",
1688 | "role": "lead"
1689 | }
1690 | ],
1691 | "description": "Library that helps with managing the version number of Git-hosted PHP projects",
1692 | "homepage": "https://github.com/sebastianbergmann/version",
1693 | "support": {
1694 | "issues": "https://github.com/sebastianbergmann/version/issues",
1695 | "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
1696 | },
1697 | "funding": [
1698 | {
1699 | "url": "https://github.com/sebastianbergmann",
1700 | "type": "github"
1701 | }
1702 | ],
1703 | "time": "2020-09-28T06:39:44+00:00"
1704 | },
1705 | {
1706 | "name": "theseer/tokenizer",
1707 | "version": "1.2.3",
1708 | "source": {
1709 | "type": "git",
1710 | "url": "https://github.com/theseer/tokenizer.git",
1711 | "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
1712 | },
1713 | "dist": {
1714 | "type": "zip",
1715 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
1716 | "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
1717 | "shasum": ""
1718 | },
1719 | "require": {
1720 | "ext-dom": "*",
1721 | "ext-tokenizer": "*",
1722 | "ext-xmlwriter": "*",
1723 | "php": "^7.2 || ^8.0"
1724 | },
1725 | "type": "library",
1726 | "autoload": {
1727 | "classmap": [
1728 | "src/"
1729 | ]
1730 | },
1731 | "notification-url": "https://packagist.org/downloads/",
1732 | "license": [
1733 | "BSD-3-Clause"
1734 | ],
1735 | "authors": [
1736 | {
1737 | "name": "Arne Blankerts",
1738 | "email": "arne@blankerts.de",
1739 | "role": "Developer"
1740 | }
1741 | ],
1742 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
1743 | "support": {
1744 | "issues": "https://github.com/theseer/tokenizer/issues",
1745 | "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
1746 | },
1747 | "funding": [
1748 | {
1749 | "url": "https://github.com/theseer",
1750 | "type": "github"
1751 | }
1752 | ],
1753 | "time": "2024-03-03T12:36:25+00:00"
1754 | }
1755 | ],
1756 | "aliases": [],
1757 | "minimum-stability": "dev",
1758 | "stability-flags": [],
1759 | "prefer-stable": false,
1760 | "prefer-lowest": false,
1761 | "platform": {
1762 | "php": ">=7.2.0"
1763 | },
1764 | "platform-dev": [],
1765 | "plugin-api-version": "2.6.0"
1766 | }
1767 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | tests
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/JWadhams/JsonLogic.php:
--------------------------------------------------------------------------------
1 | "x"] instead of strict ["var" => ["x"]]
18 | if ($fix_unary and (!is_array($values) or static::is_logic($values))) {
19 | $values = [ $values ];
20 | }
21 | return $values;
22 | }
23 |
24 | public static function is_logic($array)
25 | {
26 | return (
27 | is_array($array)
28 | and
29 | count($array) === 1
30 | and
31 | is_string(static::get_operator($array))
32 | );
33 | }
34 |
35 | public static function truthy($logic)
36 | {
37 | if ($logic === "0") {
38 | return true;
39 | }
40 | return (bool)$logic;
41 | }
42 |
43 | public static function apply($logic = [], $data = [])
44 | {
45 | //I'd rather work with array syntax
46 | if (is_object($logic)) {
47 | $logic = (array)$logic;
48 | }
49 |
50 | if (! self::is_logic($logic)) {
51 | if (is_array($logic)) {
52 | //Could be an array of logic statements. Only one way to find out.
53 | return array_map(function ($l) use ($data) {
54 | return self::apply($l, $data);
55 | }, $logic);
56 | } else {
57 | return $logic;
58 | }
59 | }
60 |
61 | $operators = [
62 | '==' => function ($a, $b) {
63 | return $a == $b;
64 | },
65 | '===' => function ($a, $b) {
66 | return $a === $b;
67 | },
68 | '!=' => function ($a, $b) {
69 | return $a != $b;
70 | },
71 | '!==' => function ($a, $b) {
72 | return $a !== $b;
73 | },
74 | '>' => function ($a, $b) {
75 | return $a > $b;
76 | },
77 | '>=' => function ($a, $b) {
78 | return $a >= $b;
79 | },
80 | '<' => function ($a, $b, $c = null) {
81 | if ($c === null) {
82 | return $a < $b;
83 | }
84 | return ($a < $b) and ($b < $c) ;
85 | },
86 | '<=' => function ($a, $b, $c = null) {
87 | if ($c === null) {
88 | return $a <= $b;
89 | }
90 | return ($a <= $b) and ($b <= $c) ;
91 | },
92 | '%' => function ($a, $b) {
93 | return $a % $b;
94 | },
95 | '!!' => function ($a) {
96 | return static::truthy($a);
97 | },
98 | '!' => function ($a) {
99 | return ! static::truthy($a);
100 | },
101 | 'log' => function ($a) {
102 | error_log($a);
103 | return $a;
104 | },
105 | 'var' => function ($a = null, $default = null) use ($data) {
106 | if ($a === null or $a === "") {
107 | return $data;
108 | }
109 | //Descending into data using dot-notation
110 | //This is actually safe for integer indexes, PHP treats $a["1"] exactly like $a[1]
111 | foreach (explode('.', $a) as $prop) {
112 | if ((is_array($data) || $data instanceof \ArrayAccess) && isset($data[$prop])) {
113 | $data = $data[$prop];
114 | } elseif (is_object($data) && isset($data->{$prop})) {
115 | $data = $data->{$prop};
116 | } else {
117 | return $default; //Trying to get a value from a primitive
118 | }
119 | }
120 | return $data;
121 | },
122 | 'missing' => function () use ($data) {
123 | /*
124 | Missing can receive many keys as many arguments, like {"missing:[1,2]}
125 | Missing can also receive *one* argument that is an array of keys,
126 | which typically happens if it's actually acting on the output of another command
127 | (like IF or MERGE)
128 | */
129 | $values = func_get_args();
130 | if (!static::is_logic($values) and isset($values[0]) and is_array($values[0])) {
131 | $values = $values[0];
132 | }
133 |
134 | $missing = [];
135 | foreach ($values as $data_key) {
136 | $value = static::apply(['var'=>$data_key], $data);
137 | if ($value === null or $value === "") {
138 | array_push($missing, $data_key);
139 | }
140 | }
141 |
142 | return $missing;
143 | },
144 | 'missing_some' => function ($minimum, $options) use ($data) {
145 | $are_missing = static::apply(['missing'=>$options], $data);
146 | if (count($options) - count($are_missing) >= $minimum) {
147 | return [];
148 | } else {
149 | return $are_missing;
150 | }
151 | },
152 | 'in' => function ($a, $b) {
153 | if (is_array($b)) {
154 | return in_array($a, $b);
155 | }
156 | if (is_string($b)) {
157 | return strpos($b, $a) !== false;
158 | }
159 | return false;
160 | },
161 | 'cat' => function () {
162 | return implode("", func_get_args());
163 | },
164 | 'max' => function () {
165 | return max(func_get_args());
166 | },
167 | 'min' => function () {
168 | return min(func_get_args());
169 | },
170 | '+' => function () {
171 | return array_sum(func_get_args());
172 | },
173 | '-' => function ($a, $b=null) {
174 | if ($b===null) {
175 | return -$a;
176 | } else {
177 | return $a - $b;
178 | }
179 | },
180 | '/' => function ($a, $b) {
181 | return $a / $b;
182 | },
183 | '*' => function () {
184 | return array_reduce(func_get_args(), function ($a, $b) {
185 | return $a*$b;
186 | }, 1);
187 | },
188 | 'merge' => function () {
189 | return array_reduce(func_get_args(), function ($a, $b) {
190 | return array_merge((array)$a, (array)$b);
191 | }, []);
192 | },
193 | 'substr' => function () {
194 | return call_user_func_array('substr', func_get_args());
195 | }
196 | ];
197 |
198 | //There can be only one operand per logic step
199 | $op = static::get_operator($logic);
200 | $values = static::get_values($logic);
201 |
202 | /**
203 | * Most rules need depth-first recursion. These rules need to manage their
204 | * own recursion. e.g., if you've added an operator with side-effects
205 | * you only want `if` to execute the minimum conditions and exactly one
206 | * consequent.
207 | */
208 | if ($op === 'if' || $op == '?:') {
209 | /* 'if' should be called with a odd number of parameters, 3 or greater
210 | This works on the pattern:
211 | if( 0 ){ 1 }else{ 2 };
212 | if( 0 ){ 1 }else if( 2 ){ 3 }else{ 4 };
213 | if( 0 ){ 1 }else if( 2 ){ 3 }else if( 4 ){ 5 }else{ 6 };
214 |
215 | The implementation is:
216 | For pairs of values (0,1 then 2,3 then 4,5 etc)
217 | If the first evaluates truthy, evaluate and return the second
218 | If the first evaluates falsy, jump to the next pair (e.g, 0,1 to 2,3)
219 | given one parameter, evaluate and return it. (it's an Else and all the If/ElseIf were false)
220 | given 0 parameters, return NULL (not great practice, but there was no Else)
221 | */
222 | for ($i = 0 ; $i < count($values) - 1 ; $i += 2) {
223 | if (static::truthy(static::apply($values[$i], $data))) {
224 | return static::apply($values[$i+1], $data);
225 | }
226 | }
227 | if (count($values) === $i+1) {
228 | return static::apply($values[$i], $data);
229 | }
230 | return null;
231 | } elseif ($op === 'and') {
232 | // Return the first falsy value, or the last value
233 | // we don't even *evaluate* values after the first falsy (short-circuit)
234 | foreach ($values as $value) {
235 | $current = static::apply($value, $data);
236 | if ( ! static::truthy($current)) {
237 | return $current;
238 | }
239 | }
240 | return $current; // Last
241 |
242 | } elseif ($op === 'or') {
243 | // Return the first truthy value, or the last value
244 | // we don't even *evaluate* values after the first truthy (short-circuit)
245 | foreach ($values as $value) {
246 | $current = static::apply($value, $data);
247 | if (static::truthy($current)) {
248 | return $current;
249 | }
250 | }
251 | return $current; // Last
252 |
253 | } elseif ($op === "filter") {
254 | $scopedData = static::apply($values[0], $data);
255 | $scopedLogic = $values[1];
256 |
257 | if (!$scopedData || !is_array($scopedData)) {
258 | return [];
259 | }
260 | // Return only the elements from the array in the first argument,
261 | // that return truthy when passed to the logic in the second argument.
262 | // For parity with JavaScript, reindex the returned array
263 | return array_values(
264 | array_filter($scopedData, function ($datum) use ($scopedLogic) {
265 | return static::truthy(static::apply($scopedLogic, $datum));
266 | })
267 | );
268 | } elseif ($op === "map") {
269 | $scopedData = static::apply($values[0], $data);
270 | $scopedLogic = $values[1];
271 |
272 | if (!$scopedData || !is_array($scopedData)) {
273 | return [];
274 | }
275 |
276 | return array_map(
277 | function ($datum) use ($scopedLogic) {
278 | return static::apply($scopedLogic, $datum);
279 | },
280 | $scopedData
281 | );
282 | } elseif ($op === "reduce") {
283 | $scopedData = static::apply($values[0], $data);
284 | $scopedLogic = $values[1];
285 | $initial = isset($values[2]) ? static::apply($values[2], $data) : null;
286 |
287 | if (!$scopedData || !is_array($scopedData)) {
288 | return $initial;
289 | }
290 |
291 | return array_reduce(
292 | $scopedData,
293 | function ($accumulator, $current) use ($scopedLogic) {
294 | return static::apply(
295 | $scopedLogic,
296 | ['current'=>$current, 'accumulator'=>$accumulator]
297 | );
298 | },
299 | $initial
300 | );
301 | } elseif ($op === "all") {
302 | $scopedData = static::apply($values[0], $data);
303 | $scopedLogic = $values[1];
304 |
305 | if (!$scopedData || !is_array($scopedData)) {
306 | return false;
307 | }
308 | $filtered = array_filter($scopedData, function ($datum) use ($scopedLogic) {
309 | return static::truthy(static::apply($scopedLogic, $datum));
310 | });
311 | return count($filtered) === count($scopedData);
312 | } elseif ($op === "none") {
313 | $filtered = static::apply(['filter' => $values], $data);
314 | return count($filtered) === 0;
315 | } elseif ($op === "some") {
316 | $filtered = static::apply(['filter' => $values], $data);
317 | return count($filtered) > 0;
318 | }
319 |
320 | if (isset(self::$custom_operations[$op])) {
321 | $operation = self::$custom_operations[$op];
322 | } elseif (isset($operators[$op])) {
323 | $operation = $operators[$op];
324 | } else {
325 | throw new \Exception("Unrecognized operator $op");
326 | }
327 |
328 | //Recursion!
329 | $values = array_map(function ($value) use ($data) {
330 | return self::apply($value, $data);
331 | }, $values);
332 |
333 | return call_user_func_array($operation, $values);
334 | }
335 |
336 | public static function uses_data($logic)
337 | {
338 | if (is_object($logic)) {
339 | $logic = (array)$logic;
340 | }
341 | $collection = [];
342 |
343 | if (self::is_logic($logic)) {
344 | $op = array_keys($logic)[0];
345 | $values = (array)$logic[$op];
346 |
347 | if ($op === "var") {
348 | //This doesn't cover the case where the arg to var is itself a rule.
349 | $collection[] = $values[0];
350 | } else {
351 | //Recursion!
352 | foreach ($values as $value) {
353 | $collection = array_merge($collection, self::uses_data($value));
354 | }
355 | }
356 | }
357 |
358 | return array_unique($collection);
359 | }
360 |
361 |
362 | public static function rule_like($rule, $pattern)
363 | {
364 | if (is_string($pattern) and $pattern[0] === '{') {
365 | $pattern = json_decode($pattern, true);
366 | }
367 |
368 | //echo "\nIs ". json_encode($rule) . " like " . json_encode($pattern) . "?\n";
369 | if ($pattern === $rule) {
370 | return true;
371 | } //TODO : Deep object equivalency?
372 | if ($pattern === "@") {
373 | return true;
374 | } //Wildcard!
375 | if ($pattern === "number") {
376 | return is_numeric($rule);
377 | }
378 | if ($pattern === "string") {
379 | return is_string($rule);
380 | }
381 | if ($pattern === "array") {
382 | return is_array($rule) and ! static::is_logic($rule);
383 | }
384 |
385 | if (static::is_logic($pattern)) {
386 | if (static::is_logic($rule)) {
387 | $pattern_op = static::get_operator($pattern);
388 | $rule_op = static::get_operator($rule);
389 |
390 | if ($pattern_op === "@" || $pattern_op === $rule_op) {
391 | //echo "\nOperators match, go deeper\n";
392 | return static::rule_like(
393 | static::get_values($rule, false),
394 | static::get_values($pattern, false)
395 | );
396 | }
397 | }
398 | return false; //$pattern is logic, rule isn't, can't be eq
399 | }
400 |
401 | if (is_array($pattern)) {
402 | if (is_array($rule)) {
403 | if (count($pattern) !== count($rule)) {
404 | return false;
405 | }
406 | /*
407 | Note, array order MATTERS, because we're using this array test logic to consider arguments, where order can matter. (e.g., + is commutative, but '-' or 'if' or 'var' are NOT)
408 |
409 | */
410 | for ($i = 0 ; $i < count($pattern) ; $i += 1) {
411 | //If any fail, we fail
412 | if (! static::rule_like($rule[$i], $pattern[$i])) {
413 | return false;
414 | }
415 | }
416 | return true; //If they *all* passed, we pass
417 | } else {
418 | return false; //Pattern is array, rule isn't
419 | }
420 | }
421 |
422 | //Not logic, not array, not a === match for rule.
423 | return false;
424 | }
425 |
426 | public static function add_operation($name, $callable)
427 | {
428 | self::$custom_operations[$name] = $callable;
429 | }
430 | }
431 |
--------------------------------------------------------------------------------
/tests/JsonLogicTest.php:
--------------------------------------------------------------------------------
1 | expectExceptionMessage('Unrecognized operator fubar');
10 | JWadhams\JsonLogic::apply(['fubar'=> [1,2]]);
11 | }
12 |
13 | /**
14 | * @dataProvider commonProvider
15 | */
16 | public function testCommon($logic, $data, $expected)
17 | {
18 | // Assert
19 | $this->assertEquals(
20 | $expected,
21 | JWadhams\JsonLogic::apply($logic, $data),
22 | "JsonLogic::apply(".json_encode($logic).", ".json_encode($data).") == ".json_encode($expected)
23 | );
24 | }
25 |
26 |
27 | public function commonProvider()
28 | {
29 | $local_path = __DIR__ . '/tests.json';
30 |
31 | if (! file_exists($local_path)) {
32 | echo "Downloading shared apply() tests from JsonLogic.com ...\n";
33 | file_put_contents($local_path, fopen("http://jsonlogic.com/tests.json", 'r'));
34 | } else {
35 | echo "Using cached apply() tests from " . @ date('r', filemtime($local_path)) ."\n";
36 | echo "(rm {$local_path} to refresh)\n";
37 | }
38 |
39 | $body = file_get_contents($local_path);
40 |
41 | $test_as_objects = json_decode($body);
42 | $test_as_associative = json_decode($body, true);
43 |
44 | if ($test_as_objects === null or $test_as_associative === null) {
45 | die("Could not parse tests.json!");
46 | }
47 |
48 | //Every scenario is double tested
49 | $common_tests = array_merge(
50 | json_decode($body),//once using PHP objects
51 | json_decode($body, true)//once using PHP associative arrays
52 | );
53 | $common_tests = array_filter($common_tests, function ($row) {
54 | //Discard comments or malformed rows
55 | return is_array($row) and count($row) >= 3;
56 | });
57 |
58 | return $common_tests;
59 | }
60 |
61 | public function patternProvider()
62 | {
63 | $local_path = __DIR__ . '/rule_like.json';
64 |
65 | if (! file_exists($local_path)) {
66 | echo "Downloading shared rule_like() tests from JsonLogic.com ...\n";
67 | file_put_contents($local_path, fopen("http://jsonlogic.com/rule_like.json", 'r'));
68 | } else {
69 | echo "Using cached rule_like() tests from " . @ date('r', filemtime($local_path)) ."\n";
70 | echo "(rm {$local_path} to refresh)\n";
71 | }
72 |
73 | $patterns = json_decode(file_get_contents($local_path), true);
74 | $patterns = array_filter($patterns, function ($row) {
75 | //Discard comments or malformed rows
76 | return is_array($row) and count($row) == 3;
77 | });
78 | return $patterns;
79 | }
80 | /**
81 | * @dataProvider patternProvider
82 | */
83 | public function testPattern($rule, $pattern, $expected)
84 | {
85 | // Assert
86 | $this->assertEquals(
87 | $expected,
88 | JWadhams\JsonLogic::rule_like($rule, $pattern),
89 | "JsonLogic::rule_like(".json_encode($rule).", ".json_encode($pattern).") == ".json_encode($expected)
90 | );
91 | }
92 |
93 | /* Snappy way to test just one rule when you need to pepper in some echos into the code*/
94 | public function testProblematicPattern()
95 | {
96 | $raw = <<assertEquals(
112 | $expected,
113 | JWadhams\JsonLogic::rule_like($rule, $pattern),
114 | "JsonLogic::rule_like(".json_encode($rule).", ".json_encode($pattern).") == ".json_encode($expected)
115 | );
116 | }
117 |
118 | public function testAddOperation()
119 | {
120 | //Set up some outside data
121 | $a = 0;
122 | // build a function operator that uses outside data by reference
123 | $add_to_a = function ($b=1) use (&$a) {
124 | $a += $b;
125 | return $a;
126 | };
127 | JWadhams\JsonLogic::add_operation("add_to_a", $add_to_a);
128 | //New operation executes, returns desired result
129 | //No args
130 | $this->assertEquals(1, JWadhams\JsonLogic::apply(["add_to_a" => []]));
131 | $this->assertEquals(1, $a, "Yay, side effects!");
132 | //Unary syntactic sugar
133 | $this->assertEquals(42, JWadhams\JsonLogic::apply(["add_to_a" => 41]));
134 | //New operation had side effects.
135 | $this->assertEquals(42, $a, "Yay, side effects!");
136 |
137 | //Calling a method with multiple var as arguments.
138 | JWadhams\JsonLogic::add_operation("times", function ($a, $b) {
139 | return $a*$b;
140 | });
141 | $this->assertEquals(
142 | JWadhams\JsonLogic::apply(
143 | ["times" => [["var"=>"a"], ["var"=>"b"]]],
144 | ['a'=>6,'b'=>7]
145 | ),
146 | 42
147 | );
148 |
149 | //Calling a method that takes an array, but the inside of the array has rules, too
150 | JWadhams\JsonLogic::add_operation("array_times", function ($a) {
151 | return $a[0] * $a[1];
152 | });
153 | $this->assertEquals(
154 | JWadhams\JsonLogic::apply(
155 | ["array_times" => [[["var"=>"a"], ["var"=>"b"]]] ],
156 | ['a'=>6,'b'=>7]
157 | ),
158 | 42
159 | );
160 |
161 | //Turning a language built-in function into an operation
162 | JWadhams\JsonLogic::add_operation("sqrt", 'sqrt');
163 | $this->assertEquals(42, JWadhams\JsonLogic::apply(["sqrt" => 1764]), "sqrt(1764)");
164 |
165 | //Turning a static method into an operation
166 | JWadhams\JsonLogic::add_operation("date_from_format", 'DateTime::createFromFormat');
167 | $this->assertEquals(
168 | new DateTime("1979-09-16 00:00:00"),
169 | JWadhams\JsonLogic::apply(["date_from_format" => ['Y-m-d h:i:s', '1979-09-16 00:00:00']]),
170 | "make a date"
171 | );
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/tests/LazyEvaluationTest.php:
--------------------------------------------------------------------------------
1 | mutates; });
12 | JWadhams\JsonLogic::add_operation('down', function () { return --$this->mutates; });
13 | }
14 |
15 | public function testLazyIf()
16 | {
17 | $this->mutates = 0;
18 | JWadhams\JsonLogic::apply(['if'=> [
19 | true,
20 | ['up' => []],
21 | ['down' => []]
22 | ]]);
23 | self::assertSame(1, $this->mutates, "Mutates should increment and not decrement");
24 |
25 | $this->mutates = 0;
26 | JWadhams\JsonLogic::apply(['if'=> [
27 | false,
28 | ['up' => []],
29 | ['down' => []]
30 | ]]);
31 | self::assertSame(-1, $this->mutates, "Mutates should decrement and not increment");
32 |
33 | }
34 |
35 | public function testAndEvaluatesEveryTruthyArgument()
36 | {
37 | $this->mutates = 0;
38 | $returnValue = JWadhams\JsonLogic::apply(['and'=> [
39 | ['up' => []],
40 | ['up' => []],
41 | ['up' => []]
42 | ]]);
43 | self::assertSame(3, $returnValue);
44 | self::assertSame(3, $this->mutates, "All mutates return truthy, all run");
45 | }
46 |
47 | public function testAndHaltsOnFirstFalsyArgument(): void
48 | {
49 | $this->mutates = 0;
50 | $returnValue = JWadhams\JsonLogic::apply(['and'=> [
51 | false,
52 | ['up' => []],
53 | ['up' => []]
54 | ]]);
55 | self::assertSame(false, $returnValue);
56 | self::assertSame(0, $this->mutates, "Mutates should never run");
57 |
58 | $this->mutates = 0;
59 | $returnValue = JWadhams\JsonLogic::apply(['and'=> [
60 | ['up' => []],
61 | false,
62 | ['up' => []]
63 | ]]);
64 | self::assertSame(false, $returnValue);
65 | self::assertSame(1, $this->mutates, "First 'up' should run, halt on 'false' don't evaluate second 'up'");
66 | }
67 |
68 | public function testOrEvaluatesEveryFalsyArgument()
69 | {
70 | $this->mutates = 1;
71 | $returnValue = JWadhams\JsonLogic::apply(['or'=> [
72 | false,
73 | false,
74 | ['down' => []]
75 | ]]);
76 | self::assertSame(0, $returnValue);
77 | self::assertSame(0, $this->mutates, "All mutates return falsy, all run");
78 | }
79 |
80 | public function testOrHaltsOnFirstTruthyArgument(): void
81 | {
82 | $this->mutates = 0;
83 | $returnValue = JWadhams\JsonLogic::apply(['or'=> [
84 | true,
85 | ['down' => []],
86 | ['down' => []]
87 | ]]);
88 | self::assertSame(true, $returnValue);
89 | self::assertSame(0, $this->mutates, "Neither 'down' should run");
90 |
91 | $this->mutates = 1;
92 | $returnValue = JWadhams\JsonLogic::apply(['or'=> [
93 | ['down' => []],
94 | ['down' => []],
95 | ['down' => []]
96 | ]]);
97 | self::assertSame(-1, $returnValue);
98 | self::assertSame(-1, $this->mutates, "First 'down' should run, second 'down' runs and returns -1 which is truthy, third 'down' does not run");
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/tests/MagicPropertiesTest.php:
--------------------------------------------------------------------------------
1 | getMagicalObject();
33 | $rule = ['var'=>'object.defined'];
34 | $data = ['object' => $object];
35 | $this->assertEquals('magic', JsonLogic::apply($rule, $data));
36 | }
37 |
38 | public function testMixtureOfRealPropertiesAndMagicWorks()
39 | {
40 | $object = $this->getMagicalObject();
41 | $rule = ['var'=>'object.defined'];
42 | $data = new stdClass();
43 | $data->object = $object;
44 | $this->assertEquals('magic', JsonLogic::apply($rule, $data));
45 | }
46 |
47 | public function testInvalidMagicReturnsDefault()
48 | {
49 | $object = $this->getMagicalObject();
50 | $rule = ['var'=>['object.undefined', 'default']];
51 | $data = ['object' => $object];
52 | $this->assertEquals('default', JsonLogic::apply($rule, $data));
53 | }
54 |
55 | public function testMethodsShouldReturnDefault()
56 | {
57 | $object = $this->getMagicalObject();
58 | $rule = ['var'=>['object.method', 'default']];
59 | $data = ['object' => $object];
60 | $this->assertEquals('default', JsonLogic::apply($rule, $data));
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/ObjectWithArrayAccessorsTest.php:
--------------------------------------------------------------------------------
1 | getArrayAccessibleObject();
40 | $rule = ['var'=>['defined', 'default']];
41 | $this->assertEquals('array-ish', $object['defined']);
42 | $this->assertEquals('array-ish', JsonLogic::apply($rule, $object));
43 | }
44 |
45 | public function testInvalidArrayAccessReturnsDefault()
46 | {
47 | $object = $this->getArrayAccessibleObject();
48 | $rule = ['var'=>['undefined', 'default']];
49 | $this->assertFalse(isset($object['undefined']));
50 | $this->assertEquals('default', JsonLogic::apply($rule, $object));
51 | }
52 |
53 | public function testCanMixPropertiesAndArrayAccess()
54 | {
55 | $object = $this->getArrayAccessibleObject();
56 | $rule = ['var'=>['property', 'default']];
57 | $this->assertFalse(isset($object['property']));
58 | $this->assertEquals('object-ish', $object->property);
59 | $this->assertEquals('object-ish', JsonLogic::apply($rule, $object));
60 | }
61 | }
62 |
--------------------------------------------------------------------------------