Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like EntityCollection 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 EntityCollection, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
11 | class EntityCollection extends Collection |
||
12 | { |
||
13 | /** |
||
14 | * Wrapper Factory |
||
15 | * |
||
16 | * @var \Analogue\ORM\System\Wrappers\Factory |
||
17 | */ |
||
18 | protected $factory; |
||
19 | |||
20 | /** |
||
21 | * EntityCollection constructor. |
||
22 | * @param array|null $entities |
||
23 | */ |
||
24 | public function __construct(array $entities = null) |
||
25 | { |
||
26 | $this->factory = new Factory; |
||
27 | |||
28 | parent::__construct($entities); |
||
29 | } |
||
30 | |||
31 | /** |
||
32 | * Find an entity in the collection by key. |
||
33 | * |
||
34 | * @param mixed $key |
||
35 | * @param mixed $default |
||
36 | * @throws MappingException |
||
37 | * @return \Analogue\ORM\Entity |
||
38 | */ |
||
39 | public function find($key, $default = null) |
||
40 | { |
||
41 | if ($key instanceof Mappable) { |
||
42 | $key = $this->getEntityKey($key); |
||
43 | } |
||
44 | |||
45 | return array_first($this->items, function ($itemKey, $entity) use ($key) { |
||
46 | return $this->getEntityKey($entity) == $key; |
||
47 | }, $default); |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * Add an entity to the collection. |
||
52 | * |
||
53 | * @param Mappable $entity |
||
54 | * @return $this |
||
55 | */ |
||
56 | public function add($entity) |
||
57 | { |
||
58 | $this->push($entity); |
||
59 | |||
60 | return $this; |
||
61 | } |
||
62 | |||
63 | /** |
||
64 | * Remove an entity from the collection |
||
65 | * |
||
66 | * @param $entity |
||
67 | * @throws MappingException |
||
68 | * @return mixed |
||
69 | */ |
||
70 | public function remove($entity) |
||
71 | { |
||
72 | $key = $this->getEntityKey($entity); |
||
73 | |||
74 | return $this->pull($key); |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * Push an item onto the end of the collection. |
||
79 | * |
||
80 | * @param mixed $value |
||
81 | * @return void |
||
82 | */ |
||
83 | public function push($value) |
||
87 | |||
88 | /** |
||
89 | * Put an item in the collection by key. |
||
90 | * |
||
91 | * @param mixed $key |
||
92 | * @param mixed $value |
||
93 | * @return void |
||
94 | */ |
||
95 | public function put($key, $value) |
||
99 | |||
100 | /** |
||
101 | * Set the item at a given offset. |
||
102 | * |
||
103 | * @param mixed $key |
||
104 | * @param mixed $value |
||
105 | * @return void |
||
106 | */ |
||
107 | public function offsetSet($key, $value) |
||
108 | { |
||
109 | if (is_null($key)) { |
||
110 | $this->items[] = $value; |
||
111 | } else { |
||
112 | $this->items[$key] = $value; |
||
113 | } |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * Determine if a key exists in the collection. |
||
118 | * |
||
119 | * @param mixed $key |
||
120 | * @param mixed|null $value |
||
121 | * @return bool |
||
122 | */ |
||
123 | public function contains($key, $value = null) |
||
124 | { |
||
125 | return !is_null($this->find($key)); |
||
126 | } |
||
127 | |||
128 | /** |
||
129 | * Fetch a nested element of the collection. |
||
130 | * |
||
131 | * @param string $key |
||
132 | * @return self |
||
133 | */ |
||
134 | public function fetch($key) |
||
135 | { |
||
136 | return new static(array_fetch($this->toArray(), $key)); |
||
137 | } |
||
138 | |||
139 | /** |
||
140 | * Generic function for returning class.key value pairs |
||
141 | * |
||
142 | * @throws MappingException |
||
143 | * @return string |
||
144 | */ |
||
145 | View Code Duplication | public function getEntityHashes() |
|
|
|||
146 | { |
||
147 | return array_map(function ($entity) { |
||
148 | $class = get_class($entity); |
||
149 | |||
150 | $mapper = Manager::getMapper($class); |
||
151 | |||
152 | $keyName = $mapper->getEntityMap()->getKeyName(); |
||
153 | |||
154 | return $class . '.' . $entity->getEntityAttribute($keyName); |
||
155 | }, |
||
156 | $this->items); |
||
157 | } |
||
158 | |||
159 | /** |
||
160 | * Get a subset of the collection from entity hashes |
||
161 | * |
||
162 | * @param array $hashes |
||
163 | * @throws MappingException |
||
164 | * @return array |
||
165 | */ |
||
166 | public function getSubsetByHashes(array $hashes) |
||
167 | { |
||
168 | $subset = []; |
||
169 | |||
170 | foreach ($this->items as $item) { |
||
171 | $class = get_class($item); |
||
172 | |||
173 | $mapper = Manager::getMapper($class); |
||
174 | |||
175 | $keyName = $mapper->getEntityMap()->getKeyName(); |
||
176 | |||
177 | if (in_array($class . '.' . $item->$keyName, $hashes)) { |
||
178 | $subset[] = $item; |
||
179 | } |
||
180 | } |
||
181 | |||
182 | return $subset; |
||
183 | } |
||
184 | |||
185 | /** |
||
186 | * Merge the collection with the given items. |
||
187 | * |
||
188 | * @param array $items |
||
189 | * @throws MappingException |
||
190 | * @return self |
||
191 | */ |
||
192 | public function merge($items) |
||
193 | { |
||
194 | $dictionary = $this->getDictionary(); |
||
195 | |||
196 | foreach ($items as $item) { |
||
197 | $dictionary[$this->getEntityKey($item)] = $item; |
||
198 | } |
||
199 | |||
200 | return new static(array_values($dictionary)); |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * Diff the collection with the given items. |
||
205 | * |
||
206 | * @param \ArrayAccess|array $items |
||
207 | * @return self |
||
208 | */ |
||
209 | View Code Duplication | public function diff($items) |
|
210 | { |
||
211 | $diff = new static; |
||
212 | |||
213 | $dictionary = $this->getDictionary($items); |
||
214 | |||
215 | foreach ($this->items as $item) { |
||
216 | if (!isset($dictionary[$this->getEntityKey($item)])) { |
||
217 | $diff->add($item); |
||
218 | } |
||
219 | } |
||
220 | |||
221 | return $diff; |
||
222 | } |
||
223 | |||
224 | /** |
||
225 | * Intersect the collection with the given items. |
||
226 | * |
||
227 | * @param \ArrayAccess|array $items |
||
228 | * @throws MappingException |
||
229 | * @return self |
||
230 | */ |
||
231 | View Code Duplication | public function intersect($items) |
|
232 | { |
||
233 | $intersect = new static; |
||
234 | |||
235 | $dictionary = $this->getDictionary($items); |
||
236 | |||
237 | foreach ($this->items as $item) { |
||
238 | if (isset($dictionary[$this->getEntityKey($item)])) { |
||
239 | $intersect->add($item); |
||
240 | } |
||
241 | } |
||
242 | |||
243 | return $intersect; |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * Returns only the models from the collection with the specified keys. |
||
248 | * |
||
249 | * @param mixed $keys |
||
250 | * @return self |
||
251 | */ |
||
252 | public function only($keys) |
||
253 | { |
||
254 | $dictionary = array_only($this->getDictionary(), $keys); |
||
255 | |||
256 | return new static(array_values($dictionary)); |
||
257 | } |
||
258 | |||
259 | /** |
||
260 | * Returns all models in the collection except the models with specified keys. |
||
261 | * |
||
262 | * @param mixed $keys |
||
263 | * @return self |
||
264 | */ |
||
265 | public function except($keys) |
||
266 | { |
||
267 | $dictionary = array_except($this->getDictionary(), $keys); |
||
268 | |||
269 | return new static(array_values($dictionary)); |
||
270 | } |
||
271 | |||
272 | /** |
||
273 | * Get a dictionary keyed by primary keys. |
||
274 | * |
||
275 | * @param \ArrayAccess|array $items |
||
276 | * @throws MappingException |
||
277 | * @return array |
||
278 | */ |
||
279 | public function getDictionary($items = null) |
||
280 | { |
||
281 | $items = is_null($items) ? $this->items : $items; |
||
282 | |||
283 | $dictionary = []; |
||
284 | |||
285 | foreach ($items as $value) { |
||
286 | $dictionary[$this->getEntityKey($value)] = $value; |
||
287 | } |
||
288 | |||
289 | return $dictionary; |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | * @throws MappingException |
||
294 | * @return array |
||
295 | */ |
||
296 | public function getEntityKeys() |
||
300 | |||
301 | /** |
||
302 | * @param $entity |
||
303 | * @throws MappingException |
||
304 | * @return mixed |
||
305 | */ |
||
306 | protected function getEntityKey($entity) |
||
307 | { |
||
308 | $keyName = Manager::getMapper($entity)->getEntityMap()->getKeyName(); |
||
309 | |||
310 | $wrapper = $this->factory->make($entity); |
||
311 | |||
312 | return $wrapper->getEntityAttribute($keyName); |
||
313 | } |
||
314 | |||
315 | /** |
||
316 | * Get the max value of a given key. |
||
317 | * |
||
318 | * @param string|null $key |
||
319 | * @throws MappingException |
||
320 | * @return mixed |
||
321 | */ |
||
322 | View Code Duplication | public function max($key = null) |
|
323 | { |
||
324 | return $this->reduce(function ($result, $item) use ($key) { |
||
325 | $wrapper = $this->factory->make($item); |
||
326 | |||
327 | return (is_null($result) || $wrapper->getEntityAttribute($key) > $result) ? |
||
328 | $wrapper->getEntityAttribute($key) : $result; |
||
329 | }); |
||
330 | } |
||
331 | |||
332 | /** |
||
333 | * Get the min value of a given key. |
||
334 | * |
||
335 | * @param string|null $key |
||
336 | * @throws MappingException |
||
337 | * @return mixed |
||
338 | */ |
||
339 | View Code Duplication | public function min($key = null) |
|
340 | { |
||
341 | return $this->reduce(function ($result, $item) use ($key) { |
||
342 | $wrapper = $this->factory->make($item); |
||
343 | |||
344 | return (is_null($result) || $wrapper->getEntityAttribute($key) < $result) |
||
345 | ? $wrapper->getEntityAttribute($key) : $result; |
||
346 | }); |
||
347 | } |
||
348 | |||
349 | /** |
||
350 | * Get an array with the values of a given key. |
||
351 | * |
||
352 | * @param string $value |
||
353 | * @param string|null $key |
||
354 | * @return self |
||
355 | */ |
||
356 | public function pluck($value, $key = null) |
||
360 | |||
361 | /** |
||
362 | * Alias for the "pluck" method. |
||
363 | * |
||
364 | * @param string $value |
||
365 | * @param string|null $key |
||
366 | * @return self |
||
367 | */ |
||
368 | public function lists($value, $key = null) |
||
372 | |||
373 | /** |
||
374 | * Return only unique items from the collection. |
||
375 | * |
||
376 | * @param string|null $key |
||
377 | * @throws MappingException |
||
378 | * @return self |
||
379 | */ |
||
380 | public function unique($key = null) |
||
381 | { |
||
382 | $dictionary = $this->getDictionary(); |
||
383 | |||
384 | return new static(array_values($dictionary)); |
||
385 | } |
||
386 | |||
387 | /** |
||
388 | * Get a base Support collection instance from this collection. |
||
389 | * |
||
390 | * @return \Illuminate\Support\Collection |
||
391 | */ |
||
392 | public function toBase() |
||
396 | } |
||
397 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.