Skip to content

Commit a58fe16

Browse files
committed
Basic JsonLogic structure 🎉
1 parent d52dc78 commit a58fe16

29 files changed

+1135
-0
lines changed

src/Configurations.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
namespace JsonLogic;
3+
4+
class Configurations
5+
{
6+
const PRESET_ECMASCRIPT = 'ECMAScript';
7+
const PRESET_JAVASCRIPT = 'ECMAScript';
8+
const PRESET_JS = 'ECMAScript';
9+
const PRESET_JSON_LOGIC = 'JsonLogic';
10+
const PRESET_PHP = 'PHP';
11+
12+
const TRUTHY_EMPTY_ARRAY = 'truthy-empty-array';
13+
const TRUTHY_STRING_0 = 'truthy-string-zero';
14+
15+
const VALIDATE_PARAMETER_BEFORE_EXECUTION = 'validate-parameters';
16+
17+
const PRESETS = [
18+
'PHP' => [
19+
'truthy-empty-array' => false,
20+
'truthy-string-zero' => false,
21+
],
22+
'ECMAScript' => [
23+
'truthy-empty-array' => true,
24+
'truthy-string-zero' => true,
25+
],
26+
'JsonLogic' => [
27+
'truthy-empty-array' => false,
28+
'truthy-string-zero' => true,
29+
],
30+
];
31+
32+
private static $configs = [
33+
'truthy-empty-array' => false,
34+
'truthy-string-zero' => true,
35+
'validate-parameters' => true,
36+
];
37+
38+
public static function get($key, $default = null)
39+
{
40+
return static::$configs[$key] ?? $default;
41+
}
42+
43+
public static function set($key, $value): void
44+
{
45+
static::$configs[$key] = $value;
46+
}
47+
48+
public static function applyLanguagePresets(string $preset): void
49+
{
50+
foreach (static::PRESETS[$preset] ?? [] as $key => $value) {
51+
static::set($key, $value);
52+
}
53+
}
54+
}

src/Fallbacks.php

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
namespace JsonLogic;
3+
4+
use JsonLogic\Operators\CallableOperator;
5+
use JsonLogic\Rule;
6+
use JsonLogic\Support\Logical;
7+
8+
use function array_keys;
9+
use function array_merge;
10+
use function array_unique;
11+
use function count;
12+
use function is_array;
13+
use function is_numeric;
14+
use function is_string;
15+
16+
/**
17+
* For fully fallback to official JsonLogic lib
18+
*/
19+
trait Fallbacks
20+
{
21+
/** @deprecated 1.0.0 */
22+
public static function get_operator($logic)
23+
{
24+
return array_keys($logic)[0];
25+
}
26+
27+
/** @deprecated 1.0.0 */
28+
public static function get_values($logic, $fix_unary = true)
29+
{
30+
$op = static::get_operator($logic);
31+
$values = $logic[$op];
32+
33+
if ($fix_unary && (!is_array($values) or static::is_logic($values))) {
34+
$values = [ $values ];
35+
}
36+
return $values;
37+
}
38+
39+
/** @deprecated 1.0.0 */
40+
public static function is_logic($array): bool
41+
{
42+
return Rule::isValid($array);
43+
}
44+
45+
/** @deprecated 1.0.0 */
46+
public static function truthy($logic): bool
47+
{
48+
return Logical::truthy($logic);
49+
}
50+
51+
/** @deprecated 1.0.0 */
52+
public static function uses_data($logic): array
53+
{
54+
if (is_object($logic)) {
55+
$logic = (array) $logic;
56+
}
57+
58+
$collection = [];
59+
60+
if (self::is_logic($logic)) {
61+
$op = array_keys($logic)[0];
62+
$values = (array) $logic[$op];
63+
64+
if ($op === 'var') {
65+
$collection[] = $values[0];
66+
} else {
67+
foreach ($values as $value) {
68+
$collection = array_merge($collection, self::uses_data($value));
69+
}
70+
}
71+
}
72+
73+
return array_unique($collection);
74+
}
75+
76+
/** @deprecated 1.0.0 */
77+
public static function rule_like($rule, $pattern): bool
78+
{
79+
if (is_string($pattern) and $pattern[0] === '{') {
80+
$pattern = json_decode($pattern, true);
81+
}
82+
83+
switch ($pattern) {
84+
case $rule:
85+
case '@':
86+
return true;
87+
case 'number':
88+
return is_numeric($rule);
89+
case 'string':
90+
return is_string($rule);
91+
case 'array':
92+
return is_array($rule) && !static::is_logic($rule);
93+
default:
94+
if (static::is_logic($pattern) && static::is_logic($rule)) {
95+
$patternOp = static::get_operator($pattern);
96+
if ($patternOp === '@' || $patternOp === static::get_operator($rule)) {
97+
return static::rule_like(
98+
static::get_values($rule, false),
99+
static::get_values($pattern, false)
100+
);
101+
}
102+
}
103+
return is_array($pattern)
104+
&& is_array($rule)
105+
&& count($pattern) === count($rule)
106+
&& !in_array(false, array_map('static::rule_like', $pattern, $rule));
107+
}
108+
}
109+
110+
/** @deprecated 1.0.0 */
111+
public static function add_operation(string $name, callable $callable): void
112+
{
113+
JsonLogic::operators()->register(
114+
$name,
115+
new CallableOperator($callable)
116+
);
117+
}
118+
}

