Total Complexity | 46 |
Total Lines | 360 |
Duplicated Lines | 0 % |
Coverage | 97.93% |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like ObjectKeys often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ObjectKeys, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
24 | final class ObjectKeys implements Implementation |
||
25 | { |
||
26 | private string $keyType; |
||
27 | private string $valueType; |
||
28 | private SpecificationInterface $keySpecification; |
||
29 | private SpecificationInterface $valueSpecification; |
||
30 | private \SplObjectStorage $values; |
||
31 | |||
32 | /** |
||
33 | * {@inheritdoc} |
||
34 | */ |
||
35 | 29 | public function __construct(string $keyType, string $valueType) |
|
36 | { |
||
37 | 29 | $this->keySpecification = Type::of($keyType); |
|
38 | |||
39 | 29 | if (!$this->keySpecification instanceof ClassType && $keyType !== 'object') { |
|
40 | 1 | throw new LogicException; |
|
41 | } |
||
42 | |||
43 | 28 | $this->valueSpecification = Type::of($valueType); |
|
44 | 28 | $this->keyType = $keyType; |
|
45 | 28 | $this->valueType = $valueType; |
|
46 | 28 | $this->values = new \SplObjectStorage; |
|
47 | 28 | } |
|
48 | |||
49 | /** |
||
50 | * {@inheritdoc} |
||
51 | */ |
||
52 | 10 | public function keyType(): string |
|
53 | { |
||
54 | 10 | return $this->keyType; |
|
55 | } |
||
56 | |||
57 | /** |
||
58 | * {@inheritdoc} |
||
59 | */ |
||
60 | 8 | public function valueType(): string |
|
61 | { |
||
62 | 8 | return $this->valueType; |
|
63 | } |
||
64 | |||
65 | /** |
||
66 | * {@inheritdoc} |
||
67 | */ |
||
68 | 8 | public function size(): int |
|
69 | { |
||
70 | 8 | return $this->values->count(); |
|
71 | } |
||
72 | |||
73 | /** |
||
74 | * {@inheritdoc} |
||
75 | */ |
||
76 | public function count(): int |
||
77 | { |
||
78 | return $this->size(); |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * {@inheritdoc} |
||
83 | */ |
||
84 | 23 | public function put($key, $value): Implementation |
|
85 | { |
||
86 | 23 | $this->keySpecification->validate($key); |
|
87 | 23 | $this->valueSpecification->validate($value); |
|
88 | |||
89 | 20 | $map = clone $this; |
|
90 | 20 | $map->values = clone $this->values; |
|
91 | 20 | $map->values[$key] = $value; |
|
92 | 20 | $map->values->rewind(); |
|
93 | |||
94 | 20 | return $map; |
|
95 | } |
||
96 | |||
97 | /** |
||
98 | * {@inheritdoc} |
||
99 | */ |
||
100 | 7 | public function get($key) |
|
101 | { |
||
102 | 7 | if (!$this->contains($key)) { |
|
103 | 1 | throw new ElementNotFoundException; |
|
104 | } |
||
105 | |||
106 | 6 | return $this->values->offsetGet($key); |
|
107 | } |
||
108 | |||
109 | /** |
||
110 | * {@inheritdoc} |
||
111 | */ |
||
112 | 10 | public function contains($key): bool |
|
113 | { |
||
114 | 10 | if (!is_object($key)) { |
|
115 | 1 | return false; |
|
116 | } |
||
117 | |||
118 | 10 | return $this->values->offsetExists($key); |
|
119 | } |
||
120 | |||
121 | /** |
||
122 | * {@inheritdoc} |
||
123 | */ |
||
124 | 5 | public function clear(): Implementation |
|
125 | { |
||
126 | 5 | $map = clone $this; |
|
127 | 5 | $map->values = new \SplObjectStorage; |
|
128 | |||
129 | 5 | return $map; |
|
130 | } |
||
131 | |||
132 | /** |
||
133 | * {@inheritdoc} |
||
134 | */ |
||
135 | 3 | public function equals(Implementation $map): bool |
|
136 | { |
||
137 | 3 | if ($map->size() !== $this->size()) { |
|
138 | 1 | return false; |
|
139 | } |
||
140 | |||
141 | 3 | foreach ($this->values as $k) { |
|
142 | 3 | $v = $this->values[$k]; |
|
143 | |||
144 | 3 | if (!$map->contains($k)) { |
|
145 | return false; |
||
146 | } |
||
147 | |||
148 | 3 | if ($map->get($k) !== $v) { |
|
149 | 3 | return false; |
|
150 | } |
||
151 | } |
||
152 | |||
153 | 1 | return true; |
|
154 | } |
||
155 | |||
156 | /** |
||
157 | * {@inheritdoc} |
||
158 | */ |
||
159 | 1 | public function filter(callable $predicate): Implementation |
|
160 | { |
||
161 | 1 | $map = $this->clear(); |
|
162 | |||
163 | 1 | foreach ($this->values as $k) { |
|
164 | 1 | $v = $this->values[$k]; |
|
165 | |||
166 | 1 | if ($predicate($k, $v) === true) { |
|
167 | 1 | $map->values[$k] = $v; |
|
168 | } |
||
169 | } |
||
170 | |||
171 | 1 | $map->values->rewind(); |
|
172 | |||
173 | 1 | return $map; |
|
174 | } |
||
175 | |||
176 | /** |
||
177 | * {@inheritdoc} |
||
178 | */ |
||
179 | 1 | public function foreach(callable $function): void |
|
180 | { |
||
181 | 1 | foreach ($this->values as $k) { |
|
182 | 1 | $v = $this->values[$k]; |
|
183 | |||
184 | 1 | $function($k, $v); |
|
185 | } |
||
186 | 1 | } |
|
187 | |||
188 | /** |
||
189 | * {@inheritdoc} |
||
190 | */ |
||
191 | 2 | public function groupBy(callable $discriminator): Map |
|
192 | { |
||
193 | 2 | if ($this->size() === 0) { |
|
194 | 1 | throw new GroupEmptyMapException; |
|
195 | } |
||
196 | |||
197 | 1 | $map = null; |
|
198 | |||
199 | 1 | foreach ($this->values as $k) { |
|
200 | 1 | $v = $this->values[$k]; |
|
201 | |||
202 | 1 | $key = $discriminator($k, $v); |
|
203 | |||
204 | 1 | if ($map === null) { |
|
205 | 1 | $map = new Map( |
|
206 | 1 | Type::determine($key), |
|
207 | 1 | Map::class |
|
208 | ); |
||
209 | } |
||
210 | |||
211 | 1 | if ($map->contains($key)) { |
|
212 | 1 | $map = $map->put( |
|
213 | 1 | $key, |
|
214 | 1 | $map->get($key)->put($k, $v) |
|
215 | ); |
||
216 | } else { |
||
217 | 1 | $map = $map->put( |
|
218 | 1 | $key, |
|
219 | 1 | $this->clearMap()->put($k, $v) |
|
220 | ); |
||
221 | } |
||
222 | } |
||
223 | |||
224 | 1 | return $map; |
|
1 ignored issue
–
show
|
|||
225 | } |
||
226 | |||
227 | /** |
||
228 | * {@inheritdoc} |
||
229 | */ |
||
230 | 7 | public function keys(): Set |
|
231 | { |
||
232 | 7 | return $this->reduce( |
|
233 | 7 | Set::of($this->keyType), |
|
234 | static function(Set $keys, $key): Set { |
||
235 | 7 | return $keys->add($key); |
|
236 | 7 | } |
|
237 | ); |
||
238 | } |
||
239 | |||
240 | /** |
||
241 | * {@inheritdoc} |
||
242 | */ |
||
243 | 8 | public function values(): Stream |
|
244 | { |
||
245 | 8 | return $this->reduce( |
|
246 | 8 | Stream::of($this->valueType), |
|
247 | static function(Stream $values, $key, $value): Stream { |
||
248 | 8 | return $values->add($value); |
|
249 | 8 | } |
|
250 | ); |
||
251 | } |
||
252 | |||
253 | /** |
||
254 | * {@inheritdoc} |
||
255 | */ |
||
256 | 3 | public function map(callable $function): Implementation |
|
257 | { |
||
258 | 3 | $map = $this->clear(); |
|
259 | |||
260 | 3 | foreach ($this->values as $k) { |
|
261 | 3 | $v = $this->values[$k]; |
|
262 | |||
263 | 3 | $return = $function($k, $v); |
|
264 | |||
265 | 3 | if ($return instanceof Pair) { |
|
266 | 2 | $this->keySpecification->validate($return->key()); |
|
267 | |||
268 | 1 | $key = $return->key(); |
|
269 | 1 | $value = $return->value(); |
|
270 | } else { |
||
271 | 2 | $key = $k; |
|
272 | 2 | $value = $return; |
|
273 | } |
||
274 | |||
275 | 2 | $this->valueSpecification->validate($value); |
|
276 | |||
277 | 1 | $map->values[$key] = $value; |
|
278 | } |
||
279 | |||
280 | 1 | $map->values->rewind(); |
|
281 | |||
282 | 1 | return $map; |
|
283 | } |
||
284 | |||
285 | /** |
||
286 | * {@inheritdoc} |
||
287 | */ |
||
288 | 1 | public function join(string $separator): Str |
|
289 | { |
||
290 | 1 | return $this->values()->join($separator); |
|
291 | } |
||
292 | |||
293 | /** |
||
294 | * {@inheritdoc} |
||
295 | */ |
||
296 | 1 | public function remove($key): Implementation |
|
297 | { |
||
298 | 1 | if (!$this->contains($key)) { |
|
299 | 1 | return $this; |
|
300 | } |
||
301 | |||
302 | 1 | $map = clone $this; |
|
303 | 1 | $map->values = clone $this->values; |
|
304 | 1 | $map->values->detach($key); |
|
305 | 1 | $map->values->rewind(); |
|
306 | |||
307 | 1 | return $map; |
|
308 | } |
||
309 | |||
310 | /** |
||
311 | * {@inheritdoc} |
||
312 | */ |
||
313 | 3 | public function merge(Implementation $map): Implementation |
|
314 | { |
||
315 | if ( |
||
316 | 3 | $this->keyType !== $map->keyType() || |
|
317 | 3 | $this->valueType !== $map->valueType() |
|
318 | ) { |
||
319 | 1 | throw new InvalidArgumentException( |
|
320 | 1 | 'The 2 maps does not reference the same types' |
|
321 | ); |
||
322 | } |
||
323 | |||
324 | 2 | return $map->reduce( |
|
325 | 2 | $this, |
|
326 | function(self $carry, $key, $value): self { |
||
327 | 2 | return $carry->put($key, $value); |
|
328 | 2 | } |
|
329 | ); |
||
330 | } |
||
331 | |||
332 | /** |
||
333 | * {@inheritdoc} |
||
334 | */ |
||
335 | 1 | public function partition(callable $predicate): Map |
|
336 | { |
||
337 | 1 | $truthy = $this->clearMap(); |
|
338 | 1 | $falsy = $this->clearMap(); |
|
339 | |||
340 | 1 | foreach ($this->values as $k) { |
|
341 | 1 | $v = $this->values[$k]; |
|
342 | |||
343 | 1 | $return = $predicate($k, $v); |
|
344 | |||
345 | 1 | if ($return === true) { |
|
346 | 1 | $truthy = $truthy->put($k, $v); |
|
347 | } else { |
||
348 | 1 | $falsy = $falsy->put($k, $v); |
|
349 | } |
||
350 | } |
||
351 | |||
352 | 1 | return Map::of('bool', Map::class) |
|
353 | 1 | (true, $truthy) |
|
354 | 1 | (false, $falsy); |
|
355 | } |
||
356 | |||
357 | /** |
||
358 | * {@inheritdoc} |
||
359 | */ |
||
360 | 9 | public function reduce($carry, callable $reducer) |
|
369 | } |
||
370 | |||
371 | 1 | public function empty(): bool |
|
372 | { |
||
373 | 1 | $this->values->rewind(); |
|
374 | |||
375 | 1 | return !$this->values->valid(); |
|
376 | } |
||
377 | |||
378 | /** |
||
379 | * @return Map<T, S> |
||
380 | */ |
||
381 | 2 | private function clearMap(): Map |
|
382 | { |
||
383 | 2 | return Map::of($this->keyType, $this->valueType); |
|
384 | } |
||
385 | } |
||
386 |