EntityMap   F
last analyzed

Complexity

Total Complexity 103

Size/Duplication

Total Lines 1166
Duplicated Lines 5.23 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 15
Bugs 4 Features 1
Metric Value
c 15
b 4
f 1
dl 61
loc 1166
rs 2.4516
wmc 103
lcom 1
cbo 13

59 Methods

Rating   Name   Duplication   Size   Complexity  
A setManager() 0 4 1
A setDriver() 0 4 1
A getDriver() 0 4 1
A getLocalRelationships() 0 4 1
A getForeignRelationships() 0 4 1
A getPivotRelationships() 0 4 1
A getCompiledAttributes() 0 12 1
A getSequence() 0 8 2
A initialize() 0 14 3
A boot() 0 6 2
A getCustomMethods() 0 8 1
B parseMethodsForRelationship() 0 25 5
C sortRelationshipsByType() 0 33 7
A activator() 0 4 1
A getAttributes() 0 4 1
A setAttributes() 0 4 1
A setDateFormat() 0 4 1
A getDateFormat() 0 4 1
A setConnection() 0 4 1
A getConnection() 0 4 1
A getTable() 0 8 2
A setTable() 0 4 1
A getClass() 0 4 2
A setClass() 0 6 1
A getRelationships() 0 4 1
A getSingleRelationships() 0 4 1
A getManyRelationships() 0 4 1
A addRelationshipMethod() 0 4 1
A getDynamicRelationships() 0 4 1
A getEagerloadedRelationships() 0 4 1
A getKeyName() 0 4 1
A getPerPage() 0 4 1
A setPerPage() 0 4 1
A usesTimestamps() 0 4 1
A usesSoftDeletes() 0 4 1
A getCreatedAtColumn() 0 4 1
A getUpdatedAtColumn() 0 4 1
A getQualifiedDeletedAtColumn() 0 4 1
A getForeignKey() 0 4 1
A hasOne() 12 12 3
A morphOne() 12 12 2
B belongsTo() 5 24 4
B morphTo() 5 41 3
A hasMany() 12 12 3
A hasManyThrough() 0 15 3
A morphMany() 15 15 2
B belongsToMany() 0 29 5
A morphToMany() 0 17 4
A morphedByMany() 0 11 3
A getBelongsToManyCaller() 0 12 3
A joiningTable() 0 18 1
A getMorphs() 0 8 3
A getMorphClass() 0 5 2
A newCollection() 0 4 1
A getEmbeddables() 0 4 1
A setEmbeddables() 0 4 1
A __call() 0 11 2
A setKeyName() 0 4 1
A getQualifiedKeyName() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
2
3
namespace Analogue\ORM;
4
5
use Analogue\ORM\Exceptions\MappingException;
6
use Exception;
7
use ReflectionClass;
8
use Analogue\ORM\System\Manager;
9
use Analogue\ORM\System\Wrappers\Factory;
10
use Analogue\ORM\Relationships\BelongsTo;
11
use Analogue\ORM\Relationships\BelongsToMany;
12
use Analogue\ORM\Relationships\HasMany;
13
use Analogue\ORM\Relationships\HasManyThrough;
14
use Analogue\ORM\Relationships\HasOne;
15
use Analogue\ORM\Relationships\MorphMany;
16
use Analogue\ORM\Relationships\MorphOne;
17
use Analogue\ORM\Relationships\MorphTo;
18
use Analogue\ORM\Relationships\MorphToMany;
19
20
/**
21
 * The Entity Map defines the Mapping behaviour of an Entity,
22
 * including relationships.
23
 */
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)
260
    {
261
        $this->manager = $manager;
262
    }
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)
328
    {
329
        $this->driver = $driver;
330
    }
331
332
    /**
333
     * Get the Driver for this mapping.
334
     *
335
     * @return string
336
     */
337
    public function getDriver()
338
    {
339
        return $this->driver;
340
    }
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()
479
    {
480
        return $this->localRelations;
481
    }
482
483
    /**
484
     * Relationships with foreign key in the related Entity record
485
     *
486
     * @return array
487
     */
488
    public function getForeignRelationships()
