Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ConditionEvaluator
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 4
992
0.00% covered (danger)
0.00%
0 / 1
 passes
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 flatten
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 evaluate
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
462
 isAssoc
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Automation\Application\Services;
6
7use ImovelZapAi\Automation\Domain\Enums\ConditionOperator;
8use ImovelZapAi\Automation\Infrastructure\Persistence\Eloquent\AutomationConditionModel;
9
10final class ConditionEvaluator
11{
12    /**
13     * @param  iterable<AutomationConditionModel>  $conditions
14     * @param  array<string, mixed>  $context
15     */
16    public function passes(iterable $conditions, array $context): bool
17    {
18        $flat = $this->flatten($context);
19
20        foreach ($conditions as $condition) {
21            if (! $this->evaluate($condition, $flat)) {
22                return false;
23            }
24        }
25
26        return true;
27    }
28
29    /**
30     * @param  array<string, mixed>  $context
31     * @return array<string, mixed>
32     */
33    public function flatten(array $context, string $prefix = ''): array
34    {
35        $result = [];
36
37        foreach ($context as $key => $value) {
38            $path = $prefix === '' ? (string) $key : "{$prefix}.{$key}";
39
40            if (is_array($value) && $this->isAssoc($value)) {
41                $result += $this->flatten($value, $path);
42            } else {
43                $result[$path] = $value;
44                $result[(string) $key] = $value;
45            }
46        }
47
48        return $result;
49    }
50
51    /**
52     * @param  array<string, mixed>  $flat
53     */
54    private function evaluate(AutomationConditionModel $condition, array $flat): bool
55    {
56        $actual = $flat[$condition->field] ?? null;
57        $expected = $condition->value;
58
59        return match ($condition->operator) {
60            ConditionOperator::Eq => (string) $actual === (string) $expected,
61            ConditionOperator::Neq => (string) $actual !== (string) $expected,
62            ConditionOperator::Gt => is_numeric($actual) && is_numeric($expected) && (float) $actual > (float) $expected,
63            ConditionOperator::Gte => is_numeric($actual) && is_numeric($expected) && (float) $actual >= (float) $expected,
64            ConditionOperator::Lt => is_numeric($actual) && is_numeric($expected) && (float) $actual < (float) $expected,
65            ConditionOperator::Lte => is_numeric($actual) && is_numeric($expected) && (float) $actual <= (float) $expected,
66            ConditionOperator::Contains => is_string($actual) && is_string($expected) && str_contains($actual, $expected),
67            ConditionOperator::In => in_array((string) $actual, array_map('trim', explode(',', (string) $expected)), true),
68            ConditionOperator::Exists => $actual !== null && $actual !== '',
69        };
70    }
71
72    /** @param  array<mixed>  $value */
73    private function isAssoc(array $value): bool
74    {
75        if ($value === []) {
76            return false;
77        }
78
79        return array_keys($value) !== range(0, count($value) - 1);
80    }
81}