1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Analogue\ORM\System; |
4
|
|
|
|
5
|
|
|
use Analogue\ORM\Relationships\Pivot; |
6
|
|
|
use Illuminate\Support\Collection; |
7
|
|
|
use Analogue\ORM\System\Wrappers\Factory; |
8
|
|
|
use Analogue\ORM\System\Proxies\EntityProxy; |
9
|
|
|
use Analogue\ORM\System\Proxies\CollectionProxy; |
10
|
|
|
use Analogue\ORM\Exceptions\MappingException; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* This class is aimed to facilitate the handling of |
14
|
|
|
* complex root aggregate scenarios. |
15
|
|
|
*/ |
16
|
|
|
class Aggregate implements InternallyMappable |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* The Root Entity |
20
|
|
|
* |
21
|
|
|
* @var \Analogue\ORM\System\Wrappers\Wrapper |
22
|
|
|
*/ |
23
|
|
|
protected $wrappedEntity; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Parent Root Aggregate |
27
|
|
|
* |
28
|
|
|
* @var \Analogue\ORM\System\Aggregate |
29
|
|
|
*/ |
30
|
|
|
protected $parent; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Parent's relationship method |
34
|
|
|
* |
35
|
|
|
* @var string |
36
|
|
|
*/ |
37
|
|
|
protected $parentRelationship; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Root Entity |
41
|
|
|
* |
42
|
|
|
* @var \Analogue\ORM\System\Aggregate |
43
|
|
|
*/ |
44
|
|
|
protected $root; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* An associative array containing entity's |
48
|
|
|
* relationships converted to Aggregates |
49
|
|
|
* |
50
|
|
|
* @var array |
51
|
|
|
*/ |
52
|
|
|
protected $relationships = []; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Relationship that need post-command synchronization |
56
|
|
|
* |
57
|
|
|
* @var array |
58
|
|
|
*/ |
59
|
|
|
protected $needSync = []; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Mapper |
63
|
|
|
* |
64
|
|
|
* @var \Analogue\ORM\System\Mapper; |
65
|
|
|
*/ |
66
|
|
|
protected $mapper; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Entity Map |
70
|
|
|
* |
71
|
|
|
* @var \Analogue\ORM\EntityMap; |
72
|
|
|
*/ |
73
|
|
|
protected $entityMap; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Create a new Aggregated Entity instance |
77
|
|
|
* |
78
|
|
|
* @param mixed $entity |
79
|
|
|
* @param Aggregate|null $parent |
80
|
|
|
* @param string $parentRelationship |
81
|
|
|
* @param Aggregate|null $root |
82
|
|
|
* @throws MappingException |
83
|
|
|
*/ |
84
|
|
|
public function __construct($entity, Aggregate $parent = null, $parentRelationship = null, Aggregate $root = null) |
85
|
|
|
{ |
86
|
|
|
$factory = new Factory; |
87
|
|
|
|
88
|
|
|
$this->wrappedEntity = $factory->make($entity); |
89
|
|
|
|
90
|
|
|
$this->parent = $parent; |
91
|
|
|
|
92
|
|
|
$this->parentRelationship = $parentRelationship; |
93
|
|
|
|
94
|
|
|
$this->root = $root; |
95
|
|
|
|
96
|
|
|
$this->mapper = Manager::getMapper($entity); |
97
|
|
|
|
98
|
|
|
$this->entityMap = $this->mapper->getEntityMap(); |
99
|
|
|
|
100
|
|
|
$this->parseRelationships(); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Parse Every relationships defined on the entity |
105
|
|
|
* |
106
|
|
|
* @throws MappingException |
107
|
|
|
* @return void |
108
|
|
|
*/ |
109
|
|
|
protected function parseRelationships() |
110
|
|
|
{ |
111
|
|
|
foreach ($this->entityMap->getSingleRelationships() as $relation) { |
112
|
|
|
$this->parseSingleRelationship($relation); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
foreach ($this->entityMap->getManyRelationships() as $relation) { |
116
|
|
|
$this->parseManyRelationship($relation); |
117
|
|
|
} |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Parse for values common to single & many relations |
122
|
|
|
* |
123
|
|
|
* @param string $relation |
124
|
|
|
* @throws MappingException |
125
|
|
|
* @return mixed|boolean |
126
|
|
|
*/ |
127
|
|
|
protected function parseForCommonValues($relation) |
128
|
|
|
{ |
129
|
|
|
if (!$this->hasAttribute($relation)) { |
130
|
|
|
// If no attribute exists for this relationships |
131
|
|
|
// we'll make it a simple empty array. This will |
132
|
|
|
// save us from constantly checking for the attributes |
133
|
|
|
// actual existence. |
134
|
|
|
$this->relationships[$relation] = []; |
135
|
|
|
return false; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
$value = $this->getRelationshipValue($relation); |
139
|
|
|
|
140
|
|
|
if (is_null($value)) { |
141
|
|
|
$this->relationships[$relation] = []; |
142
|
|
|
|
143
|
|
|
// If the relationship's content is the null value |
144
|
|
|
// and the Entity's exist in DB, we'll interpret this |
145
|
|
|
// as the need to detach all related Entities, |
146
|
|
|
// therefore a sync operation is needed. |
147
|
|
|
$this->needSync[] = $relation; |
148
|
|
|
return false; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
return $value; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Parse a 'single' relationship |
156
|
|
|
* |
157
|
|
|
* @param string $relation |
158
|
|
|
* @throws MappingException |
159
|
|
|
* @return boolean |
160
|
|
|
*/ |
161
|
|
|
protected function parseSingleRelationship($relation) |
162
|
|
|
{ |
163
|
|
|
if (!$value = $this->parseForCommonValues($relation)) { |
164
|
|
|
return true; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
if ($value instanceof Collection || is_array($value) || $value instanceof CollectionProxy) { |
168
|
|
|
throw new MappingException("Entity's attribute $relation should not be array, or collection"); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
if ($value instanceof EntityProxy && !$value->isLoaded()) { |
172
|
|
|
$this->relationships[$relation] = []; |
173
|
|
|
return true; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
// If the attribute is a loaded proxy, swap it for its |
177
|
|
|
// loaded entity. |
178
|
|
|
if ($value instanceof EntityProxy && $value->isLoaded()) { |
179
|
|
|
$value = $value->getUnderlyingObject(); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
if ($this->isParentOrRoot($value)) { |
183
|
|
|
$this->relationships[$relation] = []; |
184
|
|
|
return true; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
// At this point, we can assume the attribute is an Entity instance |
188
|
|
|
// so we'll treat it as such. |
189
|
|
|
$subAggregate = $this->createSubAggregate($value, $relation); |
190
|
|
|
|
191
|
|
|
// Even if it's a single entity, we'll store it as an array |
192
|
|
|
// just for consistency with other relationships |
193
|
|
|
$this->relationships[$relation] = [$subAggregate]; |
194
|
|
|
|
195
|
|
|
// We always need to check a loaded relation is in sync |
196
|
|
|
// with its local key |
197
|
|
|
$this->needSync[] = $relation; |
198
|
|
|
|
199
|
|
|
return true; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Check if value isn't parent or root in the aggregate |
204
|
|
|
* |
205
|
|
|
* @param mixed |
206
|
|
|
* @return boolean|null |
207
|
|
|
*/ |
208
|
|
|
protected function isParentOrRoot($value) |
209
|
|
|
{ |
210
|
|
View Code Duplication |
if (!is_null($this->root)) { |
|
|
|
|
211
|
|
|
$rootClass = get_class($this->root->getEntityObject()); |
212
|
|
|
if ($rootClass == get_class($value)) { |
213
|
|
|
return true; |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
|
View Code Duplication |
if (!is_null($this->parent)) { |
|
|
|
|
218
|
|
|
$parentClass = get_class($this->parent->getEntityObject()); |
219
|
|
|
if ($parentClass == get_class($value)) { |
220
|
|
|
return true; |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Parse a 'many' relationship |
227
|
|
|
* |
228
|
|
|
* @param string $relation |
229
|
|
|
* @throws MappingException |
230
|
|
|
* @return boolean |
231
|
|
|
*/ |
232
|
|
|
protected function parseManyRelationship($relation) |
233
|
|
|
{ |
234
|
|
|
if (!$value = $this->parseForCommonValues($relation)) { |
235
|
|
|
return true; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
if (is_array($value) || $value instanceof Collection) { |
239
|
|
|
$this->needSync[] = $relation; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
// If the relation is a proxy, we test is the relation |
243
|
|
|
// has been lazy loaded, otherwise we'll just treat |
244
|
|
|
// the subset of newly added items. |
245
|
|
|
if ($value instanceof CollectionProxy && $value->isLoaded()) { |
246
|
|
|
$this->needSync[] = $relation; |
247
|
|
|
$value = $value->getUnderlyingCollection(); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
if ($value instanceof CollectionProxy && !$value->isLoaded()) { |
251
|
|
|
$value = $value->getAddedItems(); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
// At this point $value should be either an array or an instance |
255
|
|
|
// of a collection class. |
256
|
|
|
if (!is_array($value) && !$value instanceof Collection) { |
257
|
|
|
throw new MappingException("'$relation' attribute should be array() or Collection"); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
$this->relationships[$relation] = $this->createSubAggregates($value, $relation); |
261
|
|
|
|
262
|
|
|
return true; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Return Entity's relationship attribute |
267
|
|
|
* |
268
|
|
|
* @param string $relation |
269
|
|
|
* @throws MappingException |
270
|
|
|
* @return mixed |
271
|
|
|
*/ |
272
|
|
|
protected function getRelationshipValue($relation) |
273
|
|
|
{ |
274
|
|
|
$value = $this->getEntityAttribute($relation); |
275
|
|
|
//if($relation == "role") tdd($this->wrappedEntity->getEntityAttributes()); |
|
|
|
|
276
|
|
|
if (is_bool($value) || is_float($value) || is_int($value) || is_string($value)) { |
277
|
|
|
throw new MappingException("Entity's attribute $relation should be array, object, collection or null"); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
return $value; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Create a child, aggregated entity |
285
|
|
|
* |
286
|
|
|
* @param mixed $entities |
287
|
|
|
* @param string $relation |
288
|
|
|
* @return array |
289
|
|
|
*/ |
290
|
|
|
protected function createSubAggregates($entities, $relation) |
291
|
|
|
{ |
292
|
|
|
$aggregates = []; |
293
|
|
|
|
294
|
|
|
foreach ($entities as $entity) { |
295
|
|
|
$aggregates[] = $this->createSubAggregate($entity, $relation); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
return $aggregates; |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* Create a related subAggregate |
303
|
|
|
* |
304
|
|
|
* @param mixed $entity |
305
|
|
|
* @param string $relation |
306
|
|
|
* @throws MappingException |
307
|
|
|
* @return self |
308
|
|
|
*/ |
309
|
|
|
protected function createSubAggregate($entity, $relation) |
310
|
|
|
{ |
311
|
|
|
// If root isn't defined, then this is the Aggregate Root |
312
|
|
|
if (is_null($this->root)) { |
313
|
|
|
$root = $this; |
314
|
|
|
} else { |
315
|
|
|
$root = $this->root; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
return new self($entity, $this, $relation, $root); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* Get the Entity's primary key attribute |
323
|
|
|
* |
324
|
|
|
* @return string|integer |
325
|
|
|
*/ |
326
|
|
|
public function getEntityId() |
327
|
|
|
{ |
328
|
|
|
return $this->wrappedEntity->getEntityAttribute($this->entityMap->getKeyName()); |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
/** |
332
|
|
|
* Get the name of the primary key |
333
|
|
|
* |
334
|
|
|
* @return string |
335
|
|
|
*/ |
336
|
|
|
public function getEntityKey() |
337
|
|
|
{ |
338
|
|
|
return $this->entityMap->getKeyName(); |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* Return the entity map for the current entity |
343
|
|
|
* |
344
|
|
|
* @return \Analogue\ORM\EntityMap |
345
|
|
|
*/ |
346
|
|
|
public function getEntityMap() |
347
|
|
|
{ |
348
|
|
|
return $this->entityMap; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Return the Entity's hash $class.$id |
353
|
|
|
* |
354
|
|
|
* @return string |
355
|
|
|
*/ |
356
|
|
|
public function getEntityHash() |
357
|
|
|
{ |
358
|
|
|
return $this->getEntityClass() . '.' . $this->getEntityId(); |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Get wrapped entity class |
363
|
|
|
* |
364
|
|
|
* @return string |
365
|
|
|
*/ |
366
|
|
|
public function getEntityClass() |
367
|
|
|
{ |
368
|
|
|
return $this->entityMap->getClass(); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Return the Mapper's entity cache |
373
|
|
|
* |
374
|
|
|
* @return \Analogue\ORM\System\EntityCache |
375
|
|
|
*/ |
376
|
|
|
protected function getEntityCache() |
377
|
|
|
{ |
378
|
|
|
return $this->mapper->getEntityCache(); |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* Get a relationship as an aggregated entities' array |
383
|
|
|
* |
384
|
|
|
* @param string $name |
385
|
|
|
* @return array |
386
|
|
|
*/ |
387
|
|
|
public function getRelationship($name) |
388
|
|
|
{ |
389
|
|
|
if (array_key_exists($name, $this->relationships)) { |
390
|
|
|
return $this->relationships[$name]; |
391
|
|
|
} else { |
392
|
|
|
return []; |
393
|
|
|
} |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
/** |
397
|
|
|
* [TO IMPLEMENT] |
398
|
|
|
* |
399
|
|
|
* @return array |
400
|
|
|
*/ |
401
|
|
|
public function getPivotAttributes() |
402
|
|
|
{ |
403
|
|
|
return []; |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
/** |
407
|
|
|
* Get Non existing related entities from several relationships |
408
|
|
|
* |
409
|
|
|
* @param array $relationships |
410
|
|
|
* @return array |
411
|
|
|
*/ |
412
|
|
|
public function getNonExistingRelated(array $relationships) |
413
|
|
|
{ |
414
|
|
|
$nonExisting = []; |
415
|
|
|
|
416
|
|
|
foreach ($relationships as $relation) { |
417
|
|
|
if ($this->hasAttribute($relation) && array_key_exists($relation, $this->relationships)) { |
418
|
|
|
$nonExisting = array_merge($nonExisting, $this->getNonExistingFromRelation($relation)); |
419
|
|
|
} |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
return $nonExisting; |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* Get non-existing related entities from a single relation |
427
|
|
|
* |
428
|
|
|
* @param string $relation |
429
|
|
|
* @return array |
430
|
|
|
*/ |
431
|
|
|
protected function getNonExistingFromRelation($relation) |
432
|
|
|
{ |
433
|
|
|
$nonExisting = []; |
434
|
|
|
|
435
|
|
|
foreach ($this->relationships[$relation] as $aggregate) { |
436
|
|
|
if (!$aggregate->exists()) { |
437
|
|
|
$nonExisting[] = $aggregate; |
438
|
|
|
} |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
return $nonExisting; |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
/** |
445
|
|
|
* Synchronize relationships if needed |
446
|
|
|
*/ |
447
|
|
|
public function syncRelationships(array $relationships) |
448
|
|
|
{ |
449
|
|
|
if ($this->exists()) { |
450
|
|
|
foreach ($relationships as $relation) { |
451
|
|
|
if (in_array($relation, $this->needSync)) { |
452
|
|
|
$this->synchronize($relation); |
453
|
|
|
} |
454
|
|
|
} |
455
|
|
|
} |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
/** |
459
|
|
|
* Synchronize a relationship attribute |
460
|
|
|
* |
461
|
|
|
* @param $relation |
462
|
|
|
*/ |
463
|
|
|
protected function synchronize($relation) |
464
|
|
|
{ |
465
|
|
|
$actualContent = $this->relationships[$relation]; |
466
|
|
|
|
467
|
|
|
$this->entityMap->$relation($this->getEntityObject())->sync($actualContent); |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
/** |
471
|
|
|
* Returns an array of Missing related Entities for the |
472
|
|
|
* given $relation |
473
|
|
|
* |
474
|
|
|
* @param string $relation |
475
|
|
|
* @return array |
476
|
|
|
*/ |
477
|
|
|
public function getMissingEntities($relation) |
478
|
|
|
{ |
479
|
|
|
$cachedRelations = $this->getCachedAttribute($relation); |
480
|
|
|
|
481
|
|
|
if (!is_null($cachedRelations)) { |
482
|
|
|
$missing = []; |
483
|
|
|
|
484
|
|
|
foreach ($cachedRelations as $hash) { |
|
|
|
|
485
|
|
|
if (!$this->getRelatedAggregateFromHash($hash, $relation)) { |
486
|
|
|
$missing[] = $hash; |
487
|
|
|
} |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
return $missing; |
491
|
|
|
} else { |
492
|
|
|
return []; |
493
|
|
|
} |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* Get Relationships who have dirty attributes / dirty relationships |
498
|
|
|
* |
499
|
|
|
* @return array |
500
|
|
|
*/ |
501
|
|
|
public function getDirtyRelationships() |
502
|
|
|
{ |
503
|
|
|
$dirtyAggregates = []; |
504
|
|
|
|
505
|
|
|
foreach ($this->relationships as $relation) { |
506
|
|
|
foreach ($relation as $aggregate) { |
507
|
|
|
if (!$aggregate->exists() || $aggregate->isDirty() || count($aggregate->getDirtyRelationships() > 0)) { |
508
|
|
|
$dirtyAggregates[] = $aggregate; |
509
|
|
|
} |
510
|
|
|
} |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
return $dirtyAggregates; |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
/** |
517
|
|
|
* Compare the object's raw attributes with the record in cache |
518
|
|
|
* |
519
|
|
|
* @return boolean |
520
|
|
|
*/ |
521
|
|
|
public function isDirty() |
522
|
|
|
{ |
523
|
|
|
if (count($this->getDirtyRawAttributes()) > 0) { |
524
|
|
|
return true; |
525
|
|
|
} else { |
526
|
|
|
return false; |
527
|
|
|
} |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
/** |
531
|
|
|
* Get Raw Entity's attributes, as they are represented |
532
|
|
|
* in the database, including value objects & foreign keys |
533
|
|
|
* |
534
|
|
|
* @return array |
535
|
|
|
*/ |
536
|
|
|
public function getRawAttributes() |
537
|
|
|
{ |
538
|
|
|
$attributes = $this->wrappedEntity->getEntityAttributes(); |
539
|
|
|
|
540
|
|
|
foreach ($this->entityMap->getRelationships() as $relation) { |
541
|
|
|
unset($attributes[$relation]); |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
$attributes = $this->flattenEmbeddables($attributes); |
545
|
|
|
|
546
|
|
|
$foreignKeys = $this->getForeignKeyAttributes(); |
547
|
|
|
|
548
|
|
|
return $attributes + $foreignKeys; |
549
|
|
|
} |
550
|
|
|
|
551
|
|
|
/** |
552
|
|
|
* Convert Value Objects to raw db attributes |
553
|
|
|
* |
554
|
|
|
* @param array $attributes |
555
|
|
|
* @return array |
556
|
|
|
*/ |
557
|
|
|
protected function flattenEmbeddables($attributes) |
558
|
|
|
{ |
559
|
|
|
$embeddables = $this->entityMap->getEmbeddables(); |
560
|
|
|
|
561
|
|
|
foreach ($embeddables as $localKey => $embed) { |
562
|
|
|
// Retrieve the value object from the entity's attributes |
563
|
|
|
$valueObject = $attributes[$localKey]; |
564
|
|
|
|
565
|
|
|
// Unset the corresponding key |
566
|
|
|
unset($attributes[$localKey]); |
567
|
|
|
|
568
|
|
|
// TODO Make wrapper object compatible with value objects |
569
|
|
|
$valueObjectAttributes = $valueObject->getEntityAttributes(); |
570
|
|
|
|
571
|
|
|
// Now (if setup in the entity map) we prefix the value object's |
572
|
|
|
// attributes with the snake_case name of the embedded class. |
573
|
|
|
$prefix = snake_case(class_basename($embed)); |
574
|
|
|
|
575
|
|
|
foreach ($valueObjectAttributes as $key=>$value) { |
576
|
|
|
$valueObjectAttributes[$prefix . '_' . $key] = $value; |
577
|
|
|
unset($valueObjectAttributes[$key]); |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
$attributes = array_merge($attributes, $valueObjectAttributes); |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
return $attributes; |
584
|
|
|
} |
585
|
|
|
|
586
|
|
|
/** |
587
|
|
|
* Return's entity raw attributes in the state they were at last |
588
|
|
|
* query. |
589
|
|
|
* |
590
|
|
|
* @param array|null $columns |
591
|
|
|
* @return array |
592
|
|
|
*/ |
593
|
|
View Code Duplication |
protected function getCachedRawAttributes(array $columns = null) |
|
|
|
|
594
|
|
|
{ |
595
|
|
|
$cachedAttributes = $this->getCache()->get($this->getEntityId()); |
596
|
|
|
|
597
|
|
|
if (is_null($columns)) { |
598
|
|
|
return $cachedAttributes; |
599
|
|
|
} else { |
600
|
|
|
return array_only($cachedAttributes, $columns); |
601
|
|
|
} |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
/** |
605
|
|
|
* Return a single attribute from the cache |
606
|
|
|
* @param string $key |
607
|
|
|
* @return mixed |
608
|
|
|
*/ |
609
|
|
View Code Duplication |
protected function getCachedAttribute($key) |
|
|
|
|
610
|
|
|
{ |
611
|
|
|
$cachedAttributes = $this->getCache()->get($this->getEntityId()); |
612
|
|
|
|
613
|
|
|
if (!array_key_exists($key, $cachedAttributes)) { |
614
|
|
|
return null; |
615
|
|
|
} else { |
616
|
|
|
return $cachedAttributes[$key]; |
617
|
|
|
} |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
/** |
621
|
|
|
* Convert related Entity's attributes to foreign keys |
622
|
|
|
* |
623
|
|
|
* @return array |
624
|
|
|
*/ |
625
|
|
|
protected function getForeignKeyAttributes() |
626
|
|
|
{ |
627
|
|
|
$foreignKeys = []; |
628
|
|
|
|
629
|
|
|
foreach ($this->entityMap->getLocalRelationships() as $relation) { |
630
|
|
|
// check if relationship has been parsed, meaning it has an actual object |
631
|
|
|
// in the entity's attributes |
632
|
|
|
if ($this->isActualRelationships($relation)) { |
633
|
|
|
$foreignKeys = $foreignKeys + $this->getForeignKeyAttributesFromRelation($relation); |
634
|
|
|
} |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
if (!is_null($this->parent)) { |
638
|
|
|
$foreignKeys = $foreignKeys + $this->getForeignKeyAttributesFromParent(); |
639
|
|
|
} |
640
|
|
|
|
641
|
|
|
return $foreignKeys; |
642
|
|
|
} |
643
|
|
|
|
644
|
|
|
/** |
645
|
|
|
* Return an associative array containing the key-value pair(s) from |
646
|
|
|
* the related entity. |
647
|
|
|
* |
648
|
|
|
* @param string $relation |
649
|
|
|
* @return array |
650
|
|
|
*/ |
651
|
|
|
protected function getForeignKeyAttributesFromRelation($relation) |
652
|
|
|
{ |
653
|
|
|
$localRelations = $this->entityMap->getLocalRelationships(); |
654
|
|
|
|
655
|
|
|
if (in_array($relation, $localRelations)) { |
656
|
|
|
// Call Relationship's method |
657
|
|
|
$relationship = $this->entityMap->$relation($this->getEntityObject()); |
658
|
|
|
|
659
|
|
|
$relatedAggregate = $this->relationships[$relation][0]; |
660
|
|
|
|
661
|
|
|
return $relationship->getForeignKeyValuePair($relatedAggregate->getEntityObject()); |
662
|
|
|
} else { |
663
|
|
|
return []; |
664
|
|
|
} |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
/** |
668
|
|
|
* Get foreign key attribute(s) from a parent entity in this |
669
|
|
|
* aggregate context |
670
|
|
|
* |
671
|
|
|
* @return array |
672
|
|
|
*/ |
673
|
|
|
protected function getForeignKeyAttributesFromParent() |
674
|
|
|
{ |
675
|
|
|
$parentMap = $this->parent->getEntityMap(); |
676
|
|
|
|
677
|
|
|
$parentForeignRelations = $parentMap->getForeignRelationships(); |
678
|
|
|
$parentPivotRelations = $parentMap->getPivotRelationships(); |
679
|
|
|
|
680
|
|
|
$parentRelation = $this->parentRelationship; |
681
|
|
|
|
682
|
|
|
if (in_array($parentRelation, $parentForeignRelations) |
683
|
|
|
&& !in_array($parentRelation, $parentPivotRelations) |
684
|
|
|
) { |
685
|
|
|
$parentObject = $this->parent->getEntityObject(); |
686
|
|
|
|
687
|
|
|
// Call Relationship's method on parent map |
688
|
|
|
$relationship = $parentMap->$parentRelation($parentObject); |
689
|
|
|
|
690
|
|
|
return $relationship->getForeignKeyValuePair(); |
691
|
|
|
} else { |
692
|
|
|
return []; |
693
|
|
|
} |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
/** |
697
|
|
|
* Update Pivot records on loaded relationships, by comparing the |
698
|
|
|
* values from the Entity Cache to the actual relationship inside |
699
|
|
|
* the aggregated entity. |
700
|
|
|
* |
701
|
|
|
* @return void |
702
|
|
|
*/ |
703
|
|
|
public function updatePivotRecords() |
704
|
|
|
{ |
705
|
|
|
$pivots = $this->entityMap->getPivotRelationships(); |
706
|
|
|
|
707
|
|
|
foreach ($pivots as $pivot) { |
708
|
|
|
if (array_key_exists($pivot, $this->relationships)) { |
709
|
|
|
$this->updatePivotRelation($pivot); |
710
|
|
|
} |
711
|
|
|
} |
712
|
|
|
} |
713
|
|
|
|
714
|
|
|
/** |
715
|
|
|
* Update Single pivot relationship |
716
|
|
|
* |
717
|
|
|
* @param string $relation |
718
|
|
|
* @return void |
719
|
|
|
*/ |
720
|
|
|
protected function updatePivotRelation($relation) |
721
|
|
|
{ |
722
|
|
|
$hashes = $this->getEntityHashesFromRelation($relation); |
723
|
|
|
|
724
|
|
|
$cachedAttributes = $this->getCachedRawAttributes(); |
725
|
|
|
|
726
|
|
|
if (array_key_exists($relation, $cachedAttributes)) { |
727
|
|
|
// Compare the two array of hashes to find out existing |
728
|
|
|
// pivot records, and the ones to be created. |
729
|
|
|
$new = array_diff($hashes, array_keys($cachedAttributes[$relation])); |
730
|
|
|
$existing = array_intersect($hashes, array_keys($cachedAttributes[$relation])); |
731
|
|
|
} else { |
732
|
|
|
$existing = []; |
733
|
|
|
$new = $hashes; |
734
|
|
|
} |
735
|
|
|
|
736
|
|
|
if (count($new) > 0) { |
737
|
|
|
$pivots = $this->getRelatedAggregatesFromHashes($new, $relation); |
738
|
|
|
|
739
|
|
|
$this->entityMap->$relation($this->getEntityObject())->createPivots($pivots); |
740
|
|
|
} |
741
|
|
|
|
742
|
|
|
if (count($existing) > 0) { |
743
|
|
|
foreach ($existing as $pivotHash) { |
744
|
|
|
$this->updatePivotIfDirty($pivotHash, $relation); |
745
|
|
|
} |
746
|
|
|
} |
747
|
|
|
} |
748
|
|
|
|
749
|
|
|
/** |
750
|
|
|
* Compare existing pivot record in cache and update it |
751
|
|
|
* if the pivot attributes are dirty |
752
|
|
|
* |
753
|
|
|
* @param string $pivotHash |
754
|
|
|
* @param string $relation |
755
|
|
|
* @return void |
756
|
|
|
*/ |
757
|
|
|
protected function updatePivotIfDirty($pivotHash, $relation) |
758
|
|
|
{ |
759
|
|
|
$aggregate = $this->getRelatedAggregateFromHash($pivotHash, $relation); |
760
|
|
|
|
761
|
|
|
if ($aggregate->hasAttribute('pivot')) { |
762
|
|
|
$pivot = $aggregate->getEntityAttribute('pivot')->getEntityAttributes(); |
763
|
|
|
|
764
|
|
|
$cachedPivotAttributes = $this->getPivotAttributesFromCache($pivotHash, $relation); |
765
|
|
|
|
766
|
|
|
$actualPivotAttributes = array_only($pivot, array_keys($cachedPivotAttributes)); |
767
|
|
|
|
768
|
|
|
$dirty = $this->getDirtyAttributes($actualPivotAttributes, $cachedPivotAttributes); |
769
|
|
|
|
770
|
|
|
if (count($dirty) > 0) { |
771
|
|
|
$id = $aggregate->getEntityId(); |
772
|
|
|
|
773
|
|
|
$this->entityMap->$relation($this->getEntityObject())->updateExistingPivot($id, $dirty); |
774
|
|
|
} |
775
|
|
|
} |
776
|
|
|
} |
777
|
|
|
|
778
|
|
|
/** |
779
|
|
|
* Compare two attributes array and return dirty attributes |
780
|
|
|
* |
781
|
|
|
* @param array $actual |
782
|
|
|
* @param array $cached |
783
|
|
|
* @return array |
784
|
|
|
*/ |
785
|
|
|
protected function getDirtyAttributes(array $actual, array $cached) |
786
|
|
|
{ |
787
|
|
|
$dirty = []; |
788
|
|
|
|
789
|
|
|
foreach ($actual as $key => $value) { |
790
|
|
|
if (!$this->originalIsNumericallyEquivalent($value, $cached[$key])) { |
791
|
|
|
$dirty[$key] = $actual[$key]; |
792
|
|
|
} |
793
|
|
|
} |
794
|
|
|
|
795
|
|
|
return $dirty; |
796
|
|
|
} |
797
|
|
|
|
798
|
|
|
/** |
799
|
|
|
* |
800
|
|
|
* @param string $pivotHash |
801
|
|
|
* @param string $relation |
802
|
|
|
* @return array |
803
|
|
|
*/ |
804
|
|
|
protected function getPivotAttributesFromCache($pivotHash, $relation) |
805
|
|
|
{ |
806
|
|
|
$cachedAttributes = $this->getCachedRawAttributes(); |
807
|
|
|
|
808
|
|
|
$cachedRelations = $cachedAttributes[$relation]; |
809
|
|
|
|
810
|
|
|
foreach ($cachedRelations as $cachedRelation) { |
811
|
|
|
if ($cachedRelation == $pivotHash) { |
812
|
|
|
return $cachedRelation->getPivotAttributes(); |
813
|
|
|
} |
814
|
|
|
} |
815
|
|
|
} |
816
|
|
|
|
817
|
|
|
/** |
818
|
|
|
* Returns an array of related Aggregates from its entity hashes |
819
|
|
|
* |
820
|
|
|
* @param array $hashes |
821
|
|
|
* @param string $relation |
822
|
|
|
* @return array |
823
|
|
|
*/ |
824
|
|
|
protected function getRelatedAggregatesFromHashes(array $hashes, $relation) |
825
|
|
|
{ |
826
|
|
|
$related = []; |
827
|
|
|
|
828
|
|
|
foreach ($hashes as $hash) { |
829
|
|
|
$aggregate = $this->getRelatedAggregateFromHash($hash, $relation); |
830
|
|
|
|
831
|
|
|
if (!is_null($aggregate)) { |
832
|
|
|
$related[] = $aggregate; |
833
|
|
|
} |
834
|
|
|
} |
835
|
|
|
|
836
|
|
|
return $related; |
837
|
|
|
} |
838
|
|
|
|
839
|
|
|
/** |
840
|
|
|
* Get related aggregate from its hash |
841
|
|
|
* |
842
|
|
|
* @param string $hash |
843
|
|
|
* @param string $relation |
844
|
|
|
* @return \Analogue\ORM\System\Aggregate|null |
845
|
|
|
*/ |
846
|
|
|
protected function getRelatedAggregateFromHash($hash, $relation) |
847
|
|
|
{ |
848
|
|
|
foreach ($this->relationships[$relation] as $aggregate) { |
849
|
|
|
if ($aggregate->getEntityHash() == $hash) { |
850
|
|
|
return $aggregate; |
851
|
|
|
} |
852
|
|
|
} |
853
|
|
|
return null; |
854
|
|
|
} |
855
|
|
|
|
856
|
|
|
/** |
857
|
|
|
* Return an array of Entity Hashes from a specific relation |
858
|
|
|
* |
859
|
|
|
* @param string $relation |
860
|
|
|
* @return array |
861
|
|
|
*/ |
862
|
|
|
protected function getEntityHashesFromRelation($relation) |
863
|
|
|
{ |
864
|
|
|
return array_map(function ($aggregate) { |
865
|
|
|
return $aggregate->getEntityHash(); |
866
|
|
|
}, $this->relationships[$relation]); |
867
|
|
|
} |
868
|
|
|
|
869
|
|
|
/** |
870
|
|
|
* Check the existence of an actual relationship |
871
|
|
|
* |
872
|
|
|
* @param string $relation |
873
|
|
|
* @return boolean |
874
|
|
|
*/ |
875
|
|
|
protected function isActualRelationships($relation) |
876
|
|
|
{ |
877
|
|
|
return array_key_exists($relation, $this->relationships) |
878
|
|
|
&& count($this->relationships[$relation]) > 0; |
879
|
|
|
} |
880
|
|
|
|
881
|
|
|
/** |
882
|
|
|
* Return cache instance for the current entity type |
883
|
|
|
* |
884
|
|
|
* @return \Analogue\ORM\System\EntityCache |
885
|
|
|
*/ |
886
|
|
|
protected function getCache() |
887
|
|
|
{ |
888
|
|
|
return $this->mapper->getEntityCache(); |
889
|
|
|
} |
890
|
|
|
|
891
|
|
|
/** |
892
|
|
|
* Get Only Raw Entiy's attributes which have been modified |
893
|
|
|
* since last query |
894
|
|
|
* |
895
|
|
|
* @return array |
896
|
|
|
*/ |
897
|
|
|
public function getDirtyRawAttributes() |
898
|
|
|
{ |
899
|
|
|
$attributes = $this->getRawAttributes(); |
900
|
|
|
$cachedAttributes = $this->getCachedRawAttributes(array_keys($attributes)); |
901
|
|
|
|
902
|
|
|
$dirty = []; |
903
|
|
|
|
904
|
|
|
foreach ($attributes as $key => $value) { |
905
|
|
|
if ($this->isRelation($key) || $key == 'pivot') { |
906
|
|
|
continue; |
907
|
|
|
} |
908
|
|
|
|
909
|
|
|
if (!array_key_exists($key, $cachedAttributes) && !$value instanceof Pivot) { |
910
|
|
|
$dirty[$key] = $value; |
911
|
|
|
} elseif ($value !== $cachedAttributes[$key] && |
912
|
|
|
!$this->originalIsNumericallyEquivalent($value, $cachedAttributes[$key])) { |
913
|
|
|
$dirty[$key] = $value; |
914
|
|
|
} |
915
|
|
|
} |
916
|
|
|
|
917
|
|
|
return $dirty; |
918
|
|
|
} |
919
|
|
|
|
920
|
|
|
/** |
921
|
|
|
* @param $key |
922
|
|
|
* @return bool |
923
|
|
|
*/ |
924
|
|
|
protected function isRelation($key) |
925
|
|
|
{ |
926
|
|
|
return in_array($key, $this->entityMap->getRelationships()); |
927
|
|
|
} |
928
|
|
|
|
929
|
|
|
/** |
930
|
|
|
* Determine if the new and old values for a given key are numerically equivalent. |
931
|
|
|
* |
932
|
|
|
* @param $current |
933
|
|
|
* @param $original |
934
|
|
|
* @return boolean |
935
|
|
|
*/ |
936
|
|
|
protected function originalIsNumericallyEquivalent($current, $original) |
937
|
|
|
{ |
938
|
|
|
return is_numeric($current) && is_numeric($original) && strcmp((string) $current, (string) $original) === 0; |
939
|
|
|
} |
940
|
|
|
|
941
|
|
|
/** |
942
|
|
|
* Get the underlying entity object |
943
|
|
|
* |
944
|
|
|
* @return mixed |
945
|
|
|
*/ |
946
|
|
|
public function getEntityObject() |
947
|
|
|
{ |
948
|
|
|
return $this->wrappedEntity->getObject(); |
949
|
|
|
} |
950
|
|
|
|
951
|
|
|
/** |
952
|
|
|
* Return the Mapper instance for the current Entity Type |
953
|
|
|
* |
954
|
|
|
* @return \Analogue\ORM\System\Mapper |
955
|
|
|
*/ |
956
|
|
|
public function getMapper() |
957
|
|
|
{ |
958
|
|
|
return $this->mapper; |
959
|
|
|
} |
960
|
|
|
|
961
|
|
|
/** |
962
|
|
|
* Check that the entity already exists in the database, by checking |
963
|
|
|
* if it has an EntityCache record |
964
|
|
|
* |
965
|
|
|
* @return boolean |
966
|
|
|
*/ |
967
|
|
|
public function exists() |
968
|
|
|
{ |
969
|
|
|
return $this->getCache()->has($this->getEntityId()); |
970
|
|
|
} |
971
|
|
|
|
972
|
|
|
/** |
973
|
|
|
* Set the object attribute raw values (hydration) |
974
|
|
|
* |
975
|
|
|
* @param array $attributes |
976
|
|
|
*/ |
977
|
|
|
public function setEntityAttributes(array $attributes) |
978
|
|
|
{ |
979
|
|
|
$this->wrappedEntity->setEntityAttributes($attributes); |
980
|
|
|
} |
981
|
|
|
|
982
|
|
|
/** |
983
|
|
|
* Get the raw object's values. |
984
|
|
|
* |
985
|
|
|
* @return array |
986
|
|
|
*/ |
987
|
|
|
public function getEntityAttributes() |
988
|
|
|
{ |
989
|
|
|
return $this->wrappedEntity->getEntityAttributes(); |
990
|
|
|
} |
991
|
|
|
|
992
|
|
|
/** |
993
|
|
|
* Set the raw entity attributes |
994
|
|
|
* @param string $key |
995
|
|
|
* @param string $value |
996
|
|
|
*/ |
997
|
|
|
public function setEntityAttribute($key, $value) |
998
|
|
|
{ |
999
|
|
|
$this->wrappedEntity->setEntityAttribute($key, $value); |
1000
|
|
|
} |
1001
|
|
|
|
1002
|
|
|
/** |
1003
|
|
|
* Return the entity's attribute |
1004
|
|
|
* @param string $key |
1005
|
|
|
* @return mixed |
1006
|
|
|
*/ |
1007
|
|
|
public function getEntityAttribute($key) |
1008
|
|
|
{ |
1009
|
|
|
return $this->wrappedEntity->getEntityAttribute($key); |
1010
|
|
|
} |
1011
|
|
|
|
1012
|
|
|
/** |
1013
|
|
|
* Does the attribute exists on the entity |
1014
|
|
|
* |
1015
|
|
|
* @param string $key |
1016
|
|
|
* @return boolean |
1017
|
|
|
*/ |
1018
|
|
|
public function hasAttribute($key) |
1019
|
|
|
{ |
1020
|
|
|
return $this->wrappedEntity->hasAttribute($key); |
1021
|
|
|
} |
1022
|
|
|
|
1023
|
|
|
/** |
1024
|
|
|
* Set the lazyloading proxies on the wrapped entity |
1025
|
|
|
*/ |
1026
|
|
|
public function setProxies() |
1027
|
|
|
{ |
1028
|
|
|
$this->wrappedEntity->setProxies(); |
1029
|
|
|
} |
1030
|
|
|
} |
1031
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.