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 EntityMap 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 EntityMap, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
24 | class EntityMap |
||
25 | { |
||
26 | /** |
||
27 | * The mapping driver to use with this entity |
||
28 | */ |
||
29 | protected $driver = 'illuminate'; |
||
30 | |||
31 | /** |
||
32 | * The Database Connection name for the model. |
||
33 | * |
||
34 | * @var string |
||
35 | */ |
||
36 | protected $connection; |
||
37 | |||
38 | /** |
||
39 | * The table associated with the entity. |
||
40 | * |
||
41 | * @var string|null |
||
42 | */ |
||
43 | protected $table = null; |
||
44 | |||
45 | /** |
||
46 | * The primary key for the model. |
||
47 | * |
||
48 | * @var string |
||
49 | */ |
||
50 | protected $primaryKey = 'id'; |
||
51 | |||
52 | /** |
||
53 | * Array containing a list of class attributes. Mandatory if the |
||
54 | * mapped entity is a Plain PHP Object. |
||
55 | * |
||
56 | * @var array |
||
57 | */ |
||
58 | protected $attributes = []; |
||
59 | |||
60 | /** |
||
61 | * The Custom Domain Class to use with this mapping |
||
62 | * |
||
63 | * @var string|null |
||
64 | */ |
||
65 | protected $class = null; |
||
66 | |||
67 | /** |
||
68 | * Attributes that should be treated as Value Objects |
||
69 | * |
||
70 | * @var array |
||
71 | */ |
||
72 | protected $embeddables = []; |
||
73 | |||
74 | /** |
||
75 | * Determine the relationships method used on the entity. |
||
76 | * If not set, mapper will autodetect them |
||
77 | * |
||
78 | * @var array |
||
79 | */ |
||
80 | private $relationships = []; |
||
81 | |||
82 | /** |
||
83 | * Relationships that should be treated as collection. |
||
84 | * |
||
85 | * @var array |
||
86 | */ |
||
87 | private $manyRelations = []; |
||
88 | |||
89 | /** |
||
90 | * Relationships that should be treated as single entity. |
||
91 | * |
||
92 | * @var array |
||
93 | */ |
||
94 | private $singleRelations = []; |
||
95 | |||
96 | /** |
||
97 | * Relationships for which the key is stored in the Entity itself |
||
98 | * |
||
99 | * @var array |
||
100 | */ |
||
101 | private $localRelations = []; |
||
102 | |||
103 | /** |
||
104 | * Relationships for which the key is stored in the Related Entity |
||
105 | * |
||
106 | * @var array |
||
107 | */ |
||
108 | private $foreignRelations = []; |
||
109 | |||
110 | /** |
||
111 | * Relationships which use a pivot record. |
||
112 | * |
||
113 | * @var array |
||
114 | */ |
||
115 | private $pivotRelations = []; |
||
116 | |||
117 | /** |
||
118 | * Dynamic relationships |
||
119 | * |
||
120 | * @var array |
||
121 | */ |
||
122 | private $dynamicRelationships = []; |
||
123 | |||
124 | /** |
||
125 | * The number of models to return for pagination. |
||
126 | * |
||
127 | * @var int |
||
128 | */ |
||
129 | protected $perPage = 15; |
||
130 | |||
131 | /** |
||
132 | * The relations to eager load on every query. |
||
133 | * |
||
134 | * @var array |
||
135 | */ |
||
136 | protected $with = []; |
||
137 | |||
138 | /** |
||
139 | * The class name to be used in polymorphic relations. |
||
140 | * |
||
141 | * @var string |
||
142 | */ |
||
143 | protected $morphClass; |
||
144 | |||
145 | /** |
||
146 | * Sequence name, to be used with postgreSql |
||
147 | * defaults to %table_name%_id_seq |
||
148 | * |
||
149 | * @var string|null |
||
150 | */ |
||
151 | protected $sequence = null; |
||
152 | |||
153 | /** |
||
154 | * Indicates if the entity should be timestamped. |
||
155 | * |
||
156 | * @var bool |
||
157 | */ |
||
158 | public $timestamps = false; |
||
159 | |||
160 | /** |
||
161 | * The name of the "created at" column. |
||
162 | * |
||
163 | * @var string |
||
164 | */ |
||
165 | protected $createdAtColumn = 'created_at'; |
||
166 | |||
167 | /** |
||
168 | * The name of the "updated at" column. |
||
169 | * |
||
170 | * @var string |
||
171 | */ |
||
172 | protected $updatedAtColumn = 'updated_at'; |
||
173 | |||
174 | /** |
||
175 | * Indicates if the entity uses softdeletes |
||
176 | * |
||
177 | * @var boolean |
||
178 | */ |
||
179 | public $softDeletes = false; |
||
180 | |||
181 | /** |
||
182 | * The name of the "deleted at" column. |
||
183 | * |
||
184 | * @var string |
||
185 | */ |
||
186 | protected $deletedAtColumn = 'deleted_at'; |
||
187 | |||
188 | /** |
||
189 | * The many to many relationship methods. |
||
190 | * |
||
191 | * @var array |
||
192 | */ |
||
193 | protected static $manyMethods = ['belongsToMany', 'morphToMany', 'morphedByMany']; |
||
194 | |||
195 | /** |
||
196 | * The 'Many' relationships classes, which related Entity attribute should be |
||
197 | * an array/entityCollection |
||
198 | * |
||
199 | * @var array |
||
200 | */ |
||
201 | protected static $manyClasses = ['BelongsToMany', 'HasMany', 'HasManyThrough', 'MorphMany', 'MorphToMany']; |
||
202 | |||
203 | /** |
||
204 | * The 'Single' relationships classes, which related Entity attribute should be |
||
205 | * another Entity. |
||
206 | * |
||
207 | * @var array |
||
208 | */ |
||
209 | protected static $singleClasses = ['BelongsTo', 'HasOne', 'MorphOne', 'MorphTo']; |
||
210 | |||
211 | /** |
||
212 | * Relationships with a pivot record |
||
213 | * |
||
214 | * @var array |
||
215 | */ |
||
216 | protected static $pivotClasses = ['BelongsToMany', 'MorphToMany']; |
||
217 | |||
218 | /** |
||
219 | * Relationships on which key is stored in the Entity itself |
||
220 | * |
||
221 | * @var array |
||
222 | */ |
||
223 | protected static $localClasses = ['BelongsTo', 'MorphTo']; |
||
224 | |||
225 | /** |
||
226 | * Relationships on which key is stored in the related Entity record or in a pivot record |
||
227 | * |
||
228 | * @var array |
||
229 | */ |
||
230 | protected static $foreignClasses = [ |
||
231 | 'BelongsToMany', |
||
232 | 'HasMany', |
||
233 | 'HasManyThrough', |
||
234 | 'MorphMany', |
||
235 | 'MorphToMany', |
||
236 | 'HasOne', |
||
237 | 'MorphOne', |
||
238 | ]; |
||
239 | |||
240 | /** |
||
241 | * The date format to use with the current database connection |
||
242 | * |
||
243 | * @var string |
||
244 | */ |
||
245 | protected $dateFormat; |
||
246 | |||
247 | /** |
||
248 | * The Analogue's manager instance. |
||
249 | * |
||
250 | * @var \Analogue\ORM\System\Manager |
||
251 | */ |
||
252 | private $manager; |
||
253 | |||
254 | /** |
||
255 | * Set the Manager that will be used for relationship's mapper instantiations. |
||
256 | * |
||
257 | * @param Manager $manager |
||
258 | */ |
||
259 | public function setManager(Manager $manager) |
||
263 | |||
264 | /** |
||
265 | * Return Domain class attributes, useful when mapping to a Plain PHP Object |
||
266 | * |
||
267 | * @return array |
||
268 | */ |
||
269 | public function getAttributes() |
||
270 | { |
||
271 | return $this->attributes; |
||
272 | } |
||
273 | |||
274 | /** |
||
275 | * Set the domain class attributes |
||
276 | * |
||
277 | * @param array $attributeNames |
||
278 | */ |
||
279 | public function setAttributes(array $attributeNames) |
||
280 | { |
||
281 | $this->attributes = $attributeNames; |
||
282 | } |
||
283 | |||
284 | /** |
||
285 | * Get all the attribute names for the class, including relationships, embeddables and primary key. |
||
286 | * |
||
287 | * @return array |
||
288 | */ |
||
289 | public function getCompiledAttributes() |
||
290 | { |
||
291 | $key = $this->getKeyName(); |
||
292 | |||
293 | $embeddables = array_keys($this->getEmbeddables()); |
||
294 | |||
295 | $relationships = $this->getRelationships(); |
||
296 | |||
297 | $attributes = $this->getAttributes(); |
||
298 | |||
299 | return array_merge([$key], $embeddables, $relationships, $attributes); |
||
300 | } |
||
301 | |||
302 | /** |
||
303 | * Set the date format to use with the current database connection |
||
304 | * |
||
305 | * @param string $format |
||
306 | */ |
||
307 | public function setDateFormat($format) |
||
308 | { |
||
309 | $this->dateFormat = $format; |
||
310 | } |
||
311 | |||
312 | /** |
||
313 | * Get the date format to use with the current database connection |
||
314 | * |
||
315 | * @return string |
||
316 | */ |
||
317 | public function getDateFormat() |
||
318 | { |
||
319 | return $this->dateFormat; |
||
320 | } |
||
321 | |||
322 | /** |
||
323 | * Set the Driver for this mapping |
||
324 | * |
||
325 | * @param string $driver |
||
326 | */ |
||
327 | public function setDriver($driver) |
||
331 | |||
332 | /** |
||
333 | * Get the Driver for this mapping. |
||
334 | * |
||
335 | * @return string |
||
336 | */ |
||
337 | public function getDriver() |
||
341 | |||
342 | /** |
||
343 | * Set the db connection to use on the table |
||
344 | * |
||
345 | * @param $connection |
||
346 | */ |
||
347 | public function setConnection($connection) |
||
348 | { |
||
349 | $this->connection = $connection; |
||
350 | } |
||
351 | |||
352 | /** |
||
353 | * Get the Database connection the Entity is stored on. |
||
354 | * |
||
355 | * @return string |
||
356 | */ |
||
357 | public function getConnection() |
||
358 | { |
||
359 | return $this->connection; |
||
360 | } |
||
361 | |||
362 | /** |
||
363 | * Get the table associated with the entity. |
||
364 | * |
||
365 | * @return string |
||
366 | */ |
||
367 | public function getTable() |
||
368 | { |
||
369 | if (!is_null($this->table)) { |
||
370 | return $this->table; |
||
371 | } |
||
372 | |||
373 | return str_replace('\\', '', snake_case(str_plural(class_basename($this->getClass())))); |
||
374 | } |
||
375 | |||
376 | /** |
||
377 | * Set the database table name |
||
378 | * |
||
379 | * @param string $table |
||
380 | */ |
||
381 | public function setTable($table) |
||
382 | { |
||
383 | $this->table = $table; |
||
384 | } |
||
385 | |||
386 | /** |
||
387 | * Get the pgSql sequence name |
||
388 | * |
||
389 | * @return string |
||
390 | */ |
||
391 | public function getSequence() |
||
392 | { |
||
393 | if (!is_null($this->sequence)) { |
||
394 | return $this->sequence; |
||
395 | } else { |
||
396 | return $this->getTable() . '_id_seq'; |
||
397 | } |
||
398 | } |
||
399 | |||
400 | /** |
||
401 | * Get the custom entity class |
||
402 | * |
||
403 | * @return string namespaced class name |
||
404 | */ |
||
405 | public function getClass() |
||
406 | { |
||
407 | return isset($this->class) ? $this->class : null; |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * Set the custom entity class |
||
412 | * |
||
413 | * @param string $class namespaced class name |
||
414 | */ |
||
415 | public function setClass($class) |
||
416 | { |
||
417 | // Throw exception if class not exists |
||
418 | |||
419 | $this->class = $class; |
||
420 | } |
||
421 | |||
422 | /** |
||
423 | * Get the embedded Value Objects |
||
424 | * |
||
425 | * @return array |
||
426 | */ |
||
427 | public function getEmbeddables() |
||
428 | { |
||
429 | return $this->embeddables; |
||
430 | } |
||
431 | |||
432 | /** |
||
433 | * Set the embedded Value Objects |
||
434 | * |
||
435 | * @param array $embeddables |
||
436 | */ |
||
437 | public function setEmbeddables(array $embeddables) |
||
438 | { |
||
439 | $this->embeddables = $embeddables; |
||
440 | } |
||
441 | |||
442 | /** |
||
443 | * Get the relationships to map on a custom domain |
||
444 | * class. |
||
445 | * |
||
446 | * @return array |
||
447 | */ |
||
448 | public function getRelationships() |
||
449 | { |
||
450 | return $this->relationships; |
||
451 | } |
||
452 | |||
453 | /** |
||
454 | * Relationships of the Entity type |
||
455 | * |
||
456 | * @return array |
||
457 | */ |
||
458 | public function getSingleRelationships() |
||
459 | { |
||
460 | return $this->singleRelations; |
||
461 | } |
||
462 | |||
463 | /** |
||
464 | * Relationships of type Collection |
||
465 | * |
||
466 | * @return array |
||
467 | */ |
||
468 | public function getManyRelationships() |
||
469 | { |
||
470 | return $this->manyRelations; |
||
471 | } |
||
472 | |||
473 | /** |
||
474 | * Relationships with foreign key in the mapped entity record. |
||
475 | * |
||
476 | * @return array |
||
477 | */ |
||
478 | public function getLocalRelationships() |
||
482 | |||
483 | /** |
||
484 | * Relationships with foreign key in the related Entity record |
||
485 | * |
||
486 | * @return array |
||
487 | */ |
||
488 | public function getForeignRelationships() |
||
492 | |||
493 | /** |
||
494 | * Relationships which keys are stored in a pivot record |
||
495 | * |
||
496 | * @return array |
||
497 | */ |
||
498 | public function getPivotRelationships() |
||
502 | |||
503 | /** |
||
504 | * Add a Dynamic Relationship method at runtime. This has to be done |
||
505 | * by hooking the 'initializing' event, before entityMap is initialized. |
||
506 | * |
||
507 | * @param string $name Relation name |
||
508 | * @param \Closure $relationship |
||
509 | * |
||
510 | * @return void |
||
511 | */ |
||
512 | public function addRelationshipMethod($name, \Closure $relationship) |
||
513 | { |
||
514 | $this->dynamicRelationships[$name] = $relationship; |
||
515 | } |
||
516 | |||
517 | /** |
||
518 | * Get the dynamic relationship method names. |
||
519 | * |
||
520 | * @return array |
||
521 | */ |
||
522 | public function getDynamicRelationships() |
||
523 | { |
||
524 | return array_keys($this->dynamicRelationships); |
||
525 | } |
||
526 | |||
527 | /** |
||
528 | * Get the relationships that have to be eager loaded |
||
529 | * on each request. |
||
530 | * |
||
531 | * @return array |
||
532 | */ |
||
533 | public function getEagerloadedRelationships() |
||
534 | { |
||
535 | return $this->with; |
||
536 | } |
||
537 | |||
538 | /** |
||
539 | * Get the primary key for the entity. |
||
540 | * |
||
541 | * @return string |
||
542 | */ |
||
543 | public function getKeyName() |
||
544 | { |
||
545 | return $this->primaryKey; |
||
546 | } |
||
547 | |||
548 | /** |
||
549 | * Set the primary key for the entity. |
||
550 | * |
||
551 | * @param $key |
||
552 | * @return void |
||
553 | */ |
||
554 | public function setKeyName($key) |
||
555 | { |
||
556 | $this->primaryKey = $key; |
||
557 | } |
||
558 | |||
559 | /** |
||
560 | * Get the table qualified key name. |
||
561 | * |
||
562 | * @return string |
||
563 | */ |
||
564 | public function getQualifiedKeyName() |
||
565 | { |
||
566 | return $this->getTable() . '.' . $this->getKeyName(); |
||
567 | } |
||
568 | |||
569 | /** |
||
570 | * Get the number of models to return per page. |
||
571 | * |
||
572 | * @return int |
||
573 | */ |
||
574 | public function getPerPage() |
||
575 | { |
||
576 | return $this->perPage; |
||
577 | } |
||
578 | |||
579 | /** |
||
580 | * Set the number of models to return per page. |
||
581 | * |
||
582 | * @param int $perPage |
||
583 | * @return void |
||
584 | */ |
||
585 | public function setPerPage($perPage) |
||
586 | { |
||
587 | $this->perPage = $perPage; |
||
588 | } |
||
589 | |||
590 | /** |
||
591 | * Determine if the entity uses get. |
||
592 | * |
||
593 | * @return bool |
||
594 | */ |
||
595 | public function usesTimestamps() |
||
596 | { |
||
597 | return $this->timestamps; |
||
598 | } |
||
599 | |||
600 | /** |
||
601 | * Determine if the entity uses soft deletes |
||
602 | * |
||
603 | * @return bool |
||
604 | */ |
||
605 | public function usesSoftDeletes() |
||
606 | { |
||
607 | return $this->softDeletes; |
||
608 | } |
||
609 | |||
610 | /** |
||
611 | * Get the 'created_at' column name |
||
612 | * |
||
613 | * @return string |
||
614 | */ |
||
615 | public function getCreatedAtColumn() |
||
616 | { |
||
617 | return $this->createdAtColumn; |
||
618 | } |
||
619 | |||
620 | /** |
||
621 | * Get the 'updated_at' column name |
||
622 | * |
||
623 | * @return string |
||
624 | */ |
||
625 | public function getUpdatedAtColumn() |
||
626 | { |
||
627 | return $this->updatedAtColumn; |
||
628 | } |
||
629 | |||
630 | /** |
||
631 | * Get the deleted_at column |
||
632 | * |
||
633 | * @return string |
||
634 | */ |
||
635 | public function getQualifiedDeletedAtColumn() |
||
636 | { |
||
637 | return $this->deletedAtColumn; |
||
638 | } |
||
639 | |||
640 | /** |
||
641 | * Get the default foreign key name for the model. |
||
642 | * |
||
643 | * @return string |
||
644 | */ |
||
645 | public function getForeignKey() |
||
646 | { |
||
647 | return snake_case(class_basename($this->getClass())) . '_id'; |
||
648 | } |
||
649 | |||
650 | /** |
||
651 | * Define a one-to-one relationship. |
||
652 | * |
||
653 | * @param $entity |
||
654 | * @param string $relatedClass entity class |
||
655 | * @param string $foreignKey |
||
656 | * @param string $localKey |
||
657 | * @throws MappingException |
||
658 | * @return \Analogue\ORM\Relationships\HasOne |
||
659 | */ |
||
660 | View Code Duplication | public function hasOne($entity, $relatedClass, $foreignKey = null, $localKey = null) |
|
|
|||
661 | { |
||
662 | $foreignKey = $foreignKey ?: $this->getForeignKey(); |
||
663 | |||
664 | $relatedMapper = $this->manager->mapper($relatedClass); |
||
665 | |||
666 | $relatedMap = $relatedMapper->getEntityMap(); |
||
667 | |||
668 | $localKey = $localKey ?: $this->getKeyName(); |
||
669 | |||
670 | return new HasOne($relatedMapper, $entity, $relatedMap->getTable() . '.' . $foreignKey, $localKey); |
||
671 | } |
||
672 | |||
673 | /** |
||
674 | * Define a polymorphic one-to-one relationship. |
||
675 | * |
||
676 | * @param mixed $entity |
||
677 | * @param string $related |
||
678 | * @param string $name |
||
679 | * @param string|null $type |
||
680 | * @param string|null $id |
||
681 | * @param string|null $localKey |
||
682 | * @throws MappingException |
||
683 | * @return \Analogue\ORM\Relationships\MorphOne |
||
684 | */ |
||
685 | View Code Duplication | public function morphOne($entity, $related, $name, $type = null, $id = null, $localKey = null) |
|
686 | { |
||
687 | list($type, $id) = $this->getMorphs($name, $type, $id); |
||
688 | |||
689 | $localKey = $localKey ?: $this->getKeyName(); |
||
690 | |||
691 | $relatedMapper = $this->manager->mapper($related); |
||
692 | |||
693 | $table = $relatedMapper->getEntityMap()->getTable(); |
||
694 | |||
695 | return new MorphOne($relatedMapper, $entity, $table . '.' . $type, $table . '.' . $id, $localKey); |
||
696 | } |
||
697 | |||
698 | /** |
||
699 | * Define an inverse one-to-one or many relationship. |
||
700 | * |
||
701 | * @param mixed $entity |
||
702 | * @param string $related |
||
703 | * @param string|null $foreignKey |
||
704 | * @param string|null $otherKey |
||
705 | * @param string|null $relation |
||
706 | * @throws MappingException |
||
707 | * @return \Analogue\ORM\Relationships\BelongsTo |
||
708 | */ |
||
709 | public function belongsTo($entity, $related, $foreignKey = null, $otherKey = null, $relation = null) |
||
710 | { |
||
711 | // If no relation name was given, we will use this debug backtrace to extract |
||
712 | // the calling method's name and use that as the relationship name as most |
||
713 | // of the time this will be what we desire to use for the relationships. |
||
714 | View Code Duplication | if (is_null($relation)) { |
|
715 | list(, $caller) = debug_backtrace(false); |
||
716 | |||
717 | $relation = $caller['function']; |
||
718 | } |
||
719 | |||
720 | // If no foreign key was supplied, we can use a backtrace to guess the proper |
||
721 | // foreign key name by using the name of the relationship function, which |
||
722 | // when combined with an "_id" should conventionally match the columns. |
||
723 | if (is_null($foreignKey)) { |
||
724 | $foreignKey = snake_case($relation) . '_id'; |
||
725 | } |
||
726 | |||
727 | $relatedMapper = $this->manager->mapper($related); |
||
728 | |||
729 | $otherKey = $otherKey ?: $relatedMapper->getEntityMap()->getKeyName(); |
||
730 | |||
731 | return new BelongsTo($relatedMapper, $entity, $foreignKey, $otherKey, $relation); |
||
732 | } |
||
733 | |||
734 | /** |
||
735 | * Define a polymorphic, inverse one-to-one or many relationship. |
||
736 | * |
||
737 | * @param mixed $entity |
||
738 | * @param string|null $name |
||
739 | * @param string|null $type |
||
740 | * @param string|null $id |
||
741 | * @throws MappingException |
||
742 | * @return \Analogue\ORM\Relationships\MorphTo |
||
743 | */ |
||
744 | public function morphTo($entity, $name = null, $type = null, $id = null) |
||
745 | { |
||
746 | // If no name is provided, we will use the backtrace to get the function name |
||
747 | // since that is most likely the name of the polymorphic interface. We can |
||
748 | // use that to get both the class and foreign key that will be utilized. |
||
749 | View Code Duplication | if (is_null($name)) { |
|
750 | list(, $caller) = debug_backtrace(false); |
||
751 | |||
752 | $name = snake_case($caller['function']); |
||
753 | } |
||
754 | |||
755 | list($type, $id) = $this->getMorphs($name, $type, $id); |
||
756 | |||
757 | $mapper = $this->manager->mapper(get_class($entity)); |
||
758 | |||
759 | // If the type value is null it is probably safe to assume we're eager loading |
||
760 | // the relationship. When that is the case we will pass in a dummy query as |
||
761 | // there are multiple types in the morph and we can't use single queries. |
||
762 | $factory = new Factory; |
||
763 | $wrapper = $factory->make($entity); |
||
764 | |||
765 | if (is_null($class = $wrapper->getEntityAttribute($type))) { |
||
766 | return new MorphTo( |
||
767 | $mapper, $entity, $id, null, $type, $name |
||
768 | ); |
||
769 | } |
||
770 | |||
771 | // If we are not eager loading the relationship we will essentially treat this |
||
772 | // as a belongs-to style relationship since morph-to extends that class and |
||
773 | // we will pass in the appropriate values so that it behaves as expected. |
||
774 | else { |
||
775 | $class = $this->manager->getInverseMorphMap($class); |
||
776 | $relatedMapper = $this->manager->mapper($class); |
||
777 | |||
778 | $foreignKey = $relatedMapper->getEntityMap()->getKeyName(); |
||
779 | |||
780 | return new MorphTo( |
||
781 | $relatedMapper, $entity, $id, $foreignKey, $type, $name |
||
782 | ); |
||
783 | } |
||
784 | } |
||
785 | |||
786 | /** |
||
787 | * Define a one-to-many relationship. |
||
788 | * |
||
789 | * @param mixed $entity |
||
790 | * @param string $related |
||
791 | * @param string|null $foreignKey |
||
792 | * @param string|null $localKey |
||
793 | * @throws MappingException |
||
794 | * @return \Analogue\ORM\Relationships\HasMany |
||
795 | */ |
||
796 | View Code Duplication | public function hasMany($entity, $related, $foreignKey = null, $localKey = null) |
|
797 | { |
||
798 | $foreignKey = $foreignKey ?: $this->getForeignKey(); |
||
799 | |||
800 | $relatedMapper = $this->manager->mapper($related); |
||
801 | |||
802 | $table = $relatedMapper->getEntityMap()->getTable() . '.' . $foreignKey; |
||
803 | |||
804 | $localKey = $localKey ?: $this->getKeyName(); |
||
805 | |||
806 | return new HasMany($relatedMapper, $entity, $table, $localKey); |
||
807 | } |
||
808 | |||
809 | /** |
||
810 | * Define a has-many-through relationship. |
||
811 | * |
||
812 | * @param mixed $entity |
||
813 | * @param string $related |
||
814 | * @param string $through |
||
815 | * @param string|null $firstKey |
||
816 | * @param string|null $secondKey |
||
817 | * @throws MappingException |
||
818 | * @return \Analogue\ORM\Relationships\HasManyThrough |
||
819 | */ |
||
820 | public function hasManyThrough($entity, $related, $through, $firstKey = null, $secondKey = null) |
||
821 | { |
||
822 | $relatedMapper = $this->manager->mapper($related); |
||
823 | |||
824 | $throughMapper = $this->manager->mapper($through); |
||
825 | |||
826 | |||
827 | $firstKey = $firstKey ?: $this->getForeignKey(); |
||
828 | |||
829 | $throughMap = $throughMapper->getEntityMap(); |
||
830 | |||
831 | $secondKey = $secondKey ?: $throughMap->getForeignKey(); |
||
832 | |||
833 | return new HasManyThrough($relatedMapper, $entity, $throughMap, $firstKey, $secondKey); |
||
834 | } |
||
835 | |||
836 | /** |
||
837 | * Define a polymorphic one-to-many relationship. |
||
838 | * |
||
839 | * @param mixed $entity |
||
840 | * @param string $related |
||
841 | * @param string $name |
||
842 | * @param string|null $type |
||
843 | * @param string|null $id |
||
844 | * @param string|null $localKey |
||
845 | * @return \Analogue\ORM\Relationships\MorphMany |
||
846 | */ |
||
847 | View Code Duplication | public function morphMany($entity, $related, $name, $type = null, $id = null, $localKey = null) |
|
848 | { |
||
849 | // Here we will gather up the morph type and ID for the relationship so that we |
||
850 | // can properly query the intermediate table of a relation. Finally, we will |
||
851 | // get the table and create the relationship instances for the developers. |
||
852 | list($type, $id) = $this->getMorphs($name, $type, $id); |
||
853 | |||
854 | $relatedMapper = $this->manager->mapper($related); |
||
855 | |||
856 | $table = $relatedMapper->getEntityMap()->getTable(); |
||
857 | |||
858 | $localKey = $localKey ?: $this->getKeyName(); |
||
859 | |||
860 | return new MorphMany($relatedMapper, $entity, $table . '.' . $type, $table . '.' . $id, $localKey); |
||
861 | } |
||
862 | |||
863 | /** |
||
864 | * Define a many-to-many relationship. |
||
865 | * |
||
866 | * @param mixed $entity |
||
867 | * @param string $related |
||
868 | * @param string|null $table |
||
869 | * @param string|null $foreignKey |
||
870 | * @param string|null $otherKey |
||
871 | * @param string|null $relation |
||
872 | * @throws MappingException |
||
873 | * @return \Analogue\ORM\Relationships\BelongsToMany |
||
874 | */ |
||
875 | public function belongsToMany($entity, $related, $table = null, $foreignKey = null, $otherKey = null, $relation = null) |
||
876 | { |
||
877 | // If no relationship name was passed, we will pull backtraces to get the |
||
878 | // name of the calling function. We will use that function name as the |
||
879 | // title of this relation since that is a great convention to apply. |
||
880 | if (is_null($relation)) { |
||
881 | $relation = $this->getBelongsToManyCaller(); |
||
882 | } |
||
883 | |||
884 | // First, we'll need to determine the foreign key and "other key" for the |
||
885 | // relationship. Once we have determined the keys we'll make the query |
||
886 | // instances as well as the relationship instances we need for this. |
||
887 | $foreignKey = $foreignKey ?: $this->getForeignKey(); |
||
888 | |||
889 | $relatedMapper = $this->manager->mapper($related); |
||
890 | |||
891 | $relatedMap = $relatedMapper->getEntityMap(); |
||
892 | |||
893 | $otherKey = $otherKey ?: $relatedMap->getForeignKey(); |
||
894 | |||
895 | // If no table name was provided, we can guess it by concatenating the two |
||
896 | // models using underscores in alphabetical order. The two model names |
||
897 | // are transformed to snake case from their default CamelCase also. |
||
898 | if (is_null($table)) { |
||
899 | $table = $this->joiningTable($relatedMap); |
||
900 | } |
||
901 | |||
902 | return new BelongsToMany($relatedMapper, $entity, $table, $foreignKey, $otherKey, $relation); |
||
903 | } |
||
904 | |||
905 | /** |
||
906 | * Define a polymorphic many-to-many relationship. |
||
907 | * |
||
908 | * @param mixed $entity |
||
909 | * @param string $related |
||
910 | * @param string $name |
||
911 | * @param string|null $table |
||
912 | * @param string|null $foreignKey |
||
913 | * @param string|null $otherKey |
||
914 | * @param bool $inverse |
||
915 | * @throws MappingException |
||
916 | * @return \Analogue\ORM\Relationships\MorphToMany |
||
917 | */ |
||
918 | public function morphToMany($entity, $related, $name, $table = null, $foreignKey = null, $otherKey = null, $inverse = false) |
||
919 | { |
||
920 | $caller = $this->getBelongsToManyCaller(); |
||
921 | |||
922 | // First, we will need to determine the foreign key and "other key" for the |
||
923 | // relationship. Once we have determined the keys we will make the query |
||
924 | // instances, as well as the relationship instances we need for these. |
||
925 | $foreignKey = $foreignKey ?: $name . '_id'; |
||
926 | |||
927 | $relatedMapper = $this->manager->mapper($related); |
||
928 | |||
929 | $otherKey = $otherKey ?: $relatedMapper->getEntityMap()->getForeignKey(); |
||
930 | |||
931 | $table = $table ?: str_plural($name); |
||
932 | |||
933 | return new MorphToMany($relatedMapper, $entity, $name, $table, $foreignKey, $otherKey, $caller, $inverse); |
||
934 | } |
||
935 | |||
936 | /** |
||
937 | * Define a polymorphic, inverse many-to-many relationship. |
||
938 | * |
||
939 | * @param mixed $entity |
||
940 | * @param string $related |
||
941 | * @param string $name |
||
942 | * @param string|null $table |
||
943 | * @param string|null $foreignKey |
||
944 | * @param string|null $otherKey |
||
945 | * @throws MappingException |
||
946 | * @return \Analogue\ORM\Relationships\MorphToMany |
||
947 | */ |
||
948 | public function morphedByMany($entity, $related, $name, $table = null, $foreignKey = null, $otherKey = null) |
||
949 | { |
||
950 | $foreignKey = $foreignKey ?: $this->getForeignKey(); |
||
951 | |||
952 | // For the inverse of the polymorphic many-to-many relations, we will change |
||
953 | // the way we determine the foreign and other keys, as it is the opposite |
||
954 | // of the morph-to-many method since we're figuring out these inverses. |
||
955 | $otherKey = $otherKey ?: $name . '_id'; |
||
956 | |||
957 | return $this->morphToMany($entity, $related, $name, $table, $foreignKey, $otherKey, true); |
||
958 | } |
||
959 | |||
960 | /** |
||
961 | * Get the relationship name of the belongs to many. |
||
962 | * |
||
963 | * @return string |
||
964 | */ |
||
965 | protected function getBelongsToManyCaller() |
||
966 | { |
||
967 | $self = __FUNCTION__; |
||
968 | |||
969 | $caller = array_first(debug_backtrace(false), function ($key, $trace) use ($self) { |
||
970 | $caller = $trace['function']; |
||
971 | |||
972 | return (!in_array($caller, EntityMap::$manyMethods) && $caller != $self); |
||
973 | }); |
||
974 | |||
975 | return !is_null($caller) ? $caller['function'] : null; |
||
976 | } |
||
977 | |||
978 | /** |
||
979 | * Get the joining table name for a many-to-many relation. |
||
980 | * |
||
981 | * @param EntityMap $relatedMap |
||
982 | * @return string |
||
983 | */ |
||
984 | public function joiningTable($relatedMap) |
||
985 | { |
||
986 | // The joining table name, by convention, is simply the snake cased models |
||
987 | // sorted alphabetically and concatenated with an underscore, so we can |
||
988 | // just sort the models and join them together to get the table name. |
||
989 | $base = $this->getTable(); |
||
990 | |||
991 | $related = $relatedMap->getTable(); |
||
992 | |||
993 | $tables = [$related, $base]; |
||
994 | |||
995 | // Now that we have the model names in an array we can just sort them and |
||
996 | // use the implode function to join them together with an underscores, |
||
997 | // which is typically used by convention within the database system. |
||
998 | sort($tables); |
||
999 | |||
1000 | return strtolower(implode('_', $tables)); |
||
1001 | } |
||
1002 | |||
1003 | /** |
||
1004 | * Get the polymorphic relationship columns. |
||
1005 | * |
||
1006 | * @param string $name |
||
1007 | * @param string $type |
||
1008 | * @param string $id |
||
1009 | * @return string[] |
||
1010 | */ |
||
1011 | protected function getMorphs($name, $type, $id) |
||
1012 | { |
||
1013 | $type = $type ?: $name . '_type'; |
||
1014 | |||
1015 | $id = $id ?: $name . '_id'; |
||
1016 | |||
1017 | return [$type, $id]; |
||
1018 | } |
||
1019 | |||
1020 | /** |
||
1021 | * Get the class name for polymorphic relations. |
||
1022 | * |
||
1023 | * @return string |
||
1024 | */ |
||
1025 | public function getMorphClass() |
||
1026 | { |
||
1027 | $morphClass = $this->manager->getMorphMap($this->getClass()); |
||
1028 | return $this->morphClass ?: $morphClass; |
||
1029 | } |
||
1030 | |||
1031 | /** |
||
1032 | * Create a new Entity Collection instance. |
||
1033 | * |
||
1034 | * @param array $entities |
||
1035 | * @return \Analogue\ORM\EntityCollection |
||
1036 | */ |
||
1037 | public function newCollection(array $entities = []) |
||
1038 | { |
||
1039 | return new EntityCollection($entities, $this); |
||
1040 | } |
||
1041 | |||
1042 | /** |
||
1043 | * Process EntityMap parsing at initialization time |
||
1044 | * |
||
1045 | * @return void |
||
1046 | */ |
||
1047 | public function initialize() |
||
1061 | |||
1062 | /** |
||
1063 | * Parse every relationships on the EntityMap and sort |
||
1064 | * them by type. |
||
1065 | * |
||
1066 | * @return void |
||
1067 | */ |
||
1068 | public function boot() |
||
1074 | |||
1075 | /** |
||
1076 | * Get Methods that has been added in the child class. |
||
1077 | * |
||
1078 | * @return array |
||
1079 | */ |
||
1080 | protected function getCustomMethods() |
||
1088 | |||
1089 | /** |
||
1090 | * Parse user's class methods for relationships |
||
1091 | * |
||
1092 | * @param array $customMethods |
||
1093 | * @return array |
||
1094 | */ |
||
1095 | protected function parseMethodsForRelationship(array $customMethods) |
||
1120 | |||
1121 | /** |
||
1122 | * Sort Relationships methods by type |
||
1123 | * |
||
1124 | * @return void |
||
1125 | */ |
||
1126 | protected function sortRelationshipsByType() |
||
1159 | |||
1160 | /** |
||
1161 | * Override this method for custom entity instantiation |
||
1162 | * |
||
1163 | * @return null |
||
1164 | */ |
||
1165 | public function activator() |
||
1169 | |||
1170 | /** |
||
1171 | * Call dynamic relationship, if it exists |
||
1172 | * |
||
1173 | * @param string $method |
||
1174 | * @param array $parameters |
||
1175 | * @throws Exception |
||
1176 | * @return mixed |
||
1177 | */ |
||
1178 | public function __call($method, $parameters) |
||
1179 | { |
||
1180 | if (!array_key_exists($method, $this->dynamicRelationships)) { |
||
1181 | throw new Exception(get_class($this) . " has no method $method"); |
||
1182 | } |
||
1183 | |||
1184 | // Add $this to parameters so the closure can call relationship method on the map. |
||
1185 | $parameters[] = $this; |
||
1186 | |||
1187 | return call_user_func_array([$this->dynamicRelationships[$method], $parameters]); |
||
1188 | } |
||
1189 | } |
||
1190 |
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.