src/JsonLogic.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
namespace JsonLogic;
3+
4+
class JsonLogic
5+
{
6+
use Fallbacks;
7+
8+
/** @var \JsonLogic\OperatorManager */
9+
protected static $manager;
10+
11+
public static function apply($rule, $data)
12+
{
13+
return static::rule($rule)->process($data);
14+
}
15+
16+
public static function rule($rule): Rule
17+
{
18+
switch (gettype($rule)) {
19+
case 'string':
20+
return new Rule(json_decode($rule, true) ?: []);
21+
case 'array':
22+
return new Rule($rule);
23+
case 'object':
24+
return new Rule((array) $rule);
25+
}
26+
return (new Rule($rule))->compile();
27+
}
28+
29+
public static function setOperators($operators): void
30+
{
31+
static::$manager = $operators instanceof OperatorManager
32+
? $operators
33+
: new OperatorManager($operators);
34+
}
35+
36+
public static function operators(): OperatorManager
37+
{
38+
if (!static::$manager) {
39+
static::setOperators([]);
40+
}
41+
return static::$manager;
42+
}
43+
44+
public static function isValidOperatorKeyword(string $key): bool
45+
{
46+
return static::operators()->get($key) instanceof Operators\Operator;
47+
}
48+
}

src/OperatorManager.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
namespace JsonLogic;
3+
4+
use JsonLogic\Operators\Operator;
5+
6+
class OperatorManager
7+
{
8+
protected $operators = [];
9+
10+
public function __construct(array $extras = [])
11+
{
12+
foreach (array_replace($this->buildins(), $this->customs(), $extras) as $keyword => $op) {
13+
if ($op instanceof Operator) {
14+
$this->register($keyword, $op);
15+
} elseif (class_exists($op)) {
16+
$this->register($keyword, new $op);
17+
}
18+
}
19+
}
20+
21+
public function register(string $keyword, Operator $op): void
22+
{
23+
$this->operators[$keyword] = $op;
24+
}
25+
26+
public function get(string $keyword = ''): ?Operator
27+
{
28+
return $this->operators[$keyword] ?? null;
29+
}
30+
31+
protected function buildins(): array
32+
{
33+
return [
34+
// Accessing Data
35+
'var' => Operators\VarOperator::class,
36+
'missing' => null,
37+
'missing_some' => null,
38+
// Logic and Boolean Operations
39+
'if' => null,
40+
'==' => Operators\Equals::class,
41+
'===' => Operators\Same::class,
42+
'!=' => Operators\NotEquals::class,
43+
'!==' => Operators\NotSame::class,
44+
'!' => Operators\Not::class,
45+
'!!' => Operators\Booleanify::class,
46+
'or' => Operators\OrOperator::class,
47+
'and' => Operators\AndOperator::class,
48+
// Numeric Operations
49+
'>' => Operators\GreaterThan::class,
50+
'>=' => Operators\GreaterThanOrEqual::class,
51+
'<' => Operators\LessThan::class,
52+
'<=' => Operators\LessThanOrEqual::class,
53+
'max' => null,
54+
'min' => null,
55+
'+' => null,
56+
'-' => null,
57+
'*' => null,
58+
'/' => null,
59+
'%' => null,
60+
// Array Operations
61+
'map' => null,
62+
'reduce' => null,
63+
'filter' => null,
64+
'all' => null,
65+
'none' => null,
66+
'some' => null,
67+
'merge' => null,
68+
'in' => null,
69+
// String Operations
70+
'in' => null,
71+
'cat' => null,
72+
'substr' => null,
73+
// Miscellaneous
74+
'log' => null,
75+
];
76+
}
77+
78+
protected function customs(): array
79+
{
80+
return [];
81+
}
82+
}

src/Operators/AndOperator.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
namespace JsonLogic\Operators;
3+
4+
use Closure;
5+
use JsonLogic\Rule;
6+
use JsonLogic\Support\Logical;
7+
8+
/**
9+
* @operator and
10+
*/
11+
class AndOperator extends Operator
12+
{
13+
public function params($param): Closure
14+
{
15+
$subRules = array_map(
16+
[Rule::class, 'make'],
17+
$param
18+
);
19+
return function (&$data) use (&$subRules): bool {
20+
return array_reduce(
21+
$subRules,
22+
function ($logic, Closure $sub) use (&$data): bool {
23+
return $logic && Logical::truthy(($sub)($data));
24+
},
25+
true
26+
);
27+
};
28+
}
29+
}

src/Operators/Booleanify.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
namespace JsonLogic\Operators;
3+
4+
use Closure;
5+
use JsonLogic\Rule;
6+
use JsonLogic\Support\Logical;
7+
8+
use function is_array;
9+
10+
/**
11+
* @operator !!
12+
*/
13+
class Booleanify extends Operator
14+
{
15+
protected $alias = '!!';
16+
17+
public function params($params): Closure
18+
{
19+
$sub = !is_array($params) || Rule::isValid($params)
20+
? Rule::make($params)
21+
: Rule::make($params[0] ?? $params);
22+
23+
return function (&$data) use (&$sub) {
24+
return Logical::truthy($sub($data));
25+
};
26+
}
27+
}

src/Operators/CallableOperator.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
namespace JsonLogic\Operators;
3+
4+
use Closure;
5+
6+
class CallableOperator extends Operator
7+
{
8+
private $callable;
9+
10+
public function __construct(callable $callable)
11+
{
12+
$this->callable = $callable;
13+
}
14+
15+
public function params($params): Closure
16+
{
17+
$callable = $this->callable;
18+
return function ($data) use ($callable, $params) {
19+
return $callable($params, $data);
20+
};
21+
}
22+
}

0 commit comments

Comments
 (0)