json-logic-php icon indicating copy to clipboard operation
json-logic-php copied to clipboard

[Feature request] New variables

Open trin4ik opened this issue 2 years ago • 1 comments

Hello. First of all, thank you for this project. Secondly, this is not PR, but some thoughts on the new look of JsonLogic.

Callable $data

Sometimes we need dynamic data using sql/cache/etc

JWadhams\JsonLogic::apply(
  [
    "in" => [
      "my_tag",
      ["var" => "post_tags"]
    ]
  ],
  function (?string $tags_type = null) {
    switch ($tags_type) {
      ...
      case "post_tags": {
        return DB::query("SELECT ...")->toArray();
      }
      ...
    }
    return null;
  }
);

Static variables/iterations + some sugar

The problem with var logic is recursions like some, reduce, etc. This is due to the fact that we overwrite data, e.g:

...
} elseif ($op === "filter") {
    $scopedData = static::apply($values[0], $data);
    $scopedLogic = $values[1];

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

    return array_values(
        array_filter($scopedData, function ($datum) use ($scopedLogic) {
            return static::truthy(static::apply($scopedLogic, $datum)); // <-- Now our data has been overwritten
        })
    );
} elseif ($op === "map") {
...

https://github.com/jwadhams/json-logic-php/blame/e4ea3a46f44d3d34740276f7a21f8f052b029743/src/JWadhams/JsonLogic.php#L263-L267C15

We have lost the original $data, but sometimes we need access to it. Example. My post has some tags, such as: ["php", "js", "webdev", "docker"], I want to group all cloud posts into a separate category. For searching I want to use logic like:

JsonLogic::apply(
  [
    "some" => [
      ["k8s", "docker", "cloud native"],
      [
        "in": [
          ["var" => ""],
          ["var" => "tags"] // <-- here problem
        ]
      ]
    ]
  ],
  ["tags" => ["php", "js", "webdev", "docker"]
);

Right now we can't access the original $data in recursion. Here's what I suggest. Add new logic for the $var keys (like var) and $iteration to look like this:

JsonLogic::apply(
  [
    "some" => [
      ["k8s", "docker", "cloud native"],
      [
        "in": [
          "$iteration",
          ["$var" => "tags"]
        ]
      ]
    ]
  ],
  ["tags" => ["php", "js", "webdev", "docker"]]
);

(in sample i use sugar for "$iteration", it can be used similarly: ["$iteration"], ["$iteration" => null], etc)

Why i think it is best solution:

  1. Full compatibility with current logic, no modification of old rules/logic required
  2. Logical separation between user-variables and iterative-variables

Problem/Todo

  • [ ] $iteration only at current level, I think we should make access to upper levels of $iteration.
  • [x] Cache for called $data, n+1 recursion logic problem
  • [ ] We need to switch to OOP. Static accessor - I like it, but we need some structure in code

What does the community think about this?

trin4ik avatar Sep 28 '23 16:09 trin4ik

Cache for callable

Simple and stupid cache for callable $data.

$cache_key = md5(implode(':', [
    json_encode($logic),
    (new \ReflectionFunction($data))->__toString(),
    $a
]));

if (isset(static::$callable_cache[$cache_key])) {
    return static::$callable_cache[$cache_key];
}

if no change in $logic, $a (params) and function reflection (the most important thing -- a start line of code), then we don't need to call this function again

Callable params

In the current version, we access our $data via var, or its key if it is an object/array. For example: {'var': ['items.0.qty']}. In the case of callable $data, it is much more important to be able to pass multiple parameters than to be able to "reach" the right key. Example:

JWadhams\JsonLogic::apply(
  [
    "in" => [
      "k8s",
      ['$var' => [["tags", 2]]]
    ]
  ],
  function ($type, $post_id) {
    if ($type === 'tags') {
      if ($post_id == 1) return ["php", "js"];
      if ($post_id == 2) return ["docker", "k8s"];
    }
    return [];
  }
)

Important: To maintain backward compatibility with $default, you must pass the first element of the array as an array to get the argument list on the input to the function.

trin4ik avatar Sep 29 '23 13:09 trin4ik