1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Cycle\ORM\Parser; |
||
6 | |||
7 | use Cycle\ORM\Exception\ParserException; |
||
8 | use Cycle\Database\Injection\Parameter; |
||
9 | |||
10 | /** |
||
11 | * @internal |
||
12 | */ |
||
13 | final class MultiKeyCollection |
||
14 | { |
||
15 | /** |
||
16 | * [Index] = [key1, key2, ...] |
||
17 | * |
||
18 | * @var string[][] |
||
19 | */ |
||
20 | private array $indexes = []; |
||
21 | |||
22 | /** |
||
23 | * [Index][key1-value][key2-value][...] = [ITEM1, ITEM2, ...]. |
||
24 | */ |
||
25 | private array $data = []; |
||
26 | |||
27 | /** |
||
28 | * Contains key values of last added item for each Index |
||
29 | * [index] = [key1-value, key2-value, ...] |
||
30 | */ |
||
31 | private array $lastItemKeys = []; |
||
32 | |||
33 | 4114 | public function createIndex(string $name, array $keys): void |
|
34 | { |
||
35 | 4114 | $this->indexes[$name] = $keys; |
|
36 | 4114 | $this->data[$name] = []; |
|
37 | } |
||
38 | |||
39 | 6308 | public function getIndexes(): array |
|
40 | { |
||
41 | 6308 | return \array_keys($this->indexes); |
|
42 | } |
||
43 | |||
44 | 4114 | public function hasIndex(string $index): bool |
|
45 | { |
||
46 | 4114 | return \array_key_exists($index, $this->indexes); |
|
47 | } |
||
48 | |||
49 | 3034 | public function getItemsCount(string $index, array $values): int |
|
50 | { |
||
51 | try { |
||
52 | 3034 | return \count($this->getValues($this->data[$index], $values)); |
|
53 | 2 | } catch (\Exception) { |
|
54 | 2 | return 0; |
|
55 | } |
||
56 | } |
||
57 | |||
58 | 4014 | public function getItemsSubset(string $index, array $values): array |
|
59 | { |
||
60 | 4014 | return $this->getValues($this->data[$index], $values); |
|
61 | } |
||
62 | |||
63 | 264 | public function getLastItemKeys(string $index): array |
|
64 | { |
||
65 | 264 | return $this->lastItemKeys[$index]; |
|
66 | } |
||
67 | |||
68 | 4072 | public function addItem(string $index, array &$data): void |
|
69 | { |
||
70 | 4072 | $pool = &$this->data[$index]; |
|
71 | 4072 | $itemKeys = []; |
|
72 | 4072 | foreach ($this->indexes[$index] as $key) { |
|
73 | 4072 | $keyValue = $data[$key] ?? null; |
|
74 | 4072 | if (!\is_scalar($keyValue)) { |
|
75 | 336 | throw new \InvalidArgumentException("Invalid value on the key `$key`."); |
|
76 | } |
||
77 | 4072 | $itemKeys[] = $keyValue; |
|
78 | 4072 | if (!\array_key_exists($keyValue, $pool)) { |
|
79 | 4072 | $pool[$keyValue] = []; |
|
80 | } |
||
81 | 4072 | $pool = &$pool[$keyValue]; |
|
82 | } |
||
83 | 4072 | $pool[] = &$data; |
|
84 | 4072 | $this->lastItemKeys[$index] = $itemKeys; |
|
85 | } |
||
86 | |||
87 | 2380 | public function getCriteria(string $index, bool $useParameter = false): array |
|
0 ignored issues
–
show
|
|||
88 | { |
||
89 | 2380 | $result = []; |
|
90 | 2380 | foreach ($this->data[$index] as $key => $data) { |
|
91 | 2340 | $base = [$this->indexes[$index][0] => $key]; |
|
92 | 2340 | $result[] = $this->extractAssoc($data, $base, $this->indexes[$index], 1, true); |
|
93 | } |
||
94 | 2380 | return $result === [] ? [] : \array_merge(...$result); |
|
95 | } |
||
96 | |||
97 | public function getItems(string $indexName): \Generator |
||
98 | { |
||
99 | $depth = \count($this->indexes[$indexName]); |
||
100 | |||
101 | $iterator = static function (array $data, $deep) use (&$depth, &$iterator) { |
||
102 | if ($deep < $depth) { |
||
103 | ++$deep; |
||
104 | foreach ($data as $subset) { |
||
105 | yield from $iterator($subset, $deep); |
||
106 | } |
||
107 | return; |
||
108 | } |
||
109 | yield from $data; |
||
110 | }; |
||
111 | yield from $iterator($this->data[$indexName], 1); |
||
112 | } |
||
113 | |||
114 | /** |
||
115 | * @param string[] $keys |
||
116 | */ |
||
117 | 2340 | private function extractAssoc(array $data, array $base, array $keys, int $level, bool $useParameter): array |
|
118 | { |
||
119 | // Optimization. Group last column values into single Parameter. |
||
120 | // For example, where condition will look like: |
||
121 | // key1="1" AND key2 IN (1, 2, 3) |
||
122 | // instead of: |
||
123 | // (key1="1" AND key2="1") OR (key1="1" AND key2="2") OR (key1="1" AND key2="3") |
||
124 | 2340 | if ($useParameter && $level === \count($keys) - 1 && \count($data) > 1) { |
|
125 | 96 | return [$base + [$keys[$level] => new Parameter(\array_keys($data))]]; |
|
126 | } |
||
127 | |||
128 | 2244 | if ($level >= \count($keys)) { |
|
129 | 2244 | return [$base]; |
|
130 | } |
||
131 | 168 | $result = []; |
|
132 | 168 | $field = $keys[$level]; |
|
133 | 168 | foreach ($data as $key => $value) { |
|
134 | 168 | $base[$field] = $key; |
|
135 | 168 | $result[] = $this->extractAssoc($value, $base, $keys, $level + 1, $useParameter); |
|
136 | } |
||
137 | 168 | return \array_merge(...$result); |
|
138 | } |
||
139 | |||
140 | 4014 | private function getValues(array &$dataSet, array $keys): array |
|
141 | { |
||
142 | 4014 | $value = &$dataSet; |
|
143 | 4014 | foreach ($keys as $key) { |
|
144 | 4014 | if (!\array_key_exists($key, $value)) { |
|
145 | 4 | throw new ParserException('Value not found.'); |
|
146 | } |
||
147 | 4014 | $value = &$value[$key]; |
|
148 | } |
||
149 | 4014 | return $value; |
|
150 | } |
||
151 | } |
||
152 |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.