Completed
Branch 5.6 (cd95fb)
by Rémi
10:17
created
src/System/Cache/CachedRelationship.php 1 patch
Indentation   +59 added lines, -59 removed lines patch added patch discarded remove patch
@@ -8,69 +8,69 @@
 block discarded – undo
8 8
  */
9 9
 class CachedRelationship
10 10
 {
11
-    /**
12
-     * The Hash of the related entity.
13
-     *
14
-     * @var string
15
-     */
16
-    protected $hash;
11
+	/**
12
+	 * The Hash of the related entity.
13
+	 *
14
+	 * @var string
15
+	 */
16
+	protected $hash;
17 17
 
18
-    /**
19
-     * Pivot attributes.
20
-     *
21
-     * @var array
22
-     */
23
-    protected $pivotAttributes;
18
+	/**
19
+	 * Pivot attributes.
20
+	 *
21
+	 * @var array
22
+	 */
23
+	protected $pivotAttributes;
24 24
 
25
-    /**
26
-     * CachedRelationship constructor.
27
-     *
28
-     * @param string $hash
29
-     * @param array  $pivotAttributes
30
-     */
31
-    public function __construct(string $hash, array $pivotAttributes = [])
32
-    {
33
-        $this->hash = $hash;
34
-        $this->pivotAttributes = $pivotAttributes;
35
-    }
25
+	/**
26
+	 * CachedRelationship constructor.
27
+	 *
28
+	 * @param string $hash
29
+	 * @param array  $pivotAttributes
30
+	 */
31
+	public function __construct(string $hash, array $pivotAttributes = [])
32
+	{
33
+		$this->hash = $hash;
34
+		$this->pivotAttributes = $pivotAttributes;
35
+	}
36 36
 
37
-    /**
38
-     * Return true if any pivot attributes are present.
39
-     *
40
-     * @return bool
41
-     */
42
-    public function hasPivotAttributes(): bool
43
-    {
44
-        return !empty($this->pivotAttributes);
45
-    }
37
+	/**
38
+	 * Return true if any pivot attributes are present.
39
+	 *
40
+	 * @return bool
41
+	 */
42
+	public function hasPivotAttributes(): bool
43
+	{
44
+		return !empty($this->pivotAttributes);
45
+	}
46 46
 
47
-    /**
48
-     * Returns the hash of the related entity.
49
-     *
50
-     * @return string
51
-     */
52
-    public function getHash(): string
53
-    {
54
-        return $this->hash;
55
-    }
47
+	/**
48
+	 * Returns the hash of the related entity.
49
+	 *
50
+	 * @return string
51
+	 */
52
+	public function getHash(): string
53
+	{
54
+		return $this->hash;
55
+	}
56 56
 
57
-    /**
58
-     * Get the cached values for the pivot attributes.
59
-     *
60
-     * @return array
61
-     */
62
-    public function getPivotAttributes(): array
63
-    {
64
-        return $this->pivotAttributes;
65
-    }
57
+	/**
58
+	 * Get the cached values for the pivot attributes.
59
+	 *
60
+	 * @return array
61
+	 */
62
+	public function getPivotAttributes(): array
63
+	{
64
+		return $this->pivotAttributes;
65
+	}
66 66
 
67
-    /**
68
-     * Access to the hash for fast cache comparison.
69
-     *
70
-     * @return string
71
-     */
72
-    public function __toString(): string
73
-    {
74
-        return $this->hash;
75
-    }
67
+	/**
68
+	 * Access to the hash for fast cache comparison.
69
+	 *
70
+	 * @return string
71
+	 */
72
+	public function __toString(): string
73
+	{
74
+		return $this->hash;
75
+	}
76 76
 }
Please login to merge, or discard this patch.
src/System/InternallyMappable.php 1 patch
Indentation   +59 added lines, -59 removed lines patch added patch discarded remove patch
@@ -4,71 +4,71 @@
 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 entity class name.
16
-     *
17
-     * @return string
18
-     */
19
-    public function getEntityClass(): string;
14
+	/**
15
+	 * Get the entity class name.
16
+	 *
17
+	 * @return string
18
+	 */
19
+	public function getEntityClass(): string;
20 20
 
21
-    /**
22
-     * Return the entity's primary key name.
23
-     *
24
-     * @return string
25
-     */
26
-    public function getEntityKeyName(): string;
21
+	/**
22
+	 * Return the entity's primary key name.
23
+	 *
24
+	 * @return string
25
+	 */
26
+	public function getEntityKeyName(): string;
27 27
 
28
-    /**
29
-     * Return the entity's primary key value.
30
-     *
31
-     * @return mixed
32
-     */
33
-    public function getEntityKeyValue();
28
+	/**
29
+	 * Return the entity's primary key value.
30
+	 *
31
+	 * @return mixed
32
+	 */
33
+	public function getEntityKeyValue();
34 34
 
35
-    /**
36
-     * Return the Entity's hash $class.$id.
37
-     *
38
-     * @return string
39
-     */
40
-    public function getEntityHash(): string;
35
+	/**
36
+	 * Return the Entity's hash $class.$id.
37
+	 *
38
+	 * @return string
39
+	 */
40
+	public function getEntityHash(): string;
41 41
 
42
-    /**
43
-     * Get the raw object's values.
44
-     *
45
-     * @return array
46
-     */
47
-    public function getEntityAttributes();
42
+	/**
43
+	 * Get the raw object's values.
44
+	 *
45
+	 * @return array
46
+	 */
47
+	public function getEntityAttributes();
48 48
 
49
-    /**
50
-     * Set the raw entity attributes.
51
-     *
52
-     * @param string $key
53
-     * @param mixed  $value
54
-     */
55
-    public function setEntityAttribute(string $key, $value);
49
+	/**
50
+	 * Set the raw entity attributes.
51
+	 *
52
+	 * @param string $key
53
+	 * @param mixed  $value
54
+	 */
55
+	public function setEntityAttribute(string $key, $value);
56 56
 
57
-    /**
58
-     * Return the entity's attribute.
59
-     *
60
-     * @param string $key
61
-     *
62
-     * @return mixed
63
-     */
64
-    public function getEntityAttribute(string $key);
57
+	/**
58
+	 * Return the entity's attribute.
59
+	 *
60
+	 * @param string $key
61
+	 *
62
+	 * @return mixed
63
+	 */
64
+	public function getEntityAttribute(string $key);
65 65
 
66
-    /**
67
-     * Does the entity posses the given attribute.
68
-     *
69
-     * @param string $key
70
-     *
71
-     * @return bool
72
-     */
73
-    public function hasAttribute(string $key): bool;
66
+	/**
67
+	 * Does the entity posses the given attribute.
68
+	 *
69
+	 * @param string $key
70
+	 *
71
+	 * @return bool
72
+	 */
73
+	public function hasAttribute(string $key): bool;
74 74
 }
Please login to merge, or discard this patch.
src/System/ResultBuilder.php 2 patches
Indentation   +407 added lines, -407 removed lines patch added patch discarded remove patch
@@ -7,411 +7,411 @@
 block discarded – undo
7 7
 
8 8
 class ResultBuilder
