diff --git a/src/JWadhams/JsonLogic.php b/src/JWadhams/JsonLogic.php index c8277fe..77a0fec 100644 --- a/src/JWadhams/JsonLogic.php +++ b/src/JWadhams/JsonLogic.php @@ -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)) { @@ -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; @@ -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) { @@ -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 = []; @@ -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) { @@ -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); } ]; @@ -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)) { @@ -255,12 +425,12 @@ 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)) { @@ -268,13 +438,13 @@ public static function apply($logic = [], $data = []) } 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; @@ -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 @@ -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); @@ -421,4 +591,5 @@ public static function add_operation($name, $callable) { self::$custom_operations[$name] = $callable; } + }