489
    {
490
        return $this->foreignRelations;
491
    }
492
493
    /**
494
     * Relationships which keys are stored in a pivot record
495
     *
496
     * @return array
497
     */
498
    public function getPivotRelationships()
499
    {
500
        return $this->pivotRelations;
501
    }
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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);
0 ignored issues
show
Unused Code introduced by
The call to EntityCollection::__construct() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1040
    }
1041
1042
    /**
1043
     * Process EntityMap parsing at initialization time
1044
     *
1045
     * @return void
1046
     */
1047
    public function initialize()
1048
    {
1049
        $userMethods = $this->getCustomMethods();
1050
1051
        // Parse EntityMap for method based relationship
1052
        if (count($userMethods) > 0) {
1053
            $this->relationships = $this->parseMethodsForRelationship($userMethods);
1054
        }
1055
1056
        // Parse EntityMap for dynamic relationships
1057
        if (count($this->dynamicRelationships) > 0) {
1058
            $this->relationships = $this->relationships + $this->getDynamicRelationships();
1059
        }
1060
    }
1061
1062
    /**
1063
     * Parse every relationships on the EntityMap and sort
1064
     * them by type.
1065
     *
1066
     * @return void
1067
     */
1068
    public function boot()
1069
    {
1070
        if (count($this->relationships > 0)) {
1071
            $this->sortRelationshipsByType();
1072
        }
1073
    }
1074
1075
    /**
1076
     * Get Methods that has been added in the child class.
1077
     *
1078
     * @return array
1079
     */
1080
    protected function getCustomMethods()
1081
    {
1082
        $mapMethods = get_class_methods($this);
1083
1084
        $parentsMethods = get_class_methods('Analogue\ORM\EntityMap');
1085
        
1086
        return array_diff($mapMethods, $parentsMethods);
1087
    }
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)
1096
    {
1097
        $relationships = [];
1098
1099
        $class = new ReflectionClass(get_class($this));
1100
1101
        // Get the mapped Entity class, as we will detect relationships
1102
        // methods by testing that the first argument is type-hinted to
1103
        // the same class as the mapped Entity.
1104
        $entityClass = $this->getClass();
1105
1106
        foreach ($customMethods as $methodName) {
1107
            $method = $class->getMethod($methodName);
1108
1109
            if ($method->getNumberOfParameters() > 0) {
1110
                $params = $method->getParameters();
1111
1112
                if ($params[0]->getClass() && $params[0]->getClass()->name == $entityClass) {
1113
                    $relationships[] = $methodName;
1114
                }
1115
            }
1116
        }
1117
1118
        return $relationships;
1119
    }
1120
1121
    /**
1122
     * Sort Relationships methods by type
1123
     *
1124
     * @return void
1125
     */
1126
    protected function sortRelationshipsByType()
1127
    {
1128
        $entityClass = $this->getClass();
1129
1130
        // Instantiate a dummy entity which we will pass to relationship methods.
1131
        $entity = unserialize(sprintf('O:%d:"%s":0:{}', strlen($entityClass), $entityClass));
1132
        
1133
        foreach ($this->relationships as $relation) {
1134
            $relationObject = $this->$relation($entity);
1135
1136
            $class = class_basename(get_class($relationObject));
1137
1138
            if (in_array($class, static::$singleClasses)) {
1139
                $this->singleRelations[] = $relation;
1140
            }
1141
1142
            if (in_array($class, static::$manyClasses)) {
1143
                $this->manyRelations[] = $relation;
1144
            }
1145
1146
            if (in_array($class, static::$localClasses)) {
1147
                $this->localRelations[] = $relation;
1148
            }
1149
1150
            if (in_array($class, static::$foreignClasses)) {
1151
                $this->foreignRelations[] = $relation;
1152
            }
1153
1154
            if (in_array($class, static::$pivotClasses)) {
1155
                $this->pivotRelations[] = $relation;
1156
            }
1157
        }
1158
    }
1159
1160
    /**
1161
     * Override this method for custom entity instantiation
1162
     *
1163
     * @return null
1164
     */
1165
    public function activator()
1166
    {
1167
        return null;
1168
    }
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