9 9
 {
10
-    /**
11
-     * The default mapper used to build entities with.
12
-     *
13
-     * @var \Analogue\ORM\System\Mapper
14
-     */
15
-    protected $defaultMapper;
16
-
17
-    /**
18
-     * Relations that will be eager loaded on this query.
19
-     *
20
-     * @var array
21
-     */
22
-    protected $eagerLoads;
23
-
24
-    /**
25
-     * The Entity Map for the entity to build.
26
-     *
27
-     * @var \Analogue\ORM\EntityMap
28
-     */
29
-    protected $entityMap;
30
-
31
-    /**
32
-     * An array of builders used by this class to build necessary
33
-     * entities for each result type.
34
-     *
35
-     * @var array
36
-     */
37
-    protected $builders = [];
38
-
39
-    /**
40
-     * ResultBuilder constructor.
41
-     *
42
-     * @param Mapper $defaultMapper
43
-     */
44
-    public function __construct(Mapper $defaultMapper)
45
-    {
46
-        $this->defaultMapper = $defaultMapper;
47
-        $this->entityMap = $defaultMapper->getEntityMap();
48
-    }
49
-
50
-    /**
51
-     * Convert a result set into an array of entities.
52
-     *
53
-     * @param array $results
54
-     * @param array $eagerLoads name of the relation to be eager loaded on the Entities
55
-     *
56
-     * @return array
57
-     */
58
-    public function build(array $results, array $eagerLoads)
59
-    {
60
-        // First, we'll cache the raw result set
61
-        $this->cacheResults($results);
62
-
63
-        // Parse embedded relations and build corresponding entities using the default
64
-        // mapper.
65
-        $results = $this->buildEmbeddedRelationships($results);
66
-
67
-        // Launch the queries related to eager loads, and match the
68
-        // current result set to these loaded relationships.
69
-        $results = $this->queryEagerLoadedRelationships($results, $eagerLoads);
70
-
71
-        // Note : Maybe we could use a PolymorphicResultBuilder, which would
72
-        // be shared by both STI and polymorphic relations, as they share the
73
-        // same process.
74
-
75
-        switch ($this->entityMap->getInheritanceType()) {
76
-            case 'single_table':
77
-                return $this->buildUsingSingleTableInheritance($results);
78
-
79
-            default:
80
-                return $this->buildWithDefaultMapper($results);
81
-        }
82
-    }
83
-
84
-    /**
85
-     * Cache result set.
86
-     *
87
-     * @param array $results
88
-     *
89
-     * @return void
90
-     */
91
-    protected function cacheResults(array $results)
92
-    {
93
-        switch ($this->entityMap->getInheritanceType()) {
94
-            case 'single_table':
95
-
96
-                $this->cacheSingleTableInheritanceResults($results);
97
-                break;
98
-
99
-            default:
100
-                $mapper = $this->defaultMapper;
101
-                 // When hydrating EmbeddedValue object, they'll likely won't
102
-                // have a primary key set.
103
-                if (!is_null($mapper->getEntityMap()->getKeyName())) {
104
-                    $mapper->getEntityCache()->add($results);
105
-                }
106
-                break;
107
-        }
108
-    }
109
-
110
-    /**
111
-     * Cache results from a STI result set.
112
-     *
113
-     * @param array $results
114
-     *
115
-     * @return void
116
-     */
117
-    protected function cacheSingleTableInheritanceResults(array $results)
118
-    {
119
-        foreach ($results as $result) {
120
-            $mapper = $this->getMapperForSingleRow($result);
121
-
122
-            // When hydrating EmbeddedValue object, they'll likely won't
123
-            // have a primary key set.
124
-            if (!is_null($mapper->getEntityMap()->getKeyName())) {
125
-                $mapper->getEntityCache()->add([$result]);
126
-            }
127
-        }
128
-    }
129
-
130
-    /**
131
-     * Build embedded objects and match them to the result set.
132
-     *
133
-     * @param array $results
134
-     *
135
-     * @return array
136
-     */
137
-    protected function buildEmbeddedRelationships(array $results) : array
138
-    {
139
-        $entityMap = $this->entityMap;
140
-        $instance = $this->defaultMapper->newInstance();
141
-        $embeddeds = $entityMap->getEmbeddedRelationships();
142
-
143
-        foreach ($embeddeds as $embedded) {
144
-            $results = $entityMap->$embedded($instance)->match($results, $embedded);
145
-        }
146
-
147
-        return $results;
148
-    }
149
-
150
-    /**
151
-     * Launch queries on eager loaded relationships.
152
-     *
153
-     * @param array $results
154
-     * @param array $eagerLoads
155
-     *
156
-     * @return array
157
-     */
158
-    protected function queryEagerLoadedRelationships(array $results, array $eagerLoads) : array
159
-    {
160
-        $this->eagerLoads = $this->parseRelations($eagerLoads);
161
-
162
-        return $this->eagerLoadRelations($results);
163
-    }
164
-
165
-    /**
166
-     * Parse a list of relations into individuals.
167
-     *
168
-     * @param array $relations
169
-     *
170
-     * @return array
171
-     */
172
-    protected function parseRelations(array $relations): array
173
-    {
174
-        $results = [];
175
-
176
-        foreach ($relations as $name => $constraints) {
177
-            // If the "relation" value is actually a numeric key, we can assume that no
178
-            // constraints have been specified for the eager load and we'll just put
179
-            // an empty Closure with the loader so that we can treat all the same.
180
-            if (is_numeric($name)) {
181
-                $f = function () {
182
-                };
183
-
184
-                list($name, $constraints) = [$constraints, $f];
185
-            }
186
-
187
-            // We need to separate out any nested includes. Which allows the developers
188
-            // to load deep relationships using "dots" without stating each level of
189
-            // the relationship with its own key in the array of eager load names.
190
-            $results = $this->parseNested($name, $results);
191
-
192
-            $results[$name] = $constraints;
193
-        }
194
-
195
-        return $results;
196
-    }
197
-
198
-    /**
199
-     * Parse the nested relationships in a relation.
200
-     *
201
-     * @param string $name
202
-     * @param array  $results
203
-     *
204
-     * @return array
205
-     */
206
-    protected function parseNested(string $name, array $results): array
207
-    {
208
-        $progress = [];
209
-
210
-        // If the relation has already been set on the result array, we will not set it
211
-        // again, since that would override any constraints that were already placed
212
-        // on the relationships. We will only set the ones that are not specified.
213
-        foreach (explode('.', $name) as $segment) {
214
-            $progress[] = $segment;
215
-
216
-            if (!isset($results[$last = implode('.', $progress)])) {
217
-                $results[$last] = function () {
218
-                };
219
-            }
220
-        }
221
-
222
-        return $results;
223
-    }
224
-
225
-    /**
226
-     * Eager load the relationships on a result set.
227
-     *
228
-     * @param array $results
229
-     *
230
-     * @return array
231
-     */
232
-    public function eagerLoadRelations(array $results): array
233
-    {
234
-        foreach ($this->eagerLoads as $name => $constraints) {
235
-
236
-            // For nested eager loads we'll skip loading them here and they will be set as an
237
-            // eager load on the query to retrieve the relation so that they will be eager
238
-            // loaded on that query, because that is where they get hydrated as models.
239
-            if (strpos($name, '.') === false) {
240
-                $results = $this->loadRelation($results, $name, $constraints);
241
-            }
242
-        }
243
-
244
-        return $results;
245
-    }
246
-
247
-    /**
248
-     * Eagerly load the relationship on a set of entities.
249
-     *
250
-     * @param array    $results
251
-     * @param string   $name
252
-     * @param \Closure $constraints
253
-     *
254
-     * @return array
255
-     */
256
-    protected function loadRelation(array $results, string $name, Closure $constraints) : array
257
-    {
258
-        // First we will "back up" the existing where conditions on the query so we can
259
-        // add our eager constraints. Then we will merge the wheres that were on the
260
-        // query back to it in order that any where conditions might be specified.
261
-        $relation = $this->getRelation($name);
262
-
263
-        $relation->addEagerConstraints($results);
264
-
265
-        call_user_func($constraints, $relation);
266
-
267
-        // Once we have the results, we just match those back up to their parent models
268
-        // using the relationship instance. Then we just return the finished arrays
269
-        // of models which have been eagerly hydrated and are readied for return.
270
-
271
-        return $relation->match($results, $name);
272
-    }
273
-
274
-    /**
275
-     * Get the relation instance for the given relation name.
276
-     *
277
-     * @param string $relation
278
-     *
279
-     * @return \Analogue\ORM\Relationships\Relationship
280
-     */
281
-    public function getRelation(string $relation): Relationship
282
-    {
283
-        // We want to run a relationship query without any constrains so that we will
284
-        // not have to remove these where clauses manually which gets really hacky
285
-        // and is error prone while we remove the developer's own where clauses.
286
-        $query = Relationship::noConstraints(function () use ($relation) {
287
-            return $this->entityMap->$relation($this->defaultMapper->newInstance());
288
-        });
289
-
290
-        $nested = $this->nestedRelations($relation);
291
-
292
-        // If there are nested relationships set on the query, we will put those onto
293
-        // the query instances so that they can be handled after this relationship
294
-        // is loaded. In this way they will all trickle down as they are loaded.
295
-        if (count($nested) > 0) {
296
-            $query->getQuery()->with($nested);
297
-        }
298
-
299
-        return $query;
300
-    }
301
-
302
-    /**
303
-     * Get the deeply nested relations for a given top-level relation.
304
-     *
305
-     * @param string $relation
306
-     *
307
-     * @return array
308
-     */
309
-    protected function nestedRelations(string $relation): array
310
-    {
311
-        $nested = [];
312
-
313
-        // We are basically looking for any relationships that are nested deeper than
314
-        // the given top-level relationship. We will just check for any relations
315
-        // that start with the given top relations and adds them to our arrays.
316
-        foreach ($this->eagerLoads as $name => $constraints) {
317
-            if ($this->isNested($name, $relation)) {
318
-                $nested[substr($name, strlen($relation.'.'))] = $constraints;
319
-            }
320
-        }
321
-
322
-        return $nested;
323
-    }
324
-
325
-    /**
326
-     * Determine if the relationship is nested.
327
-     *
328
-     * @param string $name
329
-     * @param string $relation
330
-     *
331
-     * @return bool
332
-     */
333
-    protected function isNested(string $name, string $relation): bool
334
-    {
335
-        $dots = str_contains($name, '.');
336
-
337
-        return $dots && starts_with($name, $relation.'.');
338
-    }
339
-
340
-    /**
341
-     * Build an entity from results, using the default mapper on this builder.
342
-     * This is the default build plan when no table inheritance is being used.
343
-     *
344
-     * @param array $results
345
-     *
346
-     * @return array
347
-     */
348
-    protected function buildWithDefaultMapper(array $results): array
349
-    {
350
-        $builder = new EntityBuilder($this->defaultMapper, array_keys($this->eagerLoads));
351
-
352
-        return array_map(function ($item) use ($builder) {
353
-            return $builder->build($item);
354
-        }, $results);
355
-    }
356
-
357
-    /**
358
-     * Build an entity from results, using single table inheritance.
359
-     *
360
-     * @param array $results
361
-     *
362
-     * @return array
363
-     */
364
-    protected function buildUsingSingleTableInheritance(array $results): array
365
-    {
366
-        return array_map(function ($item) {
367
-            $builder = $this->builderForResult($item);
368
-
369
-            return $builder->build($item);
370
-        }, $results);
371
-    }
372
-
373
-    /**
374
-     * Given a result array, return the entity builder needed to correctly
375
-     * build the result into an entity. If no getDiscriminatorColumnMap property
376
-     * has been defined on the EntityMap, we'll assume that the value stored in
377
-     * the $type column is the fully qualified class name of the entity and
378
-     * we'll use it instead.
379
-     *
380
-     * @param array $result
381
-     *
382
-     * @return EntityBuilder
383
-     */
384
-    protected function builderForResult(array $result): EntityBuilder
385
-    {
386
-        $type = $result[$this->entityMap->getDiscriminatorColumn()];
387
-
388
-        $mapper = $this->getMapperForSingleRow($result);
389
-
390
-        if (!isset($this->builders[$type])) {
391
-            $this->builders[$type] = new EntityBuilder(
392
-                $mapper,
393
-                array_keys($this->eagerLoads)
394
-            );
395
-        }
396
-
397
-        return $this->builders[$type];
398
-    }
399
-
400
-    /**
401
-     * Get mapper corresponding to the result type.
402
-     *
403
-     * @param array $result
404
-     *
405
-     * @return Mapper
406
-     */
407
-    protected function getMapperForSingleRow(array $result) : Mapper
408
-    {
409
-        $type = $result[$this->entityMap->getDiscriminatorColumn()];
410
-
411
-        $columnMap = $this->entityMap->getDiscriminatorColumnMap();
412
-
413
-        $class = isset($columnMap[$type]) ? $columnMap[$type] : $type;
414
-
415
-        return Manager::getInstance()->mapper($class);
416
-    }
10
+	/**
11
+	 * The default mapper used to build entities with.
12
+	 *
13
+	 * @var \Analogue\ORM\System\Mapper
14
+	 */
15
+	protected $defaultMapper;
16
+
17
+	/**
18
+	 * Relations that will be eager loaded on this query.
19
+	 *
20
+	 * @var array
21
+	 */
22
+	protected $eagerLoads;
23
+
24
+	/**
25
+	 * The Entity Map for the entity to build.
26
+	 *
27
+	 * @var \Analogue\ORM\EntityMap
28
+	 */
29
+	protected $entityMap;
30
+
31
+	/**
32
+	 * An array of builders used by this class to build necessary
33
+	 * entities for each result type.
34
+	 *
35
+	 * @var array
36
+	 */
37
+	protected $builders = [];
38
+
39
+	/**
40
+	 * ResultBuilder constructor.
41
+	 *
42
+	 * @param Mapper $defaultMapper
43
+	 */
44
+	public function __construct(Mapper $defaultMapper)
45
+	{
46
+		$this->defaultMapper = $defaultMapper;
47
+		$this->entityMap = $defaultMapper->getEntityMap();
48
+	}
49
+
50
+	/**
51
+	 * Convert a result set into an array of entities.
52
+	 *
53
+	 * @param array $results
54
+	 * @param array $eagerLoads name of the relation to be eager loaded on the Entities
55
+	 *
56
+	 * @return array
57
+	 */
58
+	public function build(array $results, array $eagerLoads)
59
+	{
60
+		// First, we'll cache the raw result set
61
+		$this->cacheResults($results);
62
+
63
+		// Parse embedded relations and build corresponding entities using the default
64
+		// mapper.
65
+		$results = $this->buildEmbeddedRelationships($results);
66
+
67
+		// Launch the queries related to eager loads, and match the
68
+		// current result set to these loaded relationships.
69
+		$results = $this->queryEagerLoadedRelationships($results, $eagerLoads);
70
+
71
+		// Note : Maybe we could use a PolymorphicResultBuilder, which would
72
+		// be shared by both STI and polymorphic relations, as they share the
73
+		// same process.
74
+
75
+		switch ($this->entityMap->getInheritanceType()) {
76
+			case 'single_table':
77
+				return $this->buildUsingSingleTableInheritance($results);
78
+
79
+			default:
80
+				return $this->buildWithDefaultMapper($results);
81
+		}
82
+	}
83
+
84
+	/**
85
+	 * Cache result set.
86
+	 *
87
+	 * @param array $results
88
+	 *
89
+	 * @return void
90
+	 */
91
+	protected function cacheResults(array $results)
92
+	{
93
+		switch ($this->entityMap->getInheritanceType()) {
94
+			case 'single_table':
95
+
96
+				$this->cacheSingleTableInheritanceResults($results);
97
+				break;
98
+
99
+			default:
100
+				$mapper = $this->defaultMapper;
101
+				 // When hydrating EmbeddedValue object, they'll likely won't
102
+				// have a primary key set.
103
+				if (!is_null($mapper->getEntityMap()->getKeyName())) {
104
+					$mapper->getEntityCache()->add($results);
105
+				}
106
+				break;
107
+		}
108
+	}
109
+
110
+	/**
111
+	 * Cache results from a STI result set.
112
+	 *
113
+	 * @param array $results
114
+	 *
115
+	 * @return void
116
+	 */
117
+	protected function cacheSingleTableInheritanceResults(array $results)
118
+	{
119
+		foreach ($results as $result) {
120
+			$mapper = $this->getMapperForSingleRow($result);
121
+
122
+			// When hydrating EmbeddedValue object, they'll likely won't
123
+			// have a primary key set.
124
+			if (!is_null($mapper->getEntityMap()->getKeyName())) {
125
+				$mapper->getEntityCache()->add([$result]);
126
+			}
127
+		}
128
+	}
129
+
130
+	/**
131
+	 * Build embedded objects and match them to the result set.
132
+	 *
133
+	 * @param array $results
134
+	 *
135
+	 * @return array
136
+	 */
137
+	protected function buildEmbeddedRelationships(array $results) : array
138
+	{
139
+		$entityMap = $this->entityMap;
140
+		$instance = $this->defaultMapper->newInstance();
141
+		$embeddeds = $entityMap->getEmbeddedRelationships();
142
+
143
+		foreach ($embeddeds as $embedded) {
144
+			$results = $entityMap->$embedded($instance)->match($results, $embedded);
145
+		}
146
+
147
+		return $results;
148
+	}
149
+
150
+	/**
151
+	 * Launch queries on eager loaded relationships.
152
+	 *
153
+	 * @param array $results
154
+	 * @param array $eagerLoads
155
+	 *
156
+	 * @return array
157
+	 */
158
+	protected function queryEagerLoadedRelationships(array $results, array $eagerLoads) : array
159
+	{
160
+		$this->eagerLoads = $this->parseRelations($eagerLoads);
161
+
162
+		return $this->eagerLoadRelations($results);
163
+	}
164
+
165
+	/**
166
+	 * Parse a list of relations into individuals.
167
+	 *
168
+	 * @param array $relations
169
+	 *
170
+	 * @return array
171
+	 */
172
+	protected function parseRelations(array $relations): array
173
+	{
174
+		$results = [];
175
+
176
+		foreach ($relations as $name => $constraints) {
177
+			// If the "relation" value is actually a numeric key, we can assume that no
178
+			// constraints have been specified for the eager load and we'll just put
179
+			// an empty Closure with the loader so that we can treat all the same.
180
+			if (is_numeric($name)) {
181
+				$f = function () {
182
+				};
183
+
184
+				list($name, $constraints) = [$constraints, $f];
185
+			}
186
+
187
+			// We need to separate out any nested includes. Which allows the developers
188
+			// to load deep relationships using "dots" without stating each level of
189
+			// the relationship with its own key in the array of eager load names.
190
+			$results = $this->parseNested($name, $results);
191
+
192
+			$results[$name] = $constraints;
193
+		}
194
+
195
+		return $results;
196
+	}
197
+
198
+	/**
199
+	 * Parse the nested relationships in a relation.
200
+	 *
201
+	 * @param string $name
202
+	 * @param array  $results
203
+	 *
204
+	 * @return array
205
+	 */
206
+	protected function parseNested(string $name, array $results): array
207
+	{
208
+		$progress = [];
209
+
210
+		// If the relation has already been set on the result array, we will not set it
211
+		// again, since that would override any constraints that were already placed
212
+		// on the relationships. We will only set the ones that are not specified.
213
+		foreach (explode('.', $name) as $segment) {
214
+			$progress[] = $segment;
215
+
216
+			if (!isset($results[$last = implode('.', $progress)])) {
217
+				$results[$last] = function () {
218
+				};
219
+			}
220
+		}
221
+
222
+		return $results;
223
+	}
224
+
225
+	/**
226
+	 * Eager load the relationships on a result set.
227
+	 *
228
+	 * @param array $results
229
+	 *
230
+	 * @return array
231
+	 */
232
+	public function eagerLoadRelations(array $results): array
233
+	{
234
+		foreach ($this->eagerLoads as $name => $constraints) {
235
+
236
+			// For nested eager loads we'll skip loading them here and they will be set as an
237
+			// eager load on the query to retrieve the relation so that they will be eager
238
+			// loaded on that query, because that is where they get hydrated as models.
239
+			if (strpos($name, '.') === false) {
240
+				$results = $this->loadRelation($results, $name, $constraints);
241
+			}
242
+		}
243
+
244
+		return $results;
245
+	}
246
+
247
+	/**
248
+	 * Eagerly load the relationship on a set of entities.
249
+	 *
250
+	 * @param array    $results
251
+	 * @param string   $name
252
+	 * @param \Closure $constraints
253
+	 *
254
+	 * @return array
255
+	 */
256
+	protected function loadRelation(array $results, string $name, Closure $constraints) : array
257
+	{
258
+		// First we will "back up" the existing where conditions on the query so we can
259
+		// add our eager constraints. Then we will merge the wheres that were on the
260
+		// query back to it in order that any where conditions might be specified.
261
+		$relation = $this->getRelation($name);
262
+
263
+		$relation->addEagerConstraints($results);
264
+
265
+		call_user_func($constraints, $relation);
266
+
267
+		// Once we have the results, we just match those back up to their parent models
268
+		// using the relationship instance. Then we just return the finished arrays
269
+		// of models which have been eagerly hydrated and are readied for return.
270
+
271
+		return $relation->match($results, $name);
272
+	}
273
+
274
+	/**
275
+	 * Get the relation instance for the given relation name.
276
+	 *
277
+	 * @param string $relation
278
+	 *
279
+	 * @return \Analogue\ORM\Relationships\Relationship
280
+	 */
281
+	public function getRelation(string $relation): Relationship
282
+	{
283
+		// We want to run a relationship query without any constrains so that we will
284
+		// not have to remove these where clauses manually which gets really hacky
285
+		// and is error prone while we remove the developer's own where clauses.
286
+		$query = Relationship::noConstraints(function () use ($relation) {
287
+			return $this->entityMap->$relation($this->defaultMapper->newInstance());
288
+		});
289
+
290
+		$nested = $this->nestedRelations($relation);
291
+
292
+		// If there are nested relationships set on the query, we will put those onto
293
+		// the query instances so that they can be handled after this relationship
294
+		// is loaded. In this way they will all trickle down as they are loaded.
295
+		if (count($nested) > 0) {
296
+			$query->getQuery()->with($nested);
297
+		}
298
+
299
+		return $query;
300
+	}
301
+
302
+	/**
303
+	 * Get the deeply nested relations for a given top-level relation.
304
+	 *
305
+	 * @param string $relation
306
+	 *
307
+	 * @return array
308
+	 */
309
+	protected function nestedRelations(string $relation): array
310
+	{
311
+		$nested = [];
312
+
313
+		// We are basically looking for any relationships that are nested deeper than
314
+		// the given top-level relationship. We will just check for any relations
315
+		// that start with the given top relations and adds them to our arrays.
316
+		foreach ($this->eagerLoads as $name => $constraints) {
317
+			if ($this->isNested($name, $relation)) {
318
+				$nested[substr($name, strlen($relation.'.'))] = $constraints;
319
+			}
320
+		}
321
+
322
+		return $nested;
323
+	}
324
+
325
+	/**
326
+	 * Determine if the relationship is nested.
327
+	 *
328
+	 * @param string $name
329
+	 * @param string $relation
330
+	 *
331
+	 * @return bool
332
+	 */
333
+	protected function isNested(string $name, string $relation): bool
334
+	{
335
+		$dots = str_contains($name, '.');
336
+
337
+		return $dots && starts_with($name, $relation.'.');
338
+	}
339
+
340
+	/**
341
+	 * Build an entity from results, using the default mapper on this builder.
342
+	 * This is the default build plan when no table inheritance is being used.
343
+	 *
344
+	 * @param array $results
345
+	 *
346
+	 * @return array
347
+	 */
348
+	protected function buildWithDefaultMapper(array $results): array
349
+	{
350
+		$builder = new EntityBuilder($this->defaultMapper, array_keys($this->eagerLoads));
351
+
352
+		return array_map(function ($item) use ($builder) {
353
+			return $builder->build($item);
354
+		}, $results);
355
+	}
356
+
357
+	/**
358
+	 * Build an entity from results, using single table inheritance.
359
+	 *
360
+	 * @param array $results
361
+	 *
362
+	 * @return array
363
+	 */
364
+	protected function buildUsingSingleTableInheritance(array $results): array
365
+	{
366
+		return array_map(function ($item) {
367
+			$builder = $this->builderForResult($item);
368
+
369
+			return $builder->build($item);
370
+		}, $results);
371
+	}
372
+
373
+	/**
374
+	 * Given a result array, return the entity builder needed to correctly
375
+	 * build the result into an entity. If no getDiscriminatorColumnMap property
376
+	 * has been defined on the EntityMap, we'll assume that the value stored in
377
+	 * the $type column is the fully qualified class name of the entity and
378
+	 * we'll use it instead.
379
+	 *
380
+	 * @param array $result
381
+	 *
382
+	 * @return EntityBuilder
383
+	 */
384
+	protected function builderForResult(array $result): EntityBuilder
385
+	{
386
+		$type = $result[$this->entityMap->getDiscriminatorColumn()];
387
+
388
+		$mapper = $this->getMapperForSingleRow($result);
389
+
390
+		if (!isset($this->builders[$type])) {
391
+			$this->builders[$type] = new EntityBuilder(
392
+				$mapper,
393
+				array_keys($this->eagerLoads)
394
+			);
395
+		}
396
+
397
+		return $this->builders[$type];
398
+	}
399
+
400
+	/**
401
+	 * Get mapper corresponding to the result type.
402
+	 *
403
+	 * @param array $result
404
+	 *
405
+	 * @return Mapper
406
+	 */
407
+	protected function getMapperForSingleRow(array $result) : Mapper
408
+	{
409
+		$type = $result[$this->entityMap->getDiscriminatorColumn()];
410
+
411
+		$columnMap = $this->entityMap->getDiscriminatorColumnMap();
412
+
413
+		$class = isset($columnMap[$type]) ? $columnMap[$type] : $type;
414
+
415
+		return Manager::getInstance()->mapper($class);
416
+	}
417 417
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -178,7 +178,7 @@  discard block
 block discarded – undo
178 178
             // constraints have been specified for the eager load and we'll just put
179 179
             // an empty Closure with the loader so that we can treat all the same.
180 180
             if (is_numeric($name)) {
181
-                $f = function () {
181
+                $f = function() {
182 182
                 };
183 183
 
184 184
                 list($name, $constraints) = [$constraints, $f];
@@ -214,7 +214,7 @@  discard block
 block discarded – undo
214 214
             $progress[] = $segment;
215 215
 
216 216
             if (!isset($results[$last = implode('.', $progress)])) {
217
-                $results[$last] = function () {
217
+                $results[$last] = function() {
218 218
                 };
219 219
             }
220 220
         }
@@ -283,7 +283,7 @@  discard block
 block discarded – undo
283 283
         // We want to run a relationship query without any constrains so that we will
284 284
         // not have to remove these where clauses manually which gets really hacky
285 285
         // and is error prone while we remove the developer's own where clauses.
286
-        $query = Relationship::noConstraints(function () use ($relation) {
286
+        $query = Relationship::noConstraints(function() use ($relation) {
287 287
             return $this->entityMap->$relation($this->defaultMapper->newInstance());
288 288
         });
289 289
 
@@ -349,7 +349,7 @@  discard block
 block discarded – undo
349 349
     {
350 350
         $builder = new EntityBuilder($this->defaultMapper, array_keys($this->eagerLoads));
351 351
 
352
-        return array_map(function ($item) use ($builder) {
352
+        return array_map(function($item) use ($builder) {
353 353
             return $builder->build($item);
354 354
         }, $results);
355 355
     }
@@ -363,7 +363,7 @@  discard block
 block discarded – undo
363 363
      */
364 364
     protected function buildUsingSingleTableInheritance(array $results): array
365 365
     {
366
-        return array_map(function ($item) {
366
+        return array_map(function($item) {
367 367
             $builder = $this->builderForResult($item);
368 368
 
369 369
             return $builder->build($item);
Please login to merge, or discard this patch.
src/System/EntityBuilder.php 1 patch
Indentation   +176 added lines, -176 removed lines patch added patch discarded remove patch
@@ -9,180 +9,180 @@
 block discarded – undo
9 9
  */
10 10
 class EntityBuilder
11 11
 {
12
-    /**
13
-     * The mapper for the entity to build.
14
-     *
15
-     * @var \Analogue\ORM\System\Mapper
16
-     */
17
-    protected $mapper;
18
-
19
-    /**
20
-     * The Entity Map for the entity to build.
21
-     *
22
-     * @var \Analogue\ORM\EntityMap
23
-     */
24
-    protected $entityMap;
25
-
26
-    /**
27
-     * Relations that are eager loaded on this query.
28
-     *
29
-     * @var array
30
-     */
31
-    protected $eagerLoads;
32
-
33
-    /**
34
-     * @var array
35
-     */
36
-    protected $casts;
37
-
38
-    /**
39
-     * Entity Wrapper Factory.
40
-     *
41
-     * @var \Analogue\ORM\System\Wrappers\Factory
42
-     */
43
-    protected $factory;
44
-
45
-    /**
46
-     * EntityBuilder constructor.
47
-     *
48
-     * @param Mapper $mapper
49
-     * @param array  $eagerLoads
50
-     */
51
-    public function __construct(Mapper $mapper, array $eagerLoads)
52
-    {
53
-        $this->mapper = $mapper;
54
-
55
-        $this->entityMap = $mapper->getEntityMap();
56
-
57
-        $this->eagerLoads = $eagerLoads;
58
-
59
-        $this->factory = new Factory();
60
-    }
61
-
62
-    /**
63
-     * Convert an array of attributes into an entity, or retrieve entity instance from cache.
64
-     *
65
-     * @param array $attributes
66
-     *
67
-     * @return array
68
-     */
69
-    public function build(array $attributes)
70
-    {
71
-        // If the object we are building is a value object,
72
-        // we won't be using the instance cache.
73
-        if ($this->entityMap->getKeyName() === null) {
74
-            return $this->buildEntity($attributes);
75
-        }
76
-
77
-        $instanceCache = $this->mapper->getInstanceCache();
78
-
79
-        $id = $this->getPrimaryKeyValue($attributes);
80
-
81
-        return $instanceCache->has($id) ? $instanceCache->get($id) : $this->buildEntity($attributes);
82
-    }
83
-
84
-    /**
85
-     * Actually build an entity.
86
-     *
87
-     * @param array $attributes
88
-     *
89
-     * @return mixed
90
-     */
91
-    protected function buildEntity(array $attributes)
92
-    {
93
-        $wrapper = $this->getWrapperInstance();
94
-
95
-        // Hydrate any embedded Value Object
96
-        //
97
-        // TODO Move this to the result builder instead,
98
-        // as we'll handle this the same way as they were
99
-        // eager loaded relationships.
100
-        $this->hydrateValueObjects($attributes);
101
-
102
-        $wrapper->setEntityAttributes($attributes);
103
-
104
-        $wrapper->setProxies();
105
-
106
-        $entity = $wrapper->unwrap();
107
-
108
-        // Once the object has been hydrated, we'll add
109
-        // the instance to the instance cache.
110
-        if ($this->entityMap->getKeyName() !== null) {
111
-            $id = $this->getPrimaryKeyValue($attributes);
112
-            $this->mapper->getInstanceCache()->add($entity, $id);
113
-        }
114
-
115
-        return $entity;
116
-    }
117
-
118
-    /**
119
-     * Return the primary key value from attributes.
120
-     *
121
-     * @param array $attributes
122
-     *
123
-     * @return string
124
-     */
125
-    protected function getPrimaryKeyValue(array $attributes)
126
-    {
127
-        return $attributes[$this->entityMap->getKeyName()];
128
-    }
129
-
130
-    /**
131
-     * Get the correct wrapper prototype corresponding to the object type.
132
-     *
133
-     * @throws \Analogue\ORM\Exceptions\MappingException
134
-     *
135
-     * @return InternallyMappable
136
-     */
137
-    protected function getWrapperInstance()
138
-    {
139
-        return $this->factory->make($this->mapper->newInstance());
140
-    }
141
-
142
-    /**
143
-     * Hydrate value object embedded in this entity.
144
-     *
145
-     * @param array $attributes
146
-     *
147
-     * @throws \Analogue\ORM\Exceptions\MappingException
148
-     *
149
-     * @return void
150
-     */
151
-    protected function hydrateValueObjects(&$attributes)
152
-    {
153
-        foreach ($this->entityMap->getEmbeddables() as $localKey => $valueClass) {
154
-            $this->hydrateValueObject($attributes, $localKey, $valueClass);
155
-        }
156
-    }
157
-
158
-    /**
159
-     * Hydrate a single value object.
160
-     *
161
-     * @param array  $attributes
162
-     * @param string $localKey
163
-     * @param string $valueClass
164
-     *
165
-     * @throws \Analogue\ORM\Exceptions\MappingException
166
-     *
167
-     * @return void
168
-     */
169
-    protected function hydrateValueObject(&$attributes, $localKey, $valueClass)
170
-    {
171
-        $map = $this->mapper->getManager()->getValueMap($valueClass);
172
-
173
-        $embeddedAttributes = $map->getAttributes();
174
-
175
-        $valueObject = $this->mapper->getManager()->getValueObjectInstance($valueClass);
176
-        $voWrapper = $this->factory->make($valueObject);
177
-
178
-        foreach ($embeddedAttributes as $key) {
179
-            $prefix = snake_case(class_basename($valueClass)).'_';
180
-
181
-            $voWrapper->setEntityAttribute($key, $attributes[$prefix.$key]);
182
-
183
-            unset($attributes[$prefix.$key]);
184
-        }
185
-
186
-        $attributes[$localKey] = $voWrapper->getObject();
187
-    }
12
+	/**
13
+	 * The mapper for the entity to build.
14
+	 *
15
+	 * @var \Analogue\ORM\System\Mapper
16
+	 */
17
+	protected $mapper;
18
+
19
+	/**
20
+	 * The Entity Map for the entity to build.
21
+	 *
22
+	 * @var \Analogue\ORM\EntityMap
23
+	 */
24
+	protected $entityMap;
25
+
26
+	/**
27
+	 * Relations that are eager loaded on this query.
28
+	 *
29
+	 * @var array
30
+	 */
31
+	protected $eagerLoads;
32
+
33
+	/**
34
+	 * @var array
35
+	 */
36
+	protected $casts;
37
+
38
+	/**
39
+	 * Entity Wrapper Factory.
40
+	 *
41
+	 * @var \Analogue\ORM\System\Wrappers\Factory
42
+	 */
43
+	protected $factory;
44
+
45
+	/**
46
+	 * EntityBuilder constructor.
47
+	 *
48
+	 * @param Mapper $mapper
49
+	 * @param array  $eagerLoads
50
+	 */
51
+	public function __construct(Mapper $mapper, array $eagerLoads)
52
+	{
53
+		$this->mapper = $mapper;
54
+
55
+		$this->entityMap = $mapper->getEntityMap();
56
+
57
+		$this->eagerLoads = $eagerLoads;
58
+
59
+		$this->factory = new Factory();
60
+	}
61
+
62
+	/**
63
+	 * Convert an array of attributes into an entity, or retrieve entity instance from cache.
64
+	 *
65
+	 * @param array $attributes
66
+	 *
67
+	 * @return array
68
+	 */
69
+	public function build(array $attributes)
70
+	{
71
+		// If the object we are building is a value object,
72
+		// we won't be using the instance cache.
73
+		if ($this->entityMap->getKeyName() === null) {
74
+			return $this->buildEntity($attributes);
75
+		}
76
+
77
+		$instanceCache = $this->mapper->getInstanceCache();
78
+
79
+		$id = $this->getPrimaryKeyValue($attributes);
80
+
81
+		return $instanceCache->has($id) ? $instanceCache->get($id) : $this->buildEntity($attributes);
82
+	}
83
+
84
+	/**
85
+	 * Actually build an entity.
86
+	 *
87
+	 * @param array $attributes
88
+	 *
89
+	 * @return mixed
90
+	 */
91
+	protected function buildEntity(array $attributes)
92
+	{
93
+		$wrapper = $this->getWrapperInstance();
94
+
95
+		// Hydrate any embedded Value Object
96
+		//
97
+		// TODO Move this to the result builder instead,
98
+		// as we'll handle this the same way as they were
99
+		// eager loaded relationships.
100
+		$this->hydrateValueObjects($attributes);
101
+
102
+		$wrapper->setEntityAttributes($attributes);
103
+
104
+		$wrapper->setProxies();
105
+
106
+		$entity = $wrapper->unwrap();
107
+
108
+		// Once the object has been hydrated, we'll add
109
+		// the instance to the instance cache.
110
+		if ($this->entityMap->getKeyName() !== null) {
111
+			$id = $this->getPrimaryKeyValue($attributes);
112
+			$this->mapper->getInstanceCache()->add($entity, $id);
113
+		}
114
+
115
+		return $entity;
116
+	}
117
+
118
+	/**
119
+	 * Return the primary key value from attributes.
120
+	 *
121
+	 * @param array $attributes
122
+	 *
123
+	 * @return string
124
+	 */
125
+	protected function getPrimaryKeyValue(array $attributes)
126
+	{
127
+		return $attributes[$this->entityMap->getKeyName()];
128
+	}
129
+
130
+	/**
131
+	 * Get the correct wrapper prototype corresponding to the object type.
132
+	 *
133
+	 * @throws \Analogue\ORM\Exceptions\MappingException
134
+	 *
135
+	 * @return InternallyMappable
136
+	 */
137
+	protected function getWrapperInstance()
138
+	{
139
+		return $this->factory->make($this->mapper->newInstance());
140
+	}
141
+
142
+	/**
143
+	 * Hydrate value object embedded in this entity.
144
+	 *
145
+	 * @param array $attributes
146
+	 *
147
+	 * @throws \Analogue\ORM\Exceptions\MappingException
148
+	 *
149
+	 * @return void
150
+	 */
151
+	protected function hydrateValueObjects(&$attributes)
152
+	{
153
+		foreach ($this->entityMap->getEmbeddables() as $localKey => $valueClass) {
154
+			$this->hydrateValueObject($attributes, $localKey, $valueClass);
155
+		}
156
+	}
157
+
158
+	/**
159
+	 * Hydrate a single value object.
160
+	 *
161
+	 * @param array  $attributes
162
+	 * @param string $localKey
163
+	 * @param string $valueClass
164
+	 *
165
+	 * @throws \Analogue\ORM\Exceptions\MappingException
166
+	 *
167
+	 * @return void
168
+	 */
169
+	protected function hydrateValueObject(&$attributes, $localKey, $valueClass)
170
+	{
171
+		$map = $this->mapper->getManager()->getValueMap($valueClass);
172
+
173
+		$embeddedAttributes = $map->getAttributes();
174
+
175
+		$valueObject = $this->mapper->getManager()->getValueObjectInstance($valueClass);
176
+		$voWrapper = $this->factory->make($valueObject);
177
+
178
+		foreach ($embeddedAttributes as $key) {
179
+			$prefix = snake_case(class_basename($valueClass)).'_';
180
+
181
+			$voWrapper->setEntityAttribute($key, $attributes[$prefix.$key]);
182
+
183
+			unset($attributes[$prefix.$key]);
184
+		}
185
+
186
+		$attributes[$localKey] = $voWrapper->getObject();
187
+	}
188 188
 }
Please login to merge, or discard this patch.
src/System/Aggregate.php 3 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -1060,7 +1060,7 @@
 block discarded – undo
1060 1060
      */
1061 1061
     protected function getEntityHashesFromRelation(string $relation): array
1062 1062
     {
1063
-        return array_map(function (Aggregate $aggregate) {
1063
+        return array_map(function(Aggregate $aggregate) {
1064 1064
             return $aggregate->getEntityHash();
1065 1065
         }, $this->relationships[$relation]);
1066 1066
     }
Please login to merge, or discard this patch.
Doc Comments   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -87,9 +87,9 @@
 block discarded – undo
87 87
      * Create a new Aggregated Entity instance.
88 88
      *
89 89
      * @param mixed          $entity
90
-     * @param Aggregate|null $parent
90
+     * @param null|\self $parent
91 91
      * @param string         $parentRelationship
92
-     * @param Aggregate|null $root
92
+     * @param null|\self $root
93 93
      *
94 94
      * @throws MappingException
95 95
      */
Please login to merge, or discard this patch.
Indentation   +1233 added lines, -1233 removed lines patch added patch discarded remove patch
@@ -19,1237 +19,1237 @@
 block discarded – undo
19 19
  */
20 20
 class Aggregate implements InternallyMappable
21 21
 {
22
-    /**
23
-     * The Root Entity.
24
-     *
25
-     * @var \Analogue\ORM\System\Wrappers\Wrapper
26
-     */
27
-    protected $wrappedEntity;
28
-
29
-    /**
30
-     * Class of the entity being aggregated.
31
-     *
32
-     * @var string
33
-     */
34
-    protected $class;
35
-
36
-    /**
37
-     * Parent Root Aggregate.
38
-     *
39
-     * @var \Analogue\ORM\System\Aggregate
40
-     */
41
-    protected $parent;
42
-
43
-    /**
44
-     * Parent's relationship method.
45
-     *
46
-     * @var string
47
-     */
48
-    protected $parentRelationship;
49
-
50
-    /**
51
-     * Root Entity.
52
-     *
53
-     * @var \Analogue\ORM\System\Aggregate
54
-     */
55
-    protected $root;
56
-
57
-    /**
58
-     * An associative array containing entity's
59
-     * relationships converted to Aggregates.
60
-     *
61
-     * @var array
62
-     */
63
-    protected $relationships = [];
64
-
65
-    /**
66
-     * Relationship that need post-command synchronization.
67
-     *
68
-     * @var array
69
-     */
70
-    protected $needSync = [];
71
-
72
-    /**
73
-     * Mapper.
74
-     *
75
-     * @var \Analogue\ORM\System\Mapper;
76
-     */
77
-    protected $mapper;
78
-
79
-    /**
80
-     * Entity Map.
81
-     *
82
-     * @var \Analogue\ORM\EntityMap;
83
-     */
84
-    protected $entityMap;
85
-
86
-    /**
87
-     * Create a new Aggregated Entity instance.
88
-     *
89
-     * @param mixed          $entity
90
-     * @param Aggregate|null $parent
91
-     * @param string         $parentRelationship
92
-     * @param Aggregate|null $root
93
-     *
94
-     * @throws MappingException
95
-     */
96
-    public function __construct($entity, self $parent = null, string $parentRelationship = null, self $root = null)
97
-    {
98
-        $factory = new Factory();
99
-
100
-        $this->class = get_class($entity);
101
-
102
-        $this->wrappedEntity = $factory->make($entity);
103
-
104
-        $this->parent = $parent;
105
-
106
-        $this->parentRelationship = $parentRelationship;
107
-
108
-        $this->root = $root;
109
-
110
-        $mapper = $this->getMapper();
111
-
112
-        $this->entityMap = $mapper->getEntityMap();
113
-
114
-        $this->parseRelationships();
115
-    }
116
-
117
-    /**
118
-     * Parse Every relationships defined on the entity.
119
-     *
120
-     * @throws MappingException
121
-     *
122
-     * @return void
123
-     */
124
-    protected function parseRelationships()
125
-    {
126
-        foreach ($this->entityMap->getSingleRelationships() as $relation) {
127
-            $this->parseSingleRelationship($relation);
128
-        }
129
-
130
-        foreach ($this->entityMap->getManyRelationships() as $relation) {
131
-            $this->parseManyRelationship($relation);
132
-        }
133
-    }
134
-
135
-    /**
136
-     * Parse for values common to single & many relations.
137
-     *
138
-     * @param string $relation
139
-     *
140
-     * @throws MappingException
141
-     *
142
-     * @return mixed|bool
143
-     */
144
-    protected function parseForCommonValues($relation)
145
-    {
146
-        if (!$this->hasAttribute($relation)) {
147
-            // If no attribute exists for this relationships
148
-            // we'll make it a simple empty array. This will
149
-            // save us from constantly checking for the attributes
150
-            // actual existence.
151
-            $this->relationships[$relation] = [];
152
-
153
-            return false;
154
-        }
155
-
156
-        $value = $this->getRelationshipValue($relation);
157
-
158
-        if (is_null($value)) {
159
-            $this->relationships[$relation] = [];
160
-
161
-            // If the relationship's content is the null value
162
-            // and the Entity's exist in DB, we'll interpret this
163
-            // as the need to detach all related Entities,
164
-            // therefore a sync operation is needed.
165
-            $this->needSync[] = $relation;
166
-
167
-            return false;
168
-        }
169
-
170
-        return $value;
171
-    }
172
-
173
-    /**
174
-     * Parse a 'single' relationship.
175
-     *
176
-     * @param string $relation
177
-     *
178
-     * @throws MappingException
179
-     *
180
-     * @return bool
181
-     */
182
-    protected function parseSingleRelationship(string $relation): bool
183
-    {
184
-        if (!$value = $this->parseForCommonValues($relation)) {
185
-            return true;
186
-        }
187
-
188
-        if ($value instanceof Collection || is_array($value) || $value instanceof CollectionProxy) {
189
-            throw new MappingException("Entity's attribute $relation should not be array, or collection");
190
-        }
191
-
192
-        if ($value instanceof LazyLoadingInterface && !$value->isProxyInitialized()) {
193
-            $this->relationships[$relation] = [];
194
-
195
-            return true;
196
-        }
197
-
198
-        // If the attribute is a loaded proxy, swap it for its
199
-        // loaded entity.
200
-        if ($value instanceof LazyLoadingInterface && $value->isProxyInitialized()) {
201
-            $value = $value->getWrappedValueHolderValue();
202
-        }
203
-
204
-        if ($this->isParentOrRoot($value)) {
205
-            $this->relationships[$relation] = [];
206
-
207
-            return true;
208
-        }
209
-
210
-        // At this point, we can assume the attribute is an Entity instance
211
-        // so we'll treat it as such.
212
-        $subAggregate = $this->createSubAggregate($value, $relation);
213
-
214
-        // Even if it's a single entity, we'll store it as an array
215
-        // just for consistency with other relationships
216
-        $this->relationships[$relation] = [$subAggregate];
217
-
218
-        // We always need to check a loaded relation is in sync
219
-        // with its local key
220
-        $this->needSync[] = $relation;
221
-
222
-        return true;
223
-    }
224
-
225
-    /**
226
-     * Check if value isn't parent or root in the aggregate.
227
-     *
228
-     * @param  mixed
229
-     *
230
-     * @return bool
231
-     */
232
-    protected function isParentOrRoot($value): bool
233
-    {
234
-        $id = spl_object_hash($value);
235
-        $root = $this->root ? $this->root->getWrappedEntity()->getObject() : null;
236
-        $parent = $this->parent ? $this->parent->getWrappedEntity()->getObject() : null;
237
-
238
-        if ($parent && (spl_object_hash($parent) == $id)) {
239
-            return true;
240
-        }
241
-
242
-        if ($root && (spl_object_hash($root) == $id)) {
243
-            return true;
244
-        }
245
-
246
-        return false;
247
-    }
248
-
249
-    /**
250
-     * Parse a 'many' relationship.
251
-     *
252
-     * @param string $relation
253
-     *
254
-     * @throws MappingException
255
-     *
256
-     * @return bool
257
-     */
258
-    protected function parseManyRelationship(string $relation): bool
259
-    {
260
-        if (!$value = $this->parseForCommonValues($relation)) {
261
-            return true;
262
-        }
263
-
264
-        if (is_array($value) || (!$value instanceof CollectionProxy && $value instanceof Collection)) {
265
-            $this->needSync[] = $relation;
266
-        }
267
-
268
-        // If the relation is a proxy, we test is the relation
269
-        // has been lazy loaded, otherwise we'll just treat
270
-        // the subset of newly added items.
271
-        if ($value instanceof CollectionProxy && $value->isProxyInitialized()) {
272
-            $this->needSync[] = $relation;
273
-            //$value = $value->getUnderlyingCollection();
274
-        }
275
-
276
-        if ($value instanceof CollectionProxy && !$value->isProxyInitialized()) {
277
-            $value = $value->getAddedItems();
278
-        }
279
-
280
-        // At this point $value should be either an array or an instance
281
-        // of a collection class.
282
-        if (!is_array($value) && !$value instanceof Collection) {
283
-            throw new MappingException("'$relation' attribute should be array() or Collection");
284
-        }
285
-
286
-        $this->relationships[$relation] = $this->createSubAggregates($value, $relation);
287
-
288
-        return true;
289
-    }
290
-
291
-    /**
292
-     * Return Entity's relationship attribute.
293
-     *
294
-     * @param string $relation
295
-     *
296
-     * @throws MappingException
297
-     *
298
-     * @return mixed
299
-     */
300
-    protected function getRelationshipValue(string $relation)
301
-    {
302
-        $value = $this->getEntityAttribute($relation);
303
-
304
-        if (is_scalar($value)) {
305
-            throw new MappingException("Entity's attribute $relation should be array, object, collection or null");
306
-        }
307
-
308
-        return $value;
309
-    }
310
-
311
-    /**
312
-     * Create a child, aggregated entity.
313
-     *
314
-     * @param mixed  $entities
315
-     * @param string $relation
316
-     *
317
-     * @return array
318
-     */
319
-    protected function createSubAggregates($entities, string $relation): array
320
-    {
321
-        $aggregates = [];
322
-
323
-        foreach ($entities as $entity) {
324
-            $aggregates[] = $this->createSubAggregate($entity, $relation);
325
-        }
326
-
327
-        return $aggregates;
328
-    }
329
-
330
-    /**
331
-     * Create a related subAggregate.
332
-     *
333
-     * @param mixed  $entity
334
-     * @param string $relation
335
-     *
336
-     * @throws MappingException
337
-     *
338
-     * @return Aggregate
339
-     */
340
-    protected function createSubAggregate($entity, string $relation): self
341
-    {
342
-        // If root isn't defined, then this is the Aggregate Root
343
-        $root = is_null($this->root) ? $this : $this->root;
344
-
345
-        return new self($entity, $this, $relation, $root);
346
-    }
347
-
348
-    /**
349
-     * Return the entity map for the current entity.
350
-     *
351
-     * @return \Analogue\ORM\EntityMap
352
-     */
353
-    public function getEntityMap(): EntityMap
354
-    {
355
-        return $this->entityMap;
356
-    }
357
-
358
-    /**
359
-     * {@inheritdoc}
360
-     */
361
-    public function getEntityHash(): string
362
-    {
363
-        return $this->getEntityClass().'.'.$this->getEntityKeyValue();
364
-    }
365
-
366
-    /**
367
-     * {@inheritdoc}
368
-     */
369
-    public function getEntityKeyName(): string
370
-    {
371
-        return $this->entityMap->getKeyName();
372
-    }
373
-
374
-    /**
375
-     * {@inheritdoc}
376
-     */
377
-    public function getEntityKeyValue()
378
-    {
379
-        $keyValue = $this->wrappedEntity->getEntityKeyValue();
380
-
381
-        if ($keyValue instanceof ObjectId) {
382
-            $keyValue = (string) $keyValue;
383
-        }
384
-
385
-        return $keyValue;
386
-    }
387
-
388
-    /**
389
-     * {@inheritdoc}
390
-     */
391
-    public function getEntityClass(): string
392
-    {
393
-        return $this->entityMap->getClass();
394
-    }
395
-
396
-    /**
397
-     * Return the Mapper's entity cache.
398
-     *
399
-     * @return \Analogue\ORM\System\Cache\AttributeCache
400
-     */
401
-    protected function getEntityCache(): AttributeCache
402
-    {
403
-        return $this->getMapper()->getEntityCache();
404
-    }
405
-
406
-    /**
407
-     * Get a relationship as an aggregated entities' array.
408
-     *
409
-     * @param string $name
410
-     *
411
-     * @return array
412
-     */
413
-    public function getRelationship(string $name): array
414
-    {
415
-        if (array_key_exists($name, $this->relationships)) {
416
-            return $this->relationships[$name];
417
-        }
418
-
419
-        return [];
420
-    }
421
-
422
-    /**
423
-     * [TO IMPLEMENT].
424
-     *
425
-     * @return array
426
-     */
427
-    public function getPivotAttributes(): array
428
-    {
429
-        return [];
430
-    }
431
-
432
-    /**
433
-     * Get Non existing related entities from several relationships.
434
-     *
435
-     * @param array $relationships
436
-     *
437
-     * @return array
438
-     */
439
-    public function getNonExistingRelated(array $relationships): array
440
-    {
441
-        $nonExisting = [];
442
-
443
-        foreach ($relationships as $relation) {
444
-            if ($this->hasAttribute($relation) && array_key_exists($relation, $this->relationships)) {
445
-                $nonExisting = array_merge($nonExisting, $this->getNonExistingFromRelation($relation));
446
-            }
447
-        }
448
-
449
-        return $nonExisting;
450
-    }
451
-
452
-    /**
453
-     * Get non-existing related entities from a single relation.
454
-     *
455
-     * @param string $relation
456
-     *
457
-     * @return array
458
-     */
459
-    protected function getNonExistingFromRelation(string $relation): array
460
-    {
461
-        $nonExisting = [];
462
-
463
-        foreach ($this->relationships[$relation] as $aggregate) {
464
-            if (!$aggregate->exists()) {
465
-                $nonExisting[] = $aggregate;
466
-            }
467
-        }
468
-
469
-        return $nonExisting;
470
-    }
471
-
472
-    /**
473
-     * Synchronize relationships if needed.
474
-     *
475
-     * @param array
476
-     *
477
-     * @return void
478
-     */
479
-    public function syncRelationships(array $relationships)
480
-    {
481
-        foreach ($relationships as $relation) {
482
-            if (in_array($relation, $this->needSync)) {
483
-                $this->synchronize($relation);
484
-            }
485
-        }
486
-    }
487
-
488
-    /**
489
-     * Synchronize a relationship attribute.
490
-     *
491
-     * @param string $relation
492
-     *
493
-     * @return void
494
-     */
495
-    protected function synchronize(string $relation)
496
-    {
497
-        $actualContent = $this->relationships[$relation];
498
-
499
-        $relationshipObject = $this->entityMap->$relation($this->getEntityObject());
500
-        $relationshipObject->setParent($this->wrappedEntity);
501
-        $relationshipObject->sync($actualContent);
502
-    }
503
-
504
-    /**
505
-     * Returns an array of Missing related Entities for the
506
-     * given $relation.
507
-     *
508
-     * @param string $relation
509
-     *
510
-     * @return array
511
-     */
512
-    public function getMissingEntities(string $relation): array
513
-    {
514
-        $cachedRelations = $this->getCachedAttribute($relation);
515
-
516
-        if (is_null($cachedRelations)) {
517
-            return [];
518
-        }
519
-
520
-        $missing = [];
521
-
522
-        foreach ($cachedRelations as $hash) {
523
-            if (!$this->getRelatedAggregateFromHash($hash, $relation)) {
524
-                $missing[] = $hash;
525
-            }
526
-        }
527
-
528
-        return $missing;
529
-    }
530
-
531
-    /**
532
-     * Get Relationships who have dirty attributes / dirty relationships.
533
-     *
534
-     * @return array
535
-     */
536
-    public function getDirtyRelationships(): array
537
-    {
538
-        $dirtyAggregates = [];
539
-
540
-        foreach ($this->relationships as $relation) {
541
-            foreach ($relation as $aggregate) {
542
-                if (!$aggregate->exists() || $aggregate->isDirty() || count($aggregate->getDirtyRelationships()) > 0) {
543
-                    $dirtyAggregates[] = $aggregate;
544
-                }
545
-            }
546
-        }
547
-
548
-        return $dirtyAggregates;
549
-    }
550
-
551
-    /**
552
-     * Compare the object's raw attributes with the record in cache.
553
-     *
554
-     * @return bool
555
-     */
556
-    public function isDirty(): bool
557
-    {
558
-        return count($this->getDirtyRawAttributes()) > 0;
559
-    }
560
-
561
-    /**
562
-     * Get Raw Entity's attributes, as they are represented
563
-     * in the database, including value objects, foreign keys,
564
-     * and discriminator column.
565
-     *
566
-     * @return array
567
-     */
568
-    public function getRawAttributes(): array
569
-    {
570
-        $attributes = $this->wrappedEntity->getEntityAttributes();
571
-
572
-        foreach ($this->entityMap->getNonEmbeddedRelationships() as $relation) {
573
-            unset($attributes[$relation]);
574
-        }
575
-
576
-        if ($this->entityMap->getInheritanceType() == 'single_table') {
577
-            $attributes = $this->addDiscriminatorColumn($attributes);
578
-        }
579
-
580
-        $attributes = $this->flattenEmbeddables($attributes);
581
-
582
-        $foreignKeys = $this->getForeignKeyAttributes();
583
-
584
-        return $this->mergeForeignKeysWithAttributes($foreignKeys, $attributes);
585
-    }
586
-
587
-    /**
588
-     * Merge foreign keys and attributes by comparing their
589
-     * current value to the cache, and guess the user intent.
590
-     *
591
-     * @param array $foreignKeys
592
-     * @param array $attributes
593
-     *
594
-     * @return array
595
-     */
596
-    protected function mergeForeignKeysWithAttributes(array $foreignKeys, array $attributes): array
597
-    {
598
-        $cachedAttributes = $this->getCachedRawAttributes();
599
-
600
-        foreach ($foreignKeys as $fkAttributeKey => $fkAttributeValue) {
601
-
602
-            // FK doesn't exist in attributes => we set it
603
-            if (!array_key_exists($fkAttributeKey, $attributes)) {
604
-                $attributes[$fkAttributeKey] = $fkAttributeValue;
605
-                continue;
606
-            }
607
-
608
-            // FK does exists in attributes and is equal => we set it
609
-            if ($attributes[$fkAttributeKey] === $fkAttributeValue) {
610
-                $attributes[$fkAttributeKey] = $fkAttributeValue;
611
-                continue;
612
-            }
613
-
614
-            // ForeignKey exists in attributes array, but the value is different that
615
-            // the one fetched from the relationship itself.
616
-
617
-            // Does it exist in cache
618
-            if (array_key_exists($fkAttributeKey, $cachedAttributes)) {
619
-                // attribute is different than cached value, we use it
620
-                if ($attributes[$fkAttributeKey] !== $cachedAttributes[$fkAttributeKey]) {
621
-                    continue;
622
-                }
623
-                // if not, we use the foreign key value
624
-                else {
625
-                    $attributes[$fkAttributeKey] = $fkAttributeValue;
626
-                }
627
-            } else {
628
-                if (is_null($attributes[$fkAttributeKey])) {
629
-                    $attributes[$fkAttributeKey] = $fkAttributeValue;
630
-                }
631
-            }
632
-        }
633
-
634
-        return $attributes;
635
-    }
636
-
637
-    /**
638
-     * Add Discriminator Column if it doesn't exist on the actual entity.
639
-     *
640
-     * @param array $attributes
641
-     *
642
-     * @return array
643
-     */
644
-    protected function addDiscriminatorColumn(array $attributes): array
645
-    {
646
-        $discriminatorColumn = $this->entityMap->getDiscriminatorColumn();
647
-        $entityClass = $this->entityMap->getClass();
648
-
649
-        if (!array_key_exists($discriminatorColumn, $attributes)) {
650
-
651
-            // Use key if present in discriminatorMap
652
-            $map = $this->entityMap->getDiscriminatorColumnMap();
653
-
654
-            $type = array_search($entityClass, $map);
655
-
656
-            if ($type === false) {
657
-                // Use entity FQCN if no corresponding key is set
658
-                $attributes[$discriminatorColumn] = $entityClass;
659
-            } else {
660
-                $attributes[$discriminatorColumn] = $type;
661
-            }
662
-        }
663
-
664
-        return $attributes;
665
-    }
666
-
667
-    /**
668
-     * Convert Value Objects to raw db attributes.
669
-     *
670
-     * @param array $attributes
671
-     *
672
-     * @return array
673
-     */
674
-    protected function flattenEmbeddables(array $attributes): array
675
-    {
676
-        // TODO : deprecate old implementation
677
-        $embeddables = $this->entityMap->getEmbeddables();
678
-
679
-        foreach ($embeddables as $localKey => $embed) {
680
-            // Retrieve the value object from the entity's attributes
681
-            $valueObject = $attributes[$localKey];
682
-
683
-            // Unset the corresponding key
684
-            unset($attributes[$localKey]);
685
-
686
-            // TODO Make wrapper object compatible with value objects
687
-            $valueObjectAttributes = $valueObject->getEntityAttributes();
688
-
689
-            // Now (if setup in the entity map) we prefix the value object's
690
-            // attributes with the snake_case name of the embedded class.
691
-            $prefix = snake_case(class_basename($embed));
692
-
693
-            foreach ($valueObjectAttributes as $key => $value) {
694
-                $valueObjectAttributes[$prefix.'_'.$key] = $value;
695
-                unset($valueObjectAttributes[$key]);
696
-            }
697
-
698
-            $attributes = array_merge($attributes, $valueObjectAttributes);
699
-        }
700
-
701
-        //*********************
702
-        // New implementation
703
-        // *****************->
704
-
705
-        $embeddedRelations = $this->entityMap->getEmbeddedRelationships();
706
-
707
-        foreach ($embeddedRelations as $relation) {
708
-
709
-            // Spawn a new instance we can pass to the relationship method
710
-            $parentInstance = $this->getMapper()->newInstance();
711
-            $relationInstance = $this->entityMap->$relation($parentInstance);
712
-
713
-            // Extract the object from the attributes
714
-            $embeddedObject = $attributes[$relation];
715
-
716
-            unset($attributes[$relation]);
717
-
718
-            $attributes = $relationInstance->normalize($embeddedObject) + $attributes;
719
-        }
720
-
721
-        return $attributes;
722
-    }
723
-
724
-    /**
725
-     * Return's entity raw attributes in the state they were at last
726
-     * query.
727
-     *
728
-     * @param array|null $columns
729
-     *
730
-     * @return array
731
-     */
732
-    protected function getCachedRawAttributes(array $columns = null): array
733
-    {
734
-        $cachedAttributes = $this->getCache()->get($this->getEntityKeyValue());
735
-
736
-        if (is_null($columns)) {
737
-            return $cachedAttributes;
738
-        }
739
-
740
-        return array_only($cachedAttributes, $columns);
741
-    }
742
-
743
-    /**
744
-     * Return a single attribute from the cache.
745
-     *
746
-     * @param string $key
747
-     *
748
-     * @return mixed|null
749
-     */
750
-    protected function getCachedAttribute($key)
751
-    {
752
-        $cachedAttributes = $this->getCache()->get($this->getEntityKeyValue());
753
-
754
-        if (array_key_exists($key, $cachedAttributes)) {
755
-            return $cachedAttributes[$key];
756
-        }
757
-    }
758
-
759
-    /**
760
-     * Convert related Entity's attributes to foreign keys.
761
-     *
762
-     * @return array
763
-     */
764
-    public function getForeignKeyAttributes(): array
765
-    {
766
-        $foreignKeys = [];
767
-
768
-        foreach ($this->entityMap->getLocalRelationships() as $relation) {
769
-
770
-            // If the actual relationship is a non-loaded proxy, we'll simply retrieve
771
-            // the foreign key pair without parsing the actual object. This will allow
772
-            // user to modify the actual related ID's directly by updating the corresponding
773
-            // attribute.
774
-            if ($this->isNonLoadedProxy($relation)) {
775
-                $foreignKeys = $foreignKeys + $this->getForeignKeyAttributesFromNonLoadedRelation($relation);
776
-                continue;
777
-            }
778
-
779
-            // check if relationship has been parsed, meaning it has an actual object
780
-            // in the entity's attributes
781
-            if ($this->isActualRelationships($relation)) {
782
-                $foreignKeys = $foreignKeys + $this->getForeignKeyAttributesFromRelation($relation);
783
-            } else {
784
-                $foreignKeys = $foreignKeys + $this->getNullForeignKeyFromRelation($relation);
785
-            }
786
-        }
787
-
788
-        if (!is_null($this->parent)) {
789
-            $foreignKeys = $this->getForeignKeyAttributesFromParent() + $foreignKeys;
790
-        }
791
-
792
-        return $foreignKeys;
793
-    }
794
-
795
-    /**
796
-     * Get a null foreign key value pair for an empty relationship.
797
-     *
798
-     * @param string $relation
799
-     *
800
-     * @throws MappingException
801
-     *
802
-     * @return array
803
-     */
804
-    protected function getNullForeignKeyFromRelation(string $relation): array
805
-    {
806
-        $key = $this->entityMap->getLocalKeys($relation);
807
-
808
-        if (is_array($key)) {
809
-            return $this->entityMap->getEmptyValueForLocalKey($relation);
810
-        }
811
-
812
-        if (is_null($key)) {
813
-            throw new MappingException("Foreign key for relation $relation cannot be null");
814
-        }
815
-
816
-        return [
817
-            $key => $this->entityMap->getEmptyValueForLocalKey($relation),
818
-        ];
819
-    }
820
-
821
-    /**
822
-     * Return an associative array containing the key-value pair(s) from
823
-     * the related entity.
824
-     *
825
-     * @param string $relation
826
-     *
827
-     * @return array
828
-     */
829
-    protected function getForeignKeyAttributesFromRelation(string $relation): array
830
-    {
831
-        // Call Relationship's method
832
-        $relationship = $this->entityMap->$relation($this->getEntityObject());
833
-
834
-        $relatedAggregate = $this->relationships[$relation][0];
835
-
836
-        return $relationship->getForeignKeyValuePair($relatedAggregate->getEntityObject());
837
-    }
838
-
839
-    /**
840
-     * Return an associative array containing the key-value pair(s) from
841
-     * the foreign key attribute.
842
-     *
843
-     * @param string $relation
844
-     *
845
-     * @return array
846
-     */
847
-    protected function getForeignKeyAttributesFromNonLoadedRelation(string $relation): array
848
-    {
849
-        $keys = $this->entityMap->getLocalKeys($relation);
850
-
851
-        // We'll treat single and composite keys (polymorphic) the same way.
852
-        if (!is_array($keys)) {
853
-            $keys = [$keys];
854
-        }
855
-
856
-        $foreignKey = [];
857
-
858
-        foreach ($keys as $key) {
859
-            $foreignKey[$key] = $this->getEntityAttribute($key);
860
-        }
861
-
862
-        return $foreignKey;
863
-    }
864
-
865
-    /**
866
-     * Get foreign key attribute(s) from a parent entity in this
867
-     * aggregate context.
868
-     *
869
-     * @return array
870
-     */
871
-    protected function getForeignKeyAttributesFromParent(): array
872
-    {
873
-        $parentMap = $this->parent->getEntityMap();
874
-
875
-        $parentForeignRelations = $parentMap->getForeignRelationships();
876
-        $parentPivotRelations = $parentMap->getPivotRelationships();
877
-
878
-        // The parentRelation is the name of the relationship
879
-        // methods on the parent entity map
880
-        $parentRelation = $this->parentRelationship;
881
-
882
-        if (
883
-            in_array($parentRelation, $parentForeignRelations) &&
884
-            !in_array($parentRelation, $parentPivotRelations)
885
-        ) {
886
-            $parentObject = $this->parent->getEntityObject();
887
-
888
-            // Call Relationship's method on parent map
889
-            $relationship = $parentMap->$parentRelation($parentObject);
890
-
891
-            return $relationship->getForeignKeyValuePair($parentObject);
892
-        }
893
-
894
-        return [];
895
-    }
896
-
897
-    /**
898
-     * Update Pivot records on loaded relationships, by comparing the
899
-     * values from the Entity Cache to the actual relationship inside
900
-     * the aggregated entity.
901
-     *
902
-     * @return void
903
-     */
904
-    public function updatePivotRecords()
905
-    {
906
-        $pivots = $this->entityMap->getPivotRelationships();
907
-
908
-        foreach ($pivots as $pivot) {
909
-            if (array_key_exists($pivot, $this->relationships)) {
910
-                $this->updatePivotRelation($pivot);
911
-            }
912
-        }
913
-    }
914
-
915
-    /**
916
-     * Update Single pivot relationship.
917
-     *
918
-     * @param string $relation
919
-     *
920
-     * @return void
921
-     */
922
-    protected function updatePivotRelation(string $relation)
923
-    {
924
-        $hashes = $this->getEntityHashesFromRelation($relation);
925
-
926
-        $cachedAttributes = $this->getCachedRawAttributes();
927
-
928
-        if (array_key_exists($relation, $cachedAttributes)) {
929
-            // Compare the two array of hashes to find out existing
930
-            // pivot records, and the ones to be created.
931
-            $new = array_diff($hashes, array_keys($cachedAttributes[$relation]));
932
-            $existing = array_intersect($hashes, array_keys($cachedAttributes[$relation]));
933
-        } else {
934
-            $existing = [];
935
-            $new = $hashes;
936
-        }
937
-
938
-        if (count($new) > 0) {
939
-            $pivots = $this->getRelatedAggregatesFromHashes($new, $relation);
940
-
941
-            $this->entityMap->$relation($this->getEntityObject())->createPivots($pivots);
942
-        }
943
-
944
-        if (count($existing) > 0) {
945
-            foreach ($existing as $pivotHash) {
946
-                $this->updatePivotIfDirty($pivotHash, $relation);
947
-            }
948
-        }
949
-    }
950
-
951
-    /**
952
-     * Compare existing pivot record in cache and update it
953
-     * if the pivot attributes are dirty.
954
-     *
955
-     * @param string $pivotHash
956
-     * @param string $relation
957
-     *
958
-     * @return void
959
-     */
960
-    protected function updatePivotIfDirty(string $pivotHash, string $relation)
961
-    {
962
-        $aggregate = $this->getRelatedAggregateFromHash($pivotHash, $relation);
963
-
964
-        if ($aggregate->hasAttribute('pivot')) {
965
-            $pivot = $aggregate->getEntityAttribute('pivot')->getEntityAttributes();
966
-
967
-            $cachedPivotAttributes = $this->getPivotAttributesFromCache($pivotHash, $relation);
968
-
969
-            $actualPivotAttributes = array_only($pivot, array_keys($cachedPivotAttributes));
970
-
971
-            $dirty = $this->getDirtyAttributes($actualPivotAttributes, $cachedPivotAttributes);
972
-
973
-            if (count($dirty) > 0) {
974
-                $id = $aggregate->getEntityKeyValue();
975
-
976
-                $this->entityMap->$relation($this->getEntityObject())->updateExistingPivot($id, $dirty);
977
-            }
978
-        }
979
-    }
980
-
981
-    /**
982
-     * Compare two attributes array and return dirty attributes.
983
-     *
984
-     * @param array $actual
985
-     * @param array $cached
986
-     *
987
-     * @return array
988
-     */
989
-    protected function getDirtyAttributes(array $actual, array $cached): array
990
-    {
991
-        $dirty = [];
992
-
993
-        foreach ($actual as $key => $value) {
994
-            if (!$this->originalIsNumericallyEquivalent($value, $cached[$key])) {
995
-                $dirty[$key] = $actual[$key];
996
-            }
997
-        }
998
-
999
-        return $dirty;
1000
-    }
1001
-
1002
-    /**
1003
-     * @param string $pivotHash
1004
-     * @param string $relation
1005
-     *
1006
-     * @return array|null
1007
-     */
1008
-    protected function getPivotAttributesFromCache(string $pivotHash, string $relation)
1009
-    {
1010
-        $cachedAttributes = $this->getCachedRawAttributes();
1011
-
1012
-        $cachedRelations = $cachedAttributes[$relation];
1013
-
1014
-        foreach ($cachedRelations as $cachedRelation) {
1015
-            if ($cachedRelation == $pivotHash) {
1016
-                return $cachedRelation->getPivotAttributes();
1017
-            }
1018
-        }
1019
-    }
1020
-
1021
-    /**
1022
-     * Returns an array of related Aggregates from its entity hashes.
1023
-     *
1024
-     * @param array  $hashes
1025
-     * @param string $relation
1026
-     *
1027
-     * @return array
1028
-     */
1029
-    protected function getRelatedAggregatesFromHashes(array $hashes, string $relation): array
1030
-    {
1031
-        $related = [];
1032
-
1033
-        foreach ($hashes as $hash) {
1034
-            $aggregate = $this->getRelatedAggregateFromHash($hash, $relation);
1035
-
1036
-            if ($aggregate) {
1037
-                $related[] = $aggregate;
1038
-            }
1039
-        }
1040
-
1041
-        return $related;
1042
-    }
1043
-
1044
-    /**
1045
-     * Get related aggregate from its hash.
1046
-     *
1047
-     * @param string $hash
1048
-     * @param string $relation
1049
-     *
1050
-     * @return \Analogue\ORM\System\Aggregate|null
1051
-     */
1052
-    protected function getRelatedAggregateFromHash(string $hash, string $relation)
1053
-    {
1054
-        foreach ($this->relationships[$relation] as $aggregate) {
1055
-            if ($aggregate->getEntityHash() == $hash) {
1056
-                return $aggregate;
1057
-            }
1058
-        }
1059
-    }
1060
-
1061
-    /**
1062
-     * Return an array of Entity Hashes from a specific relation.
1063
-     *
1064
-     * @param string $relation
1065
-     *
1066
-     * @return array
1067
-     */
1068
-    protected function getEntityHashesFromRelation(string $relation): array
1069
-    {
1070
-        return array_map(function (Aggregate $aggregate) {
1071
-            return $aggregate->getEntityHash();
1072
-        }, $this->relationships[$relation]);
1073
-    }
1074
-
1075
-    /**
1076
-     * Check the existence of an actual relationship.
1077
-     *
1078
-     * @param string $relation
1079
-     *
1080
-     * @return bool
1081
-     */
1082
-    protected function isActualRelationships(string $relation): bool
1083
-    {
1084
-        return array_key_exists($relation, $this->relationships)
1085
-            && count($this->relationships[$relation]) > 0;
1086
-    }
1087
-
1088
-    /**
1089
-     * Return cache instance for the current entity type.
1090
-     *
1091
-     * @return \Analogue\ORM\System\Cache\AttributeCache
1092
-     */
1093
-    protected function getCache(): AttributeCache
1094
-    {
1095
-        return $this->getMapper()->getEntityCache();
1096
-    }
1097
-
1098
-    /**
1099
-     * Get Only Raw Entity attributes which have been modified
1100
-     * since last query.
1101
-     *
1102
-     * @return array
1103
-     */
1104
-    public function getDirtyRawAttributes(): array
1105
-    {
1106
-        $attributes = $this->getRawAttributes();
1107
-
1108
-        $cachedAttributes = $this->getCachedRawAttributes(array_keys($attributes));
1109
-
1110
-        $dirty = [];
1111
-
1112
-        foreach ($attributes as $key => $value) {
1113
-            if ($this->isActualRelation($key) || $key == 'pivot') {
1114
-                continue;
1115
-            }
1116
-
1117
-            if (!array_key_exists($key, $cachedAttributes) && !$value instanceof Pivot) {
1118
-                $dirty[$key] = $value;
1119
-            } elseif ($value !== $cachedAttributes[$key] &&
1120
-                !$this->originalIsNumericallyEquivalent($value, $cachedAttributes[$key])) {
1121
-                $dirty[$key] = $value;
1122
-            }
1123
-        }
1124
-
1125
-        return $dirty;
1126
-    }
1127
-
1128
-    /**
1129
-     * @param string $key
1130
-     *
1131
-     * @return bool
1132
-     */
1133
-    protected function isActualRelation(string $key): bool
1134
-    {
1135
-        return in_array($key, $this->entityMap->getNonEmbeddedRelationships());
1136
-    }
1137
-
1138
-    /**
1139
-     * Return true if attribute is a non-loaded proxy.
1140
-     *
1141
-     * @param string $key
1142
-     *
1143
-     * @return bool
1144
-     */
1145
-    protected function isNonLoadedProxy(string $key): bool
1146
-    {
1147
-        $relation = $this->getEntityAttribute($key);
1148
-
1149
-        return $relation instanceof ProxyInterface && !$relation->isProxyInitialized();
1150
-    }
1151
-
1152
-    /**
1153
-     * Determine if the new and old values for a given key are numerically equivalent.
1154
-     *
1155
-     * @param $current
1156
-     * @param $original
1157
-     *
1158
-     * @return bool
1159
-     */
1160
-    protected function originalIsNumericallyEquivalent($current, $original): bool
1161
-    {
1162
-        return is_numeric($current) && is_numeric($original) && strcmp((string) $current, (string) $original) === 0;
1163
-    }
1164
-
1165
-    /**
1166
-     * Get the underlying entity object.
1167
-     *
1168
-     * @return mixed
1169
-     */
1170
-    public function getEntityObject()
1171
-    {
1172
-        return $this->wrappedEntity->getObject();
1173
-    }
1174
-
1175
-    /**
1176
-     * Return the Mapper instance for the current Entity Type.
1177
-     *
1178
-     * @return \Analogue\ORM\System\Mapper
1179
-     */
1180
-    public function getMapper(): Mapper
1181
-    {
1182
-        return Manager::getMapper($this->class);
1183
-    }
1184
-
1185
-    /**
1186
-     * Check that the entity already exists in the database, by checking
1187
-     * if it has an EntityCache record.
1188
-     *
1189
-     * @return bool
1190
-     */
1191
-    public function exists(): bool
1192
-    {
1193
-        return $this->getCache()->has($this->getEntityKeyValue());
1194
-    }
1195
-
1196
-    /**
1197
-     * {@inheritdoc}
1198
-     */
1199
-    public function setEntityAttributes(array $attributes)
1200
-    {
1201
-        $this->wrappedEntity->setEntityAttributes($attributes);
1202
-    }
1203
-
1204
-    /**
1205
-     * {@inheritdoc}
1206
-     */
1207
-    public function getEntityAttributes(): array
1208
-    {
1209
-        return $this->wrappedEntity->getEntityAttributes();
1210
-    }
1211
-
1212
-    /**
1213
-     * {@inheritdoc}
1214
-     */
1215
-    public function setEntityAttribute(string $key, $value)
1216
-    {
1217
-        $this->wrappedEntity->setEntityAttribute($key, $value);
1218
-    }
1219
-
1220
-    /**
1221
-     * {@inheritdoc}
1222
-     */
1223
-    public function getEntityAttribute(string $key)
1224
-    {
1225
-        return $this->wrappedEntity->getEntityAttribute($key);
1226
-    }
1227
-
1228
-    /**
1229
-     * {@inheritdoc}
1230
-     */
1231
-    public function hasAttribute(string $key): bool
1232
-    {
1233
-        return $this->wrappedEntity->hasAttribute($key);
1234
-    }
1235
-
1236
-    /**
1237
-     * Return wrapped entity.
1238
-     *
1239
-     * @return InternallyMappable
1240
-     */
1241
-    public function getWrappedEntity()
1242
-    {
1243
-        return $this->wrappedEntity;
1244
-    }
1245
-
1246
-    /**
1247
-     * Set the lazy loading proxies on the wrapped entity.
1248
-     *
1249
-     * @return void
1250
-     */
1251
-    public function setProxies()
1252
-    {
1253
-        $this->wrappedEntity->setProxies();
1254
-    }
22
+	/**
23
+	 * The Root Entity.
24
+	 *
25
+	 * @var \Analogue\ORM\System\Wrappers\Wrapper
26
+	 */
27
+	protected $wrappedEntity;
28
+
29
+	/**
30
+	 * Class of the entity being aggregated.
31
+	 *
32
+	 * @var string
33
+	 */
34
+	protected $class;
35
+
36
+	/**
37
+	 * Parent Root Aggregate.
38
+	 *
39
+	 * @var \Analogue\ORM\System\Aggregate
40
+	 */
41
+	protected $parent;
42
+
43
+	/**
44
+	 * Parent's relationship method.
45
+	 *
46
+	 * @var string
47
+	 */
48
+	protected $parentRelationship;
49
+
50
+	/**
51
+	 * Root Entity.
52
+	 *
53
+	 * @var \Analogue\ORM\System\Aggregate
54
+	 */
55
+	protected $root;
56
+
57
+	/**
58
+	 * An associative array containing entity's
59
+	 * relationships converted to Aggregates.
60
+	 *
61
+	 * @var array
62
+	 */
63
+	protected $relationships = [];
64
+
65
+	/**
66
+	 * Relationship that need post-command synchronization.
67
+	 *
68
+	 * @var array
69
+	 */
70
+	protected $needSync = [];
71
+
72
+	/**
73
+	 * Mapper.
74
+	 *
75
+	 * @var \Analogue\ORM\System\Mapper;
76
+	 */
77
+	protected $mapper;
78
+
79
+	/**
80
+	 * Entity Map.
81
+	 *
82
+	 * @var \Analogue\ORM\EntityMap;
83
+	 */
84
+	protected $entityMap;
85
+
86
+	/**
87
+	 * Create a new Aggregated Entity instance.
88
+	 *
89
+	 * @param mixed          $entity
90
+	 * @param Aggregate|null $parent
91
+	 * @param string         $parentRelationship
92
+	 * @param Aggregate|null $root
93
+	 *
94
+	 * @throws MappingException
95
+	 */
96
+	public function __construct($entity, self $parent = null, string $parentRelationship = null, self $root = null)
97
+	{
98
+		$factory = new Factory();
99
+
100
+		$this->class = get_class($entity);
101
+
102
+		$this->wrappedEntity = $factory->make($entity);
103
+
104
+		$this->parent = $parent;
105
+
106
+		$this->parentRelationship = $parentRelationship;
107
+
108
+		$this->root = $root;
109
+
110
+		$mapper = $this->getMapper();
111
+
112
+		$this->entityMap = $mapper->getEntityMap();
113
+
114
+		$this->parseRelationships();
115
+	}
116
+
117
+	/**
118
+	 * Parse Every relationships defined on the entity.
119
+	 *
120
+	 * @throws MappingException
121
+	 *
122
+	 * @return void
123
+	 */
124
+	protected function parseRelationships()
125
+	{
126
+		foreach ($this->entityMap->getSingleRelationships() as $relation) {
127
+			$this->parseSingleRelationship($relation);
128
+		}
129
+
130
+		foreach ($this->entityMap->getManyRelationships() as $relation) {
131
+			$this->parseManyRelationship($relation);
132
+		}
133
+	}
134
+
135
+	/**
136
+	 * Parse for values common to single & many relations.
137
+	 *
138
+	 * @param string $relation
139
+	 *
140
+	 * @throws MappingException
141
+	 *
142
+	 * @return mixed|bool
143
+	 */
144
+	protected function parseForCommonValues($relation)
145
+	{
146
+		if (!$this->hasAttribute($relation)) {
147
+			// If no attribute exists for this relationships
148
+			// we'll make it a simple empty array. This will
149
+			// save us from constantly checking for the attributes
150
+			// actual existence.
151
+			$this->relationships[$relation] = [];
152
+
153
+			return false;
154
+		}
155
+
156
+		$value = $this->getRelationshipValue($relation);
157
+
158
+		if (is_null($value)) {
159
+			$this->relationships[$relation] = [];
160
+
161
+			// If the relationship's content is the null value
162
+			// and the Entity's exist in DB, we'll interpret this
163
+			// as the need to detach all related Entities,
164
+			// therefore a sync operation is needed.
165
+			$this->needSync[] = $relation;
166
+
167
+			return false;
168
+		}
169
+
170
+		return $value;
171
+	}
172
+
173
+	/**
174
+	 * Parse a 'single' relationship.
175
+	 *
176
+	 * @param string $relation
177
+	 *
178
+	 * @throws MappingException
179
+	 *
180
+	 * @return bool
181
+	 */
182
+	protected function parseSingleRelationship(string $relation): bool
183
+	{
184
+		if (!$value = $this->parseForCommonValues($relation)) {
185
+			return true;
186
+		}
187
+
188
+		if ($value instanceof Collection || is_array($value) || $value instanceof CollectionProxy) {
189
+			throw new MappingException("Entity's attribute $relation should not be array, or collection");
190
+		}
191
+
192
+		if ($value instanceof LazyLoadingInterface && !$value->isProxyInitialized()) {
193
+			$this->relationships[$relation] = [];
194
+
195
+			return true;
196
+		}
197
+
198
+		// If the attribute is a loaded proxy, swap it for its
199
+		// loaded entity.
200
+		if ($value instanceof LazyLoadingInterface && $value->isProxyInitialized()) {
201
+			$value = $value->getWrappedValueHolderValue();
202
+		}
203
+
204
+		if ($this->isParentOrRoot($value)) {
205
+			$this->relationships[$relation] = [];
206
+
207
+			return true;
208
+		}
209
+
210
+		// At this point, we can assume the attribute is an Entity instance
211
+		// so we'll treat it as such.
212
+		$subAggregate = $this->createSubAggregate($value, $relation);
213
+
214
+		// Even if it's a single entity, we'll store it as an array
215
+		// just for consistency with other relationships
216
+		$this->relationships[$relation] = [$subAggregate];
217
+
218
+		// We always need to check a loaded relation is in sync
219
+		// with its local key
220
+		$this->needSync[] = $relation;
221
+
222
+		return true;
223
+	}
224
+
225
+	/**
226
+	 * Check if value isn't parent or root in the aggregate.
227
+	 *
228
+	 * @param  mixed
229
+	 *
230
+	 * @return bool
231
+	 */
232
+	protected function isParentOrRoot($value): bool
233
+	{
234
+		$id = spl_object_hash($value);
235
+		$root = $this->root ? $this->root->getWrappedEntity()->getObject() : null;
236
+		$parent = $this->parent ? $this->parent->getWrappedEntity()->getObject() : null;
237
+
238
+		if ($parent && (spl_object_hash($parent) == $id)) {
239
+			return true;
240
+		}
241
+
242
+		if ($root && (spl_object_hash($root) == $id)) {
243
+			return true;
244
+		}
245
+
246
+		return false;
247
+	}
248
+
249
+	/**
250
+	 * Parse a 'many' relationship.
251
+	 *
252
+	 * @param string $relation
253
+	 *
254
+	 * @throws MappingException
255
+	 *
256
+	 * @return bool
257
+	 */
258
+	protected function parseManyRelationship(string $relation): bool
259
+	{
260
+		if (!$value = $this->parseForCommonValues($relation)) {
261
+			return true;
262
+		}
263
+
264
+		if (is_array($value) || (!$value instanceof CollectionProxy && $value instanceof Collection)) {
265
+			$this->needSync[] = $relation;
266
+		}
267
+
268
+		// If the relation is a proxy, we test is the relation
269
+		// has been lazy loaded, otherwise we'll just treat
270
+		// the subset of newly added items.
271
+		if ($value instanceof CollectionProxy && $value->isProxyInitialized()) {
272
+			$this->needSync[] = $relation;
273
+			//$value = $value->getUnderlyingCollection();
274
+		}
275
+
276
+		if ($value instanceof CollectionProxy && !$value->isProxyInitialized()) {
277
+			$value = $value->getAddedItems();
278
+		}
279
+
280
+		// At this point $value should be either an array or an instance
281
+		// of a collection class.
282
+		if (!is_array($value) && !$value instanceof Collection) {
283
+			throw new MappingException("'$relation' attribute should be array() or Collection");
284
+		}
285
+
286
+		$this->relationships[$relation] = $this->createSubAggregates($value, $relation);
287
+
288
+		return true;
289
+	}
290
+
291
+	/**
292
+	 * Return Entity's relationship attribute.
293
+	 *
294
+	 * @param string $relation
295
+	 *
296
+	 * @throws MappingException
297
+	 *
298
+	 * @return mixed
299
+	 */
300
+	protected function getRelationshipValue(string $relation)
301
+	{
302
+		$value = $this->getEntityAttribute($relation);
303
+
304
+		if (is_scalar($value)) {
305
+			throw new MappingException("Entity's attribute $relation should be array, object, collection or null");
306
+		}
307
+
308
+		return $value;
309
+	}
310
+
311
+	/**
312
+	 * Create a child, aggregated entity.
313
+	 *
314
+	 * @param mixed  $entities
315
+	 * @param string $relation
316
+	 *
317
+	 * @return array
318
+	 */
319
+	protected function createSubAggregates($entities, string $relation): array
320
+	{
321
+		$aggregates = [];
322
+
323
+		foreach ($entities as $entity) {
324
+			$aggregates[] = $this->createSubAggregate($entity, $relation);
325
+		}
326
+
327
+		return $aggregates;
328
+	}
329
+
330
+	/**
331
+	 * Create a related subAggregate.
332
+	 *
333
+	 * @param mixed  $entity
334
+	 * @param string $relation
335
+	 *
336
+	 * @throws MappingException
337
+	 *
338
+	 * @return Aggregate
339
+	 */
340
+	protected function createSubAggregate($entity, string $relation): self
341
+	{
342
+		// If root isn't defined, then this is the Aggregate Root
343
+		$root = is_null($this->root) ? $this : $this->root;
344
+
345
+		return new self($entity, $this, $relation, $root);
346
+	}
347
+
348
+	/**
349
+	 * Return the entity map for the current entity.
350
+	 *
351
+	 * @return \Analogue\ORM\EntityMap
352
+	 */
353
+	public function getEntityMap(): EntityMap
354
+	{
355
+		return $this->entityMap;
356
+	}
357
+
358
+	/**
359
+	 * {@inheritdoc}
360
+	 */
361
+	public function getEntityHash(): string
362
+	{
363
+		return $this->getEntityClass().'.'.$this->getEntityKeyValue();
364
+	}
365
+
366
+	/**
367
+	 * {@inheritdoc}
368
+	 */
369
+	public function getEntityKeyName(): string
370
+	{
371
+		return $this->entityMap->getKeyName();
372
+	}
373
+
374
+	/**
375
+	 * {@inheritdoc}
376
+	 */
377
+	public function getEntityKeyValue()
378
+	{
379
+		$keyValue = $this->wrappedEntity->getEntityKeyValue();
380
+
381
+		if ($keyValue instanceof ObjectId) {
382
+			$keyValue = (string) $keyValue;
383
+		}
384
+
385
+		return $keyValue;
386
+	}
387
+
388
+	/**
389
+	 * {@inheritdoc}
390
+	 */
391
+	public function getEntityClass(): string
392
+	{
393
+		return $this->entityMap->getClass();
394
+	}
395
+
396
+	/**
397
+	 * Return the Mapper's entity cache.
398
+	 *
399
+	 * @return \Analogue\ORM\System\Cache\AttributeCache
400
+	 */
401
+	protected function getEntityCache(): AttributeCache
402
+	{
403
+		return $this->getMapper()->getEntityCache();
404
+	}
405
+
406
+	/**
407
+	 * Get a relationship as an aggregated entities' array.
408
+	 *
409
+	 * @param string $name
410
+	 *
411
+	 * @return array
412
+	 */
413
+	public function getRelationship(string $name): array
414
+	{
415
+		if (array_key_exists($name, $this->relationships)) {
416
+			return $this->relationships[$name];
417
+		}
418
+
419
+		return [];
420
+	}
421
+
422
+	/**
423
+	 * [TO IMPLEMENT].
424
+	 *
425
+	 * @return array
426
+	 */
427
+	public function getPivotAttributes(): array
428
+	{
429
+		return [];
430
+	}
431
+
432
+	/**
433
+	 * Get Non existing related entities from several relationships.
434
+	 *
435
+	 * @param array $relationships
436
+	 *
437
+	 * @return array
438
+	 */
439
+	public function getNonExistingRelated(array $relationships): array
440
+	{
441
+		$nonExisting = [];
442
+
443
+		foreach ($relationships as $relation) {
444
+			if ($this->hasAttribute($relation) && array_key_exists($relation, $this->relationships)) {
445
+				$nonExisting = array_merge($nonExisting, $this->getNonExistingFromRelation($relation));
446
+			}
447
+		}
448
+
449
+		return $nonExisting;
450
+	}
451
+
452
+	/**
453
+	 * Get non-existing related entities from a single relation.
454
+	 *
455
+	 * @param string $relation
456
+	 *
457
+	 * @return array
458
+	 */
459
+	protected function getNonExistingFromRelation(string $relation): array
460
+	{
461
+		$nonExisting = [];
462
+
463
+		foreach ($this->relationships[$relation] as $aggregate) {
464
+			if (!$aggregate->exists()) {
465
+				$nonExisting[] = $aggregate;
466
+			}
467
+		}
468
+
469
+		return $nonExisting;
470
+	}
471
+
472
+	/**
473
+	 * Synchronize relationships if needed.
474
+	 *
475
+	 * @param array
476
+	 *
477
+	 * @return void
478
+	 */
479
+	public function syncRelationships(array $relationships)
480
+	{
481
+		foreach ($relationships as $relation) {
482
+			if (in_array($relation, $this->needSync)) {
483
+				$this->synchronize($relation);
484
+			}
485
+		}
486
+	}
487
+
488
+	/**
489
+	 * Synchronize a relationship attribute.
490
+	 *
491
+	 * @param string $relation
492
+	 *
493
+	 * @return void
494
+	 */
495
+	protected function synchronize(string $relation)
496
+	{
497
+		$actualContent = $this->relationships[$relation];
498
+
499
+		$relationshipObject = $this->entityMap->$relation($this->getEntityObject());
500
+		$relationshipObject->setParent($this->wrappedEntity);
501
+		$relationshipObject->sync($actualContent);
502
+	}
503
+
504
+	/**
505
+	 * Returns an array of Missing related Entities for the
506
+	 * given $relation.
507
+	 *
508
+	 * @param string $relation
509
+	 *
510
+	 * @return array
511
+	 */
512
+	public function getMissingEntities(string $relation): array
513
+	{
514
+		$cachedRelations = $this->getCachedAttribute($relation);
515
+
516
+		if (is_null($cachedRelations)) {
517
+			return [];
518
+		}
519
+
520
+		$missing = [];
521
+
522
+		foreach ($cachedRelations as $hash) {
523
+			if (!$this->getRelatedAggregateFromHash($hash, $relation)) {
524
+				$missing[] = $hash;
525
+			}
526
+		}
527
+
528
+		return $missing;
529
+	}
530
+
531
+	/**
532
+	 * Get Relationships who have dirty attributes / dirty relationships.
533
+	 *
534
+	 * @return array
535
+	 */
536
+	public function getDirtyRelationships(): array
537
+	{
538
+		$dirtyAggregates = [];
539
+
540
+		foreach ($this->relationships as $relation) {
541
+			foreach ($relation as $aggregate) {
542
+				if (!$aggregate->exists() || $aggregate->isDirty() || count($aggregate->getDirtyRelationships()) > 0) {
543
+					$dirtyAggregates[] = $aggregate;
544
+				}
545
+			}
546
+		}
547
+
548
+		return $dirtyAggregates;
549
+	}
550
+
551
+	/**
552
+	 * Compare the object's raw attributes with the record in cache.
553
+	 *
554
+	 * @return bool
555
+	 */
556
+	public function isDirty(): bool
557
+	{
558
+		return count($this->getDirtyRawAttributes()) > 0;
559
+	}
560
+
561
+	/**
562
+	 * Get Raw Entity's attributes, as they are represented
563
+	 * in the database, including value objects, foreign keys,
564
+	 * and discriminator column.
565
+	 *
566
+	 * @return array
567
+	 */
568
+	public function getRawAttributes(): array
569
+	{
570
+		$attributes = $this->wrappedEntity->getEntityAttributes();
571
+
572
+		foreach ($this->entityMap->getNonEmbeddedRelationships() as $relation) {
573
+			unset($attributes[$relation]);
574
+		}
575
+
576
+		if ($this->entityMap->getInheritanceType() == 'single_table') {
577
+			$attributes = $this->addDiscriminatorColumn($attributes);
578
+		}
579
+
580
+		$attributes = $this->flattenEmbeddables($attributes);
581
+
582
+		$foreignKeys = $this->getForeignKeyAttributes();
583
+
584
+		return $this->mergeForeignKeysWithAttributes($foreignKeys, $attributes);
585
+	}
586
+
587
+	/**
588
+	 * Merge foreign keys and attributes by comparing their
589
+	 * current value to the cache, and guess the user intent.
590
+	 *
591
+	 * @param array $foreignKeys
592
+	 * @param array $attributes
593
+	 *
594
+	 * @return array
595
+	 */
596
+	protected function mergeForeignKeysWithAttributes(array $foreignKeys, array $attributes): array
597
+	{
598
+		$cachedAttributes = $this->getCachedRawAttributes();
599
+
600
+		foreach ($foreignKeys as $fkAttributeKey => $fkAttributeValue) {
601
+
602
+			// FK doesn't exist in attributes => we set it
603
+			if (!array_key_exists($fkAttributeKey, $attributes)) {
604
+				$attributes[$fkAttributeKey] = $fkAttributeValue;
605
+				continue;
606
+			}
607
+
608
+			// FK does exists in attributes and is equal => we set it
609
+			if ($attributes[$fkAttributeKey] === $fkAttributeValue) {
610
+				$attributes[$fkAttributeKey] = $fkAttributeValue;
611
+				continue;
612
+			}
613
+
614
+			// ForeignKey exists in attributes array, but the value is different that
615
+			// the one fetched from the relationship itself.
616
+
617
+			// Does it exist in cache
618
+			if (array_key_exists($fkAttributeKey, $cachedAttributes)) {
619
+				// attribute is different than cached value, we use it
620
+				if ($attributes[$fkAttributeKey] !== $cachedAttributes[$fkAttributeKey]) {
621
+					continue;
622
+				}
623
+				// if not, we use the foreign key value
624
+				else {
625
+					$attributes[$fkAttributeKey] = $fkAttributeValue;
626
+				}
627
+			} else {
628
+				if (is_null($attributes[$fkAttributeKey])) {
629
+					$attributes[$fkAttributeKey] = $fkAttributeValue;
630
+				}
631
+			}
632
+		}
633
+
634
+		return $attributes;
635
+	}
636
+
637
+	/**
638
+	 * Add Discriminator Column if it doesn't exist on the actual entity.
639
+	 *
640
+	 * @param array $attributes
641
+	 *
642
+	 * @return array
643
+	 */
644
+	protected function addDiscriminatorColumn(array $attributes): array
645
+	{
646
+		$discriminatorColumn = $this->entityMap->getDiscriminatorColumn();
647
+		$entityClass = $this->entityMap->getClass();
648
+
649
+		if (!array_key_exists($discriminatorColumn, $attributes)) {
650
+
651
+			// Use key if present in discriminatorMap
652
+			$map = $this->entityMap->getDiscriminatorColumnMap();
653
+
654
+			$type = array_search($entityClass, $map);
655
+
656
+			if ($type === false) {
657
+				// Use entity FQCN if no corresponding key is set
658
+				$attributes[$discriminatorColumn] = $entityClass;
659
+			} else {
660
+				$attributes[$discriminatorColumn] = $type;
661
+			}
662
+		}
663
+
664
+		return $attributes;
665
+	}
666
+
667
+	/**
668
+	 * Convert Value Objects to raw db attributes.
669
+	 *
670
+	 * @param array $attributes
671
+	 *
672
+	 * @return array
673
+	 */
674
+	protected function flattenEmbeddables(array $attributes): array
675
+	{
676
+		// TODO : deprecate old implementation
677
+		$embeddables = $this->entityMap->getEmbeddables();
678
+
679
+		foreach ($embeddables as $localKey => $embed) {
680
+			// Retrieve the value object from the entity's attributes
681
+			$valueObject = $attributes[$localKey];
682
+
683
+			// Unset the corresponding key
684
+			unset($attributes[$localKey]);
685
+
686
+			// TODO Make wrapper object compatible with value objects
687
+			$valueObjectAttributes = $valueObject->getEntityAttributes();
688
+
689
+			// Now (if setup in the entity map) we prefix the value object's
690
+			// attributes with the snake_case name of the embedded class.
691
+			$prefix = snake_case(class_basename($embed));
692
+
693
+			foreach ($valueObjectAttributes as $key => $value) {
694
+				$valueObjectAttributes[$prefix.'_'.$key] = $value;
695
+				unset($valueObjectAttributes[$key]);
696
+			}
697
+
698
+			$attributes = array_merge($attributes, $valueObjectAttributes);
699
+		}
700
+
701
+		//*********************
702
+		// New implementation
703
+		// *****************->
704
+
705
+		$embeddedRelations = $this->entityMap->getEmbeddedRelationships();
706
+
707
+		foreach ($embeddedRelations as $relation) {
708
+
709
+			// Spawn a new instance we can pass to the relationship method
710
+			$parentInstance = $this->getMapper()->newInstance();
711
+			$relationInstance = $this->entityMap->$relation($parentInstance);
712
+
713
+			// Extract the object from the attributes
714
+			$embeddedObject = $attributes[$relation];
715
+
716
+			unset($attributes[$relation]);
717
+
718
+			$attributes = $relationInstance->normalize($embeddedObject) + $attributes;
719
+		}
720
+
721
+		return $attributes;
722
+	}
723
+
724
+	/**
725
+	 * Return's entity raw attributes in the state they were at last
726
+	 * query.
727
+	 *
728
+	 * @param array|null $columns
729
+	 *
730
+	 * @return array
731
+	 */
732
+	protected function getCachedRawAttributes(array $columns = null): array
733
+	{
734
+		$cachedAttributes = $this->getCache()->get($this->getEntityKeyValue());
735
+
736
+		if (is_null($columns)) {
737
+			return $cachedAttributes;
738
+		}
739
+
740
+		return array_only($cachedAttributes, $columns);
741
+	}
742
+
743
+	/**
744
+	 * Return a single attribute from the cache.
745
+	 *
746
+	 * @param string $key
747
+	 *
748
+	 * @return mixed|null
749
+	 */
750
+	protected function getCachedAttribute($key)
751
+	{
752
+		$cachedAttributes = $this->getCache()->get($this->getEntityKeyValue());
753
+
754
+		if (array_key_exists($key, $cachedAttributes)) {
755
+			return $cachedAttributes[$key];
756
+		}
757
+	}
758
+
759
+	/**
760
+	 * Convert related Entity's attributes to foreign keys.
761
+	 *
762
+	 * @return array
763
+	 */
764
+	public function getForeignKeyAttributes(): array
765
+	{
766
+		$foreignKeys = [];
767
+
768
+		foreach ($this->entityMap->getLocalRelationships() as $relation) {
769
+
770
+			// If the actual relationship is a non-loaded proxy, we'll simply retrieve
771
+			// the foreign key pair without parsing the actual object. This will allow
772
+			// user to modify the actual related ID's directly by updating the corresponding
773
+			// attribute.
774
+			if ($this->isNonLoadedProxy($relation)) {
775
+				$foreignKeys = $foreignKeys + $this->getForeignKeyAttributesFromNonLoadedRelation($relation);
776
+				continue;
777
+			}
778
+
779
+			// check if relationship has been parsed, meaning it has an actual object
780
+			// in the entity's attributes
781
+			if ($this->isActualRelationships($relation)) {
782
+				$foreignKeys = $foreignKeys + $this->getForeignKeyAttributesFromRelation($relation);
783
+			} else {
784
+				$foreignKeys = $foreignKeys + $this->getNullForeignKeyFromRelation($relation);
785
+			}
786
+		}
787
+
788
+		if (!is_null($this->parent)) {
789
+			$foreignKeys = $this->getForeignKeyAttributesFromParent() + $foreignKeys;
790
+		}
791
+
792
+		return $foreignKeys;
793
+	}
794
+
795
+	/**
796
+	 * Get a null foreign key value pair for an empty relationship.
797
+	 *
798
+	 * @param string $relation
799
+	 *
800
+	 * @throws MappingException
801
+	 *
802
+	 * @return array
803
+	 */
804
+	protected function getNullForeignKeyFromRelation(string $relation): array
805
+	{
806
+		$key = $this->entityMap->getLocalKeys($relation);
807
+
808
+		if (is_array($key)) {
809
+			return $this->entityMap->getEmptyValueForLocalKey($relation);
810
+		}
811
+
812
+		if (is_null($key)) {
813
+			throw new MappingException("Foreign key for relation $relation cannot be null");
814
+		}
815
+
816
+		return [
817
+			$key => $this->entityMap->getEmptyValueForLocalKey($relation),
818
+		];
819
+	}
820
+
821
+	/**
822
+	 * Return an associative array containing the key-value pair(s) from
823
+	 * the related entity.
824
+	 *
825
+	 * @param string $relation
826
+	 *
827
+	 * @return array
828
+	 */
829
+	protected function getForeignKeyAttributesFromRelation(string $relation): array
830
+	{
831
+		// Call Relationship's method
832
+		$relationship = $this->entityMap->$relation($this->getEntityObject());
833
+
834
+		$relatedAggregate = $this->relationships[$relation][0];
835
+
836
+		return $relationship->getForeignKeyValuePair($relatedAggregate->getEntityObject());
837
+	}
838
+
839
+	/**
840
+	 * Return an associative array containing the key-value pair(s) from
841
+	 * the foreign key attribute.
842
+	 *
843
+	 * @param string $relation
844
+	 *
845
+	 * @return array
846
+	 */
847
+	protected function getForeignKeyAttributesFromNonLoadedRelation(string $relation): array
848
+	{
849
+		$keys = $this->entityMap->getLocalKeys($relation);
850
+
851
+		// We'll treat single and composite keys (polymorphic) the same way.
852
+		if (!is_array($keys)) {
853
+			$keys = [$keys];
854
+		}
855
+
856
+		$foreignKey = [];
857
+
858
+		foreach ($keys as $key) {
859
+			$foreignKey[$key] = $this->getEntityAttribute($key);
860
+		}
861
+
862
+		return $foreignKey;
863
+	}
864
+
865
+	/**
866
+	 * Get foreign key attribute(s) from a parent entity in this
867
+	 * aggregate context.
868
+	 *
869
+	 * @return array
870
+	 */
871
+	protected function getForeignKeyAttributesFromParent(): array
872
+	{
873
+		$parentMap = $this->parent->getEntityMap();
874
+
875
+		$parentForeignRelations = $parentMap->getForeignRelationships();
876
+		$parentPivotRelations = $parentMap->getPivotRelationships();
877
+
878
+		// The parentRelation is the name of the relationship
879
+		// methods on the parent entity map
880
+		$parentRelation = $this->parentRelationship;
881
+
882
+		if (
883
+			in_array($parentRelation, $parentForeignRelations) &&
884
+			!in_array($parentRelation, $parentPivotRelations)
885
+		) {
886
+			$parentObject = $this->parent->getEntityObject();
887
+
888
+			// Call Relationship's method on parent map
889
+			$relationship = $parentMap->$parentRelation($parentObject);
890
+
891
+			return $relationship->getForeignKeyValuePair($parentObject);
892
+		}
893
+
894
+		return [];
895
+	}
896
+
897
+	/**
898
+	 * Update Pivot records on loaded relationships, by comparing the
899
+	 * values from the Entity Cache to the actual relationship inside
900
+	 * the aggregated entity.
901
+	 *
902
+	 * @return void
903
+	 */
904
+	public function updatePivotRecords()
905
+	{
906
+		$pivots = $this->entityMap->getPivotRelationships();
907
+
908
+		foreach ($pivots as $pivot) {
909
+			if (array_key_exists($pivot, $this->relationships)) {
910
+				$this->updatePivotRelation($pivot);
911
+			}
912
+		}
913
+	}
914
+
915
+	/**
916
+	 * Update Single pivot relationship.
917
+	 *
918
+	 * @param string $relation
919
+	 *
920
+	 * @return void
921
+	 */
922
+	protected function updatePivotRelation(string $relation)
923
+	{
924
+		$hashes = $this->getEntityHashesFromRelation($relation);
925
+
926
+		$cachedAttributes = $this->getCachedRawAttributes();
927
+
928
+		if (array_key_exists($relation, $cachedAttributes)) {
929
+			// Compare the two array of hashes to find out existing
930
+			// pivot records, and the ones to be created.
931
+			$new = array_diff($hashes, array_keys($cachedAttributes[$relation]));
932
+			$existing = array_intersect($hashes, array_keys($cachedAttributes[$relation]));
933
+		} else {
934
+			$existing = [];
935
+			$new = $hashes;
936
+		}
937
+
938
+		if (count($new) > 0) {
939
+			$pivots = $this->getRelatedAggregatesFromHashes($new, $relation);
940
+
941
+			$this->entityMap->$relation($this->getEntityObject())->createPivots($pivots);
942
+		}
943
+
944
+		if (count($existing) > 0) {
945
+			foreach ($existing as $pivotHash) {
946
+				$this->updatePivotIfDirty($pivotHash, $relation);
947
+			}
948
+		}
949
+	}
950
+
951
+	/**
952
+	 * Compare existing pivot record in cache and update it
953
+	 * if the pivot attributes are dirty.
954
+	 *
955
+	 * @param string $pivotHash
956
+	 * @param string $relation
957
+	 *
958
+	 * @return void
959
+	 */
960
+	protected function updatePivotIfDirty(string $pivotHash, string $relation)
961
+	{
962
+		$aggregate = $this->getRelatedAggregateFromHash($pivotHash, $relation);
963
+
964
+		if ($aggregate->hasAttribute('pivot')) {
965
+			$pivot = $aggregate->getEntityAttribute('pivot')->getEntityAttributes();
966
+
967
+			$cachedPivotAttributes = $this->getPivotAttributesFromCache($pivotHash, $relation);
968
+
969
+			$actualPivotAttributes = array_only($pivot, array_keys($cachedPivotAttributes));
970
+
971
+			$dirty = $this->getDirtyAttributes($actualPivotAttributes, $cachedPivotAttributes);
972
+
973
+			if (count($dirty) > 0) {
974
+				$id = $aggregate->getEntityKeyValue();
975
+
976
+				$this->entityMap->$relation($this->getEntityObject())->updateExistingPivot($id, $dirty);
977
+			}
978
+		}
979
+	}
980
+
981
+	/**
982
+	 * Compare two attributes array and return dirty attributes.
983
+	 *
984
+	 * @param array $actual
985
+	 * @param array $cached
986
+	 *
987
+	 * @return array
988
+	 */
989
+	protected function getDirtyAttributes(array $actual, array $cached): array
990
+	{
991
+		$dirty = [];
992
+
993
+		foreach ($actual as $key => $value) {
994
+			if (!$this->originalIsNumericallyEquivalent($value, $cached[$key])) {
995
+				$dirty[$key] = $actual[$key];
996
+			}
997
+		}
998
+
999
+		return $dirty;
1000
+	}
1001
+
1002
+	/**
1003
+	 * @param string $pivotHash
1004
+	 * @param string $relation
1005
+	 *
1006
+	 * @return array|null
1007
+	 */
1008
+	protected function getPivotAttributesFromCache(string $pivotHash, string $relation)
1009
+	{
1010
+		$cachedAttributes = $this->getCachedRawAttributes();
1011
+
1012
+		$cachedRelations = $cachedAttributes[$relation];
1013
+
1014
+		foreach ($cachedRelations as $cachedRelation) {
1015
+			if ($cachedRelation == $pivotHash) {
1016
+				return $cachedRelation->getPivotAttributes();
1017
+			}
1018
+		}
1019
+	}
1020
+
1021
+	/**
1022
+	 * Returns an array of related Aggregates from its entity hashes.
1023
+	 *
1024
+	 * @param array  $hashes
1025
+	 * @param string $relation
1026
+	 *
1027
+	 * @return array
1028
+	 */
1029
+	protected function getRelatedAggregatesFromHashes(array $hashes, string $relation): array
1030
+	{
1031
+		$related = [];
1032
+
1033
+		foreach ($hashes as $hash) {
1034
+			$aggregate = $this->getRelatedAggregateFromHash($hash, $relation);
1035
+
1036
+			if ($aggregate) {
1037
+				$related[] = $aggregate;
1038
+			}
1039
+		}
1040
+
1041
+		return $related;
1042
+	}
1043
+
1044
+	/**
1045
+	 * Get related aggregate from its hash.
1046
+	 *
1047
+	 * @param string $hash
1048
+	 * @param string $relation
1049
+	 *
1050
+	 * @return \Analogue\ORM\System\Aggregate|null
1051
+	 */
1052
+	protected function getRelatedAggregateFromHash(string $hash, string $relation)
1053
+	{
1054
+		foreach ($this->relationships[$relation] as $aggregate) {
1055
+			if ($aggregate->getEntityHash() == $hash) {
1056
+				return $aggregate;
1057
+			}
1058
+		}
1059
+	}
1060
+
1061
+	/**
1062
+	 * Return an array of Entity Hashes from a specific relation.
1063
+	 *
1064
+	 * @param string $relation
1065
+	 *
1066
+	 * @return array
1067
+	 */
1068
+	protected function getEntityHashesFromRelation(string $relation): array
1069
+	{
1070
+		return array_map(function (Aggregate $aggregate) {
1071
+			return $aggregate->getEntityHash();
1072
+		}, $this->relationships[$relation]);
1073
+	}
1074
+
1075
+	/**
1076
+	 * Check the existence of an actual relationship.
1077
+	 *
1078
+	 * @param string $relation
1079
+	 *
1080
+	 * @return bool
1081
+	 */
1082
+	protected function isActualRelationships(string $relation): bool
1083
+	{
1084
+		return array_key_exists($relation, $this->relationships)
1085
+			&& count($this->relationships[$relation]) > 0;
1086
+	}
1087
+
1088
+	/**
1089
+	 * Return cache instance for the current entity type.
1090
+	 *
1091
+	 * @return \Analogue\ORM\System\Cache\AttributeCache
1092
+	 */
1093
+	protected function getCache(): AttributeCache
1094
+	{
1095
+		return $this->getMapper()->getEntityCache();
1096
+	}
1097
+
1098
+	/**
1099
+	 * Get Only Raw Entity attributes which have been modified
1100
+	 * since last query.
1101
+	 *
1102
+	 * @return array
1103
+	 */
1104
+	public function getDirtyRawAttributes(): array
1105
+	{
1106
+		$attributes = $this->getRawAttributes();
1107
+
1108
+		$cachedAttributes = $this->getCachedRawAttributes(array_keys($attributes));
1109
+
1110
+		$dirty = [];
1111
+
1112
+		foreach ($attributes as $key => $value) {
1113
+			if ($this->isActualRelation($key) || $key == 'pivot') {
1114
+				continue;
1115
+			}
1116
+
1117
+			if (!array_key_exists($key, $cachedAttributes) && !$value instanceof Pivot) {
1118
+				$dirty[$key] = $value;
1119
+			} elseif ($value !== $cachedAttributes[$key] &&
1120
+				!$this->originalIsNumericallyEquivalent($value, $cachedAttributes[$key])) {
1121
+				$dirty[$key] = $value;
1122
+			}
1123
+		}
1124
+
1125
+		return $dirty;
1126
+	}
1127
+
1128
+	/**
1129
+	 * @param string $key
1130
+	 *
1131
+	 * @return bool
1132
+	 */
1133
+	protected function isActualRelation(string $key): bool
1134
+	{
1135
+		return in_array($key, $this->entityMap->getNonEmbeddedRelationships());
1136
+	}
1137
+
1138
+	/**
1139
+	 * Return true if attribute is a non-loaded proxy.
1140
+	 *
1141
+	 * @param string $key
1142
+	 *
1143
+	 * @return bool
1144
+	 */
1145
+	protected function isNonLoadedProxy(string $key): bool
1146
+	{
1147
+		$relation = $this->getEntityAttribute($key);
1148
+
1149
+		return $relation instanceof ProxyInterface && !$relation->isProxyInitialized();
1150
+	}
1151
+
1152
+	/**
1153
+	 * Determine if the new and old values for a given key are numerically equivalent.
1154
+	 *
1155
+	 * @param $current
1156
+	 * @param $original
1157
+	 *
1158
+	 * @return bool
1159
+	 */
1160
+	protected function originalIsNumericallyEquivalent($current, $original): bool
1161
+	{
1162
+		return is_numeric($current) && is_numeric($original) && strcmp((string) $current, (string) $original) === 0;
1163
+	}
1164
+
1165
+	/**
1166
+	 * Get the underlying entity object.
1167
+	 *
1168
+	 * @return mixed
1169
+	 */
1170
+	public function getEntityObject()
1171
+	{
1172
+		return $this->wrappedEntity->getObject();
1173
+	}
1174
+
1175
+	/**
1176
+	 * Return the Mapper instance for the current Entity Type.
1177
+	 *
1178
+	 * @return \Analogue\ORM\System\Mapper
1179
+	 */
1180
+	public function getMapper(): Mapper
1181
+	{
1182
+		return Manager::getMapper($this->class);
1183
+	}
1184
+
1185
+	/**
1186
+	 * Check that the entity already exists in the database, by checking
1187
+	 * if it has an EntityCache record.
1188
+	 *
1189
+	 * @return bool
1190
+	 */
1191
+	public function exists(): bool
1192
+	{
1193
+		return $this->getCache()->has($this->getEntityKeyValue());
1194
+	}
1195
+
1196
+	/**
1197
+	 * {@inheritdoc}
1198
+	 */
1199
+	public function setEntityAttributes(array $attributes)
1200
+	{
1201
+		$this->wrappedEntity->setEntityAttributes($attributes);
1202
+	}
1203
+
1204
+	/**
1205
+	 * {@inheritdoc}
1206
+	 */
1207
+	public function getEntityAttributes(): array
1208
+	{
1209
+		return $this->wrappedEntity->getEntityAttributes();
1210
+	}
1211
+
1212
+	/**
1213
+	 * {@inheritdoc}
1214
+	 */
1215
+	public function setEntityAttribute(string $key, $value)
1216
+	{
1217
+		$this->wrappedEntity->setEntityAttribute($key, $value);
1218
+	}
1219
+
1220
+	/**
1221
+	 * {@inheritdoc}
1222
+	 */
1223
+	public function getEntityAttribute(string $key)
1224
+	{
1225
+		return $this->wrappedEntity->getEntityAttribute($key);
1226
+	}
1227
+
1228
+	/**
1229
+	 * {@inheritdoc}
1230
+	 */
1231
+	public function hasAttribute(string $key): bool
1232
+	{
1233
+		return $this->wrappedEntity->hasAttribute($key);
1234
+	}
1235
+
1236
+	/**
1237
+	 * Return wrapped entity.
1238
+	 *
1239
+	 * @return InternallyMappable
1240
+	 */
1241
+	public function getWrappedEntity()
1242
+	{
1243
+		return $this->wrappedEntity;
1244
+	}
1245
+
1246
+	/**
1247
+	 * Set the lazy loading proxies on the wrapped entity.
1248
+	 *
1249
+	 * @return void
1250
+	 */
1251
+	public function setProxies()
1252
+	{
1253
+		$this->wrappedEntity->setProxies();
1254
+	}
1255 1255
 }
Please login to merge, or discard this patch.
src/Entity.php 1 patch
Indentation   +134 added lines, -134 removed lines patch added patch discarded remove patch
@@ -4,138 +4,138 @@
 block discarded – undo
4 4
 
5 5
 class Entity extends ValueObject
6 6
 {
7
-    /**
8
-     * Entities Hidden Attributes, that will be discarded when converting
9
-     * the entity to Array/Json
10
-     * (can include any embedded object's attribute).
11
-     *
12
-     * @var array
13
-     */
14
-    protected $hidden = [];
15
-
16
-    /**
17
-     * Return the entity's attribute.
18
-     *
19
-     * @param string $key
20
-     *
21
-     * @return mixed
22
-     */
23
-    public function __get($key)
24
-    {
25
-        if ($this->hasGetMutator($key)) {
26
-            $method = 'get'.$this->getMutatorMethod($key);
27
-
28
-            $attribute = null;
29
-
30
-            if (isset($this->attributes[$key])) {
31
-                $attribute = $this->attributes[$key];
32
-            }
33
-
34
-            return $this->$method($attribute);
35
-        }
36
-        if (!array_key_exists($key, $this->attributes)) {
37
-            return;
38
-        }
39
-
40
-        return $this->attributes[$key];
41
-    }
42
-
43
-    /**
44
-     * Dynamically set attributes on the entity.
45
-     *
46
-     * @param string $key
47
-     * @param mixed  $value
48
-     *
49
-     * @return void
50
-     */
51
-    public function __set($key, $value)
52
-    {
53
-        if ($this->hasSetMutator($key)) {
54
-            $method = 'set'.$this->getMutatorMethod($key);
55
-
56
-            $this->$method($value);
57
-        } else {
58
-            $this->attributes[$key] = $value;
59
-        }
60
-    }
61
-
62
-    /**
63
-     * Is a getter method defined ?
64
-     *
65
-     * @param string $key
66
-     *
67
-     * @return bool
68
-     */
69
-    protected function hasGetMutator($key)
70
-    {
71
-        return method_exists($this, 'get'.$this->getMutatorMethod($key));
72
-    }
73
-
74
-    /**
75
-     * Is a setter method defined ?
76
-     *
77
-     * @param string $key
78
-     *
79
-     * @return bool
80
-     */
81
-    protected function hasSetMutator($key)
82
-    {
83
-        return method_exists($this, 'set'.$this->getMutatorMethod($key));
84
-    }
85
-
86
-    /**
87
-     * @param $key
88
-     *
89
-     * @return string
90
-     */
91
-    protected function getMutatorMethod($key)
92
-    {
93
-        $key = ucwords(str_replace(['-', '_'], ' ', $key));
94
-
95
-        return str_replace(' ', '', $key).'Attribute';
96
-    }
97
-
98
-    /**
99
-     * Convert every attributes to value / arrays.
100
-     *
101
-     * @return array
102
-     */
103
-    public function toArray()
104
-    {
105
-        // First, call the trait method before filtering
106
-        // with Entity specific methods
107
-        $attributes = $this->attributesToArray($this->attributes);
108
-
109
-        foreach ($this->attributes as $key => $attribute) {
110
-            if (in_array($key, $this->hidden)) {
111
-                unset($attributes[$key]);
112
-                continue;
113
-            }
114
-            if ($this->hasGetMutator($key)) {
115
-                $method = 'get'.$this->getMutatorMethod($key);
116
-                $attributes[$key] = $this->$method($attribute);
117
-            }
118
-        }
119
-
120
-        return $attributes;
121
-    }
122
-
123
-    /**
124
-     * Fill an entity with key-value pairs.
125
-     *
126
-     * @param array $attributes
127
-     *
128
-     * @return void
129
-     */
130
-    public function fill(array $attributes)
131
-    {
132
-        foreach ($attributes as $key => $attribute) {
133
-            if ($this->hasSetMutator($key)) {
134
-                $method = 'set'.$this->getMutatorMethod($key);
135
-                $this->attributes[$key] = $this->$method($attribute);
136
-            } else {
137
-                $this->attributes[$key] = $attribute;
138
-            }
139
-        }
140
-    }
7
+	/**
8
+	 * Entities Hidden Attributes, that will be discarded when converting
9
+	 * the entity to Array/Json
10
+	 * (can include any embedded object's attribute).
11
+	 *
12
+	 * @var array
13
+	 */
14
+	protected $hidden = [];
15
+
16
+	/**
17
+	 * Return the entity's attribute.
18
+	 *
19
+	 * @param string $key
20
+	 *
21
+	 * @return mixed
22
+	 */
23
+	public function __get($key)
24
+	{
25
+		if ($this->hasGetMutator($key)) {
26
+			$method = 'get'.$this->getMutatorMethod($key);
27
+
28
+			$attribute = null;
29
+
30
+			if (isset($this->attributes[$key])) {
31
+				$attribute = $this->attributes[$key];
32
+			}
33
+
34
+			return $this->$method($attribute);
35
+		}
36
+		if (!array_key_exists($key, $this->attributes)) {
37
+			return;
38
+		}
39
+
40
+		return $this->attributes[$key];
41
+	}
42
+
43
+	/**
44
+	 * Dynamically set attributes on the entity.
45
+	 *
46
+	 * @param string $key
47
+	 * @param mixed  $value
48
+	 *
49
+	 * @return void
50
+	 */
51
+	public function __set($key, $value)
52
+	{
53
+		if ($this->hasSetMutator($key)) {
54
+			$method = 'set'.$this->getMutatorMethod($key);
55
+
56
+			$this->$method($value);
57
+		} else {
58
+			$this->attributes[$key] = $value;
59
+		}
60
+	}
61
+
62
+	/**
63
+	 * Is a getter method defined ?
64
+	 *
65
+	 * @param string $key
66
+	 *
67
+	 * @return bool
68
+	 */
69
+	protected function hasGetMutator($key)
70
+	{
71
+		return method_exists($this, 'get'.$this->getMutatorMethod($key));
72
+	}
73
+
74
+	/**
75
+	 * Is a setter method defined ?
76
+	 *
77
+	 * @param string $key
78
+	 *
79
+	 * @return bool
80
+	 */
81
+	protected function hasSetMutator($key)
82
+	{
83
+		return method_exists($this, 'set'.$this->getMutatorMethod($key));
84
+	}
85
+
86
+	/**
87
+	 * @param $key
88
+	 *
89
+	 * @return string
90
+	 */
91
+	protected function getMutatorMethod($key)
92
+	{
93
+		$key = ucwords(str_replace(['-', '_'], ' ', $key));
94
+
95
+		return str_replace(' ', '', $key).'Attribute';
96
+	}
97
+
98
+	/**
99
+	 * Convert every attributes to value / arrays.
100
+	 *
101
+	 * @return array
102
+	 */
103
+	public function toArray()
104
+	{
105
+		// First, call the trait method before filtering
106
+		// with Entity specific methods
107
+		$attributes = $this->attributesToArray($this->attributes);
108
+
109
+		foreach ($this->attributes as $key => $attribute) {
110
+			if (in_array($key, $this->hidden)) {
111
+				unset($attributes[$key]);
112
+				continue;
113
+			}
114
+			if ($this->hasGetMutator($key)) {
115
+				$method = 'get'.$this->getMutatorMethod($key);
116
+				$attributes[$key] = $this->$method($attribute);
117
+			}
118
+		}
119
+
120
+		return $attributes;
121
+	}
122
+
123
+	/**
124
+	 * Fill an entity with key-value pairs.
125
+	 *
126
+	 * @param array $attributes
127
+	 *
128
+	 * @return void
129
+	 */
130
+	public function fill(array $attributes)
131
+	{
132
+		foreach ($attributes as $key => $attribute) {
133
+			if ($this->hasSetMutator($key)) {
134
+				$method = 'set'.$this->getMutatorMethod($key);
135
+				$this->attributes[$key] = $this->$method($attribute);
136
+			} else {
137
+				$this->attributes[$key] = $attribute;
138
+			}
139
+		}
140
+	}
141 141
 }
Please login to merge, or discard this patch.
src/helpers.php 1 patch
Indentation   +21 added lines, -21 removed lines patch added patch discarded remove patch
@@ -4,29 +4,29 @@
 block discarded – undo
4 4
 
5 5
 if (!function_exists('analogue')) {
6 6
 
7
-    /**
8
-     * Return analogue's manager instance.
9
-     *
10
-     * @return \Analogue\ORM\System\Manager
11
-     */
12
-    function analogue()
13
-    {
14
-        return Manager::getInstance();
15
-    }
7
+	/**
8
+	 * Return analogue's manager instance.
9
+	 *
10
+	 * @return \Analogue\ORM\System\Manager
11
+	 */
12
+	function analogue()
13
+	{
14
+		return Manager::getInstance();
15
+	}
16 16
 }
17 17
 
18 18
 if (!function_exists('mapper')) {
19 19
 
20
-    /**
21
-     * Create a mapper for a given entity (static alias).
22
-     *
23
-     * @param \Analogue\ORM\Mappable|string $entity
24
-     * @param mixed                         $entityMap
25
-     *
26
-     * @return \Analogue\ORM\System\Mapper
27
-     */
28
-    function mapper($entity, $entityMap = null)
29
-    {
30
-        return Manager::getMapper($entity, $entityMap);
31
-    }
20
+	/**
21
+	 * Create a mapper for a given entity (static alias).
22
+	 *
23
+	 * @param \Analogue\ORM\Mappable|string $entity
24
+	 * @param mixed                         $entityMap
25
+	 *
26
+	 * @return \Analogue\ORM\System\Mapper
27
+	 */
28
+	function mapper($entity, $entityMap = null)
29
+	{
30
+		return Manager::getMapper($entity, $entityMap);
31
+	}
32 32
 }
Please login to merge, or discard this patch.
src/Commands/Command.php 1 patch
Indentation   +24 added lines, -24 removed lines patch added patch discarded remove patch
@@ -7,32 +7,32 @@
 block discarded – undo
7 7
 
8 8
 abstract class Command
9 9
 {
10
-    /**
11
-     * The aggregated entity on which the command is executed.
12
-     *
13
-     * @var \Analogue\ORM\System\Aggregate
14
-     */
15
-    protected $aggregate;
10
+	/**
11
+	 * The aggregated entity on which the command is executed.
12
+	 *
13
+	 * @var \Analogue\ORM\System\Aggregate
14
+	 */
15
+	protected $aggregate;
16 16
 
17
-    /**
18
-     * Query Builder instance.
19
-     *
20
-     * @var \Illuminate\Database\Query\Builder
21
-     */
22
-    protected $query;
17
+	/**
18
+	 * Query Builder instance.
19
+	 *
20
+	 * @var \Illuminate\Database\Query\Builder
21
+	 */
22
+	protected $query;
23 23
 
24
-    /**
25
-     * Command constructor.
26
-     *
27
-     * @param Aggregate                          $aggregate
28
-     * @param \Illuminate\Database\Query\Builder $query
29
-     */
30
-    public function __construct(Aggregate $aggregate, Builder $query)
31
-    {
32
-        $this->aggregate = $aggregate;
24
+	/**
25
+	 * Command constructor.
26
+	 *
27
+	 * @param Aggregate                          $aggregate
28
+	 * @param \Illuminate\Database\Query\Builder $query
29
+	 */
30
+	public function __construct(Aggregate $aggregate, Builder $query)
31
+	{
32
+		$this->aggregate = $aggregate;
33 33
 
34
-        $this->query = $query->from($aggregate->getEntityMap()->getTable());
35
-    }
34
+		$this->query = $query->from($aggregate->getEntityMap()->getTable());
35
+	}
36 36
 
37
-    abstract public function execute();
37
+	abstract public function execute();
38 38
 }
Please login to merge, or discard this patch.
src/Commands/Delete.php 1 patch
Indentation   +32 added lines, -32 removed lines patch added patch discarded remove patch
@@ -6,36 +6,36 @@
 block discarded – undo
6 6
 
7 7
 class Delete extends Command
8 8
 {
9
-    /**
10
-     * Execute the Delete Statement.
11
-     *
12
-     * @throws MappingException
13
-     * @throws \InvalidArgumentException
14
-     *
15
-     * @return false|void
16
-     */
17
-    public function execute()
18
-    {
19
-        $aggregate = $this->aggregate;
20
-
21
-        $entity = $aggregate->getEntityObject();
22
-        $wrappedEntity = $aggregate->getWrappedEntity();
23
-        $mapper = $aggregate->getMapper();
24
-
25
-        if ($mapper->fireEvent('deleting', $wrappedEntity) === false) {
26
-            return false;
27
-        }
28
-
29
-        $keyName = $aggregate->getEntityMap()->getKeyName();
30
-
31
-        $id = $this->aggregate->getEntityKeyValue();
32
-
33
-        if (is_null($id)) {
34
-            throw new MappingException('Executed a delete command on an entity with "null" as primary key');
35
-        }
36
-
37
-        $this->query->where($keyName, '=', $id)->delete();
38
-
39
-        $mapper->fireEvent('deleted', $wrappedEntity, false);
40
-    }
9
+	/**
10
+	 * Execute the Delete Statement.
11
+	 *
12
+	 * @throws MappingException
13
+	 * @throws \InvalidArgumentException
14
+	 *
15
+	 * @return false|void
16
+	 */
17
+	public function execute()
18
+	{
19
+		$aggregate = $this->aggregate;
20
+
21
+		$entity = $aggregate->getEntityObject();
22
+		$wrappedEntity = $aggregate->getWrappedEntity();
23
+		$mapper = $aggregate->getMapper();
24
+
25
+		if ($mapper->fireEvent('deleting', $wrappedEntity) === false) {
26
+			return false;
27
+		}
28
+
29
+		$keyName = $aggregate->getEntityMap()->getKeyName();
30
+
31
+		$id = $this->aggregate->getEntityKeyValue();
32
+
33
+		if (is_null($id)) {
34
+			throw new MappingException('Executed a delete command on an entity with "null" as primary key');
35
+		}
36
+
37
+		$this->query->where($keyName, '=', $id)->delete();
38
+
39
+		$mapper->fireEvent('deleted', $wrappedEntity, false);
40
+	}
41 41
 }
Please login to merge, or discard this patch.