├── .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 | --------------------------------------------------------------------------------