Completed
Branch master (901ac8)
by Rémi
11:17
created
src/System/ScopeInterface.php 1 patch
Indentation   +16 added lines, -16 removed lines patch added patch discarded remove patch
@@ -4,21 +4,21 @@
 block discarded – undo
4 4
 
5 5
 interface ScopeInterface
6 6
 {
7
-    /**
8
-     * Apply the scope to a given Query builder.
9
-     *
10
-     * @param \Analogue\ORM\System\Query $builder
11
-     *
12
-     * @return void
13
-     */
14
-    public function apply(Query $builder);
7
+	/**
8
+	 * Apply the scope to a given Query builder.
9
+	 *
10
+	 * @param \Analogue\ORM\System\Query $builder
11
+	 *
12
+	 * @return void
13
+	 */
14
+	public function apply(Query $builder);
15 15
 
16
-    /**
17
-     * Remove the scope from the Query builder.
18
-     *
19
-     * @param \Analogue\ORM\System\Query $builder
20
-     *
21
-     * @return void
22
-     */
23
-    public function remove(Query $builder);
16
+	/**
17
+	 * Remove the scope from the Query builder.
18
+	 *
19
+	 * @param \Analogue\ORM\System\Query $builder
20
+	 *
21
+	 * @return void
22
+	 */
23
+	public function remove(Query $builder);
24 24
 }
Please login to merge, or discard this patch.
src/System/Aggregate.php 2 patches
Indentation   +1115 added lines, -1115 removed lines patch added patch discarded remove patch
@@ -16,1119 +16,1119 @@
 block discarded – undo
16 16
  */
17 17
 class Aggregate implements InternallyMappable
