Skip to content
213 changes: 192 additions & 21 deletions src/JWadhams/JsonLogic.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static function truthy($logic)
return (bool)$logic;
}

public static function apply($logic = [], $data = [])
public static function apply($logic = [], $data = [], $cumulative = false, $originalData = [])
{
//I'd rather work with array syntax
if (is_object($logic)) {
Expand All @@ -49,9 +49,18 @@ public static function apply($logic = [], $data = [])

if (! self::is_logic($logic)) {
if (is_array($logic)) {
if ($cumulative) {
$modifiedData = $data;
$count = 0;
foreach($logic as $rule) {
$modifiedData = self::apply($rule, $modifiedData, false, $originalData);
$count++;
}
return $modifiedData;
}
//Could be an array of logic statements. Only one way to find out.
return array_map(function ($l) use ($data) {
return self::apply($l, $data);
return array_map(function ($l) use ($data, $originalData) {
return self::apply($l, $data, false, $originalData);
}, $logic);
} else {
return $logic;
Expand Down Expand Up @@ -118,10 +127,17 @@ public static function apply($logic = [], $data = [])
error_log($a);
return $a;
},
'var' => function ($a = null, $default = null) use ($data) {
'var' => function ($a = null, $default = null, $useOriginalData = false, $newData = []) use ($data, $originalData) {
if ($useOriginalData) {
$data = $originalData;
}
if (!empty($newData)) {
$data = $newData;
}
if ($a === null or $a === "") {
return $data;
}

//Descending into data using dot-notation
//This is actually safe for integer indexes, PHP treats $a["1"] exactly like $a[1]
foreach (explode('.', $a) as $prop) {
Expand All @@ -142,9 +158,14 @@ public static function apply($logic = [], $data = [])
which typically happens if it's actually acting on the output of another command
(like IF or MERGE)
*/
$values = func_get_args();
if (!static::is_logic($values) and isset($values[0]) and is_array($values[0])) {
$values = $values[0];
if (is_bool(func_get_arg(0)) && func_num_args() > 1) {
$data = func_get_arg(1);
$values = func_get_arg(2);
} else {
$values = func_get_args();
if (!static::is_logic($values) and isset($values[0]) and is_array($values[0])) {
$values = $values[0];
}
}

$missing = [];
Expand Down Expand Up @@ -184,6 +205,9 @@ public static function apply($logic = [], $data = [])
return min(func_get_args());
},
'+' => function () {
if (is_array(func_get_arg(0))) {
return array_sum(func_get_arg(0));
}
return array_sum(func_get_args());
},
'-' => function ($a, $b=null) {
Expand All @@ -208,6 +232,152 @@ public static function apply($logic = [], $data = [])
},
'substr' => function () {
return call_user_func_array('substr', func_get_args());
},
'cartesian' => function ($a, $b) {
$result = array();
if (is_array($a) && is_array($b)) {
foreach ($a as $itemA) {
foreach ($a as $itemB) {
$res = array_merge([$itemA], [$itemB]);
$result[] = $res;
}
}
}
return $result;
},
'modify' => function ($a, $b, $c) {
$properties = explode('.', $b);
if (is_object($a)) {
if (count($properties) > 1) {
$data = $a;
foreach ($properties as $key => $prop) {
if ($key !== array_key_last($properties)) {
if (is_object($data) && property_exists($data, $prop)) {
$data = $data->{$prop};
} else {
if (array_key_exists($prop, $data)) {
$data = $data[$prop];
}
}
}
}
if (is_array($data)) {
$newData = [];
foreach ($data as $entry) {
$entry->{end($properties)} = $c;
$newData[] = $entry;
}
} else {
$data->{end($properties)} = $c;
$newData = $data;
}
$a->{reset($properties)} = $newData;
} else {
$a->{$b} = $c;
}
} else {
foreach ($a as $mainkey => $item) {
if (count($properties) > 1) {
$data = $item;
foreach ($properties as $key => $prop) {
if ($key !== array_key_last($properties)) {
if (is_object($data) && property_exists($data, $prop)) {
$data = $data->{$prop};
} else {
if (array_key_exists($prop, $data)) {
$data = $data[$prop];
}
}
}
}
if (is_array($data)) {
$newData = [];
foreach ($data as $entry) {
$entry->{end($properties)} = $c;
$newData[] = $entry;
}
} else {
$data->{end($properties)} = $c;
$newData = $data;
}
$item->{reset($properties)} = $newData;
} else {
$item->{$b} = $c;
}
}
}
return $a;
},
'remove' => function ($a, $b) {
if (is_object($a)) {
unset($a->{$b});
} else {
foreach ($a as $item) {
unset($item->{$b});
}
}
return $a;
},
'group' => function ($a, $b) {
$group[$b] = $a;
return $group;
},
'sqrt' => function($a, $precision = null) {
if (!is_null($precision) && is_numeric($precision)) {
return round(sqrt($a), $precision);
}
return sqrt($a);
},
'join' => function() use ($data) {
$joinedArray = [];
foreach(func_get_args() as $argument) {
if (is_array($argument)) {
$joinedArray = array_merge($joinedArray, $argument);
} else {
$joinedArray[] = $argument;
}
}
return $joinedArray;
},
'create' => function() {
$data = func_get_arg(0);
if (empty($data)) {
$data = [];
}
$argumentNumber = 0;
$object = new \stdClass();
foreach (func_get_args() as $argument) {
$argumentNumber++;
if ($argumentNumber == 1) {
continue;
}

// key and value => size = 2
if (count($argument) == 2) {
$key = $argument[0];
$object->$key = $argument[1];
}
}
array_push($data, $object);
return $data;
},
'delete' => function($a, $b, $c) {
$result = [];
foreach ($a as $key => $item) {
if (!property_exists($item, $b) || $item->$b != $c) {
$result[] = $item;
}
}
return $result;
},
'count' => function($a) {
if (is_array($a)) {
return count($a);
}
return 1;
},
'slice' => function($array, $offset, $length) {
return array_slice($array, $offset, $length);
}
];

Expand Down Expand Up @@ -236,16 +406,16 @@ public static function apply($logic = [], $data = [])
given 0 parameters, return NULL (not great practice, but there was no Else)
*/
for ($i = 0 ; $i < count($values) - 1 ; $i += 2) {
if (static::truthy(static::apply($values[$i], $data))) {
return static::apply($values[$i+1], $data);
if (static::truthy(static::apply($values[$i], $data, false, $originalData))) {
return static::apply($values[$i+1], $data, false, $originalData);
}
}
if (count($values) === $i+1) {
return static::apply($values[$i], $data);
return static::apply($values[$i], $data, false, $originalData);
}
return null;
} elseif ($op === "filter") {
$scopedData = static::apply($values[0], $data);
$scopedData = static::apply($values[0], $data, false, $originalData);
$scopedLogic = $values[1];

if (!$scopedData || !is_array($scopedData)) {
Expand All @@ -255,26 +425,26 @@ public static function apply($logic = [], $data = [])
// that return truthy when passed to the logic in the second argument.
// For parity with JavaScript, reindex the returned array
return array_values(
array_filter($scopedData, function ($datum) use ($scopedLogic) {
return static::truthy(static::apply($scopedLogic, $datum));
array_filter($scopedData, function ($datum) use ($scopedLogic, $originalData) {
return static::truthy(static::apply($scopedLogic, $datum, false, $originalData));
})
);
} elseif ($op === "map") {
$scopedData = static::apply($values[0], $data);
$scopedData = static::apply($values[0], $data, false, $originalData);
$scopedLogic = $values[1];

if (!$scopedData || !is_array($scopedData)) {
return [];
}

return array_map(
function ($datum) use ($scopedLogic) {
return static::apply($scopedLogic, $datum);
function ($datum) use ($scopedLogic, $originalData) {
return static::apply($scopedLogic, $datum, false, $originalData);
},
$scopedData
);
} elseif ($op === "reduce") {
$scopedData = static::apply($values[0], $data);
$scopedData = static::apply($values[0], $data, false, $originalData);
$scopedLogic = $values[1];
$initial = isset($values[2]) ? $values[2] : null;

Expand All @@ -284,10 +454,10 @@ function ($datum) use ($scopedLogic) {

return array_reduce(
$scopedData,
function ($accumulator, $current) use ($scopedLogic) {
function ($accumulator, $current) use ($scopedLogic, $originalData) {
return static::apply(
$scopedLogic,
['current'=>$current, 'accumulator'=>$accumulator]
['current'=>$current, 'accumulator'=>$accumulator], false, $originalData
);
},
$initial
Expand Down Expand Up @@ -320,8 +490,8 @@ function ($accumulator, $current) use ($scopedLogic) {
}

//Recursion!
$values = array_map(function ($value) use ($data) {
return self::apply($value, $data);
$values = array_map(function ($value) use ($data, $originalData) {
return self::apply($value, $data, false, $originalData);
}, $values);

return call_user_func_array($operation, $values);
Expand Down Expand Up @@ -421,4 +591,5 @@ public static function add_operation($name, $callable)
{
self::$custom_operations[$name] = $callable;
}

}