Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
QueryCacheKeyGenerator
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
4 / 4
10
100.00% covered (success)
100.00%
1 / 1
 generate
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 extractProperties
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 normalizeValue
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 extractObjectProperties
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3// ╔════════════════════════════════════════════════════════════╗
4// ║ MIT Licence (#Expat) - https://opensource.org/licenses/MIT ║
5// ║ Copyright 2026 Frederic Poeydomenge <dyno@phexium.com>     ║
6// ╚════════════════════════════════════════════════════════════╝
7
8declare(strict_types=1);
9
10namespace Phexium\Plugin\QueryBus\Internal;
11
12use Phexium\Application\Query\QueryInterface;
13use Phexium\Domain\Id\IdInterface;
14use ReflectionObject;
15use Stringable;
16
17final class QueryCacheKeyGenerator
18{
19    public static function generate(QueryInterface $query): string
20    {
21        $className = $query::class;
22        $properties = self::extractProperties($query);
23        $hash = md5(json_encode($properties, JSON_THROW_ON_ERROR));
24
25        return sprintf('query:%s:%s', $className, $hash);
26    }
27
28    private static function extractProperties(QueryInterface $query): array
29    {
30        $reflection = new ReflectionObject($query);
31        $properties = [];
32
33        foreach ($reflection->getProperties() as $property) {
34            $name = $property->getName();
35            $value = $property->getValue($query);
36            $properties[$name] = self::normalizeValue($value);
37        }
38
39        ksort($properties);
40
41        return $properties;
42    }
43
44    private static function normalizeValue(mixed $value): mixed
45    {
46        if ($value instanceof IdInterface) {
47            return (string) $value;
48        }
49
50        if ($value instanceof Stringable) {
51            return (string) $value;
52        }
53
54        if (is_array($value)) {
55            return array_map(self::normalizeValue(...), $value);
56        }
57
58        if (is_object($value)) {
59            return self::extractObjectProperties($value);
60        }
61
62        return $value;
63    }
64
65    private static function extractObjectProperties(object $object): array
66    {
67        $reflection = new ReflectionObject($object);
68        $properties = [];
69
70        foreach ($reflection->getProperties() as $property) {
71            $name = $property->getName();
72            $value = $property->getValue($object);
73            $properties[$name] = self::normalizeValue($value);
74        }
75
76        ksort($properties);
77
78        return $properties;
79    }
80}