18 18
 {
19
-    /**
20
-     * The Root Entity.
21
-     *
22
-     * @var \Analogue\ORM\System\Wrappers\Wrapper
23
-     */
24
-    protected $wrappedEntity;
25
-
26
-    /**
27
-     * Class of the entity being aggregated.
28
-     *
29
-     * @var string
30
-     */
31
-    protected $class;
32
-
33
-    /**
34
-     * Parent Root Aggregate.
35
-     *
36
-     * @var \Analogue\ORM\System\Aggregate
37
-     */
38
-    protected $parent;
39
-
40
-    /**
41
-     * Parent's relationship method.
42
-     *
43
-     * @var string
44
-     */
45
-    protected $parentRelationship;
46
-
47
-    /**
48
-     * Root Entity.
49
-     *
50
-     * @var \Analogue\ORM\System\Aggregate
51
-     */
52
-    protected $root;
53
-
54
-    /**
55
-     * An associative array containing entity's
56
-     * relationships converted to Aggregates.
57
-     *
58
-     * @var array
59
-     */
60
-    protected $relationships = [];
61
-
62
-    /**
63
-     * Relationship that need post-command synchronization.
64
-     *
65
-     * @var array
66
-     */
67
-    protected $needSync = [];
68
-
69
-    /**
70
-     * Mapper.
71
-     *
72
-     * @var \Analogue\ORM\System\Mapper;
73
-     */
74
-    protected $mapper;
75
-
76
-    /**
77
-     * Entity Map.
78
-     *
79
-     * @var \Analogue\ORM\EntityMap;
80
-     */
81
-    protected $entityMap;
82
-
83
-    /**
84
-     * Create a new Aggregated Entity instance.
85
-     *
86
-     * @param mixed          $entity
87
-     * @param Aggregate|null $parent
88
-     * @param string         $parentRelationship
89
-     * @param Aggregate|null $root
90
-     *
91
-     * @throws MappingException
92
-     */
93
-    public function __construct($entity, Aggregate $parent = null, $parentRelationship = null, Aggregate $root = null)
94
-    {
95
-        $factory = new Factory();
96
-
97
-        $this->class = get_class($entity);
98
-
99
-        $this->wrappedEntity = $factory->make($entity);
100
-
101
-        $this->parent = $parent;
102
-
103
-        $this->parentRelationship = $parentRelationship;
104
-
105
-        $this->root = $root;
106
-
107
-        $mapper = $this->getMapper($entity);
108
-
109
-        $this->entityMap = $mapper->getEntityMap();
110
-
111
-        $this->parseRelationships();
112
-    }
113
-
114
-    /**
115
-     * Parse Every relationships defined on the entity.
116
-     *
117
-     * @throws MappingException
118
-     *
119
-     * @return void
120
-     */
121
-    protected function parseRelationships()
122
-    {
123
-        foreach ($this->entityMap->getSingleRelationships() as $relation) {
124
-            $this->parseSingleRelationship($relation);
125
-        }
126
-
127
-        foreach ($this->entityMap->getManyRelationships() as $relation) {
128
-            $this->parseManyRelationship($relation);
129
-        }
130
-    }
131
-
132
-    /**
133
-     * Parse for values common to single & many relations.
134
-     *
135
-     * @param string $relation
136
-     *
137
-     * @throws MappingException
138
-     *
139
-     * @return mixed|bool
140
-     */
141
-    protected function parseForCommonValues($relation)
142
-    {
143
-        if (!$this->hasAttribute($relation)) {
144
-            // If no attribute exists for this relationships
145
-            // we'll make it a simple empty array. This will
146
-            // save us from constantly checking for the attributes
147
-            // actual existence.
148
-            $this->relationships[$relation] = [];
149
-
150
-            return false;
151
-        }
152
-
153
-        $value = $this->getRelationshipValue($relation);
154
-
155
-        if (is_null($value)) {
156
-            $this->relationships[$relation] = [];
157
-
158
-            // If the relationship's content is the null value
159
-            // and the Entity's exist in DB, we'll interpret this
160
-            // as the need to detach all related Entities,
161
-            // therefore a sync operation is needed.
162
-            $this->needSync[] = $relation;
163
-
164
-            return false;
165
-        }
166
-
167
-        return $value;
168
-    }
169
-
170
-    /**
171
-     * Parse a 'single' relationship.
172
-     *
173
-     * @param string $relation
174
-     *
175
-     * @throws MappingException
176
-     *
177
-     * @return bool
178
-     */
179
-    protected function parseSingleRelationship($relation)
180
-    {
181
-        if (!$value = $this->parseForCommonValues($relation)) {
182
-            return true;
183
-        }
184
-
185
-        if ($value instanceof Collection || is_array($value) || $value instanceof CollectionProxy) {
186
-            throw new MappingException("Entity's attribute $relation should not be array, or collection");
187
-        }
188
-
189
-        if ($value instanceof LazyLoadingInterface && !$value->isProxyInitialized()) {
190
-            $this->relationships[$relation] = [];
191
-
192
-            return true;
193
-        }
194
-
195
-        // If the attribute is a loaded proxy, swap it for its
196
-        // loaded entity.
197
-        if ($value instanceof LazyLoadingInterface && $value->isProxyInitialized()) {
198
-            $value = $value->getWrappedValueHolderValue();
199
-        }
200
-
201
-        if ($this->isParentOrRoot($value)) {
202
-            $this->relationships[$relation] = [];
203
-
204
-            return true;
205
-        }
206
-
207
-        // At this point, we can assume the attribute is an Entity instance
208
-        // so we'll treat it as such.
209
-        $subAggregate = $this->createSubAggregate($value, $relation);
210
-
211
-        // Even if it's a single entity, we'll store it as an array
212
-        // just for consistency with other relationships
213
-        $this->relationships[$relation] = [$subAggregate];
214
-
215
-        // We always need to check a loaded relation is in sync
216
-        // with its local key
217
-        $this->needSync[] = $relation;
218
-
219
-        return true;
220
-    }
221
-
222
-    /**
223
-     * Check if value isn't parent or root in the aggregate.
224
-     *
225
-     * @param  mixed
226
-     *
227
-     * @return bool|null
228
-     */
229
-    protected function isParentOrRoot($value)
230
-    {
231
-        if (!is_null($this->root)) {
232
-            $rootClass = get_class($this->root->getEntityObject());
233
-            if ($rootClass == get_class($value)) {
234
-                return true;
235
-            }
236
-        }
237
-
238
-        if (!is_null($this->parent)) {
239
-            $parentClass = get_class($this->parent->getEntityObject());
240
-            if ($parentClass == get_class($value)) {
241
-                return true;
242
-            }
243
-        }
244
-    }
245
-
246
-    /**
247
-     * Parse a 'many' relationship.
248
-     *
249
-     * @param string $relation
250
-     *
251
-     * @throws MappingException
252
-     *
253
-     * @return bool
254
-     */
255
-    protected function parseManyRelationship($relation)
256
-    {
257
-        if (!$value = $this->parseForCommonValues($relation)) {
258
-            return true;
259
-        }
260
-
261
-        if (is_array($value) || (!$value instanceof CollectionProxy && $value instanceof Collection)) {
262
-            $this->needSync[] = $relation;
263
-        }
264
-
265
-        // If the relation is a proxy, we test is the relation
266
-        // has been lazy loaded, otherwise we'll just treat
267
-        // the subset of newly added items.
268
-        if ($value instanceof CollectionProxy && $value->isProxyInitialized()) {
269
-            $this->needSync[] = $relation;
270
-            //$value = $value->getUnderlyingCollection();
271
-        }
272
-
273
-        if ($value instanceof CollectionProxy && !$value->isProxyInitialized()) {
274
-            $value = $value->getAddedItems();
275
-        }
276
-
277
-        // At this point $value should be either an array or an instance
278
-        // of a collection class.
279
-        if (!is_array($value) && !$value instanceof Collection) {
280
-            throw new MappingException("'$relation' attribute should be array() or Collection");
281
-        }
282
-
283
-        $this->relationships[$relation] = $this->createSubAggregates($value, $relation);
284
-
285
-        return true;
286
-    }
287
-
288
-    /**
289
-     * Return Entity's relationship attribute.
290
-     *
291
-     * @param string $relation
292
-     *
293
-     * @throws MappingException
294
-     *
295
-     * @return mixed
296
-     */
297
-    protected function getRelationshipValue($relation)
298
-    {
299
-        $value = $this->getEntityAttribute($relation);
300
-        //if($relation == "role") tdd($this->wrappedEntity->getEntityAttributes());
301
-        if (is_bool($value) || is_float($value) || is_int($value) || is_string($value)) {
302
-            throw new MappingException("Entity's attribute $relation should be array, object, collection or null");
303
-        }
304
-
305
-        return $value;
306
-    }
307
-
308
-    /**
309
-     * Create a child, aggregated entity.
310
-     *
311
-     * @param mixed  $entities
312
-     * @param string $relation
313
-     *
314
-     * @return array
315
-     */
316
-    protected function createSubAggregates($entities, $relation)
317
-    {
318
-        $aggregates = [];
319
-
320
-        foreach ($entities as $entity) {
321
-            $aggregates[] = $this->createSubAggregate($entity, $relation);
322
-        }
323
-
324
-        return $aggregates;
325
-    }
326
-
327
-    /**
328
-     * Create a related subAggregate.
329
-     *
330
-     * @param mixed  $entity
331
-     * @param string $relation
332
-     *
333
-     * @throws MappingException
334
-     *
335
-     * @return self
336
-     */
337
-    protected function createSubAggregate($entity, $relation)
338
-    {
339
-        // If root isn't defined, then this is the Aggregate Root
340
-        if (is_null($this->root)) {
341
-            $root = $this;
342
-        } else {
343
-            $root = $this->root;
344
-        }
345
-
346
-        return new self($entity, $this, $relation, $root);
347
-    }
348
-
349
-    /**
350
-     * Get the Entity's primary key attribute.
351
-     *
352
-     * @return string|int
353
-     */
354
-    public function getEntityId()
355
-    {
356
-        return $this->wrappedEntity->getEntityKey();
357
-    }
358
-
359
-    /**
360
-     * Get the name of the primary key.
361
-     *
362
-     * @return string
363
-     */
364
-    public function getEntityKey()
365
-    {
366
-        return $this->entityMap->getKeyName();
367
-    }
368
-
369
-    /**
370
-     * Return the entity map for the current entity.
371
-     *
372
-     * @return \Analogue\ORM\EntityMap
373
-     */
374
-    public function getEntityMap()
375
-    {
376
-        return $this->entityMap;
377
-    }
378
-
379
-    /**
380
-     * Return the Entity's hash $class.$id.
381
-     *
382
-     * @return string
383
-     */
384
-    public function getEntityHash()
385
-    {
386
-        return $this->getEntityClass().'.'.$this->getEntityId();
387
-    }
388
-
389
-    /**
390
-     * Get wrapped entity class.
391
-     *
392
-     * @return string
393
-     */
394
-    public function getEntityClass()
395
-    {
396
-        return $this->entityMap->getClass();
397
-    }
398
-
399
-    /**
400
-     * Return the Mapper's entity cache.
401
-     *
402
-     * @return \Analogue\ORM\System\EntityCache
403
-     */
404
-    protected function getEntityCache()
405
-    {
406
-        return $this->getMapper()->getEntityCache();
407
-    }
408
-
409
-    /**
410
-     * Get a relationship as an aggregated entities' array.
411
-     *
412
-     * @param string $name
413
-     *
414
-     * @return array
415
-     */
416
-    public function getRelationship($name)
417
-    {
418
-        if (array_key_exists($name, $this->relationships)) {
419
-            return $this->relationships[$name];
420
-        } else {
421
-            return [];
422
-        }
423
-    }
424
-
425
-    /**
426
-     * [TO IMPLEMENT].
427
-     *
428
-     * @return array
429
-     */
430
-    public function getPivotAttributes()
431
-    {
432
-        return [];
433
-    }
434
-
435
-    /**
436
-     * Get Non existing related entities from several relationships.
437
-     *
438
-     * @param array $relationships
439
-     *
440
-     * @return array
441
-     */
442
-    public function getNonExistingRelated(array $relationships)
443
-    {
444
-        $nonExisting = [];
445
-
446
-        foreach ($relationships as $relation) {
447
-            if ($this->hasAttribute($relation) && array_key_exists($relation, $this->relationships)) {
448
-                $nonExisting = array_merge($nonExisting, $this->getNonExistingFromRelation($relation));
449
-            }
450
-        }
451
-
452
-        return $nonExisting;
453
-    }
454
-
455
-    /**
456
-     * Get non-existing related entities from a single relation.
457
-     *
458
-     * @param string $relation
459
-     *
460
-     * @return array
461
-     */
462
-    protected function getNonExistingFromRelation($relation)
463
-    {
464
-        $nonExisting = [];
465
-
466
-        foreach ($this->relationships[$relation] as $aggregate) {
467
-            if (!$aggregate->exists()) {
468
-                $nonExisting[] = $aggregate;
469
-            }
470
-        }
471
-
472
-        return $nonExisting;
473
-    }
474
-
475
-    /**
476
-     * Synchronize relationships if needed.
477
-     *
478
-     * @param array
479
-     *
480
-     * @return void
481
-     */
482
-    public function syncRelationships(array $relationships)
483
-    {
484
-        foreach ($relationships as $relation) {
485
-            if (in_array($relation, $this->needSync)) {
486
-                $this->synchronize($relation);
487
-            }
488
-        }
489
-    }
490
-
491
-    /**
492
-     * Synchronize a relationship attribute.
493
-     *
494
-     * @param $relation
495
-     *
496
-     * @return void
497
-     */
498
-    protected function synchronize($relation)
499
-    {
500
-        $actualContent = $this->relationships[$relation];
501
-
502
-        $relationshipObject = $this->entityMap->$relation($this->getEntityObject());
503
-        $relationshipObject->setParent($this->wrappedEntity);
504
-        $relationshipObject->sync($actualContent);
505
-    }
506
-
507
-    /**
508
-     * Returns an array of Missing related Entities for the
509
-     * given $relation.
510
-     *
511
-     * @param string $relation
512
-     *
513
-     * @return array
514
-     */
515
-    public function getMissingEntities($relation)
516
-    {
517
-        $cachedRelations = $this->getCachedAttribute($relation);
518
-
519
-        if (!is_null($cachedRelations)) {
520
-            $missing = [];
521
-
522
-            foreach ($cachedRelations as $hash) {
523
-                if (!$this->getRelatedAggregateFromHash($hash, $relation)) {
524
-                    $missing[] = $hash;
525
-                }
526
-            }
527
-
528
-            return $missing;
529
-        } else {
530
-            return [];
531
-        }
532
-    }
533
-
534
-    /**
535
-     * Get Relationships who have dirty attributes / dirty relationships.
536
-     *
537
-     * @return array
538
-     */
539
-    public function getDirtyRelationships()
540
-    {
541
-        $dirtyAggregates = [];
542
-
543
-        foreach ($this->relationships as $relation) {
544
-            foreach ($relation as $aggregate) {
545
-                if (!$aggregate->exists() || $aggregate->isDirty() || count($aggregate->getDirtyRelationships()) > 0) {
546
-                    $dirtyAggregates[] = $aggregate;
547
-                }
548
-            }
549
-        }
550
-
551
-        return $dirtyAggregates;
552
-    }
553
-
554
-    /**
555
-     * Compare the object's raw attributes with the record in cache.
556
-     *
557
-     * @return bool
558
-     */
559
-    public function isDirty()
560
-    {
561
-        if (count($this->getDirtyRawAttributes()) > 0) {
562
-            return true;
563
-        } else {
564
-            return false;
565
-        }
566
-    }
567
-
568
-    /**
569
-     * Get Raw Entity's attributes, as they are represented
570
-     * in the database, including value objects, foreign keys,
571
-     * and discriminator column.
572
-     *
573
-     * @return array
574
-     */
575
-    public function getRawAttributes()
576
-    {
577
-        $attributes = $this->wrappedEntity->getEntityAttributes();
578
-
579
-        foreach ($this->entityMap->getRelationships() as $relation) {
580
-            unset($attributes[$relation]);
581
-        }
582
-
583
-        if ($this->entityMap->getInheritanceType() == 'single_table') {
584
-            $attributes = $this->addDiscriminatorColumn($attributes);
585
-        }
586
-
587
-        $attributes = $this->flattenEmbeddables($attributes);
588
-
589
-        $foreignKeys = $this->getForeignKeyAttributes();
590
-
591
-        return $attributes + $foreignKeys;
592
-    }
593
-
594
-    /**
595
-     * Add Discriminator Column if it doesn't exist on the actual entity.
596
-     *
597
-     * @param array $attributes
598
-     *
599
-     * @return array
600
-     */
601
-    protected function addDiscriminatorColumn($attributes)
602
-    {
603
-        $discriminatorColumn = $this->entityMap->getDiscriminatorColumn();
604
-        $entityClass = $this->entityMap->getClass();
605
-
606
-        if (!array_key_exists($discriminatorColumn, $attributes)) {
607
-
608
-            // Use key if present in discriminatorMap
609
-            $map = $this->entityMap->getDiscriminatorColumnMap();
610
-
611
-            $type = array_search($entityClass, $map);
612
-
613
-            if ($type === false) {
614
-                // Use entity FQDN if no corresponding key is set
615
-                $attributes[$discriminatorColumn] = $entityClass;
616
-            } else {
617
-                $attributes[$discriminatorColumn] = $type;
618
-            }
619
-        }
620
-
621
-        return $attributes;
622
-    }
623
-
624
-    /**
625
-     * Convert Value Objects to raw db attributes.
626
-     *
627
-     * @param array $attributes
628
-     *
629
-     * @return array
630
-     */
631
-    protected function flattenEmbeddables($attributes)
632
-    {
633
-        $embeddables = $this->entityMap->getEmbeddables();
634
-
635
-        foreach ($embeddables as $localKey => $embed) {
636
-            // Retrieve the value object from the entity's attributes
637
-            $valueObject = $attributes[$localKey];
638
-
639
-            // Unset the corresponding key
640
-            unset($attributes[$localKey]);
641
-
642
-            // TODO Make wrapper object compatible with value objects
643
-            $valueObjectAttributes = $valueObject->getEntityAttributes();
644
-
645
-            // Now (if setup in the entity map) we prefix the value object's
646
-            // attributes with the snake_case name of the embedded class.
647
-            $prefix = snake_case(class_basename($embed));
648
-
649
-            foreach ($valueObjectAttributes as $key=>$value) {
650
-                $valueObjectAttributes[$prefix.'_'.$key] = $value;
651
-                unset($valueObjectAttributes[$key]);
652
-            }
653
-
654
-            $attributes = array_merge($attributes, $valueObjectAttributes);
655
-        }
656
-
657
-        return $attributes;
658
-    }
659
-
660
-    /**
661
-     * Return's entity raw attributes in the state they were at last
662
-     * query.
663
-     *
664
-     * @param array|null $columns
665
-     *
666
-     * @return array
667
-     */
668
-    protected function getCachedRawAttributes(array $columns = null)
669
-    {
670
-        $cachedAttributes = $this->getCache()->get($this->getEntityId());
671
-
672
-        if (is_null($columns)) {
673
-            return $cachedAttributes;
674
-        } else {
675
-            return array_only($cachedAttributes, $columns);
676
-        }
677
-    }
678
-
679
-    /**
680
-     * Return a single attribute from the cache.
681
-     *
682
-     * @param string $key
683
-     *
684
-     * @return mixed
685
-     */
686
-    protected function getCachedAttribute($key)
687
-    {
688
-        $cachedAttributes = $this->getCache()->get($this->getEntityId());
689
-
690
-        if (!array_key_exists($key, $cachedAttributes)) {
691
-            return;
692
-        } else {
693
-            return $cachedAttributes[$key];
694
-        }
695
-    }
696
-
697
-    /**
698
-     * Convert related Entity's attributes to foreign keys.
699
-     *
700
-     * @return array
701
-     */
702
-    protected function getForeignKeyAttributes()
703
-    {
704
-        $foreignKeys = [];
705
-
706
-        foreach ($this->entityMap->getLocalRelationships() as $relation) {
707
-            // check if relationship has been parsed, meaning it has an actual object
708
-            // in the entity's attributes
709
-            if ($this->isActualRelationships($relation)) {
710
-                $foreignKeys = $foreignKeys + $this->getForeignKeyAttributesFromRelation($relation);
711
-            }
712
-        }
713
-
714
-        if (!is_null($this->parent)) {
715
-            $foreignKeys = $foreignKeys + $this->getForeignKeyAttributesFromParent();
716
-        }
717
-
718
-        return $foreignKeys;
719
-    }
720
-
721
-    /**
722
-     * Return an associative array containing the key-value pair(s) from
723
-     * the related entity.
724
-     *
725
-     * @param string $relation
726
-     *
727
-     * @return array
728
-     */
729
-    protected function getForeignKeyAttributesFromRelation($relation)
730
-    {
731
-        $localRelations = $this->entityMap->getLocalRelationships();
732
-
733
-        if (in_array($relation, $localRelations)) {
734
-            // Call Relationship's method
735
-            $relationship = $this->entityMap->$relation($this->getEntityObject());
736
-
737
-            $relatedAggregate = $this->relationships[$relation][0];
738
-
739
-            return $relationship->getForeignKeyValuePair($relatedAggregate->getEntityObject());
740
-        } else {
741
-            return [];
742
-        }
743
-    }
744
-
745
-    /**
746
-     * Get foreign key attribute(s) from a parent entity in this
747
-     * aggregate context.
748
-     *
749
-     * @return array
750
-     */
751
-    protected function getForeignKeyAttributesFromParent()
752
-    {
753
-        $parentMap = $this->parent->getEntityMap();
754
-
755
-        $parentForeignRelations = $parentMap->getForeignRelationships();
756
-        $parentPivotRelations = $parentMap->getPivotRelationships();
757
-
758
-        // The parentRelation is the name of the relationship
759
-        // methods on the parent entity map
760
-        $parentRelation = $this->parentRelationship;
761
-
762
-        if (in_array($parentRelation, $parentForeignRelations)
763
-            && !in_array($parentRelation, $parentPivotRelations)
764
-        ) {
765
-            $parentObject = $this->parent->getEntityObject();
766
-
767
-            // Call Relationship's method on parent map
768
-            $relationship = $parentMap->$parentRelation($parentObject);
769
-
770
-            return $relationship->getForeignKeyValuePair();
771
-        } else {
772
-            return [];
773
-        }
774
-    }
775
-
776
-    /**
777
-     * Update Pivot records on loaded relationships, by comparing the
778
-     * values from the Entity Cache to the actual relationship inside
779
-     * the aggregated entity.
780
-     *
781
-     * @return void
782
-     */
783
-    public function updatePivotRecords()
784
-    {
785
-        $pivots = $this->entityMap->getPivotRelationships();
786
-
787
-        foreach ($pivots as $pivot) {
788
-            if (array_key_exists($pivot, $this->relationships)) {
789
-                $this->updatePivotRelation($pivot);
790
-            }
791
-        }
792
-    }
793
-
794
-    /**
795
-     * Update Single pivot relationship.
796
-     *
797
-     * @param string $relation
798
-     *
799
-     * @return void
800
-     */
801
-    protected function updatePivotRelation($relation)
802
-    {
803
-        $hashes = $this->getEntityHashesFromRelation($relation);
804
-
805
-        $cachedAttributes = $this->getCachedRawAttributes();
806
-
807
-        if (array_key_exists($relation, $cachedAttributes)) {
808
-            // Compare the two array of hashes to find out existing
809
-            // pivot records, and the ones to be created.
810
-            $new = array_diff($hashes, array_keys($cachedAttributes[$relation]));
811
-            $existing = array_intersect($hashes, array_keys($cachedAttributes[$relation]));
812
-        } else {
813
-            $existing = [];
814
-            $new = $hashes;
815
-        }
816
-
817
-        if (count($new) > 0) {
818
-            $pivots = $this->getRelatedAggregatesFromHashes($new, $relation);
819
-
820
-            $this->entityMap->$relation($this->getEntityObject())->createPivots($pivots);
821
-        }
822
-
823
-        if (count($existing) > 0) {
824
-            foreach ($existing as $pivotHash) {
825
-                $this->updatePivotIfDirty($pivotHash, $relation);
826
-            }
827
-        }
828
-    }
829
-
830
-    /**
831
-     * Compare existing pivot record in cache and update it
832
-     * if the pivot attributes are dirty.
833
-     *
834
-     * @param string $pivotHash
835
-     * @param string $relation
836
-     *
837
-     * @return void
838
-     */
839
-    protected function updatePivotIfDirty($pivotHash, $relation)
840
-    {
841
-        $aggregate = $this->getRelatedAggregateFromHash($pivotHash, $relation);
842
-
843
-        if ($aggregate->hasAttribute('pivot')) {
844
-            $pivot = $aggregate->getEntityAttribute('pivot')->getEntityAttributes();
845
-
846
-            $cachedPivotAttributes = $this->getPivotAttributesFromCache($pivotHash, $relation);
847
-
848
-            $actualPivotAttributes = array_only($pivot, array_keys($cachedPivotAttributes));
849
-
850
-            $dirty = $this->getDirtyAttributes($actualPivotAttributes, $cachedPivotAttributes);
851
-
852
-            if (count($dirty) > 0) {
853
-                $id = $aggregate->getEntityId();
854
-
855
-                $this->entityMap->$relation($this->getEntityObject())->updateExistingPivot($id, $dirty);
856
-            }
857
-        }
858
-    }
859
-
860
-    /**
861
-     * Compare two attributes array and return dirty attributes.
862
-     *
863
-     * @param array $actual
864
-     * @param array $cached
865
-     *
866
-     * @return array
867
-     */
868
-    protected function getDirtyAttributes(array $actual, array $cached)
869
-    {
870
-        $dirty = [];
871
-
872
-        foreach ($actual as $key => $value) {
873
-            if (!$this->originalIsNumericallyEquivalent($value, $cached[$key])) {
874
-                $dirty[$key] = $actual[$key];
875
-            }
876
-        }
877
-
878
-        return $dirty;
879
-    }
880
-
881
-    /**
882
-     * @param string $pivotHash
883
-     * @param string $relation
884
-     *
885
-     * @return array
886
-     */
887
-    protected function getPivotAttributesFromCache($pivotHash, $relation)
888
-    {
889
-        $cachedAttributes = $this->getCachedRawAttributes();
890
-
891
-        $cachedRelations = $cachedAttributes[$relation];
892
-
893
-        foreach ($cachedRelations as $cachedRelation) {
894
-            if ($cachedRelation == $pivotHash) {
895
-                return $cachedRelation->getPivotAttributes();
896
-            }
897
-        }
898
-    }
899
-
900
-    /**
901
-     * Returns an array of related Aggregates from its entity hashes.
902
-     *
903
-     * @param array  $hashes
904
-     * @param string $relation
905
-     *
906
-     * @return array
907
-     */
908
-    protected function getRelatedAggregatesFromHashes(array $hashes, $relation)
909
-    {
910
-        $related = [];
911
-
912
-        foreach ($hashes as $hash) {
913
-            $aggregate = $this->getRelatedAggregateFromHash($hash, $relation);
914
-
915
-            if (!is_null($aggregate)) {
916
-                $related[] = $aggregate;
917
-            }
918
-        }
919
-
920
-        return $related;
921
-    }
922
-
923
-    /**
924
-     * Get related aggregate from its hash.
925
-     *
926
-     * @param string $hash
927
-     * @param string $relation
928
-     *
929
-     * @return \Analogue\ORM\System\Aggregate|null
930
-     */
931
-    protected function getRelatedAggregateFromHash($hash, $relation)
932
-    {
933
-        foreach ($this->relationships[$relation] as $aggregate) {
934
-            if ($aggregate->getEntityHash() == $hash) {
935
-                return $aggregate;
936
-            }
937
-        }
938
-    }
939
-
940
-    /**
941
-     * Return an array of Entity Hashes from a specific relation.
942
-     *
943
-     * @param string $relation
944
-     *
945
-     * @return array
946
-     */
947
-    protected function getEntityHashesFromRelation($relation)
948
-    {
949
-        return array_map(function ($aggregate) {
950
-            return $aggregate->getEntityHash();
951
-        }, $this->relationships[$relation]);
952
-    }
953
-
954
-    /**
955
-     * Check the existence of an actual relationship.
956
-     *
957
-     * @param string $relation
958
-     *
959
-     * @return bool
960
-     */
961
-    protected function isActualRelationships($relation)
962
-    {
963
-        return array_key_exists($relation, $this->relationships)
964
-            && count($this->relationships[$relation]) > 0;
965
-    }
966
-
967
-    /**
968
-     * Return cache instance for the current entity type.
969
-     *
970
-     * @return \Analogue\ORM\System\EntityCache
971
-     */
972
-    protected function getCache()
973
-    {
974
-        return $this->getMapper()->getEntityCache();
975
-    }
976
-
977
-    /**
978
-     * Get Only Raw Entiy's attributes which have been modified
979
-     * since last query.
980
-     *
981
-     * @return array
982
-     */
983
-    public function getDirtyRawAttributes()
984
-    {
985
-        $attributes = $this->getRawAttributes();
986
-        $cachedAttributes = $this->getCachedRawAttributes(array_keys($attributes));
987
-
988
-        $dirty = [];
989
-
990
-        foreach ($attributes as $key => $value) {
991
-            if ($this->isRelation($key) || $key == 'pivot') {
992
-                continue;
993
-            }
994
-
995
-            if (!array_key_exists($key, $cachedAttributes) && !$value instanceof Pivot) {
996
-                $dirty[$key] = $value;
997
-            } elseif ($value !== $cachedAttributes[$key] &&
998
-                !$this->originalIsNumericallyEquivalent($value, $cachedAttributes[$key])) {
999
-                $dirty[$key] = $value;
1000
-            }
1001
-        }
1002
-
1003
-        return $dirty;
1004
-    }
1005
-
1006
-    /**
1007
-     * @param $key
1008
-     *
1009
-     * @return bool
1010
-     */
1011
-    protected function isRelation($key)
1012
-    {
1013
-        return in_array($key, $this->entityMap->getRelationships());
1014
-    }
1015
-
1016
-    /**
1017
-     * Determine if the new and old values for a given key are numerically equivalent.
1018
-     *
1019
-     * @param $current
1020
-     * @param $original
1021
-     *
1022
-     * @return bool
1023
-     */
1024
-    protected function originalIsNumericallyEquivalent($current, $original)
1025
-    {
1026
-        return is_numeric($current) && is_numeric($original) && strcmp((string) $current, (string) $original) === 0;
1027
-    }
1028
-
1029
-    /**
1030
-     * Get the underlying entity object.
1031
-     *
1032
-     * @return mixed
1033
-     */
1034
-    public function getEntityObject()
1035
-    {
1036
-        return $this->wrappedEntity->getObject();
1037
-    }
1038
-
1039
-    /**
1040
-     * Return the Mapper instance for the current Entity Type.
1041
-     *
1042
-     * @return \Analogue\ORM\System\Mapper
1043
-     */
1044
-    public function getMapper()
1045
-    {
1046
-        return Manager::getMapper($this->class);
1047
-    }
1048
-
1049
-    /**
1050
-     * Check that the entity already exists in the database, by checking
1051
-     * if it has an EntityCache record.
1052
-     *
1053
-     * @return bool
1054
-     */
1055
-    public function exists()
1056
-    {
1057
-        return $this->getCache()->has($this->getEntityId());
1058
-    }
1059
-
1060
-    /**
1061
-     * Set the object attribute raw values (hydration).
1062
-     *
1063
-     * @param array $attributes
1064
-     */
1065
-    public function setEntityAttributes(array $attributes)
1066
-    {
1067
-        $this->wrappedEntity->setEntityAttributes($attributes);
1068
-    }
1069
-
1070
-    /**
1071
-     * Get the raw object's values.
1072
-     *
1073
-     * @return array
1074
-     */
1075
-    public function getEntityAttributes()
1076
-    {
1077
-        return $this->wrappedEntity->getEntityAttributes();
1078
-    }
1079
-
1080
-    /**
1081
-     * Set the raw entity attributes.
1082
-     *
1083
-     * @param string $key
1084
-     * @param string $value
1085
-     */
1086
-    public function setEntityAttribute($key, $value)
1087
-    {
1088
-        $this->wrappedEntity->setEntityAttribute($key, $value);
1089
-    }
1090
-
1091
-    /**
1092
-     * Return the entity's attribute.
1093
-     *
1094
-     * @param string $key
1095
-     *
1096
-     * @return mixed
1097
-     */
1098
-    public function getEntityAttribute($key)
1099
-    {
1100
-        return $this->wrappedEntity->getEntityAttribute($key);
1101
-    }
1102
-
1103
-    /**
1104
-     * Does the attribute exists on the entity.
1105
-     *
1106
-     * @param string $key
1107
-     *
1108
-     * @return bool
1109
-     */
1110
-    public function hasAttribute($key)
1111
-    {
1112
-        return $this->wrappedEntity->hasAttribute($key);
1113
-    }
1114
-
1115
-    /**
1116
-     * Set the lazyloading proxies on the wrapped entity.
1117
-     *
1118
-     * @return void
1119
-     */
1120
-    public function setProxies()
1121
-    {
1122
-        $this->wrappedEntity->setProxies();
1123
-    }
1124
-
1125
-    /**
1126
-     * Hydrate the actual entity.
1127
-     *
1128
-     * @return void
1129
-     */
1130
-    public function hydrate()
1131
-    {
1132
-        $this->wrappedEntity->hydrate();
1133
-    }
19
+	/**
20
+	 * The Root Entity.
21
+	 *
22
+	 * @var \Analogue\ORM\System\Wrappers\Wrapper
23
+	 */
24
+	protected $wrappedEntity;
25
+
26
+	/**
27
+	 * Class of the entity being aggregated.
28
+	 *
29
+	 * @var string
30
+	 */
31
+	protected $class;
32
+
33
+	/**
34
+	 * Parent Root Aggregate.
35
+	 *
36
+	 * @var \Analogue\ORM\System\Aggregate
37
+	 */
38
+	protected $parent;
39
+
40
+	/**
41
+	 * Parent's relationship method.
42
+	 *
43
+	 * @var string
44
+	 */
45
+	protected $parentRelationship;
46
+
47
+	/**
48
+	 * Root Entity.
49
+	 *
50
+	 * @var \Analogue\ORM\System\Aggregate
51
+	 */
52
+	protected $root;
53
+
54
+	/**
55
+	 * An associative array containing entity's
56
+	 * relationships converted to Aggregates.
57
+	 *
58
+	 * @var array
59
+	 */
60
+	protected $relationships = [];
61
+
62
+	/**
63
+	 * Relationship that need post-command synchronization.
64
+	 *
65
+	 * @var array
66
+	 */
67
+	protected $needSync = [];
68
+
69
+	/**
70
+	 * Mapper.
71
+	 *
72
+	 * @var \Analogue\ORM\System\Mapper;
73
+	 */
74
+	protected $mapper;
75
+
76
+	/**
77
+	 * Entity Map.
78
+	 *
79
+	 * @var \Analogue\ORM\EntityMap;
80
+	 */
81
+	protected $entityMap;
82
+
83
+	/**
84
+	 * Create a new Aggregated Entity instance.
85
+	 *
86
+	 * @param mixed          $entity
87
+	 * @param Aggregate|null $parent
88
+	 * @param string         $parentRelationship
89
+	 * @param Aggregate|null $root
90
+	 *
91
+	 * @throws MappingException
92
+	 */
93
+	public function __construct($entity, Aggregate $parent = null, $parentRelationship = null, Aggregate $root = null)
94
+	{
95
+		$factory = new Factory();
96
+
97
+		$this->class = get_class($entity);
98
+
99
+		$this->wrappedEntity = $factory->make($entity);
100
+
101
+		$this->parent = $parent;
102
+
103
+		$this->parentRelationship = $parentRelationship;
104
+
105
+		$this->root = $root;
106
+
107
+		$mapper = $this->getMapper($entity);
108
+
109
+		$this->entityMap = $mapper->getEntityMap();
110
+
111
+		$this->parseRelationships();
112
+	}
113
+
114
+	/**
115
+	 * Parse Every relationships defined on the entity.
116
+	 *
117
+	 * @throws MappingException
118
+	 *
119
+	 * @return void
120
+	 */
121
+	protected function parseRelationships()
122
+	{
123
+		foreach ($this->entityMap->getSingleRelationships() as $relation) {
124
+			$this->parseSingleRelationship($relation);
125
+		}
126
+
127
+		foreach ($this->entityMap->getManyRelationships() as $relation) {
128
+			$this->parseManyRelationship($relation);
129
+		}
130
+	}
131
+
132
+	/**
133
+	 * Parse for values common to single & many relations.
134
+	 *
135
+	 * @param string $relation
136
+	 *
137
+	 * @throws MappingException
138
+	 *
139
+	 * @return mixed|bool
140
+	 */
141
+	protected function parseForCommonValues($relation)
142
+	{
143
+		if (!$this->hasAttribute($relation)) {
144
+			// If no attribute exists for this relationships
145
+			// we'll make it a simple empty array. This will
146
+			// save us from constantly checking for the attributes
147
+			// actual existence.
148
+			$this->relationships[$relation] = [];
149
+
150
+			return false;
151
+		}
152
+
153
+		$value = $this->getRelationshipValue($relation);
154
+
155
+		if (is_null($value)) {
156
+			$this->relationships[$relation] = [];
157
+
158
+			// If the relationship's content is the null value
159
+			// and the Entity's exist in DB, we'll interpret this
160
+			// as the need to detach all related Entities,
161
+			// therefore a sync operation is needed.
162
+			$this->needSync[] = $relation;
163
+
164
+			return false;
165
+		}
166
+
167
+		return $value;
168
+	}
169
+
170
+	/**
171
+	 * Parse a 'single' relationship.
172
+	 *
173
+	 * @param string $relation
174
+	 *
175
+	 * @throws MappingException
176
+	 *
177
+	 * @return bool
178
+	 */
179
+	protected function parseSingleRelationship($relation)
180
+	{
181
+		if (!$value = $this->parseForCommonValues($relation)) {
182
+			return true;
183
+		}
184
+
185
+		if ($value instanceof Collection || is_array($value) || $value instanceof CollectionProxy) {
186
+			throw new MappingException("Entity's attribute $relation should not be array, or collection");
187
+		}
188
+
189
+		if ($value instanceof LazyLoadingInterface && !$value->isProxyInitialized()) {
190
+			$this->relationships[$relation] = [];
191
+
192
+			return true;
193
+		}
194
+
195
+		// If the attribute is a loaded proxy, swap it for its
196
+		// loaded entity.
197
+		if ($value instanceof LazyLoadingInterface && $value->isProxyInitialized()) {
198
+			$value = $value->getWrappedValueHolderValue();
199
+		}
200
+
201
+		if ($this->isParentOrRoot($value)) {
202
+			$this->relationships[$relation] = [];
203
+
204
+			return true;
205
+		}
206
+
207
+		// At this point, we can assume the attribute is an Entity instance
208
+		// so we'll treat it as such.
209
+		$subAggregate = $this->createSubAggregate($value, $relation);
210
+
211
+		// Even if it's a single entity, we'll store it as an array
212
+		// just for consistency with other relationships
213
+		$this->relationships[$relation] = [$subAggregate];
214
+
215
+		// We always need to check a loaded relation is in sync
216
+		// with its local key
217
+		$this->needSync[] = $relation;
218
+
219
+		return true;
220
+	}
221
+
222
+	/**
223
+	 * Check if value isn't parent or root in the aggregate.
224
+	 *
225
+	 * @param  mixed
226
+	 *
227
+	 * @return bool|null
228
+	 */
229
+	protected function isParentOrRoot($value)
230
+	{
231
+		if (!is_null($this->root)) {
232
+			$rootClass = get_class($this->root->getEntityObject());
233
+			if ($rootClass == get_class($value)) {
234
+				return true;
235
+			}
236
+		}
237
+
238
+		if (!is_null($this->parent)) {
239
+			$parentClass = get_class($this->parent->getEntityObject());
240
+			if ($parentClass == get_class($value)) {
241
+				return true;
242
+			}
243
+		}
244
+	}
245
+
246
+	/**
247
+	 * Parse a 'many' relationship.
248
+	 *
249
+	 * @param string $relation
250
+	 *
251
+	 * @throws MappingException
252
+	 *
253
+	 * @return bool
254
+	 */
255
+	protected function parseManyRelationship($relation)
256
+	{
257
+		if (!$value = $this->parseForCommonValues($relation)) {
258
+			return true;
259
+		}
260
+
261
+		if (is_array($value) || (!$value instanceof CollectionProxy && $value instanceof Collection)) {
262
+			$this->needSync[] = $relation;
263
+		}
264
+
265
+		// If the relation is a proxy, we test is the relation
266
+		// has been lazy loaded, otherwise we'll just treat
267
+		// the subset of newly added items.
268
+		if ($value instanceof CollectionProxy && $value->isProxyInitialized()) {
269
+			$this->needSync[] = $relation;
270
+			//$value = $value->getUnderlyingCollection();
271
+		}
272
+
273
+		if ($value instanceof CollectionProxy && !$value->isProxyInitialized()) {
274
+			$value = $value->getAddedItems();
275
+		}
276
+
277
+		// At this point $value should be either an array or an instance
278
+		// of a collection class.
279
+		if (!is_array($value) && !$value instanceof Collection) {
280
+			throw new MappingException("'$relation' attribute should be array() or Collection");
281
+		}
282
+
283
+		$this->relationships[$relation] = $this->createSubAggregates($value, $relation);
284
+
285
+		return true;
286
+	}
287
+
288
+	/**
289
+	 * Return Entity's relationship attribute.
290
+	 *
291
+	 * @param string $relation
292
+	 *
293
+	 * @throws MappingException
294
+	 *
295
+	 * @return mixed
296
+	 */
297
+	protected function getRelationshipValue($relation)
298
+	{
299
+		$value = $this->getEntityAttribute($relation);
300
+		//if($relation == "role") tdd($this->wrappedEntity->getEntityAttributes());
301
+		if (is_bool($value) || is_float($value) || is_int($value) || is_string($value)) {
302
+			throw new MappingException("Entity's attribute $relation should be array, object, collection or null");
303
+		}
304
+
305
+		return $value;
306
+	}
307
+
308
+	/**
309
+	 * Create a child, aggregated entity.
310
+	 *
311
+	 * @param mixed  $entities
312
+	 * @param string $relation
313
+	 *
314
+	 * @return array
315
+	 */
316
+	protected function createSubAggregates($entities, $relation)
317
+	{
318
+		$aggregates = [];
319
+
320
+		foreach ($entities as $entity) {
321
+			$aggregates[] = $this->createSubAggregate($entity, $relation);
322
+		}
323
+
324
+		return $aggregates;
325
+	}
326
+
327
+	/**
328
+	 * Create a related subAggregate.
329
+	 *
330
+	 * @param mixed  $entity
331
+	 * @param string $relation
332
+	 *
333
+	 * @throws MappingException
334
+	 *
335
+	 * @return self
336
+	 */
337
+	protected function createSubAggregate($entity, $relation)
338
+	{
339
+		// If root isn't defined, then this is the Aggregate Root
340
+		if (is_null($this->root)) {
341
+			$root = $this;
342
+		} else {
343
+			$root = $this->root;
344
+		}
345
+
346
+		return new self($entity, $this, $relation, $root);
347
+	}
348
+
349
+	/**
350
+	 * Get the Entity's primary key attribute.
351
+	 *
352
+	 * @return string|int
353
+	 */
354
+	public function getEntityId()
355
+	{
356
+		return $this->wrappedEntity->getEntityKey();
357
+	}
358
+
359
+	/**
360
+	 * Get the name of the primary key.
361
+	 *
362
+	 * @return string
363
+	 */
364
+	public function getEntityKey()
365
+	{
366
+		return $this->entityMap->getKeyName();
367
+	}
368
+
369
+	/**
370
+	 * Return the entity map for the current entity.
371
+	 *
372
+	 * @return \Analogue\ORM\EntityMap
373
+	 */
374
+	public function getEntityMap()
375
+	{
376
+		return $this->entityMap;
377
+	}
378
+
379
+	/**
380
+	 * Return the Entity's hash $class.$id.
381
+	 *
382
+	 * @return string
383
+	 */
384
+	public function getEntityHash()
385
+	{
386
+		return $this->getEntityClass().'.'.$this->getEntityId();
387
+	}
388
+
389
+	/**
390
+	 * Get wrapped entity class.
391
+	 *
392
+	 * @return string
393
+	 */
394
+	public function getEntityClass()
395
+	{
396
+		return $this->entityMap->getClass();
397
+	}
398
+
399
+	/**
400
+	 * Return the Mapper's entity cache.
401
+	 *
402
+	 * @return \Analogue\ORM\System\EntityCache
403
+	 */
404
+	protected function getEntityCache()
405
+	{
406
+		return $this->getMapper()->getEntityCache();
407
+	}
408
+
409
+	/**
410
+	 * Get a relationship as an aggregated entities' array.
411
+	 *
412
+	 * @param string $name
413
+	 *
414
+	 * @return array
415
+	 */
416
+	public function getRelationship($name)
417
+	{
418
+		if (array_key_exists($name, $this->relationships)) {
419
+			return $this->relationships[$name];
420
+		} else {
421
+			return [];
422
+		}
423
+	}
424
+
425
+	/**
426
+	 * [TO IMPLEMENT].
427
+	 *
428
+	 * @return array
429
+	 */
430
+	public function getPivotAttributes()
431
+	{
432
+		return [];
433
+	}
434
+
435
+	/**
436
+	 * Get Non existing related entities from several relationships.
437
+	 *
438
+	 * @param array $relationships
439
+	 *
440
+	 * @return array
441
+	 */
442
+	public function getNonExistingRelated(array $relationships)
443
+	{
444
+		$nonExisting = [];
445
+
446
+		foreach ($relationships as $relation) {
447
+			if ($this->hasAttribute($relation) && array_key_exists($relation, $this->relationships)) {
448
+				$nonExisting = array_merge($nonExisting, $this->getNonExistingFromRelation($relation));
449
+			}
450
+		}
451
+
452
+		return $nonExisting;
453
+	}
454
+
455
+	/**
456
+	 * Get non-existing related entities from a single relation.
457
+	 *
458
+	 * @param string $relation
459
+	 *
460
+	 * @return array
461
+	 */
462
+	protected function getNonExistingFromRelation($relation)
463
+	{
464
+		$nonExisting = [];
465
+
466
+		foreach ($this->relationships[$relation] as $aggregate) {
467
+			if (!$aggregate->exists()) {
468
+				$nonExisting[] = $aggregate;
469
+			}
470
+		}
471
+
472
+		return $nonExisting;
473
+	}
474
+
475
+	/**
476
+	 * Synchronize relationships if needed.
477
+	 *
478
+	 * @param array
479
+	 *
480
+	 * @return void
481
+	 */
482
+	public function syncRelationships(array $relationships)
483
+	{
484
+		foreach ($relationships as $relation) {
485
+			if (in_array($relation, $this->needSync)) {
486
+				$this->synchronize($relation);
487
+			}
488
+		}
489
+	}
490
+
491
+	/**
492
+	 * Synchronize a relationship attribute.
493
+	 *
494
+	 * @param $relation
495
+	 *
496
+	 * @return void
497
+	 */
498
+	protected function synchronize($relation)
499
+	{
500
+		$actualContent = $this->relationships[$relation];
501
+
502
+		$relationshipObject = $this->entityMap->$relation($this->getEntityObject());
503
+		$relationshipObject->setParent($this->wrappedEntity);
504
+		$relationshipObject->sync($actualContent);
505
+	}
506
+
507
+	/**
508
+	 * Returns an array of Missing related Entities for the
509
+	 * given $relation.
510
+	 *
511
+	 * @param string $relation
512
+	 *
513
+	 * @return array
514
+	 */
515
+	public function getMissingEntities($relation)
516
+	{
517
+		$cachedRelations = $this->getCachedAttribute($relation);
518
+
519
+		if (!is_null($cachedRelations)) {
520
+			$missing = [];
521
+
522
+			foreach ($cachedRelations as $hash) {
523
+				if (!$this->getRelatedAggregateFromHash($hash, $relation)) {
524
+					$missing[] = $hash;
525
+				}
526
+			}
527
+
528
+			return $missing;
529
+		} else {
530
+			return [];
531
+		}
532
+	}
533
+
534
+	/**
535
+	 * Get Relationships who have dirty attributes / dirty relationships.
536
+	 *
537
+	 * @return array
538
+	 */
539
+	public function getDirtyRelationships()
540
+	{
541
+		$dirtyAggregates = [];
542
+
543
+		foreach ($this->relationships as $relation) {
544
+			foreach ($relation as $aggregate) {
545
+				if (!$aggregate->exists() || $aggregate->isDirty() || count($aggregate->getDirtyRelationships()) > 0) {
546
+					$dirtyAggregates[] = $aggregate;
547
+				}
548
+			}
549
+		}
550
+
551
+		return $dirtyAggregates;
552
+	}
553
+
554
+	/**
555
+	 * Compare the object's raw attributes with the record in cache.
556
+	 *
557
+	 * @return bool
558
+	 */
559
+	public function isDirty()
560
+	{
561
+		if (count($this->getDirtyRawAttributes()) > 0) {
562
+			return true;
563
+		} else {
564
+			return false;
565
+		}
566
+	}
567
+
568
+	/**
569
+	 * Get Raw Entity's attributes, as they are represented
570
+	 * in the database, including value objects, foreign keys,
571
+	 * and discriminator column.
572
+	 *
573
+	 * @return array
574
+	 */
575
+	public function getRawAttributes()
576
+	{
577
+		$attributes = $this->wrappedEntity->getEntityAttributes();
578
+
579
+		foreach ($this->entityMap->getRelationships() as $relation) {
580
+			unset($attributes[$relation]);
581
+		}
582
+
583
+		if ($this->entityMap->getInheritanceType() == 'single_table') {
584
+			$attributes = $this->addDiscriminatorColumn($attributes);
585
+		}
586
+
587
+		$attributes = $this->flattenEmbeddables($attributes);
588
+
589
+		$foreignKeys = $this->getForeignKeyAttributes();
590
+
591
+		return $attributes + $foreignKeys;
592
+	}
593
+
594
+	/**
595
+	 * Add Discriminator Column if it doesn't exist on the actual entity.
596
+	 *
597
+	 * @param array $attributes
598
+	 *
599
+	 * @return array
600
+	 */
601
+	protected function addDiscriminatorColumn($attributes)
602
+	{
603
+		$discriminatorColumn = $this->entityMap->getDiscriminatorColumn();
604
+		$entityClass = $this->entityMap->getClass();
605
+
606
+		if (!array_key_exists($discriminatorColumn, $attributes)) {
607
+
608
+			// Use key if present in discriminatorMap
609
+			$map = $this->entityMap->getDiscriminatorColumnMap();
610
+
611
+			$type = array_search($entityClass, $map);
612
+
613
+			if ($type === false) {
614
+				// Use entity FQDN if no corresponding key is set
615
+				$attributes[$discriminatorColumn] = $entityClass;
616
+			} else {
617
+				$attributes[$discriminatorColumn] = $type;
618
+			}
619
+		}
620
+
621
+		return $attributes;
622
+	}
623
+
624
+	/**
625
+	 * Convert Value Objects to raw db attributes.
626
+	 *
627
+	 * @param array $attributes
628
+	 *
629
+	 * @return array
630
+	 */
631
+	protected function flattenEmbeddables($attributes)
632
+	{
633
+		$embeddables = $this->entityMap->getEmbeddables();
634
+
635
+		foreach ($embeddables as $localKey => $embed) {
636
+			// Retrieve the value object from the entity's attributes
637
+			$valueObject = $attributes[$localKey];
638
+
639
+			// Unset the corresponding key
640
+			unset($attributes[$localKey]);
641
+
642
+			// TODO Make wrapper object compatible with value objects
643
+			$valueObjectAttributes = $valueObject->getEntityAttributes();
644
+
645
+			// Now (if setup in the entity map) we prefix the value object's
646
+			// attributes with the snake_case name of the embedded class.
647
+			$prefix = snake_case(class_basename($embed));
648
+
649
+			foreach ($valueObjectAttributes as $key=>$value) {
650
+				$valueObjectAttributes[$prefix.'_'.$key] = $value;
651
+				unset($valueObjectAttributes[$key]);
652
+			}
653
+
654
+			$attributes = array_merge($attributes, $valueObjectAttributes);
655
+		}
656
+
657
+		return $attributes;
658
+	}
659
+
660
+	/**
661
+	 * Return's entity raw attributes in the state they were at last
662
+	 * query.
663
+	 *
664
+	 * @param array|null $columns
665
+	 *
666
+	 * @return array
667
+	 */
668
+	protected function getCachedRawAttributes(array $columns = null)
669
+	{
670
+		$cachedAttributes = $this->getCache()->get($this->getEntityId());
671
+
672
+		if (is_null($columns)) {
673
+			return $cachedAttributes;
674
+		} else {
675
+			return array_only($cachedAttributes, $columns);
676
+		}
677
+	}
678
+
679
+	/**
680
+	 * Return a single attribute from the cache.
681
+	 *
682
+	 * @param string $key
683
+	 *
684
+	 * @return mixed
685
+	 */
686
+	protected function getCachedAttribute($key)
687
+	{
688
+		$cachedAttributes = $this->getCache()->get($this->getEntityId());
689
+
690
+		if (!array_key_exists($key, $cachedAttributes)) {
691
+			return;
692
+		} else {
693
+			return $cachedAttributes[$key];
694
+		}
695
+	}
696
+
697
+	/**
698
+	 * Convert related Entity's attributes to foreign keys.
699
+	 *
700
+	 * @return array
701
+	 */
702
+	protected function getForeignKeyAttributes()
703
+	{
704
+		$foreignKeys = [];
705
+
706
+		foreach ($this->entityMap->getLocalRelationships() as $relation) {
707
+			// check if relationship has been parsed, meaning it has an actual object
708
+			// in the entity's attributes
709
+			if ($this->isActualRelationships($relation)) {
710
+				$foreignKeys = $foreignKeys + $this->getForeignKeyAttributesFromRelation($relation);
711
+			}
712
+		}
713
+
714
+		if (!is_null($this->parent)) {
715
+			$foreignKeys = $foreignKeys + $this->getForeignKeyAttributesFromParent();
716
+		}
717
+
718
+		return $foreignKeys;
719
+	}
720
+
721
+	/**
722
+	 * Return an associative array containing the key-value pair(s) from
723
+	 * the related entity.
724
+	 *
725
+	 * @param string $relation
726
+	 *
727
+	 * @return array
728
+	 */
729
+	protected function getForeignKeyAttributesFromRelation($relation)
730
+	{
731
+		$localRelations = $this->entityMap->getLocalRelationships();
732
+
733
+		if (in_array($relation, $localRelations)) {
734
+			// Call Relationship's method
735
+			$relationship = $this->entityMap->$relation($this->getEntityObject());
736
+
737
+			$relatedAggregate = $this->relationships[$relation][0];
738
+
739
+			return $relationship->getForeignKeyValuePair($relatedAggregate->getEntityObject());
740
+		} else {
741
+			return [];
742
+		}
743
+	}
744
+
745
+	/**
746
+	 * Get foreign key attribute(s) from a parent entity in this
747
+	 * aggregate context.
748
+	 *
749
+	 * @return array
750
+	 */
751
+	protected function getForeignKeyAttributesFromParent()
752
+	{
753
+		$parentMap = $this->parent->getEntityMap();
754
+
755
+		$parentForeignRelations = $parentMap->getForeignRelationships();
756
+		$parentPivotRelations = $parentMap->getPivotRelationships();
757
+
758
+		// The parentRelation is the name of the relationship
759
+		// methods on the parent entity map
760
+		$parentRelation = $this->parentRelationship;
761
+
762
+		if (in_array($parentRelation, $parentForeignRelations)
763
+			&& !in_array($parentRelation, $parentPivotRelations)
764
+		) {
765
+			$parentObject = $this->parent->getEntityObject();
766
+
767
+			// Call Relationship's method on parent map
768
+			$relationship = $parentMap->$parentRelation($parentObject);
769
+
770
+			return $relationship->getForeignKeyValuePair();
771
+		} else {
772
+			return [];
773
+		}
774
+	}
775
+
776
+	/**
777
+	 * Update Pivot records on loaded relationships, by comparing the
778
+	 * values from the Entity Cache to the actual relationship inside
779
+	 * the aggregated entity.
780
+	 *
781
+	 * @return void
782
+	 */
783
+	public function updatePivotRecords()
784
+	{
785
+		$pivots = $this->entityMap->getPivotRelationships();
786
+
787
+		foreach ($pivots as $pivot) {
788
+			if (array_key_exists($pivot, $this->relationships)) {
789
+				$this->updatePivotRelation($pivot);
790
+			}
791
+		}
792
+	}
793
+
794
+	/**
795
+	 * Update Single pivot relationship.
796
+	 *
797
+	 * @param string $relation
798
+	 *
799
+	 * @return void
800
+	 */
801
+	protected function updatePivotRelation($relation)
802
+	{
803
+		$hashes = $this->getEntityHashesFromRelation($relation);
804
+
805
+		$cachedAttributes = $this->getCachedRawAttributes();
806
+
807
+		if (array_key_exists($relation, $cachedAttributes)) {
808
+			// Compare the two array of hashes to find out existing
809
+			// pivot records, and the ones to be created.
810
+			$new = array_diff($hashes, array_keys($cachedAttributes[$relation]));
811
+			$existing = array_intersect($hashes, array_keys($cachedAttributes[$relation]));
812
+		} else {
813
+			$existing = [];
814
+			$new = $hashes;
815
+		}
816
+
817
+		if (count($new) > 0) {
818
+			$pivots = $this->getRelatedAggregatesFromHashes($new, $relation);
819
+
820
+			$this->entityMap->$relation($this->getEntityObject())->createPivots($pivots);
821
+		}
822
+
823
+		if (count($existing) > 0) {
824
+			foreach ($existing as $pivotHash) {
825
+				$this->updatePivotIfDirty($pivotHash, $relation);
826
+			}
827
+		}
828
+	}
829
+
830
+	/**
831
+	 * Compare existing pivot record in cache and update it
832
+	 * if the pivot attributes are dirty.
833
+	 *
834
+	 * @param string $pivotHash
835
+	 * @param string $relation
836
+	 *
837
+	 * @return void
838
+	 */
839
+	protected function updatePivotIfDirty($pivotHash, $relation)
840
+	{
841
+		$aggregate = $this->getRelatedAggregateFromHash($pivotHash, $relation);
842
+
843
+		if ($aggregate->hasAttribute('pivot')) {
844
+			$pivot = $aggregate->getEntityAttribute('pivot')->getEntityAttributes();
845
+
846
+			$cachedPivotAttributes = $this->getPivotAttributesFromCache($pivotHash, $relation);
847
+
848
+			$actualPivotAttributes = array_only($pivot, array_keys($cachedPivotAttributes));
849
+
850
+			$dirty = $this->getDirtyAttributes($actualPivotAttributes, $cachedPivotAttributes);
851
+
852
+			if (count($dirty) > 0) {
853
+				$id = $aggregate->getEntityId();
854
+
855
+				$this->entityMap->$relation($this->getEntityObject())->updateExistingPivot($id, $dirty);
856
+			}
857
+		}
858
+	}
859
+
860
+	/**
861
+	 * Compare two attributes array and return dirty attributes.
862
+	 *
863
+	 * @param array $actual
864
+	 * @param array $cached
865
+	 *
866
+	 * @return array
867
+	 */
868
+	protected function getDirtyAttributes(array $actual, array $cached)
869
+	{
870
+		$dirty = [];
871
+
872
+		foreach ($actual as $key => $value) {
873
+			if (!$this->originalIsNumericallyEquivalent($value, $cached[$key])) {
874
+				$dirty[$key] = $actual[$key];
875
+			}
876
+		}
877
+
878
+		return $dirty;
879
+	}
880
+
881
+	/**
882
+	 * @param string $pivotHash
883
+	 * @param string $relation
884
+	 *
885
+	 * @return array
886
+	 */
887
+	protected function getPivotAttributesFromCache($pivotHash, $relation)
888
+	{
889
+		$cachedAttributes = $this->getCachedRawAttributes();
890
+
891
+		$cachedRelations = $cachedAttributes[$relation];
892
+
893
+		foreach ($cachedRelations as $cachedRelation) {
894
+			if ($cachedRelation == $pivotHash) {
895
+				return $cachedRelation->getPivotAttributes();
896
+			}
897
+		}
898
+	}
899
+
900
+	/**
901
+	 * Returns an array of related Aggregates from its entity hashes.
902
+	 *
903
+	 * @param array  $hashes
904
+	 * @param string $relation
905
+	 *
906
+	 * @return array
907
+	 */
908
+	protected function getRelatedAggregatesFromHashes(array $hashes, $relation)
909
+	{
910
+		$related = [];
911
+
912
+		foreach ($hashes as $hash) {
913
+			$aggregate = $this->getRelatedAggregateFromHash($hash, $relation);
914
+
915
+			if (!is_null($aggregate)) {
916
+				$related[] = $aggregate;
917
+			}
918
+		}
919
+
920
+		return $related;
921
+	}
922
+
923
+	/**
924
+	 * Get related aggregate from its hash.
925
+	 *
926
+	 * @param string $hash
927
+	 * @param string $relation
928
+	 *
929
+	 * @return \Analogue\ORM\System\Aggregate|null
930
+	 */
931
+	protected function getRelatedAggregateFromHash($hash, $relation)
932
+	{
933
+		foreach ($this->relationships[$relation] as $aggregate) {
934
+			if ($aggregate->getEntityHash() == $hash) {
935
+				return $aggregate;
936
+			}
937
+		}
938
+	}
939
+
940
+	/**
941
+	 * Return an array of Entity Hashes from a specific relation.
942
+	 *
943
+	 * @param string $relation
944
+	 *
945
+	 * @return array
946
+	 */
947
+	protected function getEntityHashesFromRelation($relation)
948
+	{
949
+		return array_map(function ($aggregate) {
950
+			return $aggregate->getEntityHash();
951
+		}, $this->relationships[$relation]);
952
+	}
953
+
954
+	/**
955
+	 * Check the existence of an actual relationship.
956
+	 *
957
+	 * @param string $relation
958
+	 *
959
+	 * @return bool
960
+	 */
961
+	protected function isActualRelationships($relation)
962
+	{
963
+		return array_key_exists($relation, $this->relationships)
964
+			&& count($this->relationships[$relation]) > 0;
965
+	}
966
+
967
+	/**
968
+	 * Return cache instance for the current entity type.
969
+	 *
970
+	 * @return \Analogue\ORM\System\EntityCache
971
+	 */
972
+	protected function getCache()
973
+	{
974
+		return $this->getMapper()->getEntityCache();
975
+	}
976
+
977
+	/**
978
+	 * Get Only Raw Entiy's attributes which have been modified
979
+	 * since last query.
980
+	 *
981
+	 * @return array
982
+	 */
983
+	public function getDirtyRawAttributes()
984
+	{
985
+		$attributes = $this->getRawAttributes();
986
+		$cachedAttributes = $this->getCachedRawAttributes(array_keys($attributes));
987
+
988
+		$dirty = [];
989
+
990
+		foreach ($attributes as $key => $value) {
991
+			if ($this->isRelation($key) || $key == 'pivot') {
992
+				continue;
993
+			}
994
+
995
+			if (!array_key_exists($key, $cachedAttributes) && !$value instanceof Pivot) {
996
+				$dirty[$key] = $value;
997
+			} elseif ($value !== $cachedAttributes[$key] &&
998
+				!$this->originalIsNumericallyEquivalent($value, $cachedAttributes[$key])) {
999
+				$dirty[$key] = $value;
1000
+			}
1001
+		}
1002
+
1003
+		return $dirty;
1004
+	}
1005
+
1006
+	/**
1007
+	 * @param $key
1008
+	 *
1009
+	 * @return bool
1010
+	 */
1011
+	protected function isRelation($key)
1012
+	{
1013
+		return in_array($key, $this->entityMap->getRelationships());
1014
+	}
1015
+
1016
+	/**
1017
+	 * Determine if the new and old values for a given key are numerically equivalent.
1018
+	 *
1019
+	 * @param $current
1020
+	 * @param $original
1021
+	 *
1022
+	 * @return bool
1023
+	 */
1024
+	protected function originalIsNumericallyEquivalent($current, $original)
1025
+	{
1026
+		return is_numeric($current) && is_numeric($original) && strcmp((string) $current, (string) $original) === 0;
1027
+	}
1028
+
1029
+	/**
1030
+	 * Get the underlying entity object.
1031
+	 *
1032
+	 * @return mixed
1033
+	 */
1034
+	public function getEntityObject()
1035
+	{
1036
+		return $this->wrappedEntity->getObject();
1037
+	}
1038
+
1039
+	/**
1040
+	 * Return the Mapper instance for the current Entity Type.
1041
+	 *
1042
+	 * @return \Analogue\ORM\System\Mapper
1043
+	 */
1044
+	public function getMapper()
1045
+	{
1046
+		return Manager::getMapper($this->class);
1047
+	}
1048
+
1049
+	/**
1050
+	 * Check that the entity already exists in the database, by checking
1051
+	 * if it has an EntityCache record.
1052
+	 *
1053
+	 * @return bool
1054
+	 */
1055
+	public function exists()
1056
+	{
1057
+		return $this->getCache()->has($this->getEntityId());
1058
+	}
1059
+
1060
+	/**
1061
+	 * Set the object attribute raw values (hydration).
1062
+	 *
1063
+	 * @param array $attributes
1064
+	 */
1065
+	public function setEntityAttributes(array $attributes)
1066
+	{
1067
+		$this->wrappedEntity->setEntityAttributes($attributes);
1068
+	}
1069
+
1070
+	/**
1071
+	 * Get the raw object's values.
1072
+	 *
1073
+	 * @return array
1074
+	 */
1075
+	public function getEntityAttributes()
1076
+	{
1077
+		return $this->wrappedEntity->getEntityAttributes();
1078
+	}
1079
+
1080
+	/**
1081
+	 * Set the raw entity attributes.
1082
+	 *
1083
+	 * @param string $key
1084
+	 * @param string $value
1085
+	 */
1086
+	public function setEntityAttribute($key, $value)
1087
+	{
1088
+		$this->wrappedEntity->setEntityAttribute($key, $value);
1089
+	}
1090
+
1091
+	/**
1092
+	 * Return the entity's attribute.
1093
+	 *
1094
+	 * @param string $key
1095
+	 *
1096
+	 * @return mixed
1097
+	 */
1098
+	public function getEntityAttribute($key)
1099
+	{
1100
+		return $this->wrappedEntity->getEntityAttribute($key);
1101
+	}
1102
+
1103
+	/**
1104
+	 * Does the attribute exists on the entity.
1105
+	 *
1106
+	 * @param string $key
1107
+	 *
1108
+	 * @return bool
1109
+	 */
1110
+	public function hasAttribute($key)
1111
+	{
1112
+		return $this->wrappedEntity->hasAttribute($key);
1113
+	}
1114
+
1115
+	/**
1116
+	 * Set the lazyloading proxies on the wrapped entity.
1117
+	 *
1118
+	 * @return void
1119
+	 */
1120
+	public function setProxies()
1121
+	{
1122
+		$this->wrappedEntity->setProxies();
1123
+	}
1124
+
1125
+	/**
1126
+	 * Hydrate the actual entity.
1127
+	 *
1128
+	 * @return void
1129
+	 */
1130
+	public function hydrate()
1131
+	{
1132
+		$this->wrappedEntity->hydrate();
1133
+	}
1134 1134
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -946,7 +946,7 @@
 block discarded – undo
946 946
      */
947 947
     protected function getEntityHashesFromRelation($relation)
948 948
     {
949
-        return array_map(function ($aggregate) {
949
+        return array_map(function($aggregate) {
950 950
             return $aggregate->getEntityHash();
951 951
         }, $this->relationships[$relation]);
952 952
     }
Please login to merge, or discard this patch.
src/System/InternallyMappable.php 1 patch
Indentation   +35 added lines, -35 removed lines patch added patch discarded remove patch
@@ -4,43 +4,43 @@
 block discarded – undo
4 4
 
5 5
 interface InternallyMappable
6 6
 {
7
-    /**
8
-     * Set the object attribute raw values (hydration).
9
-     *
10
-     * @param array $attributes
11
-     */
12
-    public function setEntityAttributes(array $attributes);
7
+	/**
8
+	 * Set the object attribute raw values (hydration).
9
+	 *
10
+	 * @param array $attributes
11
+	 */
12
+	public function setEntityAttributes(array $attributes);
13 13
 
14
-    /**
15
-     * Get the raw object's values.
16
-     *
17
-     * @return array
18
-     */
19
-    public function getEntityAttributes();
14
+	/**
15
+	 * Get the raw object's values.
16
+	 *
17
+	 * @return array
18
+	 */
19
+	public function getEntityAttributes();
20 20
 
21
-    /**
22
-     * Set the raw entity attributes.
23
-     *
24
-     * @param string $key
25
-     * @param string $value
26
-     */
27
-    public function setEntityAttribute($key, $value);
21
+	/**
22
+	 * Set the raw entity attributes.
23
+	 *
24
+	 * @param string $key
25
+	 * @param string $value
26
+	 */
27
+	public function setEntityAttribute($key, $value);
28 28
 
29
-    /**
30
-     * Return the entity's attribute.
31
-     *
32
-     * @param string $key
33
-     *
34
-     * @return mixed
35
-     */
36
-    public function getEntityAttribute($key);
29
+	/**
30
+	 * Return the entity's attribute.
31
+	 *
32
+	 * @param string $key
33
+	 *
34
+	 * @return mixed
35
+	 */
36
+	public function getEntityAttribute($key);
37 37
 
38
-    /**
39
-     * Does the entity posses the given attribute.
40
-     *
41
-     * @param string $key
42
-     *
43
-     * @return bool
44
-     */
45
-    public function hasAttribute($key);
38
+	/**
39
+	 * Does the entity posses the given attribute.
40
+	 *
41
+	 * @param string $key
42
+	 *
43
+	 * @return bool
44
+	 */
45
+	public function hasAttribute($key);
46 46
 }
Please login to merge, or discard this patch.
src/System/Proxies/ProxyFactory.php 2 patches
Indentation   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -14,47 +14,47 @@
 block discarded – undo
14 14
  */
15 15
 class ProxyFactory
16 16
 {
17
-    public function make($entity, $relation, $class)
18
-    {
19
-        $entityMap = Manager::getMapper($entity)->getEntityMap();
20
-
21
-        $singleRelations = $entityMap->getSingleRelationships();
22
-        $manyRelations = $entityMap->getManyRelationships();
23
-
24
-        if (in_array($relation, $singleRelations)) {
25
-            return $this->makeEntityProxy($entity, $relation, $class);
26
-        }
27
-
28
-        if (in_array($relation, $manyRelations)) {
29
-            return new CollectionProxy($entity, $relation);
30
-        }
31
-
32
-        throw new MappingException('Could not identity relation '.$relation);
33
-    }
34
-
35
-    /**
36
-     * Create an instance of a proxy object, extending the actual
37
-     * related class.
38
-     *
39
-     * @param mixed  $entity   parent object
40
-     * @param string $relation the name of the relationship method
41
-     * @param string $class    the class name of the related object
42
-     *
43
-     * @return mixed
44
-     */
45
-    protected function makeEntityProxy($entity, $relation, $class)
46
-    {
47
-        $factory = new LazyLoadingValueHolderFactory();
48
-
49
-        $initializer = function (&$wrappedObject, LazyLoadingInterface $proxy, $method, array $parameters, &$initializer) use ($entity, $relation) {
50
-            $entityMap = Manager::getMapper($entity)->getEntityMap();
51
-
52
-            $wrappedObject = $entityMap->$relation($entity)->getResults($relation);
53
-
54
-            $initializer = null; // disable initialization
55
-            return true; // confirm that initialization occurred correctly
56
-        };
57
-
58
-        return $factory->createProxy($class, $initializer);
59
-    }
17
+	public function make($entity, $relation, $class)
18
+	{
19
+		$entityMap = Manager::getMapper($entity)->getEntityMap();
20
+
21
+		$singleRelations = $entityMap->getSingleRelationships();
22
+		$manyRelations = $entityMap->getManyRelationships();
23
+
24
+		if (in_array($relation, $singleRelations)) {
25
+			return $this->makeEntityProxy($entity, $relation, $class);
26
+		}
27
+
28
+		if (in_array($relation, $manyRelations)) {
29
+			return new CollectionProxy($entity, $relation);
30
+		}
31
+
32
+		throw new MappingException('Could not identity relation '.$relation);
33
+	}
34
+
35
+	/**
36
+	 * Create an instance of a proxy object, extending the actual
37
+	 * related class.
38
+	 *
39
+	 * @param mixed  $entity   parent object
40
+	 * @param string $relation the name of the relationship method
41
+	 * @param string $class    the class name of the related object
42
+	 *
43
+	 * @return mixed
44
+	 */
45
+	protected function makeEntityProxy($entity, $relation, $class)
46
+	{
47
+		$factory = new LazyLoadingValueHolderFactory();
48
+
49
+		$initializer = function (&$wrappedObject, LazyLoadingInterface $proxy, $method, array $parameters, &$initializer) use ($entity, $relation) {
50
+			$entityMap = Manager::getMapper($entity)->getEntityMap();
51
+
52
+			$wrappedObject = $entityMap->$relation($entity)->getResults($relation);
53
+
54
+			$initializer = null; // disable initialization
55
+			return true; // confirm that initialization occurred correctly
56
+		};
57
+
58
+		return $factory->createProxy($class, $initializer);
59
+	}
60 60
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -46,7 +46,7 @@
 block discarded – undo
46 46
     {
47 47
         $factory = new LazyLoadingValueHolderFactory();
48 48
 
49
-        $initializer = function (&$wrappedObject, LazyLoadingInterface $proxy, $method, array $parameters, &$initializer) use ($entity, $relation) {
49
+        $initializer = function(&$wrappedObject, LazyLoadingInterface $proxy, $method, array $parameters, &$initializer) use ($entity, $relation) {
50 50
             $entityMap = Manager::getMapper($entity)->getEntityMap();
51 51
 
52 52
             $wrappedObject = $entityMap->$relation($entity)->getResults($relation);
Please login to merge, or discard this patch.
src/System/SingleTableInheritanceScope.php 2 patches
Indentation   +106 added lines, -106 removed lines patch added patch discarded remove patch
@@ -6,110 +6,110 @@
 block discarded – undo
6 6
 
7 7
 class SingleTableInheritanceScope implements ScopeInterface
8 8
 {
9
-    /**
10
-     * Discriminator column name.
11
-     *
12
-     * @var string
13
-     */
14
-    protected $column;
15
-
16
-    /**
17
-     * Discriminator column allowed types.
18
-     *
19
-     * @var array
20
-     */
21
-    protected $types = [];
22
-
23
-    public function __construct(EntityMap $entityMap)
24
-    {
25
-        // Putting the heavy logic in here, so we won't have
26
-        // to go through it each time we reach for a query
27
-        // builder.
28
-
29
-        $this->column = $entityMap->getDiscriminatorColumn();
30
-
31
-        // First we need to retrieve the base class & it's normalized
32
-        // type string
33
-        $class = $entityMap->getClass();
34
-        $this->types[] = $this->getTypeStringForEntity($class, $entityMap);
35
-
36
-        // Then, we parse all registered entities for any potential
37
-        // child class.
38
-        $classes = Manager::getInstance()->getRegisteredEntities();
39
-
40
-        foreach ($classes as $otherClass => $entityMap) {
41
-            if (is_subclass_of($otherClass, $class)) {
42
-                $this->types[] = $this->getTypeStringForEntity($otherClass, $entityMap);
43
-            }
44
-        }
45
-    }
46
-
47
-    /**
48
-     * Get the normalized value to use for query on discriminator column.
49
-     *
50
-     * @param string    $class
51
-     * @param EntityMap $entityMap
52
-     *
53
-     * @return string
54
-     */
55
-    protected function getTypeStringForEntity($class, EntityMap $entityMap)
56
-    {
57
-        $class = $entityMap->getClass();
58
-
59
-        $type = array_keys(
60
-            $entityMap->getDiscriminatorColumnMap(),
61
-            $class
62
-        );
63
-
64
-        if (count($type) == 0) {
65
-            return $class;
66
-        }
67
-
68
-        return $type[0];
69
-    }
70
-
71
-    /**
72
-     * Apply the scope to a given Analogue query builder.
73
-     *
74
-     * @param \Analogue\ORM\System\Query $query
75
-     *
76
-     * @return void
77
-     */
78
-    public function apply(Query $query)
79
-    {
80
-        $query->whereIn($this->column, $this->types);
81
-    }
82
-
83
-    /**
84
-     * Remove the scope from the given Analogue query builder.
85
-     *
86
-     * @param mixed $query
87
-     *
88
-     * @return void
89
-     */
90
-    public function remove(Query $query)
91
-    {
92
-        $query = $query->getQuery();
93
-
94
-        foreach ((array) $query->wheres as $key => $where) {
95
-            if ($this->isSingleTableConstraint($where, $this->column)) {
96
-                unset($query->wheres[$key]);
97
-
98
-                $query->wheres = array_values($query->wheres);
99
-            }
100
-        }
101
-    }
102
-
103
-    /**
104
-     * Determine if the given where clause is a single table inheritance constraint.
105
-     *
106
-     * @param array  $where
107
-     * @param string $column
108
-     *
109
-     * @return bool
110
-     */
111
-    protected function isSingleTableConstraint(array $where, $column)
112
-    {
113
-        return $where['column'] == $column;
114
-    }
9
+	/**
10
+	 * Discriminator column name.
11
+	 *
12
+	 * @var string
13
+	 */
14
+	protected $column;
15
+
16
+	/**
17
+	 * Discriminator column allowed types.
18
+	 *
19
+	 * @var array
20
+	 */
21
+	protected $types = [];
22
+
23
+	public function __construct(EntityMap $entityMap)
24
+	{
25
+		// Putting the heavy logic in here, so we won't have
26
+		// to go through it each time we reach for a query
27
+		// builder.
28
+
29
+		$this->column = $entityMap->getDiscriminatorColumn();
30
+
31
+		// First we need to retrieve the base class & it's normalized
32
+		// type string
33
+		$class = $entityMap->getClass();
34
+		$this->types[] = $this->getTypeStringForEntity($class, $entityMap);
35
+
36
+		// Then, we parse all registered entities for any potential
37
+		// child class.
38
+		$classes = Manager::getInstance()->getRegisteredEntities();
39
+
40
+		foreach ($classes as $otherClass => $entityMap) {
41
+			if (is_subclass_of($otherClass, $class)) {
42
+				$this->types[] = $this->getTypeStringForEntity($otherClass, $entityMap);
43
+			}
44
+		}
45
+	}
46
+
47
+	/**
48
+	 * Get the normalized value to use for query on discriminator column.
49
+	 *
50
+	 * @param string    $class
51
+	 * @param EntityMap $entityMap
52
+	 *
53
+	 * @return string
54
+	 */
55
+	protected function getTypeStringForEntity($class, EntityMap $entityMap)
56
+	{
57
+		$class = $entityMap->getClass();
58
+
59
+		$type = array_keys(
60
+			$entityMap->getDiscriminatorColumnMap(),
61
+			$class
62
+		);
63
+
64
+		if (count($type) == 0) {
65
+			return $class;
66
+		}
67
+
68
+		return $type[0];
69
+	}
70
+
71
+	/**
72
+	 * Apply the scope to a given Analogue query builder.
73
+	 *
74
+	 * @param \Analogue\ORM\System\Query $query
75
+	 *
76
+	 * @return void
77
+	 */
78
+	public function apply(Query $query)
79
+	{
80
+		$query->whereIn($this->column, $this->types);
81
+	}
82
+
83
+	/**
84
+	 * Remove the scope from the given Analogue query builder.
85
+	 *
86
+	 * @param mixed $query
87
+	 *
88
+	 * @return void
89
+	 */
90
+	public function remove(Query $query)
91
+	{
92
+		$query = $query->getQuery();
93
+
94
+		foreach ((array) $query->wheres as $key => $where) {
95
+			if ($this->isSingleTableConstraint($where, $this->column)) {
96
+				unset($query->wheres[$key]);
97
+
98
+				$query->wheres = array_values($query->wheres);
99
+			}
100
+		}
101
+	}
102
+
103
+	/**
104
+	 * Determine if the given where clause is a single table inheritance constraint.
105
+	 *
106
+	 * @param array  $where
107
+	 * @param string $column
108
+	 *
109
+	 * @return bool
110
+	 */
111
+	protected function isSingleTableConstraint(array $where, $column)
112
+	{
113
+		return $where['column'] == $column;
114
+	}
115 115
 }
Please login to merge, or discard this patch.
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -33,7 +33,7 @@
 block discarded – undo
33 33
     /**
34 34
      * Remove the scope from the given Analogue query builder.
35 35
      *
36
-     * @param mixed $query
36
+     * @param Query $query
37 37
      *
38 38
      * @return void
39 39
      */
Please login to merge, or discard this patch.
src/MappableTrait.php 1 patch
Indentation   +57 added lines, -57 removed lines patch added patch discarded remove patch
@@ -8,65 +8,65 @@
 block discarded – undo
8 8
  */
9 9
 trait MappableTrait
10 10
 {
11
-    /**
12
-     * The Entity's Attributes.
13
-     *
14
-     * @var array
15
-     */
16
-    //protected $attributes = [];
11
+	/**
12
+	 * The Entity's Attributes.
13
+	 *
14
+	 * @var array
15
+	 */
16
+	//protected $attributes = [];
17 17
 
18
-    /**
19
-     * Method used by the mapper to set the object
20
-     * attribute raw values (hydration).
21
-     *
22
-     * @param array $attributes
23
-     *
24
-     * @return void
25
-     */
26
-    public function setEntityAttributes(array $attributes)
27
-    {
28
-        $this->attributes = $attributes;
29
-    }
18
+	/**
19
+	 * Method used by the mapper to set the object
20
+	 * attribute raw values (hydration).
21
+	 *
22
+	 * @param array $attributes
23
+	 *
24
+	 * @return void
25
+	 */
26
+	public function setEntityAttributes(array $attributes)
27
+	{
28
+		$this->attributes = $attributes;
29
+	}
30 30
 
31
-    /**
32
-     * Method used by the mapper to get the
33
-     * raw object's values.
34
-     *
35
-     * @return array
36
-     */
37
-    public function getEntityAttributes()
38
-    {
39
-        return $this->attributes;
40
-    }
31
+	/**
32
+	 * Method used by the mapper to get the
33
+	 * raw object's values.
34
+	 *
35
+	 * @return array
36
+	 */
37
+	public function getEntityAttributes()
38
+	{
39
+		return $this->attributes;
40
+	}
41 41
 
42
-    /**
43
-     * Method used by the mapper to set raw
44
-     * key-value pair.
45
-     *
46
-     * @param string $key
47
-     * @param string $value
48
-     *
49
-     * @return void
50
-     */
51
-    public function setEntityAttribute($key, $value)
52
-    {
53
-        $this->attributes[$key] = $value;
54
-    }
42
+	/**
43
+	 * Method used by the mapper to set raw
44
+	 * key-value pair.
45
+	 *
46
+	 * @param string $key
47
+	 * @param string $value
48
+	 *
49
+	 * @return void
50
+	 */
51
+	public function setEntityAttribute($key, $value)
52
+	{
53
+		$this->attributes[$key] = $value;
54
+	}
55 55
 
56
-    /**
57
-     * Method used by the mapper to get single
58
-     * key-value pair.
59
-     *
60
-     * @param string $key
61
-     *
62
-     * @return mixed|null
63
-     */
64
-    public function getEntityAttribute($key)
65
-    {
66
-        if (array_key_exists($key, $this->attributes)) {
67
-            return $this->attributes[$key];
68
-        } else {
69
-            return;
70
-        }
71
-    }
56
+	/**
57
+	 * Method used by the mapper to get single
58
+	 * key-value pair.
59
+	 *
60
+	 * @param string $key
61
+	 *
62
+	 * @return mixed|null
63
+	 */
64
+	public function getEntityAttribute($key)
65
+	{
66
+		if (array_key_exists($key, $this->attributes)) {
67
+			return $this->attributes[$key];
68
+		} else {
69
+			return;
70
+		}
71
+	}
72 72
 }
Please login to merge, or discard this patch.
src/Mappable.php 1 patch
Indentation   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -4,17 +4,17 @@
 block discarded – undo
4 4
 
5 5
 interface Mappable
6 6
 {
7
-    /**
8
-     * Set the object attribute raw values (hydration).
9
-     *
10
-     * @param array $attributes
11
-     */
12
-    public function setEntityAttributes(array $attributes);
7
+	/**
8
+	 * Set the object attribute raw values (hydration).
9
+	 *
10
+	 * @param array $attributes
11
+	 */
12
+	public function setEntityAttributes(array $attributes);
13 13
 
14
-    /**
15
-     * Get the raw object's values.
16
-     *
17
-     * @return array
18
-     */
19
-    public function getEntityAttributes();
14
+	/**
15
+	 * Get the raw object's values.
16
+	 *
17
+	 * @return array
18
+	 */
19
+	public function getEntityAttributes();
20 20
 }
Please login to merge, or discard this patch.
src/Relationships/MorphPivot.php 1 patch
Indentation   +52 added lines, -52 removed lines patch added patch discarded remove patch
@@ -6,63 +6,63 @@
 block discarded – undo
6 6
 
7 7
 class MorphPivot extends Pivot
8 8
 {
9
-    /**
10
-     * The type of the polymorphic relation.
11
-     *
12
-     * Explicitly define this so it's not included in saved attributes.
13
-     *
14
-     * @var string
15
-     */
16
-    protected $morphType;
9
+	/**
10
+	 * The type of the polymorphic relation.
11
+	 *
12
+	 * Explicitly define this so it's not included in saved attributes.
13
+	 *
14
+	 * @var string
15
+	 */
16
+	protected $morphType;
17 17
 
18
-    /**
19
-     * The value of the polymorphic relation.
20
-     *
21
-     * Explicitly define this so it's not included in saved attributes.
22
-     *
23
-     * @var string
24
-     */
25
-    protected $morphClass;
18
+	/**
19
+	 * The value of the polymorphic relation.
20
+	 *
21
+	 * Explicitly define this so it's not included in saved attributes.
22
+	 *
23
+	 * @var string
24
+	 */
25
+	protected $morphClass;
26 26
 
27
-    /**
28
-     * Set the keys for a save update query.
29
-     *
30
-     * @param Query $query
31
-     *
32
-     * @return Query
33
-     */
34
-    protected function setKeysForSaveQuery(Query $query)
35
-    {
36
-        $query->where($this->morphType, $this->morphClass);
27
+	/**
28
+	 * Set the keys for a save update query.
29
+	 *
30
+	 * @param Query $query
31
+	 *
32
+	 * @return Query
33
+	 */
34
+	protected function setKeysForSaveQuery(Query $query)
35
+	{
36
+		$query->where($this->morphType, $this->morphClass);
37 37
 
38
-        return parent::setKeysForSaveQuery($query);
39
-    }
38
+		return parent::setKeysForSaveQuery($query);
39
+	}
40 40
 
41
-    /**
42
-     * Set the morph type for the pivot.
43
-     *
44
-     * @param string $morphType
45
-     *
46
-     * @return self
47
-     */
48
-    public function setMorphType($morphType)
49
-    {
50
-        $this->morphType = $morphType;
41
+	/**
42
+	 * Set the morph type for the pivot.
43
+	 *
44
+	 * @param string $morphType
45
+	 *
46
+	 * @return self
47
+	 */
48
+	public function setMorphType($morphType)
49
+	{
50
+		$this->morphType = $morphType;
51 51
 
52
-        return $this;
53
-    }
52
+		return $this;
53
+	}
54 54
 
55
-    /**
56
-     * Set the morph class for the pivot.
57
-     *
58
-     * @param string $morphClass
59
-     *
60
-     * @return self
61
-     */
62
-    public function setMorphClass($morphClass)
63
-    {
64
-        $this->morphClass = $morphClass;
55
+	/**
56
+	 * Set the morph class for the pivot.
57
+	 *
58
+	 * @param string $morphClass
59
+	 *
60
+	 * @return self
61
+	 */
62
+	public function setMorphClass($morphClass)
63
+	{
64
+		$this->morphClass = $morphClass;
65 65
 
66
-        return $this;
67
-    }
66
+		return $this;
67
+	}
68 68
 }
Please login to merge, or discard this patch.
src/Relationships/BelongsTo.php 2 patches
Indentation   +287 added lines, -287 removed lines patch added patch discarded remove patch
@@ -10,291 +10,291 @@
 block discarded – undo
10 10
 
11 11
 class BelongsTo extends Relationship
12 12
 {
13
-    /**
14
-     * The foreign key of the parent model.
15
-     *
16
-     * @var string
17
-     */
18
-    protected $foreignKey;
19
-
20
-    /**
21
-     * The associated key on the parent model.
22
-     *
23
-     * @var string
24
-     */
25
-    protected $otherKey;
26
-
27
-    /**
28
-     * The name of the relationship.
29
-     *
30
-     * @var string
31
-     */
32
-    protected $relation;
33
-
34
-    /**
35
-     * Indicate if the parent entity hold the key for the relation.
36
-     *
37
-     * @var bool
38
-     */
39
-    protected static $ownForeignKey = true;
40
-
41
-    /**
42
-     * Create a new belongs to relationship instance.
43
-     *
44
-     * @param Mapper   $mapper
45
-     * @param Mappable $parent
46
-     * @param string   $foreignKey
47
-     * @param string   $otherKey
48
-     * @param string   $relation
49
-     */
50
-    public function __construct(Mapper $mapper, $parent, $foreignKey, $otherKey, $relation)
51
-    {
52
-        $this->otherKey = $otherKey;
53
-        $this->relation = $relation;
54
-        $this->foreignKey = $foreignKey;
55
-
56
-        parent::__construct($mapper, $parent);
57
-    }
58
-
59
-    /**
60
-     * Get the results of the relationship.
61
-     *
62
-     * @param  $relation
63
-     *
64
-     * @return \Analogue\ORM\Entity
65
-     */
66
-    public function getResults($relation)
67
-    {
68
-        $result = $this->query->first();
69
-
70
-        $this->cacheRelation($result, $relation);
71
-
72
-        return $result;
73
-    }
74
-
75
-    /**
76
-     * Set the base constraints on the relation query.
77
-     *
78
-     * @return void
79
-     */
80
-    public function addConstraints()
81
-    {
82
-        if (static::$constraints) {
83
-            // For belongs to relationships, which are essentially the inverse of has one
84
-            // or has many relationships, we need to actually query on the primary key
85
-            // of the related models matching on the foreign key that's on a parent.
86
-            $this->query->where($this->otherKey, '=', $this->parent->getEntityAttribute($this->foreignKey));
87
-        }
88
-    }
89
-
90
-    /**
91
-     * Add the constraints for a relationship count query.
92
-     *
93
-     * @param Query $query
94
-     * @param Query $parent
95
-     *
96
-     * @return Query
97
-     */
98
-    public function getRelationCountQuery(Query $query, Query $parent)
99
-    {
100
-        $query->select(new Expression('count(*)'));
101
-
102
-        $otherKey = $this->wrap($query->getTable().'.'.$this->otherKey);
103
-
104
-        return $query->where($this->getQualifiedForeignKey(), '=', new Expression($otherKey));
105
-    }
106
-
107
-    /**
108
-     * Set the constraints for an eager load of the relation.
109
-     *
110
-     * @param array $results
111
-     *
112
-     * @return void
113
-     */
114
-    public function addEagerConstraints(array $results)
115
-    {
116
-        // We'll grab the primary key name of the related models since it could be set to
117
-        // a non-standard name and not "id". We will then construct the constraint for
118
-        // our eagerly loading query so it returns the proper models from execution.
119
-        $key = $this->otherKey;
120
-
121
-        $this->query->whereIn($key, $this->getEagerModelKeys($results));
122
-    }
123
-
124
-    /**
125
-     * Gather the keys from an array of related models.
126
-     *
127
-     * @param array $results
128
-     *
129
-     * @return array
130
-     */
131
-    protected function getEagerModelKeys(array $results)
132
-    {
133
-        $keys = [];
134
-
135
-        // First we need to gather all of the keys from the result set so we know what
136
-        // to query for via the eager loading query. We will add them to an array then
137
-        // execute a "where in" statement to gather up all of those related records.
138
-        foreach ($results as $result) {
139
-            if (!is_null($value = $result[$this->foreignKey])) {
140
-                $keys[] = $value;
141
-            }
142
-        }
143
-
144
-        // If there are no keys that were not null we will just return an array with 0 in
145
-        // it so the query doesn't fail, but will not return any results, which should
146
-        // be what this developer is expecting in a case where this happens to them.
147
-        if (count($keys) == 0) {
148
-            return [0];
149
-        }
150
-
151
-        return array_values(array_unique($keys));
152
-    }
153
-
154
-    /**
155
-     * Match the Results array to an eagerly loaded relation.
156
-     *
157
-     * @param array  $results
158
-     * @param string $relation
159
-     *
160
-     * @return array
161
-     */
162
-    public function match(array $results, $relation)
163
-    {
164
-        $foreign = $this->foreignKey;
165
-
166
-        $other = $this->otherKey;
167
-
168
-        // Execute the relationship and get related entities as an EntityCollection
169
-        $entities = $this->getEager();
170
-
171
-        // First we will get to build a dictionary of the child models by their primary
172
-        // key of the relationship, then we can easily match the children back onto
173
-        // the parents using that dictionary and the primary key of the children.
174
-        $dictionary = [];
175
-
176
-        // TODO ; see if otherKey is the primary key of the related entity, we can
177
-        // simply use the EntityCollection key to match entities to results, which
178
-        // will be much more efficient, and use this method as a fallback if the
179
-        // otherKey is not the same as the primary Key.
180
-        foreach ($entities as $entity) {
181
-            $entity = $this->factory->make($entity);
182
-            $dictionary[$entity->getEntityAttribute($other)] = $entity->getObject();
183
-        }
184
-
185
-        // Once we have the dictionary constructed, we can loop through all the parents
186
-        // and match back onto their children using these keys of the dictionary and
187
-        // the primary key of the children to map them onto the correct instances.
188
-        return array_map(function ($result) use ($dictionary, $foreign, $relation) {
189
-            if (isset($dictionary[$result[$foreign]])) {
190
-                $result[$relation] = $dictionary[$result[$foreign]];
191
-            } else {
192
-                $result[$relation] = null;
193
-            }
194
-
195
-            return $result;
196
-        }, $results);
197
-    }
198
-
199
-    public function sync(array $entities)
200
-    {
201
-        if (count($entities) > 1) {
202
-            throw new MappingException("Single Relationship shouldn't be synced with more than one entity");
203
-        }
204
-
205
-        if (count($entities) == 1) {
206
-            return $this->associate($entities[0]);
207
-        }
208
-
209
-        return false;
210
-    }
211
-
212
-    /**
213
-     * Associate the model instance to the given parent.
214
-     *
215
-     * @param mixed $entity
216
-     *
217
-     * @return void
218
-     */
219
-    public function associate($entity)
220
-    {
221
-        $this->parent->setEntityAttribute($this->foreignKey, $entity->getEntityAttribute($this->otherKey));
222
-    }
223
-
224
-    /**
225
-     * Dissociate previously associated model from the given parent.
226
-     *
227
-     * @return Mappable
228
-     */
229
-    public function dissociate()
230
-    {
231
-        // The Mapper will retrieve this association within the object model, we won't be using
232
-        // the foreign key attribute inside the parent Entity.
233
-        //
234
-        //$this->parent->setEntityAttribute($this->foreignKey, null);
235
-
236
-        $this->parent->setEntityAttribute($this->relation, null);
237
-    }
238
-
239
-    /**
240
-     * Get the foreign key of the relationship.
241
-     *
242
-     * @return string
243
-     */
244
-    public function getForeignKey()
245
-    {
246
-        return $this->foreignKey;
247
-    }
248
-
249
-    /**
250
-     * Get the foreign key value pair for a related object.
251
-     *
252
-     * @param mixed $related
253
-     *
254
-     * @return array
255
-     */
256
-    public function getForeignKeyValuePair($related)
257
-    {
258
-        $foreignKey = $this->getForeignKey();
259
-
260
-        if ($related) {
261
-            $wrapper = $this->factory->make($related);
262
-
263
-            $relatedKey = $this->relatedMap->getKeyName();
264
-
265
-            return [$foreignKey => $wrapper->getEntityAttribute($relatedKey)];
266
-        } else {
267
-            return [$foreignKey => null];
268
-        }
269
-    }
270
-
271
-    /**
272
-     * Get the fully qualified foreign key of the relationship.
273
-     *
274
-     * @return string
275
-     */
276
-    public function getQualifiedForeignKey()
277
-    {
278
-        return $this->parentMap->getTable().'.'.$this->foreignKey;
279
-    }
280
-
281
-    /**
282
-     * Get the associated key of the relationship.
283
-     *
284
-     * @return string
285
-     */
286
-    public function getOtherKey()
287
-    {
288
-        return $this->otherKey;
289
-    }
290
-
291
-    /**
292
-     * Get the fully qualified associated key of the relationship.
293
-     *
294
-     * @return string
295
-     */
296
-    public function getQualifiedOtherKeyName()
297
-    {
298
-        return $this->relatedMap->getTable().'.'.$this->otherKey;
299
-    }
13
+	/**
14
+	 * The foreign key of the parent model.
15
+	 *
16
+	 * @var string
17
+	 */
18
+	protected $foreignKey;
19
+
20
+	/**
21
+	 * The associated key on the parent model.
22
+	 *
23
+	 * @var string
24
+	 */
25
+	protected $otherKey;
26
+
27
+	/**
28
+	 * The name of the relationship.
29
+	 *
30
+	 * @var string
31
+	 */
32
+	protected $relation;
33
+
34
+	/**
35
+	 * Indicate if the parent entity hold the key for the relation.
36
+	 *
37
+	 * @var bool
38
+	 */
39
+	protected static $ownForeignKey = true;
40
+
41
+	/**
42
+	 * Create a new belongs to relationship instance.
43
+	 *
44
+	 * @param Mapper   $mapper
45
+	 * @param Mappable $parent
46
+	 * @param string   $foreignKey
47
+	 * @param string   $otherKey
48
+	 * @param string   $relation
49
+	 */
50
+	public function __construct(Mapper $mapper, $parent, $foreignKey, $otherKey, $relation)
51
+	{
52
+		$this->otherKey = $otherKey;
53
+		$this->relation = $relation;
54
+		$this->foreignKey = $foreignKey;
55
+
56
+		parent::__construct($mapper, $parent);
57
+	}
58
+
59
+	/**
60
+	 * Get the results of the relationship.
61
+	 *
62
+	 * @param  $relation
63
+	 *
64
+	 * @return \Analogue\ORM\Entity
65
+	 */
66
+	public function getResults($relation)
67
+	{
68
+		$result = $this->query->first();
69
+
70
+		$this->cacheRelation($result, $relation);
71
+
72
+		return $result;
73
+	}
74
+
75
+	/**
76
+	 * Set the base constraints on the relation query.
77
+	 *
78
+	 * @return void
79
+	 */
80
+	public function addConstraints()
81
+	{
82
+		if (static::$constraints) {
83
+			// For belongs to relationships, which are essentially the inverse of has one
84
+			// or has many relationships, we need to actually query on the primary key
85
+			// of the related models matching on the foreign key that's on a parent.
86
+			$this->query->where($this->otherKey, '=', $this->parent->getEntityAttribute($this->foreignKey));
87
+		}
88
+	}
89
+
90
+	/**
91
+	 * Add the constraints for a relationship count query.
92
+	 *
93
+	 * @param Query $query
94
+	 * @param Query $parent
95
+	 *
96
+	 * @return Query
97
+	 */
98
+	public function getRelationCountQuery(Query $query, Query $parent)
99
+	{
100
+		$query->select(new Expression('count(*)'));
101
+
102
+		$otherKey = $this->wrap($query->getTable().'.'.$this->otherKey);
103
+
104
+		return $query->where($this->getQualifiedForeignKey(), '=', new Expression($otherKey));
105
+	}
106
+
107
+	/**
108
+	 * Set the constraints for an eager load of the relation.
109
+	 *
110
+	 * @param array $results
111
+	 *
112
+	 * @return void
113
+	 */
114
+	public function addEagerConstraints(array $results)
115
+	{
116
+		// We'll grab the primary key name of the related models since it could be set to
117
+		// a non-standard name and not "id". We will then construct the constraint for
118
+		// our eagerly loading query so it returns the proper models from execution.
119
+		$key = $this->otherKey;
120
+
121
+		$this->query->whereIn($key, $this->getEagerModelKeys($results));
122
+	}
123
+
124
+	/**
125
+	 * Gather the keys from an array of related models.
126
+	 *
127
+	 * @param array $results
128
+	 *
129
+	 * @return array
130
+	 */
131
+	protected function getEagerModelKeys(array $results)
132
+	{
133
+		$keys = [];
134
+
135
+		// First we need to gather all of the keys from the result set so we know what
136
+		// to query for via the eager loading query. We will add them to an array then
137
+		// execute a "where in" statement to gather up all of those related records.
138
+		foreach ($results as $result) {
139
+			if (!is_null($value = $result[$this->foreignKey])) {
140
+				$keys[] = $value;
141
+			}
142
+		}
143
+
144
+		// If there are no keys that were not null we will just return an array with 0 in
145
+		// it so the query doesn't fail, but will not return any results, which should
146
+		// be what this developer is expecting in a case where this happens to them.
147
+		if (count($keys) == 0) {
148
+			return [0];
149
+		}
150
+
151
+		return array_values(array_unique($keys));
152
+	}
153
+
154
+	/**
155
+	 * Match the Results array to an eagerly loaded relation.
156
+	 *
157
+	 * @param array  $results
158
+	 * @param string $relation
159
+	 *
160
+	 * @return array
161
+	 */
162
+	public function match(array $results, $relation)
163
+	{
164
+		$foreign = $this->foreignKey;
165
+
166
+		$other = $this->otherKey;
167
+
168
+		// Execute the relationship and get related entities as an EntityCollection
169
+		$entities = $this->getEager();
170
+
171
+		// First we will get to build a dictionary of the child models by their primary
172
+		// key of the relationship, then we can easily match the children back onto
173
+		// the parents using that dictionary and the primary key of the children.
174
+		$dictionary = [];
175
+
176
+		// TODO ; see if otherKey is the primary key of the related entity, we can
177
+		// simply use the EntityCollection key to match entities to results, which
178
+		// will be much more efficient, and use this method as a fallback if the
179
+		// otherKey is not the same as the primary Key.
180
+		foreach ($entities as $entity) {
181
+			$entity = $this->factory->make($entity);
182
+			$dictionary[$entity->getEntityAttribute($other)] = $entity->getObject();
183
+		}
184
+
185
+		// Once we have the dictionary constructed, we can loop through all the parents
186
+		// and match back onto their children using these keys of the dictionary and
187
+		// the primary key of the children to map them onto the correct instances.
188
+		return array_map(function ($result) use ($dictionary, $foreign, $relation) {
189
+			if (isset($dictionary[$result[$foreign]])) {
190
+				$result[$relation] = $dictionary[$result[$foreign]];
191
+			} else {
192
+				$result[$relation] = null;
193
+			}
194
+
195
+			return $result;
196
+		}, $results);
197
+	}
198
+
199
+	public function sync(array $entities)
200
+	{
201
+		if (count($entities) > 1) {
202
+			throw new MappingException("Single Relationship shouldn't be synced with more than one entity");
203
+		}
204
+
205
+		if (count($entities) == 1) {
206
+			return $this->associate($entities[0]);
207
+		}
208
+
209
+		return false;
210
+	}
211
+
212
+	/**
213
+	 * Associate the model instance to the given parent.
214
+	 *
215
+	 * @param mixed $entity
216
+	 *
217
+	 * @return void
218
+	 */
219
+	public function associate($entity)
220
+	{
221
+		$this->parent->setEntityAttribute($this->foreignKey, $entity->getEntityAttribute($this->otherKey));
222
+	}
223
+
224
+	/**
225
+	 * Dissociate previously associated model from the given parent.
226
+	 *
227
+	 * @return Mappable
228
+	 */
229
+	public function dissociate()
230
+	{
231
+		// The Mapper will retrieve this association within the object model, we won't be using
232
+		// the foreign key attribute inside the parent Entity.
233
+		//
234
+		//$this->parent->setEntityAttribute($this->foreignKey, null);
235
+
236
+		$this->parent->setEntityAttribute($this->relation, null);
237
+	}
238
+
239
+	/**
240
+	 * Get the foreign key of the relationship.
241
+	 *
242
+	 * @return string
243
+	 */
244
+	public function getForeignKey()
245
+	{
246
+		return $this->foreignKey;
247
+	}
248
+
249
+	/**
250
+	 * Get the foreign key value pair for a related object.
251
+	 *
252
+	 * @param mixed $related
253
+	 *
254
+	 * @return array
255
+	 */
256
+	public function getForeignKeyValuePair($related)
257
+	{
258
+		$foreignKey = $this->getForeignKey();
259
+
260
+		if ($related) {
261
+			$wrapper = $this->factory->make($related);
262
+
263
+			$relatedKey = $this->relatedMap->getKeyName();
264
+
265
+			return [$foreignKey => $wrapper->getEntityAttribute($relatedKey)];
266
+		} else {
267
+			return [$foreignKey => null];
268
+		}
269
+	}
270
+
271
+	/**
272
+	 * Get the fully qualified foreign key of the relationship.
273
+	 *
274
+	 * @return string
275
+	 */
276
+	public function getQualifiedForeignKey()
277
+	{
278
+		return $this->parentMap->getTable().'.'.$this->foreignKey;
279
+	}
280
+
281
+	/**
282
+	 * Get the associated key of the relationship.
283
+	 *
284
+	 * @return string
285
+	 */
286
+	public function getOtherKey()
287
+	{
288
+		return $this->otherKey;
289
+	}
290
+
291
+	/**
292
+	 * Get the fully qualified associated key of the relationship.
293
+	 *
294
+	 * @return string
295
+	 */
296
+	public function getQualifiedOtherKeyName()
297
+	{
298
+		return $this->relatedMap->getTable().'.'.$this->otherKey;
299
+	}
300 300
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -185,7 +185,7 @@
 block discarded – undo
185 185
         // Once we have the dictionary constructed, we can loop through all the parents
186 186
         // and match back onto their children using these keys of the dictionary and
187 187
         // the primary key of the children to map them onto the correct instances.
188
-        return array_map(function ($result) use ($dictionary, $foreign, $relation) {
188
+        return array_map(function($result) use ($dictionary, $foreign, $relation) {
189 189
             if (isset($dictionary[$result[$foreign]])) {
190 190
                 $result[$relation] = $dictionary[$result[$foreign]];
191 191
             } else {
Please login to merge, or discard this patch.