Completed
Branch master (cbd196)
by Rémi
08:54
created
src/System/Query.php 1 patch
Indentation   +673 added lines, -673 removed lines patch added patch discarded remove patch
@@ -20,677 +20,677 @@
 block discarded – undo
20 20
  */
21 21
 class Query
22 22
 {
23
-    /**
24
-     * Mapper Instance.
25
-     *
26
-     * @var \Analogue\ORM\System\Mapper
27
-     */
28
-    protected $mapper;
29
-
30
-    /**
31
-     * DB Adatper.
32
-     *
33
-     * @var \Analogue\ORM\Drivers\DBAdapter
34
-     */
35
-    protected $adapter;
36
-
37
-    /**
38
-     * Query Builder Instance.
39
-     *
40
-     * @var \Analogue\ORM\Drivers\QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter
41
-     */
42
-    protected $query;
43
-
44
-    /**
45
-     * Entity Map Instance.
46
-     *
47
-     * @var \Analogue\ORM\EntityMap
48
-     */
49
-    protected $entityMap;
50
-
51
-    /**
52
-     * The relationships that should be eager loaded.
53
-     *
54
-     * @var array
55
-     */
56
-    protected $eagerLoad = [];
57
-
58
-    /**
59
-     * All of the registered builder macros.
60
-     *
61
-     * @var array
62
-     */
63
-    protected $macros = [];
64
-
65
-    /**
66
-     * The methods that should be returned from query builder.
67
-     *
68
-     * @var array
69
-     */
70
-    protected $passthru = [
71
-        'toSql',
72
-        'lists',
73
-        'pluck',
74
-        'count',
75
-        'min',
76
-        'max',
77
-        'avg',
78
-        'sum',
79
-        'exists',
80
-        'getBindings',
81
-    ];
82
-
83
-    /**
84
-     * Query Builder Blacklist.
85
-     */
86
-    protected $blacklist = [
87
-        'insert',
88
-        'insertGetId',
89
-        'lock',
90
-        'lockForUpdate',
91
-        'sharedLock',
92
-        'update',
93
-        'increment',
94
-        'decrement',
95
-        'delete',
96
-        'truncate',
97
-        'raw',
98
-    ];
99
-
100
-    /**
101
-     * Create a new Analogue Query Builder instance.
102
-     *
103
-     * @param Mapper    $mapper
104
-     * @param DBAdapter $adapter
105
-     */
106
-    public function __construct(Mapper $mapper, DBAdapter $adapter)
107
-    {
108
-        $this->mapper = $mapper;
109
-
110
-        $this->adapter = $adapter;
111
-
112
-        $this->entityMap = $mapper->getEntityMap();
113
-
114
-        // Specify the table to work on
115
-        $this->query = $adapter->getQuery()->from($this->entityMap->getTable());
116
-
117
-        $this->with($this->entityMap->getEagerloadedRelationships());
118
-    }
119
-
120
-    /**
121
-     * Run the query and return the result.
122
-     *
123
-     * @param array $columns
124
-     *
125
-     * @return \Analogue\ORM\EntityCollection
126
-     */
127
-    public function get($columns = ['*']) : Collection
128
-    {
129
-        return $this->getEntities($columns);
130
-    }
131
-
132
-    /**
133
-     * Find an entity by its primary key.
134
-     *
135
-     * @param string|int $id
136
-     * @param array      $columns
137
-     *
138
-     * @return \Analogue\ORM\Mappable
139
-     */
140
-    public function find($id, $columns = ['*'])
141
-    {
142
-        if (is_array($id)) {
143
-            return $this->findMany($id, $columns);
144
-        }
145
-
146
-        $this->query->where($this->entityMap->getKeyName(), '=', $id);
147
-
148
-        return $this->first($columns);
149
-    }
150
-
151
-    /**
152
-     * Find many entities by their primary keys.
153
-     *
154
-     * @param array $id
155
-     * @param array $columns
156
-     *
157
-     * @return EntityCollection
158
-     */
159
-    public function findMany($id, $columns = ['*'])
160
-    {
161
-        if (empty($id)) {
162
-            return new EntityCollection();
163
-        }
164
-
165
-        $this->query->whereIn($this->entityMap->getKeyName(), $id);
166
-
167
-        return $this->get($columns);
168
-    }
169
-
170
-    /**
171
-     * Find a model by its primary key or throw an exception.
172
-     *
173
-     * @param mixed $id
174
-     * @param array $columns
175
-     *
176
-     * @throws \Analogue\ORM\Exceptions\EntityNotFoundException
177
-     *
178
-     * @return mixed|self
179
-     */
180
-    public function findOrFail($id, $columns = ['*'])
181
-    {
182
-        if (!is_null($entity = $this->find($id, $columns))) {
183
-            return $entity;
184
-        }
185
-
186
-        throw (new EntityNotFoundException())->setEntity(get_class($this->entityMap));
187
-    }
188
-
189
-    /**
190
-     * Execute the query and get the first result.
191
-     *
192
-     * @param array $columns
193
-     *
194
-     * @return \Analogue\ORM\Entity
195
-     */
196
-    public function first($columns = ['*'])
197
-    {
198
-        return $this->take(1)->get($columns)->first();
199
-    }
200
-
201
-    /**
202
-     * Execute the query and get the first result or throw an exception.
203
-     *
204
-     * @param array $columns
205
-     *
206
-     * @throws EntityNotFoundException
207
-     *
208
-     * @return \Analogue\ORM\Entity
209
-     */
210
-    public function firstOrFail($columns = ['*'])
211
-    {
212
-        if (!is_null($entity = $this->first($columns))) {
213
-            return $entity;
214
-        }
215
-
216
-        throw (new EntityNotFoundException())->setEntity(get_class($this->entityMap));
217
-    }
218
-
219
-    /**
220
-     * Pluck a single column from the database.
221
-     *
222
-     * @param string $column
223
-     *
224
-     * @return mixed
225
-     */
226
-    public function pluck($column)
227
-    {
228
-        $result = $this->first([$column]);
229
-
230
-        if ($result) {
231
-            return $result->{$column};
232
-        }
233
-    }
234
-
235
-    /**
236
-     * Chunk the results of the query.
237
-     *
238
-     * @param int      $count
239
-     * @param callable $callback
240
-     *
241
-     * @return void
242
-     */
243
-    public function chunk($count, callable $callback)
244
-    {
245
-        $results = $this->forPage($page = 1, $count)->get();
246
-
247
-        while (count($results) > 0) {
248
-            // On each chunk result set, we will pass them to the callback and then let the
249
-            // developer take care of everything within the callback, which allows us to
250
-            // keep the memory low for spinning through large result sets for working.
251
-            call_user_func($callback, $results);
252
-
253
-            $page++;
254
-
255
-            $results = $this->forPage($page, $count)->get();
256
-        }
257
-    }
258
-
259
-    /**
260
-     * Get an array with the values of a given column.
261
-     *
262
-     * @param string $column
263
-     * @param string $key
264
-     *
265
-     * @return array
266
-     */
267
-    public function lists($column, $key = null)
268
-    {
269
-        return $this->query->pluck($column, $key);
270
-    }
271
-
272
-    /**
273
-     * Get a paginator for the "select" statement.
274
-     *
275
-     * @param int   $perPage
276
-     * @param array $columns
277
-     *
278
-     * @return LengthAwarePaginator
279
-     */
280
-    public function paginate($perPage = null, $columns = ['*'])
281
-    {
282
-        $total = $this->query->getCountForPagination();
283
-
284
-        $this->query->forPage(
285
-            $page = Paginator::resolveCurrentPage(),
286
-            $perPage = $perPage ?: $this->entityMap->getPerPage()
287
-        );
288
-
289
-        return new LengthAwarePaginator($this->get($columns)->all(), $total, $perPage, $page, [
290
-            'path' => Paginator::resolveCurrentPath(),
291
-        ]);
292
-    }
293
-
294
-    /**
295
-     * Get a paginator for a grouped statement.
296
-     *
297
-     * @param \Illuminate\Pagination\Factory $paginator
298
-     * @param int                            $perPage
299
-     * @param array                          $columns
300
-     *
301
-     * @return \Illuminate\Pagination\Paginator
302
-     */
303
-    protected function groupedPaginate($paginator, $perPage, $columns)
304
-    {
305
-        $results = $this->get($columns)->all();
306
-
307
-        return $this->query->buildRawPaginator($paginator, $results, $perPage);
308
-    }
309
-
310
-    /**
311
-     * Get a paginator for an ungrouped statement.
312
-     *
313
-     * @param \Illuminate\Pagination\Factory $paginator
314
-     * @param int                            $perPage
315
-     * @param array                          $columns
316
-     *
317
-     * @return \Illuminate\Pagination\Paginator
318
-     */
319
-    protected function ungroupedPaginate($paginator, $perPage, $columns)
320
-    {
321
-        $total = $this->query->getPaginationCount();
322
-
323
-        // Once we have the paginator we need to set the limit and offset values for
324
-        // the query so we can get the properly paginated items. Once we have an
325
-        // array of items we can create the paginator instances for the items.
326
-        $page = $paginator->getCurrentPage($total);
327
-
328
-        $this->query->forPage($page, $perPage);
329
-
330
-        return $paginator->make($this->get($columns)->all(), $total, $perPage);
331
-    }
332
-
333
-    /**
334
-     * Paginate the given query into a simple paginator.
335
-     *
336
-     * @param int   $perPage
337
-     * @param array $columns
338
-     *
339
-     * @return \Illuminate\Contracts\Pagination\Paginator
340
-     */
341
-    public function simplePaginate($perPage = null, $columns = ['*'])
342
-    {
343
-        $page = Paginator::resolveCurrentPage();
344
-
345
-        $perPage = $perPage ?: $this->entityMap->getPerPage();
346
-
347
-        $this->skip(($page - 1) * $perPage)->take($perPage + 1);
348
-
349
-        return new Paginator($this->get($columns)->all(), $perPage, $page, ['path' => Paginator::resolveCurrentPath()]);
350
-    }
351
-
352
-    /**
353
-     * Add a basic where clause to the query.
354
-     *
355
-     * @param string $column
356
-     * @param string $operator
357
-     * @param mixed  $value
358
-     * @param string $boolean
359
-     *
360
-     * @return $this
361
-     */
362
-    public function where($column, $operator = null, $value = null, $boolean = 'and')
363
-    {
364
-        if ($column instanceof Closure) {
365
-            $query = $this->newQueryWithoutScopes();
366
-
367
-            call_user_func($column, $query);
368
-
369
-            $this->query->addNestedWhereQuery($query->getQuery(), $boolean);
370
-        } else {
371
-            call_user_func_array([$this->query, 'where'], func_get_args());
372
-        }
373
-
374
-        return $this;
375
-    }
376
-
377
-    /**
378
-     * Add an "or where" clause to the query.
379
-     *
380
-     * @param string $column
381
-     * @param string $operator
382
-     * @param mixed  $value
383
-     *
384
-     * @return \Analogue\ORM\System\Query
385
-     */
386
-    public function orWhere($column, $operator = null, $value = null)
387
-    {
388
-        return $this->where($column, $operator, $value, 'or');
389
-    }
390
-
391
-    /**
392
-     * Add a relationship count condition to the query.
393
-     *
394
-     * @param string   $relation
395
-     * @param string   $operator
396
-     * @param int      $count
397
-     * @param string   $boolean
398
-     * @param \Closure $callback
399
-     *
400
-     * @return \Analogue\ORM\System\Query
401
-     */
402
-    public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
403
-    {
404
-        $entity = $this->mapper->newInstance();
405
-
406
-        $relation = $this->getHasRelationQuery($relation, $entity);
407
-
408
-        $query = $relation->getRelationCountQuery($relation->getRelatedMapper()->getQuery(), $this);
409
-
410
-        if ($callback) {
411
-            call_user_func($callback, $query);
412
-        }
413
-
414
-        return $this->addHasWhere($query, $relation, $operator, $count, $boolean);
415
-    }
416
-
417
-    /**
418
-     * Add a relationship count condition to the query with where clauses.
419
-     *
420
-     * @param string   $relation
421
-     * @param \Closure $callback
422
-     * @param string   $operator
423
-     * @param int      $count
424
-     *
425
-     * @return \Analogue\ORM\System\Query
426
-     */
427
-    public function whereHas($relation, Closure $callback, $operator = '>=', $count = 1)
428
-    {
429
-        return $this->has($relation, $operator, $count, 'and', $callback);
430
-    }
431
-
432
-    /**
433
-     * Add a relationship count condition to the query with an "or".
434
-     *
435
-     * @param string $relation
436
-     * @param string $operator
437
-     * @param int    $count
438
-     *
439
-     * @return \Analogue\ORM\System\Query
440
-     */
441
-    public function orHas($relation, $operator = '>=', $count = 1)
442
-    {
443
-        return $this->has($relation, $operator, $count, 'or');
444
-    }
445
-
446
-    /**
447
-     * Add a relationship count condition to the query with where clauses and an "or".
448
-     *
449
-     * @param string   $relation
450
-     * @param \Closure $callback
451
-     * @param string   $operator
452
-     * @param int      $count
453
-     *
454
-     * @return \Analogue\ORM\System\Query
455
-     */
456
-    public function orWhereHas($relation, Closure $callback, $operator = '>=', $count = 1)
457
-    {
458
-        return $this->has($relation, $operator, $count, 'or', $callback);
459
-    }
460
-
461
-    /**
462
-     * Add the "has" condition where clause to the query.
463
-     *
464
-     * @param \Analogue\ORM\System\Query               $hasQuery
465
-     * @param \Analogue\ORM\Relationships\Relationship $relation
466
-     * @param string                                   $operator
467
-     * @param int                                      $count
468
-     * @param string                                   $boolean
469
-     *
470
-     * @return \Analogue\ORM\System\Query
471
-     */
472
-    protected function addHasWhere(Query $hasQuery, Relationship $relation, $operator, $count, $boolean)
473
-    {
474
-        $this->mergeWheresToHas($hasQuery, $relation);
475
-
476
-        if (is_numeric($count)) {
477
-            $count = new Expression($count);
478
-        }
479
-
480
-        return $this->where(new Expression('('.$hasQuery->toSql().')'), $operator, $count, $boolean);
481
-    }
482
-
483
-    /**
484
-     * Merge the "wheres" from a relation query to a has query.
485
-     *
486
-     * @param \Analogue\ORM\System\Query               $hasQuery
487
-     * @param \Analogue\ORM\Relationships\Relationship $relation
488
-     *
489
-     * @return void
490
-     */
491
-    protected function mergeWheresToHas(Query $hasQuery, Relationship $relation)
492
-    {
493
-        // Here we have the "has" query and the original relation. We need to copy over any
494
-        // where clauses the developer may have put in the relationship function over to
495
-        // the has query, and then copy the bindings from the "has" query to the main.
496
-        $relationQuery = $relation->getBaseQuery();
497
-
498
-        $hasQuery->mergeWheres(
499
-            $relationQuery->wheres, $relationQuery->getBindings()
500
-        );
501
-
502
-        $this->query->mergeBindings($hasQuery->getQuery());
503
-    }
504
-
505
-    /**
506
-     * Get the "has relation" base query instance.
507
-     *
508
-     * @param string $relation
509
-     * @param        $entity
510
-     *
511
-     * @return \Analogue\ORM\System\Query
512
-     */
513
-    protected function getHasRelationQuery($relation, $entity)
514
-    {
515
-        return Relationship::noConstraints(function () use ($relation, $entity) {
516
-            return $this->entityMap->$relation($entity);
517
-        });
518
-    }
519
-
520
-    /**
521
-     * Get the table for the current query object.
522
-     *
523
-     * @return string
524
-     */
525
-    public function getTable()
526
-    {
527
-        return $this->entityMap->getTable();
528
-    }
529
-
530
-    /**
531
-     * Set the relationships that should be eager loaded.
532
-     *
533
-     * @param mixed $relations
534
-     *
535
-     * @return $this
536
-     */
537
-    public function with($relations)
538
-    {
539
-        if (is_string($relations)) {
540
-            $relations = func_get_args();
541
-        }
542
-
543
-        $this->eagerLoad = array_merge($this->eagerLoad, $relations);
544
-
545
-        return $this;
546
-    }
547
-
548
-    /**
549
-     * Get the relationships being eagerly loaded.
550
-     *
551
-     * @return array
552
-     */
553
-    public function getEagerLoads()
554
-    {
555
-        return $this->eagerLoad;
556
-    }
557
-
558
-    /**
559
-     * Add the Entity primary key if not in requested columns.
560
-     *
561
-     * @param array $columns
562
-     *
563
-     * @return array
564
-     */
565
-    protected function enforceIdColumn($columns)
566
-    {
567
-        if (!in_array($this->entityMap->getKeyName(), $columns)) {
568
-            $columns[] = $this->entityMap->getKeyName();
569
-        }
570
-
571
-        return $columns;
572
-    }
573
-
574
-    /**
575
-     * Get the hydrated models without eager loading.
576
-     *
577
-     * @param array $columns
578
-     *
579
-     * @return \Analogue\ORM\EntityCollection
580
-     */
581
-    public function getEntities($columns = ['*'])
582
-    {
583
-        // As we need the primary key to feed the
584
-        // entity cache, we need it loaded on each
585
-        // request
586
-        $columns = $this->enforceIdColumn($columns);
587
-
588
-        // Run the query
589
-        $results = $this->query->get($columns);
590
-
591
-        // Pass result set to the mapper and return the EntityCollection
592
-        return $this->mapper->map($results, $this->getEagerLoads());
593
-
594
-        // Create a result builder.
595
-        //$builder = new ResultBuilder($this->mapper);
596
-
597
-        //return $builder->build($results, $this->getEagerLoads());
598
-    }
599
-
600
-    /**
601
-     * Extend the builder with a given callback.
602
-     *
603
-     * @param string   $name
604
-     * @param \Closure $callback
605
-     *
606
-     * @return void
607
-     */
608
-    public function macro($name, Closure $callback)
609
-    {
610
-        $this->macros[$name] = $callback;
611
-    }
612
-
613
-    /**
614
-     * Get the given macro by name.
615
-     *
616
-     * @param string $name
617
-     *
618
-     * @return \Closure
619
-     */
620
-    public function getMacro($name)
621
-    {
622
-        return array_get($this->macros, $name);
623
-    }
624
-
625
-    /**
626
-     * Get a new query builder for the model's table.
627
-     *
628
-     * @return \Analogue\ORM\System\Query
629
-     */
630
-    public function newQuery()
631
-    {
632
-        $builder = new self($this->mapper, $this->adapter);
633
-
634
-        return $this->applyGlobalScopes($builder);
635
-    }
636
-
637
-    /**
638
-     * Get a new query builder without any scope applied.
639
-     *
640
-     * @return \Analogue\ORM\System\Query
641
-     */
642
-    public function newQueryWithoutScopes()
643
-    {
644
-        return new self($this->mapper, $this->adapter);
645
-    }
646
-
647
-    /**
648
-     * Get the Mapper instance for this Query Builder.
649
-     *
650
-     * @return \Analogue\ORM\System\Mapper
651
-     */
652
-    public function getMapper()
653
-    {
654
-        return $this->mapper;
655
-    }
656
-
657
-    /**
658
-     * Get the underlying query adapter.
659
-     *
660
-     * (REFACTOR: this method should move out, we need to provide the client classes
661
-     * with the adapter instead.)
662
-     *
663
-     * @return \Analogue\ORM\Drivers\QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter
664
-     */
665
-    public function getQuery()
666
-    {
667
-        return $this->query;
668
-    }
669
-
670
-    /**
671
-     * Dynamically handle calls into the query instance.
672
-     *
673
-     * @param string $method
674
-     * @param array  $parameters
675
-     *
676
-     * @throws Exception
677
-     *
678
-     * @return mixed
679
-     */
680
-    public function __call($method, $parameters)
681
-    {
682
-        if (isset($this->macros[$method])) {
683
-            array_unshift($parameters, $this);
684
-
685
-            return call_user_func_array($this->macros[$method], $parameters);
686
-        }
687
-
688
-        if (in_array($method, $this->blacklist)) {
689
-            throw new Exception("Method $method doesn't exist");
690
-        }
691
-
692
-        $result = call_user_func_array([$this->query, $method], $parameters);
693
-
694
-        return in_array($method, $this->passthru) ? $result : $this;
695
-    }
23
+	/**
24
+	 * Mapper Instance.
25
+	 *
26
+	 * @var \Analogue\ORM\System\Mapper
27
+	 */
28
+	protected $mapper;
29
+
30
+	/**
31
+	 * DB Adatper.
32
+	 *
33
+	 * @var \Analogue\ORM\Drivers\DBAdapter
34
+	 */
35
+	protected $adapter;
36
+
37
+	/**
38
+	 * Query Builder Instance.
39
+	 *
40
+	 * @var \Analogue\ORM\Drivers\QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter
41
+	 */
42
+	protected $query;
43
+
44
+	/**
45
+	 * Entity Map Instance.
46
+	 *
47
+	 * @var \Analogue\ORM\EntityMap
48
+	 */
49
+	protected $entityMap;
50
+
51
+	/**
52
+	 * The relationships that should be eager loaded.
53
+	 *
54
+	 * @var array
55
+	 */
56
+	protected $eagerLoad = [];
57
+
58
+	/**
59
+	 * All of the registered builder macros.
60
+	 *
61
+	 * @var array
62
+	 */
63
+	protected $macros = [];
64
+
65
+	/**
66
+	 * The methods that should be returned from query builder.
67
+	 *
68
+	 * @var array
69
+	 */
70
+	protected $passthru = [
71
+		'toSql',
72
+		'lists',
73
+		'pluck',
74
+		'count',
75
+		'min',
76
+		'max',
77
+		'avg',
78
+		'sum',
79
+		'exists',
80
+		'getBindings',
81
+	];
82
+
83
+	/**
84
+	 * Query Builder Blacklist.
85
+	 */
86
+	protected $blacklist = [
87
+		'insert',
88
+		'insertGetId',
89
+		'lock',
90
+		'lockForUpdate',
91
+		'sharedLock',
92
+		'update',
93
+		'increment',
94
+		'decrement',
95
+		'delete',
96
+		'truncate',
97
+		'raw',
98
+	];
99
+
100
+	/**
101
+	 * Create a new Analogue Query Builder instance.
102
+	 *
103
+	 * @param Mapper    $mapper
104
+	 * @param DBAdapter $adapter
105
+	 */
106
+	public function __construct(Mapper $mapper, DBAdapter $adapter)
107
+	{
108
+		$this->mapper = $mapper;
109
+
110
+		$this->adapter = $adapter;
111
+
112
+		$this->entityMap = $mapper->getEntityMap();
113
+
114
+		// Specify the table to work on
115
+		$this->query = $adapter->getQuery()->from($this->entityMap->getTable());
116
+
117
+		$this->with($this->entityMap->getEagerloadedRelationships());
118
+	}
119
+
120
+	/**
121
+	 * Run the query and return the result.
122
+	 *
123
+	 * @param array $columns
124
+	 *
125
+	 * @return \Analogue\ORM\EntityCollection
126
+	 */
127
+	public function get($columns = ['*']) : Collection
128
+	{
129
+		return $this->getEntities($columns);
130
+	}
131
+
132
+	/**
133
+	 * Find an entity by its primary key.
134
+	 *
135
+	 * @param string|int $id
136
+	 * @param array      $columns
137
+	 *
138
+	 * @return \Analogue\ORM\Mappable
139
+	 */
140
+	public function find($id, $columns = ['*'])
141
+	{
142
+		if (is_array($id)) {
143
+			return $this->findMany($id, $columns);
144
+		}
145
+
146
+		$this->query->where($this->entityMap->getKeyName(), '=', $id);
147
+
148
+		return $this->first($columns);
149
+	}
150
+
151
+	/**
152
+	 * Find many entities by their primary keys.
153
+	 *
154
+	 * @param array $id
155
+	 * @param array $columns
156
+	 *
157
+	 * @return EntityCollection
158
+	 */
159
+	public function findMany($id, $columns = ['*'])
160
+	{
161
+		if (empty($id)) {
162
+			return new EntityCollection();
163
+		}
164
+
165
+		$this->query->whereIn($this->entityMap->getKeyName(), $id);
166
+
167
+		return $this->get($columns);
168
+	}
169
+
170
+	/**
171
+	 * Find a model by its primary key or throw an exception.
172
+	 *
173
+	 * @param mixed $id
174
+	 * @param array $columns
175
+	 *
176
+	 * @throws \Analogue\ORM\Exceptions\EntityNotFoundException
177
+	 *
178
+	 * @return mixed|self
179
+	 */
180
+	public function findOrFail($id, $columns = ['*'])
181
+	{
182
+		if (!is_null($entity = $this->find($id, $columns))) {
183
+			return $entity;
184
+		}
185
+
186
+		throw (new EntityNotFoundException())->setEntity(get_class($this->entityMap));
187
+	}
188
+
189
+	/**
190
+	 * Execute the query and get the first result.
191
+	 *
192
+	 * @param array $columns
193
+	 *
194
+	 * @return \Analogue\ORM\Entity
195
+	 */
196
+	public function first($columns = ['*'])
197
+	{
198
+		return $this->take(1)->get($columns)->first();
199
+	}
200
+
201
+	/**
202
+	 * Execute the query and get the first result or throw an exception.
203
+	 *
204
+	 * @param array $columns
205
+	 *
206
+	 * @throws EntityNotFoundException
207
+	 *
208
+	 * @return \Analogue\ORM\Entity
209
+	 */
210
+	public function firstOrFail($columns = ['*'])
211
+	{
212
+		if (!is_null($entity = $this->first($columns))) {
213
+			return $entity;
214
+		}
215
+
216
+		throw (new EntityNotFoundException())->setEntity(get_class($this->entityMap));
217
+	}
218
+
219
+	/**
220
+	 * Pluck a single column from the database.
221
+	 *
222
+	 * @param string $column
223
+	 *
224
+	 * @return mixed
225
+	 */
226
+	public function pluck($column)
227
+	{
228
+		$result = $this->first([$column]);
229
+
230
+		if ($result) {
231
+			return $result->{$column};
232
+		}
233
+	}
234
+
235
+	/**
236
+	 * Chunk the results of the query.
237
+	 *
238
+	 * @param int      $count
239
+	 * @param callable $callback
240
+	 *
241
+	 * @return void
242
+	 */
243
+	public function chunk($count, callable $callback)
244
+	{
245
+		$results = $this->forPage($page = 1, $count)->get();
246
+
247
+		while (count($results) > 0) {
248
+			// On each chunk result set, we will pass them to the callback and then let the
249
+			// developer take care of everything within the callback, which allows us to
250
+			// keep the memory low for spinning through large result sets for working.
251
+			call_user_func($callback, $results);
252
+
253
+			$page++;
254
+
255
+			$results = $this->forPage($page, $count)->get();
256
+		}
257
+	}
258
+
259
+	/**
260
+	 * Get an array with the values of a given column.
261
+	 *
262
+	 * @param string $column
263
+	 * @param string $key
264
+	 *
265
+	 * @return array
266
+	 */
267
+	public function lists($column, $key = null)
268
+	{
269
+		return $this->query->pluck($column, $key);
270
+	}
271
+
272
+	/**
273
+	 * Get a paginator for the "select" statement.
274
+	 *
275
+	 * @param int   $perPage
276
+	 * @param array $columns
277
+	 *
278
+	 * @return LengthAwarePaginator
279
+	 */
280
+	public function paginate($perPage = null, $columns = ['*'])
281
+	{
282
+		$total = $this->query->getCountForPagination();
283
+
284
+		$this->query->forPage(
285
+			$page = Paginator::resolveCurrentPage(),
286
+			$perPage = $perPage ?: $this->entityMap->getPerPage()
287
+		);
288
+
289
+		return new LengthAwarePaginator($this->get($columns)->all(), $total, $perPage, $page, [
290
+			'path' => Paginator::resolveCurrentPath(),
291
+		]);
292
+	}
293
+
294
+	/**
295
+	 * Get a paginator for a grouped statement.
296
+	 *
297
+	 * @param \Illuminate\Pagination\Factory $paginator
298
+	 * @param int                            $perPage
299
+	 * @param array                          $columns
300
+	 *
301
+	 * @return \Illuminate\Pagination\Paginator
302
+	 */
303
+	protected function groupedPaginate($paginator, $perPage, $columns)
304
+	{
305
+		$results = $this->get($columns)->all();
306
+
307
+		return $this->query->buildRawPaginator($paginator, $results, $perPage);
308
+	}
309
+
310
+	/**
311
+	 * Get a paginator for an ungrouped statement.
312
+	 *
313
+	 * @param \Illuminate\Pagination\Factory $paginator
314
+	 * @param int                            $perPage
315
+	 * @param array                          $columns
316
+	 *
317
+	 * @return \Illuminate\Pagination\Paginator
318
+	 */
319
+	protected function ungroupedPaginate($paginator, $perPage, $columns)
320
+	{
321
+		$total = $this->query->getPaginationCount();
322
+
323
+		// Once we have the paginator we need to set the limit and offset values for
324
+		// the query so we can get the properly paginated items. Once we have an
325
+		// array of items we can create the paginator instances for the items.
326
+		$page = $paginator->getCurrentPage($total);
327
+
328
+		$this->query->forPage($page, $perPage);
329
+
330
+		return $paginator->make($this->get($columns)->all(), $total, $perPage);
331
+	}
332
+
333
+	/**
334
+	 * Paginate the given query into a simple paginator.
335
+	 *
336
+	 * @param int   $perPage
337
+	 * @param array $columns
338
+	 *
339
+	 * @return \Illuminate\Contracts\Pagination\Paginator
340
+	 */
341
+	public function simplePaginate($perPage = null, $columns = ['*'])
342
+	{
343
+		$page = Paginator::resolveCurrentPage();
344
+
345
+		$perPage = $perPage ?: $this->entityMap->getPerPage();
346
+
347
+		$this->skip(($page - 1) * $perPage)->take($perPage + 1);
348
+
349
+		return new Paginator($this->get($columns)->all(), $perPage, $page, ['path' => Paginator::resolveCurrentPath()]);
350
+	}
351
+
352
+	/**
353
+	 * Add a basic where clause to the query.
354
+	 *
355
+	 * @param string $column
356
+	 * @param string $operator
357
+	 * @param mixed  $value
358
+	 * @param string $boolean
359
+	 *
360
+	 * @return $this
361
+	 */
362
+	public function where($column, $operator = null, $value = null, $boolean = 'and')
363
+	{
364
+		if ($column instanceof Closure) {
365
+			$query = $this->newQueryWithoutScopes();
366
+
367
+			call_user_func($column, $query);
368
+
369
+			$this->query->addNestedWhereQuery($query->getQuery(), $boolean);
370
+		} else {
371
+			call_user_func_array([$this->query, 'where'], func_get_args());
372
+		}
373
+
374
+		return $this;
375
+	}
376
+
377
+	/**
378
+	 * Add an "or where" clause to the query.
379
+	 *
380
+	 * @param string $column
381
+	 * @param string $operator
382
+	 * @param mixed  $value
383
+	 *
384
+	 * @return \Analogue\ORM\System\Query
385
+	 */
386
+	public function orWhere($column, $operator = null, $value = null)
387
+	{
388
+		return $this->where($column, $operator, $value, 'or');
389
+	}
390
+
391
+	/**
392
+	 * Add a relationship count condition to the query.
393
+	 *
394
+	 * @param string   $relation
395
+	 * @param string   $operator
396
+	 * @param int      $count
397
+	 * @param string   $boolean
398
+	 * @param \Closure $callback
399
+	 *
400
+	 * @return \Analogue\ORM\System\Query
401
+	 */
402
+	public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
403
+	{
404
+		$entity = $this->mapper->newInstance();
405
+
406
+		$relation = $this->getHasRelationQuery($relation, $entity);
407
+
408
+		$query = $relation->getRelationCountQuery($relation->getRelatedMapper()->getQuery(), $this);
409
+
410
+		if ($callback) {
411
+			call_user_func($callback, $query);
412
+		}
413
+
414
+		return $this->addHasWhere($query, $relation, $operator, $count, $boolean);
415
+	}
416
+
417
+	/**
418
+	 * Add a relationship count condition to the query with where clauses.
419
+	 *
420
+	 * @param string   $relation
421
+	 * @param \Closure $callback
422
+	 * @param string   $operator
423
+	 * @param int      $count
424
+	 *
425
+	 * @return \Analogue\ORM\System\Query
426
+	 */
427
+	public function whereHas($relation, Closure $callback, $operator = '>=', $count = 1)
428
+	{
429
+		return $this->has($relation, $operator, $count, 'and', $callback);
430
+	}
431
+
432
+	/**
433
+	 * Add a relationship count condition to the query with an "or".
434
+	 *
435
+	 * @param string $relation
436
+	 * @param string $operator
437
+	 * @param int    $count
438
+	 *
439
+	 * @return \Analogue\ORM\System\Query
440
+	 */
441
+	public function orHas($relation, $operator = '>=', $count = 1)
442
+	{
443
+		return $this->has($relation, $operator, $count, 'or');
444
+	}
445
+
446
+	/**
447
+	 * Add a relationship count condition to the query with where clauses and an "or".
448
+	 *
449
+	 * @param string   $relation
450
+	 * @param \Closure $callback
451
+	 * @param string   $operator
452
+	 * @param int      $count
453
+	 *
454
+	 * @return \Analogue\ORM\System\Query
455
+	 */
456
+	public function orWhereHas($relation, Closure $callback, $operator = '>=', $count = 1)
457
+	{
458
+		return $this->has($relation, $operator, $count, 'or', $callback);
459
+	}
460
+
461
+	/**
462
+	 * Add the "has" condition where clause to the query.
463
+	 *
464
+	 * @param \Analogue\ORM\System\Query               $hasQuery
465
+	 * @param \Analogue\ORM\Relationships\Relationship $relation
466
+	 * @param string                                   $operator
467
+	 * @param int                                      $count
468
+	 * @param string                                   $boolean
469
+	 *
470
+	 * @return \Analogue\ORM\System\Query
471
+	 */
472
+	protected function addHasWhere(Query $hasQuery, Relationship $relation, $operator, $count, $boolean)
473
+	{
474
+		$this->mergeWheresToHas($hasQuery, $relation);
475
+
476
+		if (is_numeric($count)) {
477
+			$count = new Expression($count);
478
+		}
479
+
480
+		return $this->where(new Expression('('.$hasQuery->toSql().')'), $operator, $count, $boolean);
481
+	}
482
+
483
+	/**
484
+	 * Merge the "wheres" from a relation query to a has query.
485
+	 *
486
+	 * @param \Analogue\ORM\System\Query               $hasQuery
487
+	 * @param \Analogue\ORM\Relationships\Relationship $relation
488
+	 *
489
+	 * @return void
490
+	 */
491
+	protected function mergeWheresToHas(Query $hasQuery, Relationship $relation)
492
+	{
493
+		// Here we have the "has" query and the original relation. We need to copy over any
494
+		// where clauses the developer may have put in the relationship function over to
495
+		// the has query, and then copy the bindings from the "has" query to the main.
496
+		$relationQuery = $relation->getBaseQuery();
497
+
498
+		$hasQuery->mergeWheres(
499
+			$relationQuery->wheres, $relationQuery->getBindings()
500
+		);
501
+
502
+		$this->query->mergeBindings($hasQuery->getQuery());
503
+	}
504
+
505
+	/**
506
+	 * Get the "has relation" base query instance.
507
+	 *
508
+	 * @param string $relation
509
+	 * @param        $entity
510
+	 *
511
+	 * @return \Analogue\ORM\System\Query
512
+	 */
513
+	protected function getHasRelationQuery($relation, $entity)
514
+	{
515
+		return Relationship::noConstraints(function () use ($relation, $entity) {
516
+			return $this->entityMap->$relation($entity);
517
+		});
518
+	}
519
+
520
+	/**
521
+	 * Get the table for the current query object.
522
+	 *
523
+	 * @return string
524
+	 */
525
+	public function getTable()
526
+	{
527
+		return $this->entityMap->getTable();
528
+	}
529
+
530
+	/**
531
+	 * Set the relationships that should be eager loaded.
532
+	 *
533
+	 * @param mixed $relations
534
+	 *
535
+	 * @return $this
536
+	 */
537
+	public function with($relations)
538
+	{
539
+		if (is_string($relations)) {
540
+			$relations = func_get_args();
541
+		}
542
+
543
+		$this->eagerLoad = array_merge($this->eagerLoad, $relations);
544
+
545
+		return $this;
546
+	}
547
+
548
+	/**
549
+	 * Get the relationships being eagerly loaded.
550
+	 *
551
+	 * @return array
552
+	 */
553
+	public function getEagerLoads()
554
+	{
555
+		return $this->eagerLoad;
556
+	}
557
+
558
+	/**
559
+	 * Add the Entity primary key if not in requested columns.
560
+	 *
561
+	 * @param array $columns
562
+	 *
563
+	 * @return array
564
+	 */
565
+	protected function enforceIdColumn($columns)
566
+	{
567
+		if (!in_array($this->entityMap->getKeyName(), $columns)) {
568
+			$columns[] = $this->entityMap->getKeyName();
569
+		}
570
+
571
+		return $columns;
572
+	}
573
+
574
+	/**
575
+	 * Get the hydrated models without eager loading.
576
+	 *
577
+	 * @param array $columns
578
+	 *
579
+	 * @return \Analogue\ORM\EntityCollection
580
+	 */
581
+	public function getEntities($columns = ['*'])
582
+	{
583
+		// As we need the primary key to feed the
584
+		// entity cache, we need it loaded on each
585
+		// request
586
+		$columns = $this->enforceIdColumn($columns);
587
+
588
+		// Run the query
589
+		$results = $this->query->get($columns);
590
+
591
+		// Pass result set to the mapper and return the EntityCollection
592
+		return $this->mapper->map($results, $this->getEagerLoads());
593
+
594
+		// Create a result builder.
595
+		//$builder = new ResultBuilder($this->mapper);
596
+
597
+		//return $builder->build($results, $this->getEagerLoads());
598
+	}
599
+
600
+	/**
601
+	 * Extend the builder with a given callback.
602
+	 *
603
+	 * @param string   $name
604
+	 * @param \Closure $callback
605
+	 *
606
+	 * @return void
607
+	 */
608
+	public function macro($name, Closure $callback)
609
+	{
610
+		$this->macros[$name] = $callback;
611
+	}
612
+
613
+	/**
614
+	 * Get the given macro by name.
615
+	 *
616
+	 * @param string $name
617
+	 *
618
+	 * @return \Closure
619
+	 */
620
+	public function getMacro($name)
621
+	{
622
+		return array_get($this->macros, $name);
623
+	}
624
+
625
+	/**
626
+	 * Get a new query builder for the model's table.
627
+	 *
628
+	 * @return \Analogue\ORM\System\Query
629
+	 */
630
+	public function newQuery()
631
+	{
632
+		$builder = new self($this->mapper, $this->adapter);
633
+
634
+		return $this->applyGlobalScopes($builder);
635
+	}
636
+
637
+	/**
638
+	 * Get a new query builder without any scope applied.
639
+	 *
640
+	 * @return \Analogue\ORM\System\Query
641
+	 */
642
+	public function newQueryWithoutScopes()
643
+	{
644
+		return new self($this->mapper, $this->adapter);
645
+	}
646
+
647
+	/**
648
+	 * Get the Mapper instance for this Query Builder.
649
+	 *
650
+	 * @return \Analogue\ORM\System\Mapper
651
+	 */
652
+	public function getMapper()
653
+	{
654
+		return $this->mapper;
655
+	}
656
+
657
+	/**
658
+	 * Get the underlying query adapter.
659
+	 *
660
+	 * (REFACTOR: this method should move out, we need to provide the client classes
661
+	 * with the adapter instead.)
662
+	 *
663
+	 * @return \Analogue\ORM\Drivers\QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter
664
+	 */
665
+	public function getQuery()
666
+	{
667
+		return $this->query;
668
+	}
669
+
670
+	/**
671
+	 * Dynamically handle calls into the query instance.
672
+	 *
673
+	 * @param string $method
674
+	 * @param array  $parameters
675
+	 *
676
+	 * @throws Exception
677
+	 *
678
+	 * @return mixed
679
+	 */
680
+	public function __call($method, $parameters)
681
+	{
682
+		if (isset($this->macros[$method])) {
683
+			array_unshift($parameters, $this);
684
+
685
+			return call_user_func_array($this->macros[$method], $parameters);
686
+		}
687
+
688
+		if (in_array($method, $this->blacklist)) {
689
+			throw new Exception("Method $method doesn't exist");
690
+		}
691
+
692
+		$result = call_user_func_array([$this->query, $method], $parameters);
693
+
694
+		return in_array($method, $this->passthru) ? $result : $this;
695
+	}
696 696
 }
Please login to merge, or discard this patch.
src/System/Wrappers/ObjectWrapper.php 1 patch
Indentation   +221 added lines, -221 removed lines patch added patch discarded remove patch
@@ -10,226 +10,226 @@
 block discarded – undo
10 10
  */
11 11
 class ObjectWrapper extends Wrapper
12 12
 {
13
-    /**
14
-     * Internal Representation of analogue's entity attributes.
15
-     *
16
-     * @var array
17
-     */
18
-    protected $attributes = [];
19
-
20
-    /**
21
-     * Object properties that are not a part of the entity attributes,
22
-     * but which are needed to correctly hydrate the Object.
23
-     *
24
-     * @var array
25
-     */
26
-    protected $unmanagedProperties = [];
27
-
28
-    /**
29
-     * The hydrator for the wrapped object.
30
-     *
31
-     * @var HydratorInterface
32
-     */
33
-    protected $hydrator;
34
-
35
-    /**
36
-     * Object Wrapper constructor.
37
-     *
38
-     * @param mixed                  $object
39
-     * @param Analogue\ORM\EntityMap $entityMap
40
-     *
41
-     * @return void
42
-     */
43
-    public function __construct($entity, $entityMap, HydratorInterface $hydrator)
44
-    {
45
-        $this->hydrator = $hydrator;
46
-        parent::__construct($entity, $entityMap);
47
-        $this->attributes = $this->dehydrate($entity);
48
-    }
49
-
50
-    /**
51
-     * Returns the wrapped entity.
52
-     *
53
-     * @return mixed
54
-     */
55
-    public function getObject()
56
-    {
57
-        $this->hydrate();
58
-
59
-        return $this->entity;
60
-    }
61
-
62
-    /**
63
-     * Extract entity attributes / properties to an array of attributes.
64
-     *
65
-     * @param mixed $entity
66
-     *
67
-     * @return array
68
-     */
69
-    protected function dehydrate($entity) : array
70
-    {
71
-        $properties = $this->hydrator->extract($entity);
72
-
73
-        $this->unmanagedProperties = array_except($properties, $this->getManagedProperties());
74
-
75
-        return $this->attributesFromProperties($properties);
76
-    }
77
-
78
-    /**
79
-     * Hydrate object's properties/attribute from the internal array representation.
80
-     *
81
-     * @return mixed
82
-     */
83
-    public function hydrate()
84
-    {
85
-        $properties = $this->propertiesFromAttributes($this->attributes) + $this->unmanagedProperties;
13
+	/**
14
+	 * Internal Representation of analogue's entity attributes.
15
+	 *
16
+	 * @var array
17
+	 */
18
+	protected $attributes = [];
19
+
20
+	/**
21
+	 * Object properties that are not a part of the entity attributes,
22
+	 * but which are needed to correctly hydrate the Object.
23
+	 *
24
+	 * @var array
25
+	 */
26
+	protected $unmanagedProperties = [];
27
+
28
+	/**
29
+	 * The hydrator for the wrapped object.
30
+	 *
31
+	 * @var HydratorInterface
32
+	 */
33
+	protected $hydrator;
34
+
35
+	/**
36
+	 * Object Wrapper constructor.
37
+	 *
38
+	 * @param mixed                  $object
39
+	 * @param Analogue\ORM\EntityMap $entityMap
40
+	 *
41
+	 * @return void
42
+	 */
43
+	public function __construct($entity, $entityMap, HydratorInterface $hydrator)
44
+	{
45
+		$this->hydrator = $hydrator;
46
+		parent::__construct($entity, $entityMap);
47
+		$this->attributes = $this->dehydrate($entity);
48
+	}
49
+
50
+	/**
51
+	 * Returns the wrapped entity.
52
+	 *
53
+	 * @return mixed
54
+	 */
55
+	public function getObject()
56
+	{
57
+		$this->hydrate();
58
+
59
+		return $this->entity;
60
+	}
61
+
62
+	/**
63
+	 * Extract entity attributes / properties to an array of attributes.
64
+	 *
65
+	 * @param mixed $entity
66
+	 *
67
+	 * @return array
68
+	 */
69
+	protected function dehydrate($entity) : array
70
+	{
71
+		$properties = $this->hydrator->extract($entity);
72
+
73
+		$this->unmanagedProperties = array_except($properties, $this->getManagedProperties());
74
+
75
+		return $this->attributesFromProperties($properties);
76
+	}
77
+
78
+	/**
79
+	 * Hydrate object's properties/attribute from the internal array representation.
80
+	 *
81
+	 * @return mixed
82
+	 */
83
+	public function hydrate()
84
+	{
85
+		$properties = $this->propertiesFromAttributes($this->attributes) + $this->unmanagedProperties;
86 86
         
87
-        $this->hydrator->hydrate($properties, $this->entity);
88
-    }
89
-
90
-    /**
91
-     * Return properties that will be extracted from the entity.
92
-     *
93
-     * @return array
94
-     */
95
-    protected function getManagedProperties() : array
96
-    {
97
-        $properties = $this->entityMap->getProperties();
98
-
99
-        $attributesName = $this->entityMap->getAttributesArrayName();
100
-
101
-        return $attributesName == null ? $properties : array_merge($properties, [$attributesName]);
102
-    }
103
-
104
-    /**
105
-     * Convert object's properties to analogue's internal attributes representation.
106
-     *
107
-     * @param array $properties
108
-     *
109
-     * @return array
110
-     */
111
-    protected function attributesFromProperties(array $properties) : array
112
-    {
113
-        // First, we'll only keep the entities that are part of the Entity's
114
-        // attributes
115
-        $managedProperties = $this->getManagedProperties();
116
-
117
-        $properties = array_only($properties, $managedProperties);
118
-
119
-        // If the entity does not uses the attributes array to store
120
-        // part of its attributes, we'll directly return the properties
121
-        if (!$this->entityMap->usesAttributesArray()) {
122
-            return $properties;
123
-        }
124
-
125
-        $arrayName = $this->entityMap->getAttributesArrayName();
126
-
127
-        if (!array_key_exists($arrayName, $properties)) {
128
-            throw new MappingException("Property $arrayName not set on object of type ".$this->getEntityClass());
129
-        }
130
-
131
-        if (!is_array($properties[$arrayName])) {
132
-            throw new MappingException("Property $arrayName should be an array.");
133
-        }
134
-
135
-        $attributes = $properties[$arrayName];
136
-
137
-        unset($properties[$arrayName]);
138
-
139
-        return $properties + $attributes;
140
-    }
141
-
142
-    /**
143
-     * Convert internal representation of attributes to an array of properties
144
-     * that can hydrate the actual object.
145
-     *
146
-     * @param array $attributes
147
-     *
148
-     * @return array
149
-     */
150
-    protected function propertiesFromAttributes(array $attributes) : array
151
-    {
152
-        $attributes = $this->attributes;
153
-
154
-        // Get all managed properties
155
-        $propertyNames = $this->entityMap->getProperties();
156
-
157
-        $propertyAttributes = array_only($attributes, $propertyNames);
158
-        $attributesArray = array_except($attributes, $propertyNames);
159
-
160
-        $attributesArrayName = $this->entityMap->getAttributesArrayName();
161
-
162
-        if ($attributesArrayName) {
163
-            $propertyAttributes[$attributesArrayName] = $attributesArray;
164
-        }
165
-
166
-        return $propertyAttributes;
167
-    }
168
-
169
-    /**
170
-     * Method used by the mapper to set the object
171
-     * attribute raw values (hydration).
172
-     *
173
-     * @param array $attributes
174
-     *
175
-     * @return void
176
-     */
177
-    public function setEntityAttributes(array $attributes)
178
-    {
179
-        $this->attributes = $attributes;
180
-    }
181
-
182
-    /**
183
-     * Method used by the mapper to get the
184
-     * raw object's values.
185
-     *
186
-     * @return array
187
-     */
188
-    public function getEntityAttributes() : array
189
-    {
190
-        return $this->attributes;
191
-    }
192
-
193
-    /**
194
-     * Method used by the mapper to set raw
195
-     * key-value pair.
196
-     *
197
-     * @param string $key
198
-     * @param string $value
199
-     *
200
-     * @return void
201
-     */
202
-    public function setEntityAttribute($key, $value)
203
-    {
204
-        $this->attributes[$key] = $value;
205
-    }
206
-
207
-    /**
208
-     * Method used by the mapper to get single
209
-     * key-value pair.
210
-     *
211
-     * @param string $key
212
-     *
213
-     * @return mixed|null
214
-     */
215
-    public function getEntityAttribute($key)
216
-    {
217
-        if ($this->hasAttribute($key)) {
218
-            return $this->attributes[$key];
219
-        } else {
220
-            return;
221
-        }
222
-    }
223
-
224
-    /**
225
-     * Test if a given attribute exists.
226
-     *
227
-     * @param string $key
228
-     *
229
-     * @return bool
230
-     */
231
-    public function hasAttribute($key) : bool
232
-    {
233
-        return array_key_exists($key, $this->attributes) ? true : false;
234
-    }
87
+		$this->hydrator->hydrate($properties, $this->entity);
88
+	}
89
+
90
+	/**
91
+	 * Return properties that will be extracted from the entity.
92
+	 *
93
+	 * @return array
94
+	 */
95
+	protected function getManagedProperties() : array
96
+	{
97
+		$properties = $this->entityMap->getProperties();
98
+
99
+		$attributesName = $this->entityMap->getAttributesArrayName();
100
+
101
+		return $attributesName == null ? $properties : array_merge($properties, [$attributesName]);
102
+	}
103
+
104
+	/**
105
+	 * Convert object's properties to analogue's internal attributes representation.
106
+	 *
107
+	 * @param array $properties
108
+	 *
109
+	 * @return array
110
+	 */
111
+	protected function attributesFromProperties(array $properties) : array
112
+	{
113
+		// First, we'll only keep the entities that are part of the Entity's
114
+		// attributes
115
+		$managedProperties = $this->getManagedProperties();
116
+
117
+		$properties = array_only($properties, $managedProperties);
118
+
119
+		// If the entity does not uses the attributes array to store
120
+		// part of its attributes, we'll directly return the properties
121
+		if (!$this->entityMap->usesAttributesArray()) {
122
+			return $properties;
123
+		}
124
+
125
+		$arrayName = $this->entityMap->getAttributesArrayName();
126
+
127
+		if (!array_key_exists($arrayName, $properties)) {
128
+			throw new MappingException("Property $arrayName not set on object of type ".$this->getEntityClass());
129
+		}
130
+
131
+		if (!is_array($properties[$arrayName])) {
132
+			throw new MappingException("Property $arrayName should be an array.");
133
+		}
134
+
135
+		$attributes = $properties[$arrayName];
136
+
137
+		unset($properties[$arrayName]);
138
+
139
+		return $properties + $attributes;
140
+	}
141
+
142
+	/**
143
+	 * Convert internal representation of attributes to an array of properties
144
+	 * that can hydrate the actual object.
145
+	 *
146
+	 * @param array $attributes
147
+	 *
148
+	 * @return array
149
+	 */
150
+	protected function propertiesFromAttributes(array $attributes) : array
151
+	{
152
+		$attributes = $this->attributes;
153
+
154
+		// Get all managed properties
155
+		$propertyNames = $this->entityMap->getProperties();
156
+
157
+		$propertyAttributes = array_only($attributes, $propertyNames);
158
+		$attributesArray = array_except($attributes, $propertyNames);
159
+
160
+		$attributesArrayName = $this->entityMap->getAttributesArrayName();
161
+
162
+		if ($attributesArrayName) {
163
+			$propertyAttributes[$attributesArrayName] = $attributesArray;
164
+		}
165
+
166
+		return $propertyAttributes;
167
+	}
168
+
169
+	/**
170
+	 * Method used by the mapper to set the object
171
+	 * attribute raw values (hydration).
172
+	 *
173
+	 * @param array $attributes
174
+	 *
175
+	 * @return void
176
+	 */
177
+	public function setEntityAttributes(array $attributes)
178
+	{
179
+		$this->attributes = $attributes;
180
+	}
181
+
182
+	/**
183
+	 * Method used by the mapper to get the
184
+	 * raw object's values.
185
+	 *
186
+	 * @return array
187
+	 */
188
+	public function getEntityAttributes() : array
189
+	{
190
+		return $this->attributes;
191
+	}
192
+
193
+	/**
194
+	 * Method used by the mapper to set raw
195
+	 * key-value pair.
196
+	 *
197
+	 * @param string $key
198
+	 * @param string $value
199
+	 *
200
+	 * @return void
201
+	 */
202
+	public function setEntityAttribute($key, $value)
203
+	{
204
+		$this->attributes[$key] = $value;
205
+	}
206
+
207
+	/**
208
+	 * Method used by the mapper to get single
209
+	 * key-value pair.
210
+	 *
211
+	 * @param string $key
212
+	 *
213
+	 * @return mixed|null
214
+	 */
215
+	public function getEntityAttribute($key)
216
+	{
217
+		if ($this->hasAttribute($key)) {
218
+			return $this->attributes[$key];
219
+		} else {
220
+			return;
221
+		}
222
+	}
223
+
224
+	/**
225
+	 * Test if a given attribute exists.
226
+	 *
227
+	 * @param string $key
228
+	 *
229
+	 * @return bool
230
+	 */
231
+	public function hasAttribute($key) : bool
232
+	{
233
+		return array_key_exists($key, $this->attributes) ? true : false;
234
+	}
235 235
 }
Please login to merge, or discard this patch.
src/System/Wrappers/Wrapper.php 1 patch
Indentation   +236 added lines, -236 removed lines patch added patch discarded remove patch
@@ -10,240 +10,240 @@
 block discarded – undo
10 10
  */
11 11
 abstract class Wrapper implements InternallyMappable
12 12
 {
13
-    /**
14
-     * Original Entity Object.
15
-     *
16
-     * @var mixed
17
-     */
18
-    protected $entity;
19
-
20
-    /**
21
-     * Corresponding EntityMap.
22
-     *
23
-     * @var \Analogue\ORM\EntityMap
24
-     */
25
-    protected $entityMap;
26
-
27
-    /**
28
-     * @var \Analogue\ORM\System\Proxirs\ProxyFactory
29
-     */
30
-    protected $proxyFactory;
31
-
32
-    /**
33
-     * Wrapper constructor.
34
-     *
35
-     * @param $entity
36
-     * @param $entityMap
37
-     */
38
-    public function __construct($entity, $entityMap)
39
-    {
40
-        $this->entity = $entity;
41
-        $this->entityMap = $entityMap;
42
-        $this->proxyFactory = new ProxyFactory();
43
-    }
44
-
45
-    /**
46
-     * Return the wrapped entity class.
47
-     *
48
-     * @return mixed
49
-     */
50
-    public function getEntityClass()
51
-    {
52
-        return get_class($this->entity);
53
-    }
54
-
55
-    /**
56
-     * Return the entity's primary key valuye.
57
-     *
58
-     * @return string
59
-     */
60
-    public function getEntityKey()
61
-    {
62
-        return $this->getEntityAttribute($this->entityMap->getKeyName());
63
-    }
64
-
65
-    /**
66
-     * Return the Entity class/primary key couple,
67
-     * which is used for internall operations.
68
-     *
69
-     * @return string
70
-     */
71
-    public function getEntityHash()
72
-    {
73
-        return $this->getEntityClass().'.'.$this->getEntityKey();
74
-    }
75
-
76
-    /**
77
-     * Returns the wrapped entity.
78
-     *
79
-     * @return mixed
80
-     */
81
-    public function getObject()
82
-    {
83
-        return $this->entity;
84
-    }
85
-
86
-    /**
87
-     * Returns the wrapped entity's map.
88
-     *
89
-     * @return mixed
90
-     */
91
-    public function getMap()
92
-    {
93
-        return $this->entityMap;
94
-    }
95
-
96
-    /**
97
-     * Set the lazyloading proxies on the wrapped entity objet.
98
-     *
99
-     * @param array $relations list of relations to be lazy loaded
100
-     *
101
-     * @return void
102
-     */
103
-    public function setProxies(array $relations = null)
104
-    {
105
-        $attributes = $this->getEntityAttributes();
106
-        $proxies = [];
107
-
108
-        //if (is_null($relations)) {
109
-            $relations = $this->getRelationsToProxy();
110
-        //}
111
-        //dump($relations);
112
-        // Before calling the relationship methods, we'll set the relationship
113
-        // method to null, to avoid hydration error on class properties
114
-        foreach ($relations as $relation) {
115
-            $this->setEntityAttribute($relation, null);
116
-        }
117
-
118
-        foreach ($relations as $relation) {
119
-
120
-            // First, we check that the relation has not been already
121
-            // set, in which case, we'll just pass.
122
-            if (array_key_exists($relation, $attributes) && !is_null($attributes[$relation])) {
123
-                continue;
124
-            }
125
-
126
-            // If the key is handled locally and we know it not to be set,
127
-            // we'll set the relationship to null
128
-            if (!$this->relationNeedsProxy($relation, $attributes)) {
129
-                $proxies[$relation] = null;
130
-            } else {
131
-                $targetClass = $this->getClassToProxy($relation, $attributes);
132
-                $proxies[$relation] = $this->proxyFactory->make($this->getObject(), $relation, $targetClass);
133
-            }
134
-        }
135
-
136
-        foreach ($proxies as $key => $value) {
137
-            $this->setEntityAttribute($key, $value);
138
-        }
139
-
140
-    }
141
-
142
-    /**
143
-     * Get Target class to proxy for a one to one.
144
-     *
145
-     * @param string $relation
146
-     * @param array  $attributes
147
-     *
148
-     * @return string
149
-     */
150
-    protected function getClassToProxy($relation, array $attributes)
151
-    {
152
-        if ($this->entityMap->isPolymorphic($relation)) {
153
-            $localTypeAttribute = $this->entityMap->getLocalKeys($relation)['type'];
154
-
155
-            return $attributes[$localTypeAttribute];
156
-        }
157
-
158
-        return $this->entityMap->getTargettedClass($relation);
159
-    }
160
-
161
-    /**
162
-     * Determine which relations we have to build proxy for, by parsing
163
-     * attributes and finding methods that aren't set.
164
-     *
165
-     * @return array
166
-     */
167
-    protected function getRelationsToProxy()
168
-    {
169
-        $proxies = [];
170
-        $attributes = $this->getEntityAttributes();
171
-
172
-        foreach ($this->entityMap->getRelationships() as $relation) {
173
-            if (!array_key_exists($relation, $attributes)) {
174
-                $proxies[] = $relation;
175
-            }
176
-        }
177
-
178
-        return $proxies;
179
-    }
180
-
181
-    /**
182
-     * Determine if the relation needs a proxy or not.
183
-     *
184
-     * @param string $relation
185
-     * @param array  $attributes
186
-     *
187
-     * @return bool
188
-     */
189
-    protected function relationNeedsProxy($relation, $attributes)
190
-    {
191
-        if (in_array($relation, $this->entityMap->getRelationshipsWithoutProxy())) {
192
-            return false;
193
-        }
194
-
195
-        $localKey = $this->entityMap->getLocalKeys($relation);
196
-
197
-        if (is_null($localKey)) {
198
-            return true;
199
-        }
200
-
201
-        if (is_array($localKey)) {
202
-            $localKey = $localKey['id'];
203
-        }
204
-
205
-        if (!isset($attributes[$localKey])) {
206
-            return false;
207
-        }
208
-
209
-        if (is_null($attributes[$localKey])) {
210
-            return false;
211
-        }
212
-
213
-        return true;
214
-    }
215
-
216
-    /**
217
-     * @param string $key
218
-     * @param string $value
219
-     *
220
-     * @return mixed
221
-     */
222
-    abstract public function setEntityAttribute($key, $value);
223
-
224
-    /**
225
-     * @param string $key
226
-     *
227
-     * @return mixed
228
-     */
229
-    abstract public function getEntityAttribute($key);
230
-
231
-    /**
232
-     * @param array $attributes
233
-     *
234
-     * @return mixed
235
-     */
236
-    abstract public function setEntityAttributes(array $attributes);
237
-
238
-    /**
239
-     * @return mixed
240
-     */
241
-    abstract public function getEntityAttributes();
242
-
243
-    /**
244
-     * @param string $key
245
-     *
246
-     * @return mixed
247
-     */
248
-    abstract public function hasAttribute($key);
13
+	/**
14
+	 * Original Entity Object.
15
+	 *
16
+	 * @var mixed
17
+	 */
18
+	protected $entity;
19
+
20
+	/**
21
+	 * Corresponding EntityMap.
22
+	 *
23
+	 * @var \Analogue\ORM\EntityMap
24
+	 */
25
+	protected $entityMap;
26
+
27
+	/**
28
+	 * @var \Analogue\ORM\System\Proxirs\ProxyFactory
29
+	 */
30
+	protected $proxyFactory;
31
+
32
+	/**
33
+	 * Wrapper constructor.
34
+	 *
35
+	 * @param $entity
36
+	 * @param $entityMap
37
+	 */
38
+	public function __construct($entity, $entityMap)
39
+	{
40
+		$this->entity = $entity;
41
+		$this->entityMap = $entityMap;
42
+		$this->proxyFactory = new ProxyFactory();
43
+	}
44
+
45
+	/**
46
+	 * Return the wrapped entity class.
47
+	 *
48
+	 * @return mixed
49
+	 */
50
+	public function getEntityClass()
51
+	{
52
+		return get_class($this->entity);
53
+	}
54
+
55
+	/**
56
+	 * Return the entity's primary key valuye.
57
+	 *
58
+	 * @return string
59
+	 */
60
+	public function getEntityKey()
61
+	{
62
+		return $this->getEntityAttribute($this->entityMap->getKeyName());
63
+	}
64
+
65
+	/**
66
+	 * Return the Entity class/primary key couple,
67
+	 * which is used for internall operations.
68
+	 *
69
+	 * @return string
70
+	 */
71
+	public function getEntityHash()
72
+	{
73
+		return $this->getEntityClass().'.'.$this->getEntityKey();
74
+	}
75
+
76
+	/**
77
+	 * Returns the wrapped entity.
78
+	 *
79
+	 * @return mixed
80
+	 */
81
+	public function getObject()
82
+	{
83
+		return $this->entity;
84
+	}
85
+
86
+	/**
87
+	 * Returns the wrapped entity's map.
88
+	 *
89
+	 * @return mixed
90
+	 */
91
+	public function getMap()
92
+	{
93
+		return $this->entityMap;
94
+	}
95
+
96
+	/**
97
+	 * Set the lazyloading proxies on the wrapped entity objet.
98
+	 *
99
+	 * @param array $relations list of relations to be lazy loaded
100
+	 *
101
+	 * @return void
102
+	 */
103
+	public function setProxies(array $relations = null)
104
+	{
105
+		$attributes = $this->getEntityAttributes();
106
+		$proxies = [];
107
+
108
+		//if (is_null($relations)) {
109
+			$relations = $this->getRelationsToProxy();
110
+		//}
111
+		//dump($relations);
112
+		// Before calling the relationship methods, we'll set the relationship
113
+		// method to null, to avoid hydration error on class properties
114
+		foreach ($relations as $relation) {
115
+			$this->setEntityAttribute($relation, null);
116
+		}
117
+
118
+		foreach ($relations as $relation) {
119
+
120
+			// First, we check that the relation has not been already
121
+			// set, in which case, we'll just pass.
122
+			if (array_key_exists($relation, $attributes) && !is_null($attributes[$relation])) {
123
+				continue;
124
+			}
125
+
126
+			// If the key is handled locally and we know it not to be set,
127
+			// we'll set the relationship to null
128
+			if (!$this->relationNeedsProxy($relation, $attributes)) {
129
+				$proxies[$relation] = null;
130
+			} else {
131
+				$targetClass = $this->getClassToProxy($relation, $attributes);
132
+				$proxies[$relation] = $this->proxyFactory->make($this->getObject(), $relation, $targetClass);
133
+			}
134
+		}
135
+
136
+		foreach ($proxies as $key => $value) {
137
+			$this->setEntityAttribute($key, $value);
138
+		}
139
+
140
+	}
141
+
142
+	/**
143
+	 * Get Target class to proxy for a one to one.
144
+	 *
145
+	 * @param string $relation
146
+	 * @param array  $attributes
147
+	 *
148
+	 * @return string
149
+	 */
150
+	protected function getClassToProxy($relation, array $attributes)
151
+	{
152
+		if ($this->entityMap->isPolymorphic($relation)) {
153
+			$localTypeAttribute = $this->entityMap->getLocalKeys($relation)['type'];
154
+
155
+			return $attributes[$localTypeAttribute];
156
+		}
157
+
158
+		return $this->entityMap->getTargettedClass($relation);
159
+	}
160
+
161
+	/**
162
+	 * Determine which relations we have to build proxy for, by parsing
163
+	 * attributes and finding methods that aren't set.
164
+	 *
165
+	 * @return array
166
+	 */
167
+	protected function getRelationsToProxy()
168
+	{
169
+		$proxies = [];
170
+		$attributes = $this->getEntityAttributes();
171
+
172
+		foreach ($this->entityMap->getRelationships() as $relation) {
173
+			if (!array_key_exists($relation, $attributes)) {
174
+				$proxies[] = $relation;
175
+			}
176
+		}
177
+
178
+		return $proxies;
179
+	}
180
+
181
+	/**
182
+	 * Determine if the relation needs a proxy or not.
183
+	 *
184
+	 * @param string $relation
185
+	 * @param array  $attributes
186
+	 *
187
+	 * @return bool
188
+	 */
189
+	protected function relationNeedsProxy($relation, $attributes)
190
+	{
191
+		if (in_array($relation, $this->entityMap->getRelationshipsWithoutProxy())) {
192
+			return false;
193
+		}
194
+
195
+		$localKey = $this->entityMap->getLocalKeys($relation);
196
+
197
+		if (is_null($localKey)) {
198
+			return true;
199
+		}
200
+
201
+		if (is_array($localKey)) {
202
+			$localKey = $localKey['id'];
203
+		}
204
+
205
+		if (!isset($attributes[$localKey])) {
206
+			return false;
207
+		}
208
+
209
+		if (is_null($attributes[$localKey])) {
210
+			return false;
211
+		}
212
+
213
+		return true;
214
+	}
215
+
216
+	/**
217
+	 * @param string $key
218
+	 * @param string $value
219
+	 *
220
+	 * @return mixed
221
+	 */
222
+	abstract public function setEntityAttribute($key, $value);
223
+
224
+	/**
225
+	 * @param string $key
226
+	 *
227
+	 * @return mixed
228
+	 */
229
+	abstract public function getEntityAttribute($key);
230
+
231
+	/**
232
+	 * @param array $attributes
233
+	 *
234
+	 * @return mixed
235
+	 */
236
+	abstract public function setEntityAttributes(array $attributes);
237
+
238
+	/**
239
+	 * @return mixed
240
+	 */
241
+	abstract public function getEntityAttributes();
242
+
243
+	/**
244
+	 * @param string $key
245
+	 *
246
+	 * @return mixed
247
+	 */
248
+	abstract public function hasAttribute($key);
249 249
 }
Please login to merge, or discard this patch.
src/System/Mapper.php 1 patch
Indentation   +630 added lines, -630 removed lines patch added patch discarded remove patch
@@ -25,634 +25,634 @@
 block discarded – undo
25 25
  */
26 26
 class Mapper
27 27
 {
28
-    /**
29
-     * The Manager instance.
30
-     *
31
-     * @var \Analogue\ORM\System\Manager
32
-     */
33
-    protected $manager;
34
-
35
-    /**
36
-     * Instance of EntityMapper Object.
37
-     *
38
-     * @var \Analogue\ORM\EntityMap
39
-     */
40
-    protected $entityMap;
41
-
42
-    /**
43
-     * The instance of db adapter.
44
-     *
45
-     * @var \Analogue\ORM\Drivers\DBAdapter
46
-     */
47
-    protected $adapter;
48
-
49
-    /**
50
-     * Event dispatcher instance.
51
-     *
52
-     * @var \Illuminate\Contracts\Events\Dispatcher
53
-     */
54
-    protected $dispatcher;
55
-
56
-    /**
57
-     * Entity Cache.
58
-     *
59
-     * @var \Analogue\ORM\System\EntityCache
60
-     */
61
-    protected $cache;
62
-
63
-    /**
64
-     * Global scopes.
65
-     *
66
-     * @var array
67
-     */
68
-    protected $globalScopes = [];
69
-
70
-    /**
71
-     * Custom Commands.
72
-     *
73
-     * @var array
74
-     */
75
-    protected $customCommands = [];
76
-
77
-    /**
78
-     * @param EntityMap  $entityMap
79
-     * @param DBAdapter  $adapter
80
-     * @param Dispatcher $dispatcher
81
-     * @param Manager    $manager
82
-     */
83
-    public function __construct(EntityMap $entityMap, DBAdapter $adapter, Dispatcher $dispatcher, Manager $manager)
84
-    {
85
-        $this->entityMap = $entityMap;
86
-
87
-        $this->adapter = $adapter;
88
-
89
-        $this->dispatcher = $dispatcher;
90
-
91
-        $this->manager = $manager;
92
-
93
-        $this->cache = new EntityCache($entityMap);
94
-    }
95
-
96
-    /**
97
-     * Map results to a Collection.
98
-     *
99
-     * @param array|Collection $results
100
-     *
101
-     * @return Collection
102
-     */
103
-    public function map($results, array $eagerLoads = []) : Collection
104
-    {
105
-        $builder = new ResultBuilder($this);
106
-
107
-        if ($results instanceof collection) {
108
-            // Get underlying collection array
109
-            $results = $results->all();
110
-        }
111
-
112
-        if (!is_array($results)) {
113
-            throw new InvalidArgumentException("'results' should be an array or collection.");
114
-        }
115
-
116
-        // First, we'll cast every single result to array
117
-        $results = array_map(function ($item) {
118
-            return (array) $item;
119
-        }, $results);
120
-
121
-        // Then, we'll cache every single results as raw attributes, before
122
-        // adding relationships, which will be cached when the relationship's
123
-        // query takes place.
124
-        $this->getEntityCache()->add($results);
125
-
126
-        $entities = $builder->build($results, $eagerLoads);
127
-
128
-        return $this->entityMap->newCollection($entities);
129
-    }
130
-
131
-    /**
132
-     * Return all records for a mapped object.
133
-     *
134
-     * @return EntityCollection
135
-     */
136
-    public function all()
137
-    {
138
-        return $this->query()->get();
139
-    }
140
-
141
-    /**
142
-     * Persist an entity or an entity collection into the database.
143
-     *
144
-     * @param Mappable|\Traversable|array $entity
145
-     *
146
-     * @throws \InvalidArgumentException
147
-     * @throws MappingException
148
-     *
149
-     * @return Mappable|\Traversable|array
150
-     */
151
-    public function store($entity)
152
-    {
153
-        if ($this->manager->isTraversable($entity)) {
154
-            return $this->storeCollection($entity);
155
-        } else {
156
-            return $this->storeEntity($entity);
157
-        }
158
-    }
159
-
160
-    /**
161
-     * Store an entity collection inside a single DB Transaction.
162
-     *
163
-     * @param \Traversable|array $entities
164
-     *
165
-     * @throws \InvalidArgumentException
166
-     * @throws MappingException
167
-     *
168
-     * @return \Traversable|array
169
-     */
170
-    protected function storeCollection($entities)
171
-    {
172
-        $this->adapter->beginTransaction();
173
-
174
-        foreach ($entities as $entity) {
175
-            $this->storeEntity($entity);
176
-        }
177
-
178
-        $this->adapter->commit();
179
-
180
-        return $entities;
181
-    }
182
-
183
-    /**
184
-     * Store a single entity into the database.
185
-     *
186
-     * @param Mappable $entity
187
-     *
188
-     * @throws \InvalidArgumentException
189
-     * @throws MappingException
190
-     *
191
-     * @return \Analogue\ORM\Entity
192
-     */
193
-    protected function storeEntity($entity)
194
-    {   
195
-        $this->checkEntityType($entity);
196
-
197
-        $store = new Store($this->aggregate($entity), $this->newQueryBuilder());
198
-
199
-        return $store->execute();
200
-    }
201
-
202
-    /**
203
-     * Check that the entity correspond to the current mapper.
204
-     *
205
-     * @param mixed $entity
206
-     *
207
-     * @throws InvalidArgumentException
208
-     *
209
-     * @return void
210
-     */
211
-    protected function checkEntityType($entity)
212
-    {
213
-        if (get_class($entity) != $this->entityMap->getClass() && !is_subclass_of($entity, $this->entityMap->getClass())) {
214
-            $expected = $this->entityMap->getClass();
215
-            $actual = get_class($entity);
216
-            throw new InvalidArgumentException("Expected : $expected, got $actual.");
217
-        }
218
-    }
219
-
220
-    /**
221
-     * Convert an entity into an aggregate root.
222
-     *
223
-     * @param mixed $entity
224
-     *
225
-     * @throws MappingException
226
-     *
227
-     * @return \Analogue\ORM\System\Aggregate
228
-     */
229
-    protected function aggregate($entity)
230
-    {
231
-        return new Aggregate($entity);
232
-    }
233
-
234
-    /**
235
-     * Get a the Underlying QueryAdapter.
236
-     *
237
-     * @return \Analogue\ORM\Drivers\QueryAdapter
238
-     */
239
-    public function newQueryBuilder()
240
-    {
241
-        return $this->adapter->getQuery();
242
-    }
243
-
244
-    /**
245
-     * Delete an entity or an entity collection from the database.
246
-     *
247
-     * @param  Mappable|\Traversable|array
248
-     *
249
-     * @throws MappingException
250
-     * @throws \InvalidArgumentException
251
-     *
252
-     * @return \Traversable|array
253
-     */
254
-    public function delete($entity)
255
-    {
256
-        if ($this->manager->isTraversable($entity)) {
257
-            return $this->deleteCollection($entity);
258
-        } else {
259
-            $this->deleteEntity($entity);
260
-        }
261
-    }
262
-
263
-    /**
264
-     * Delete an Entity Collection inside a single db transaction.
265
-     *
266
-     * @param \Traversable|array $entities
267
-     *
268
-     * @throws \InvalidArgumentException
269
-     * @throws MappingException
270
-     *
271
-     * @return \Traversable|array
272
-     */
273
-    protected function deleteCollection($entities)
274
-    {
275
-        $this->adapter->beginTransaction();
276
-
277
-        foreach ($entities as $entity) {
278
-            $this->deleteEntity($entity);
279
-        }
280
-
281
-        $this->adapter->commit();
282
-
283
-        return $entities;
284
-    }
285
-
286
-    /**
287
-     * Delete a single entity from the database.
288
-     *
289
-     * @param Mappable $entity
290
-     *
291
-     * @throws \InvalidArgumentException
292
-     * @throws MappingException
293
-     *
294
-     * @return void
295
-     */
296
-    protected function deleteEntity($entity)
297
-    {
298
-        $this->checkEntityType($entity);
299
-
300
-        $delete = new Delete($this->aggregate($entity), $this->newQueryBuilder());
301
-
302
-        $delete->execute();
303
-    }
304
-
305
-    /**
306
-     * Return the entity map for this mapper.
307
-     *
308
-     * @return EntityMap
309
-     */
310
-    public function getEntityMap()
311
-    {
312
-        return $this->entityMap;
313
-    }
314
-
315
-    /**
316
-     * Get the entity cache for the current mapper.
317
-     *
318
-     * @return EntityCache $entityCache
319
-     */
320
-    public function getEntityCache()
321
-    {
322
-        return $this->cache;
323
-    }
324
-
325
-    /**
326
-     * Fire the given event for the entity.
327
-     *
328
-     * @param string               $event
329
-     * @param \Analogue\ORM\Entity $entity
330
-     * @param bool                 $halt
331
-     *
332
-     * @throws InvalidArgumentException
333
-     *
334
-     * @return mixed
335
-     */
336
-    public function fireEvent($event, $entity, $halt = true)
337
-    {
338
-        if ($entity instanceof Wrapper) {
339
-            throw new InvalidArgumentException('Fired Event with invalid Entity Object');
340
-        }
341
-
342
-        $event = "analogue.{$event}.".$this->entityMap->getClass();
343
-
344
-        $method = $halt ? 'until' : 'fire';
345
-
346
-        return $this->dispatcher->$method($event, $entity);
347
-    }
348
-
349
-    /**
350
-     * Register an entity event with the dispatcher.
351
-     *
352
-     * @param string   $event
353
-     * @param \Closure $callback
354
-     *
355
-     * @return void
356
-     */
357
-    public function registerEvent($event, $callback)
358
-    {
359
-        $name = $this->entityMap->getClass();
360
-
361
-        $this->dispatcher->listen("analogue.{$event}.{$name}", $callback);
362
-    }
363
-
364
-    /**
365
-     * Add a global scope to this mapper query builder.
366
-     *
367
-     * @param ScopeInterface $scope
368
-     *
369
-     * @return void
370
-     */
371
-    public function addGlobalScope(ScopeInterface $scope)
372
-    {
373
-        $this->globalScopes[get_class($scope)] = $scope;
374
-    }
375
-
376
-    /**
377
-     * Determine if the mapper has a global scope.
378
-     *
379
-     * @param \Analogue\ORM\System\ScopeInterface $scope
380
-     *
381
-     * @return bool
382
-     */
383
-    public function hasGlobalScope($scope)
384
-    {
385
-        return !is_null($this->getGlobalScope($scope));
386
-    }
387
-
388
-    /**
389
-     * Get a global scope registered with the modal.
390
-     *
391
-     * @param \Analogue\ORM\System\ScopeInterface $scope
392
-     *
393
-     * @return \Analogue\ORM\System\ScopeInterface|null
394
-     */
395
-    public function getGlobalScope($scope)
396
-    {
397
-        return array_first($this->globalScopes, function ($key, $value) use ($scope) {
398
-            return $scope instanceof $value;
399
-        });
400
-    }
401
-
402
-    /**
403
-     * Get a new query instance without a given scope.
404
-     *
405
-     * @param \Analogue\ORM\System\ScopeInterface $scope
406
-     *
407
-     * @return \Analogue\ORM\System\Query
408
-     */
409
-    public function newQueryWithoutScope($scope)
410
-    {
411
-        $this->getGlobalScope($scope)->remove($query = $this->getQuery(), $this);
412
-
413
-        return $query;
414
-    }
415
-
416
-    /**
417
-     * Get the Analogue Query Builder for this instance.
418
-     *
419
-     * @return \Analogue\ORM\System\Query
420
-     */
421
-    public function getQuery()
422
-    {
423
-        $query = new Query($this, $this->adapter);
424
-
425
-        return $this->applyGlobalScopes($query);
426
-    }
427
-
428
-    /**
429
-     * Apply all of the global scopes to an Analogue Query builder.
430
-     *
431
-     * @param Query $query
432
-     *
433
-     * @return \Analogue\ORM\System\Query
434
-     */
435
-    public function applyGlobalScopes($query)
436
-    {
437
-        foreach ($this->getGlobalScopes() as $scope) {
438
-            $scope->apply($query, $this);
439
-        }
440
-
441
-        return $query;
442
-    }
443
-
444
-    /**
445
-     * Get the global scopes for this class instance.
446
-     *
447
-     * @return \Analogue\ORM\System\ScopeInterface
448
-     */
449
-    public function getGlobalScopes()
450
-    {
451
-        return $this->globalScopes;
452
-    }
453
-
454
-    /**
455
-     * Add a dynamic method that extends the mapper/repository.
456
-     *
457
-     * @param string $command
458
-     */
459
-    public function addCustomCommand($command)
460
-    {
461
-        $name = lcfirst(class_basename($command));
462
-
463
-        $this->customCommands[$name] = $command;
464
-    }
465
-
466
-    /**
467
-     * Create a new instance of the mapped entity class.
468
-     *
469
-     * @return mixed
470
-     */
471
-    public function newInstance()
472
-    {
473
-        $class = $this->entityMap->getClass();
474
-
475
-        if ($this->entityMap->useDependencyInjection()) {
476
-            return $this->newInstanceUsingDependencyInjection($class);
477
-        }
478
-
479
-        return $this->newInstanceUsingInstantiator($class);
480
-    }
481
-
482
-    /**
483
-     * Return a new object instance using dependency injection.
484
-     *
485
-     * @param string $class
486
-     *
487
-     * @return mixed
488
-     */
489
-    protected function newInstanceUsingDependencyInjection($class)
490
-    {
491
-        if (!class_exists(Container::class)) {
492
-            throw new ErrorException("Illuminate\Container\Container is required to use Dependency Injection");
493
-        }
494
-
495
-        return Container::getInstance()->make($class);
496
-    }
497
-
498
-    /**
499
-     * Return a new object instance using doctrine's instantiator.
500
-     *
501
-     * @param string $class
502
-     *
503
-     * @return mixed
504
-     */
505
-    protected function newInstanceUsingInstantiator($class)
506
-    {
507
-        $instantiator = new \Doctrine\Instantiator\Instantiator();
508
-
509
-        return $instantiator->instantiate($class);
510
-    }
511
-
512
-    /**
513
-     * Get an unscoped Analogue Query Builder for this instance.
514
-     *
515
-     * @return \Analogue\ORM\System\Query
516
-     */
517
-    public function globalQuery()
518
-    {
519
-        return $this->newQueryWithoutScopes();
520
-    }
521
-
522
-    /**
523
-     * Get a new query builder that doesn't have any global scopes.
524
-     *
525
-     * @return Query
526
-     */
527
-    public function newQueryWithoutScopes()
528
-    {
529
-        return $this->removeGlobalScopes($this->getQuery());
530
-    }
531
-
532
-    /**
533
-     * Remove all of the global scopes from an Analogue Query builder.
534
-     *
535
-     * @param Query $query
536
-     *
537
-     * @return \Analogue\ORM\System\Query
538
-     */
539
-    public function removeGlobalScopes($query)
540
-    {
541
-        foreach ($this->getGlobalScopes() as $scope) {
542
-            $scope->remove($query, $this);
543
-        }
544
-
545
-        return $query;
546
-    }
547
-
548
-    /**
549
-     * Return the manager instance.
550
-     *
551
-     * @return \Analogue\ORM\System\Manager
552
-     */
553
-    public function getManager()
554
-    {
555
-        return $this->manager;
556
-    }
557
-
558
-    /**
559
-     * Dynamically handle calls to custom commands, or Redirects to query().
560
-     *
561
-     * @param string $method
562
-     * @param array  $parameters
563
-     *
564
-     * @throws \Exception
565
-     *
566
-     * @return mixed
567
-     */
568
-    public function __call($method, $parameters)
569
-    {
570
-        // Check if method is a custom command on the mapper
571
-        if ($this->hasCustomCommand($method)) {
572
-            if (count($parameters) == 0) {
573
-                throw new \Exception("$method must at least have 1 argument");
574
-            }
575
-
576
-            return $this->executeCustomCommand($method, $parameters[0]);
577
-        }
578
-
579
-        // Redirect call on a new query instance
580
-        return call_user_func_array([$this->query(), $method], $parameters);
581
-    }
582
-
583
-    /**
584
-     * Check if this mapper supports this command.
585
-     *
586
-     * @param string $command
587
-     *
588
-     * @return bool
589
-     */
590
-    public function hasCustomCommand($command)
591
-    {
592
-        return in_array($command, $this->getCustomCommands());
593
-    }
594
-
595
-    /**
596
-     * Get all the custom commands registered on this mapper.
597
-     *
598
-     * @return array
599
-     */
600
-    public function getCustomCommands()
601
-    {
602
-        return array_keys($this->customCommands);
603
-    }
604
-
605
-    /**
606
-     * Execute a custom command on an Entity.
607
-     *
608
-     * @param string                 $command
609
-     * @param mixed|Collection|array $entity
610
-     *
611
-     * @throws \InvalidArgumentException
612
-     * @throws MappingException
613
-     *
614
-     * @return mixed
615
-     */
616
-    public function executeCustomCommand($command, $entity)
617
-    {
618
-        $commandClass = $this->customCommands[$command];
619
-
620
-        if ($this->manager->isTraversable($entity)) {
621
-            foreach ($entity as $instance) {
622
-                $this->executeSingleCustomCommand($commandClass, $instance);
623
-            }
624
-        } else {
625
-            return $this->executeSingleCustomCommand($commandClass, $entity);
626
-        }
627
-    }
628
-
629
-    /**
630
-     * Execute a single command instance.
631
-     *
632
-     * @param string $commandClass
633
-     * @param mixed  $entity
634
-     *
635
-     * @throws \InvalidArgumentException
636
-     * @throws MappingException
637
-     *
638
-     * @return mixed
639
-     */
640
-    protected function executeSingleCustomCommand($commandClass, $entity)
641
-    {
642
-        $this->checkEntityType($entity);
643
-
644
-        $instance = new $commandClass($this->aggregate($entity), $this->newQueryBuilder());
645
-
646
-        return $instance->execute();
647
-    }
648
-
649
-    /**
650
-     * Get the Analogue Query Builder for this instance.
651
-     *
652
-     * @return \Analogue\ORM\System\Query
653
-     */
654
-    public function query()
655
-    {
656
-        return $this->getQuery();
657
-    }
28
+	/**
29
+	 * The Manager instance.
30
+	 *
31
+	 * @var \Analogue\ORM\System\Manager
32
+	 */
33
+	protected $manager;
34
+
35
+	/**
36
+	 * Instance of EntityMapper Object.
37
+	 *
38
+	 * @var \Analogue\ORM\EntityMap
39
+	 */
40
+	protected $entityMap;
41
+
42
+	/**
43
+	 * The instance of db adapter.
44
+	 *
45
+	 * @var \Analogue\ORM\Drivers\DBAdapter
46
+	 */
47
+	protected $adapter;
48
+
49
+	/**
50
+	 * Event dispatcher instance.
51
+	 *
52
+	 * @var \Illuminate\Contracts\Events\Dispatcher
53
+	 */
54
+	protected $dispatcher;
55
+
56
+	/**
57
+	 * Entity Cache.
58
+	 *
59
+	 * @var \Analogue\ORM\System\EntityCache
60
+	 */
61
+	protected $cache;
62
+
63
+	/**
64
+	 * Global scopes.
65
+	 *
66
+	 * @var array
67
+	 */
68
+	protected $globalScopes = [];
69
+
70
+	/**
71
+	 * Custom Commands.
72
+	 *
73
+	 * @var array
74
+	 */
75
+	protected $customCommands = [];
76
+
77
+	/**
78
+	 * @param EntityMap  $entityMap
79
+	 * @param DBAdapter  $adapter
80
+	 * @param Dispatcher $dispatcher
81
+	 * @param Manager    $manager
82
+	 */
83
+	public function __construct(EntityMap $entityMap, DBAdapter $adapter, Dispatcher $dispatcher, Manager $manager)
84
+	{
85
+		$this->entityMap = $entityMap;
86
+
87
+		$this->adapter = $adapter;
88
+
89
+		$this->dispatcher = $dispatcher;
90
+
91
+		$this->manager = $manager;
92
+
93
+		$this->cache = new EntityCache($entityMap);
94
+	}
95
+
96
+	/**
97
+	 * Map results to a Collection.
98
+	 *
99
+	 * @param array|Collection $results
100
+	 *
101
+	 * @return Collection
102
+	 */
103
+	public function map($results, array $eagerLoads = []) : Collection
104
+	{
105
+		$builder = new ResultBuilder($this);
106
+
107
+		if ($results instanceof collection) {
108
+			// Get underlying collection array
109
+			$results = $results->all();
110
+		}
111
+
112
+		if (!is_array($results)) {
113
+			throw new InvalidArgumentException("'results' should be an array or collection.");
114
+		}
115
+
116
+		// First, we'll cast every single result to array
117
+		$results = array_map(function ($item) {
118
+			return (array) $item;
119
+		}, $results);
120
+
121
+		// Then, we'll cache every single results as raw attributes, before
122
+		// adding relationships, which will be cached when the relationship's
123
+		// query takes place.
124
+		$this->getEntityCache()->add($results);
125
+
126
+		$entities = $builder->build($results, $eagerLoads);
127
+
128
+		return $this->entityMap->newCollection($entities);
129
+	}
130
+
131
+	/**
132
+	 * Return all records for a mapped object.
133
+	 *
134
+	 * @return EntityCollection
135
+	 */
136
+	public function all()
137
+	{
138
+		return $this->query()->get();
139
+	}
140
+
141
+	/**
142
+	 * Persist an entity or an entity collection into the database.
143
+	 *
144
+	 * @param Mappable|\Traversable|array $entity
145
+	 *
146
+	 * @throws \InvalidArgumentException
147
+	 * @throws MappingException
148
+	 *
149
+	 * @return Mappable|\Traversable|array
150
+	 */
151
+	public function store($entity)
152
+	{
153
+		if ($this->manager->isTraversable($entity)) {
154
+			return $this->storeCollection($entity);
155
+		} else {
156
+			return $this->storeEntity($entity);
157
+		}
158
+	}
159
+
160
+	/**
161
+	 * Store an entity collection inside a single DB Transaction.
162
+	 *
163
+	 * @param \Traversable|array $entities
164
+	 *
165
+	 * @throws \InvalidArgumentException
166
+	 * @throws MappingException
167
+	 *
168
+	 * @return \Traversable|array
169
+	 */
170
+	protected function storeCollection($entities)
171
+	{
172
+		$this->adapter->beginTransaction();
173
+
174
+		foreach ($entities as $entity) {
175
+			$this->storeEntity($entity);
176
+		}
177
+
178
+		$this->adapter->commit();
179
+
180
+		return $entities;
181
+	}
182
+
183
+	/**
184
+	 * Store a single entity into the database.
185
+	 *
186
+	 * @param Mappable $entity
187
+	 *
188
+	 * @throws \InvalidArgumentException
189
+	 * @throws MappingException
190
+	 *
191
+	 * @return \Analogue\ORM\Entity
192
+	 */
193
+	protected function storeEntity($entity)
194
+	{   
195
+		$this->checkEntityType($entity);
196
+
197
+		$store = new Store($this->aggregate($entity), $this->newQueryBuilder());
198
+
199
+		return $store->execute();
200
+	}
201
+
202
+	/**
203
+	 * Check that the entity correspond to the current mapper.
204
+	 *
205
+	 * @param mixed $entity
206
+	 *
207
+	 * @throws InvalidArgumentException
208
+	 *
209
+	 * @return void
210
+	 */
211
+	protected function checkEntityType($entity)
212
+	{
213
+		if (get_class($entity) != $this->entityMap->getClass() && !is_subclass_of($entity, $this->entityMap->getClass())) {
214
+			$expected = $this->entityMap->getClass();
215
+			$actual = get_class($entity);
216
+			throw new InvalidArgumentException("Expected : $expected, got $actual.");
217
+		}
218
+	}
219
+
220
+	/**
221
+	 * Convert an entity into an aggregate root.
222
+	 *
223
+	 * @param mixed $entity
224
+	 *
225
+	 * @throws MappingException
226
+	 *
227
+	 * @return \Analogue\ORM\System\Aggregate
228
+	 */
229
+	protected function aggregate($entity)
230
+	{
231
+		return new Aggregate($entity);
232
+	}
233
+
234
+	/**
235
+	 * Get a the Underlying QueryAdapter.
236
+	 *
237
+	 * @return \Analogue\ORM\Drivers\QueryAdapter
238
+	 */
239
+	public function newQueryBuilder()
240
+	{
241
+		return $this->adapter->getQuery();
242
+	}
243
+
244
+	/**
245
+	 * Delete an entity or an entity collection from the database.
246
+	 *
247
+	 * @param  Mappable|\Traversable|array
248
+	 *
249
+	 * @throws MappingException
250
+	 * @throws \InvalidArgumentException
251
+	 *
252
+	 * @return \Traversable|array
253
+	 */
254
+	public function delete($entity)
255
+	{
256
+		if ($this->manager->isTraversable($entity)) {
257
+			return $this->deleteCollection($entity);
258
+		} else {
259
+			$this->deleteEntity($entity);
260
+		}
261
+	}
262
+
263
+	/**
264
+	 * Delete an Entity Collection inside a single db transaction.
265
+	 *
266
+	 * @param \Traversable|array $entities
267
+	 *
268
+	 * @throws \InvalidArgumentException
269
+	 * @throws MappingException
270
+	 *
271
+	 * @return \Traversable|array
272
+	 */
273
+	protected function deleteCollection($entities)
274
+	{
275
+		$this->adapter->beginTransaction();
276
+
277
+		foreach ($entities as $entity) {
278
+			$this->deleteEntity($entity);
279
+		}
280
+
281
+		$this->adapter->commit();
282
+
283
+		return $entities;
284
+	}
285
+
286
+	/**
287
+	 * Delete a single entity from the database.
288
+	 *
289
+	 * @param Mappable $entity
290
+	 *
291
+	 * @throws \InvalidArgumentException
292
+	 * @throws MappingException
293
+	 *
294
+	 * @return void
295
+	 */
296
+	protected function deleteEntity($entity)
297
+	{
298
+		$this->checkEntityType($entity);
299
+
300
+		$delete = new Delete($this->aggregate($entity), $this->newQueryBuilder());
301
+
302
+		$delete->execute();
303
+	}
304
+
305
+	/**
306
+	 * Return the entity map for this mapper.
307
+	 *
308
+	 * @return EntityMap
309
+	 */
310
+	public function getEntityMap()
311
+	{
312
+		return $this->entityMap;
313
+	}
314
+
315
+	/**
316
+	 * Get the entity cache for the current mapper.
317
+	 *
318
+	 * @return EntityCache $entityCache
319
+	 */
320
+	public function getEntityCache()
321
+	{
322
+		return $this->cache;
323
+	}
324
+
325
+	/**
326
+	 * Fire the given event for the entity.
327
+	 *
328
+	 * @param string               $event
329
+	 * @param \Analogue\ORM\Entity $entity
330
+	 * @param bool                 $halt
331
+	 *
332
+	 * @throws InvalidArgumentException
333
+	 *
334
+	 * @return mixed
335
+	 */
336
+	public function fireEvent($event, $entity, $halt = true)
337
+	{
338
+		if ($entity instanceof Wrapper) {
339
+			throw new InvalidArgumentException('Fired Event with invalid Entity Object');
340
+		}
341
+
342
+		$event = "analogue.{$event}.".$this->entityMap->getClass();
343
+
344
+		$method = $halt ? 'until' : 'fire';
345
+
346
+		return $this->dispatcher->$method($event, $entity);
347
+	}
348
+
349
+	/**
350
+	 * Register an entity event with the dispatcher.
351
+	 *
352
+	 * @param string   $event
353
+	 * @param \Closure $callback
354
+	 *
355
+	 * @return void
356
+	 */
357
+	public function registerEvent($event, $callback)
358
+	{
359
+		$name = $this->entityMap->getClass();
360
+
361
+		$this->dispatcher->listen("analogue.{$event}.{$name}", $callback);
362
+	}
363
+
364
+	/**
365
+	 * Add a global scope to this mapper query builder.
366
+	 *
367
+	 * @param ScopeInterface $scope
368
+	 *
369
+	 * @return void
370
+	 */
371
+	public function addGlobalScope(ScopeInterface $scope)
372
+	{
373
+		$this->globalScopes[get_class($scope)] = $scope;
374
+	}
375
+
376
+	/**
377
+	 * Determine if the mapper has a global scope.
378
+	 *
379
+	 * @param \Analogue\ORM\System\ScopeInterface $scope
380
+	 *
381
+	 * @return bool
382
+	 */
383
+	public function hasGlobalScope($scope)
384
+	{
385
+		return !is_null($this->getGlobalScope($scope));
386
+	}
387
+
388
+	/**
389
+	 * Get a global scope registered with the modal.
390
+	 *
391
+	 * @param \Analogue\ORM\System\ScopeInterface $scope
392
+	 *
393
+	 * @return \Analogue\ORM\System\ScopeInterface|null
394
+	 */
395
+	public function getGlobalScope($scope)
396
+	{
397
+		return array_first($this->globalScopes, function ($key, $value) use ($scope) {
398
+			return $scope instanceof $value;
399
+		});
400
+	}
401
+
402
+	/**
403
+	 * Get a new query instance without a given scope.
404
+	 *
405
+	 * @param \Analogue\ORM\System\ScopeInterface $scope
406
+	 *
407
+	 * @return \Analogue\ORM\System\Query
408
+	 */
409
+	public function newQueryWithoutScope($scope)
410
+	{
411
+		$this->getGlobalScope($scope)->remove($query = $this->getQuery(), $this);
412
+
413
+		return $query;
414
+	}
415
+
416
+	/**
417
+	 * Get the Analogue Query Builder for this instance.
418
+	 *
419
+	 * @return \Analogue\ORM\System\Query
420
+	 */
421
+	public function getQuery()
422
+	{
423
+		$query = new Query($this, $this->adapter);
424
+
425
+		return $this->applyGlobalScopes($query);
426
+	}
427
+
428
+	/**
429
+	 * Apply all of the global scopes to an Analogue Query builder.
430
+	 *
431
+	 * @param Query $query
432
+	 *
433
+	 * @return \Analogue\ORM\System\Query
434
+	 */
435
+	public function applyGlobalScopes($query)
436
+	{
437
+		foreach ($this->getGlobalScopes() as $scope) {
438
+			$scope->apply($query, $this);
439
+		}
440
+
441
+		return $query;
442
+	}
443
+
444
+	/**
445
+	 * Get the global scopes for this class instance.
446
+	 *
447
+	 * @return \Analogue\ORM\System\ScopeInterface
448
+	 */
449
+	public function getGlobalScopes()
450
+	{
451
+		return $this->globalScopes;
452
+	}
453
+
454
+	/**
455
+	 * Add a dynamic method that extends the mapper/repository.
456
+	 *
457
+	 * @param string $command
458
+	 */
459
+	public function addCustomCommand($command)
460
+	{
461
+		$name = lcfirst(class_basename($command));
462
+
463
+		$this->customCommands[$name] = $command;
464
+	}
465
+
466
+	/**
467
+	 * Create a new instance of the mapped entity class.
468
+	 *
469
+	 * @return mixed
470
+	 */
471
+	public function newInstance()
472
+	{
473
+		$class = $this->entityMap->getClass();
474
+
475
+		if ($this->entityMap->useDependencyInjection()) {
476
+			return $this->newInstanceUsingDependencyInjection($class);
477
+		}
478
+
479
+		return $this->newInstanceUsingInstantiator($class);
480
+	}
481
+
482
+	/**
483
+	 * Return a new object instance using dependency injection.
484
+	 *
485
+	 * @param string $class
486
+	 *
487
+	 * @return mixed
488
+	 */
489
+	protected function newInstanceUsingDependencyInjection($class)
490
+	{
491
+		if (!class_exists(Container::class)) {
492
+			throw new ErrorException("Illuminate\Container\Container is required to use Dependency Injection");
493
+		}
494
+
495
+		return Container::getInstance()->make($class);
496
+	}
497
+
498
+	/**
499
+	 * Return a new object instance using doctrine's instantiator.
500
+	 *
501
+	 * @param string $class
502
+	 *
503
+	 * @return mixed
504
+	 */
505
+	protected function newInstanceUsingInstantiator($class)
506
+	{
507
+		$instantiator = new \Doctrine\Instantiator\Instantiator();
508
+
509
+		return $instantiator->instantiate($class);
510
+	}
511
+
512
+	/**
513
+	 * Get an unscoped Analogue Query Builder for this instance.
514
+	 *
515
+	 * @return \Analogue\ORM\System\Query
516
+	 */
517
+	public function globalQuery()
518
+	{
519
+		return $this->newQueryWithoutScopes();
520
+	}
521
+
522
+	/**
523
+	 * Get a new query builder that doesn't have any global scopes.
524
+	 *
525
+	 * @return Query
526
+	 */
527
+	public function newQueryWithoutScopes()
528
+	{
529
+		return $this->removeGlobalScopes($this->getQuery());
530
+	}
531
+
532
+	/**
533
+	 * Remove all of the global scopes from an Analogue Query builder.
534
+	 *
535
+	 * @param Query $query
536
+	 *
537
+	 * @return \Analogue\ORM\System\Query
538
+	 */
539
+	public function removeGlobalScopes($query)
540
+	{
541
+		foreach ($this->getGlobalScopes() as $scope) {
542
+			$scope->remove($query, $this);
543
+		}
544
+
545
+		return $query;
546
+	}
547
+
548
+	/**
549
+	 * Return the manager instance.
550
+	 *
551
+	 * @return \Analogue\ORM\System\Manager
552
+	 */
553
+	public function getManager()
554
+	{
555
+		return $this->manager;
556
+	}
557
+
558
+	/**
559
+	 * Dynamically handle calls to custom commands, or Redirects to query().
560
+	 *
561
+	 * @param string $method
562
+	 * @param array  $parameters
563
+	 *
564
+	 * @throws \Exception
565
+	 *
566
+	 * @return mixed
567
+	 */
568
+	public function __call($method, $parameters)
569
+	{
570
+		// Check if method is a custom command on the mapper
571
+		if ($this->hasCustomCommand($method)) {
572
+			if (count($parameters) == 0) {
573
+				throw new \Exception("$method must at least have 1 argument");
574
+			}
575
+
576
+			return $this->executeCustomCommand($method, $parameters[0]);
577
+		}
578
+
579
+		// Redirect call on a new query instance
580
+		return call_user_func_array([$this->query(), $method], $parameters);
581
+	}
582
+
583
+	/**
584
+	 * Check if this mapper supports this command.
585
+	 *
586
+	 * @param string $command
587
+	 *
588
+	 * @return bool
589
+	 */
590
+	public function hasCustomCommand($command)
591
+	{
592
+		return in_array($command, $this->getCustomCommands());
593
+	}
594
+
595
+	/**
596
+	 * Get all the custom commands registered on this mapper.
597
+	 *
598
+	 * @return array
599
+	 */
600
+	public function getCustomCommands()
601
+	{
602
+		return array_keys($this->customCommands);
603
+	}
604
+
605
+	/**
606
+	 * Execute a custom command on an Entity.
607
+	 *
608
+	 * @param string                 $command
609
+	 * @param mixed|Collection|array $entity
610
+	 *
611
+	 * @throws \InvalidArgumentException
612
+	 * @throws MappingException
613
+	 *
614
+	 * @return mixed
615
+	 */
616
+	public function executeCustomCommand($command, $entity)
617
+	{
618
+		$commandClass = $this->customCommands[$command];
619
+
620
+		if ($this->manager->isTraversable($entity)) {
621
+			foreach ($entity as $instance) {
622
+				$this->executeSingleCustomCommand($commandClass, $instance);
623
+			}
624
+		} else {
625
+			return $this->executeSingleCustomCommand($commandClass, $entity);
626
+		}
627
+	}
628
+
629
+	/**
630
+	 * Execute a single command instance.
631
+	 *
632
+	 * @param string $commandClass
633
+	 * @param mixed  $entity
634
+	 *
635
+	 * @throws \InvalidArgumentException
636
+	 * @throws MappingException
637
+	 *
638
+	 * @return mixed
639
+	 */
640
+	protected function executeSingleCustomCommand($commandClass, $entity)
641
+	{
642
+		$this->checkEntityType($entity);
643
+
644
+		$instance = new $commandClass($this->aggregate($entity), $this->newQueryBuilder());
645
+
646
+		return $instance->execute();
647
+	}
648
+
649
+	/**
650
+	 * Get the Analogue Query Builder for this instance.
651
+	 *
652
+	 * @return \Analogue\ORM\System\Query
653
+	 */
654
+	public function query()
655
+	{
656
+		return $this->getQuery();
657
+	}
658 658
 }
Please login to merge, or discard this patch.
src/System/Proxies/CollectionProxy.php 1 patch
Indentation   +1310 added lines, -1310 removed lines patch added patch discarded remove patch
@@ -9,1314 +9,1314 @@
 block discarded – undo
9 9
 
10 10
 class CollectionProxy extends EntityCollection implements ProxyInterface
11 11
 {
12
-    /**
13
-     * Indicate if the relationship has been lazy loaded.
14
-     *
15
-     * @var bool
16
-     */
17
-    protected $relationshipLoaded = false;
18
-
19
-    protected $addedItems = [];
20
-
21
-    /**
22
-     * Create a new collection.
23
-     *
24
-     * @param mixed  $entity
25
-     * @param string $relation
26
-     *
27
-     * @return void
28
-     */
29
-    public function __construct($entity, $relation)
30
-    {
31
-        $this->parentEntity = $entity;
32
-        $this->relationshipMethod = $relation;
33
-    }
34
-
35
-    /**
36
-     * Return Items that has been added without lady loading
37
-     * the underlying collection.
38
-     *
39
-     * @return array
40
-     */
41
-    public function getAddedItems()
42
-    {
43
-        return $this->addedItems;
44
-    }
45
-
46
-    /**
47
-     * Force initialization of the proxy.
48
-     *
49
-     * @return bool true if the proxy could be initialized
50
-     */
51
-    public function initializeProxy() : bool
52
-    {
53
-        if ($this->isProxyInitialized()) {
54
-            return true;
55
-        }
56
-
57
-        $relation = $this->relationshipMethod;
58
-        $entity = $this->parentEntity;
59
-
60
-        $entityMap = Manager::getMapper($entity)->getEntityMap();
61
-
62
-        $this->items = $entityMap->$relation($entity)->getResults($relation)->all() + $this->addedItems;
63
-
64
-        $this->relationshipLoaded = true;
65
-
66
-        return true;
67
-    }
68
-
69
-    /**
70
-     * Retrieves current initialization status of the proxy.
71
-     *
72
-     * @return bool
73
-     */
74
-    public function isProxyInitialized() : bool
75
-    {
76
-        return $this->relationshipLoaded;
77
-    }
78
-
79
-    /**
80
-     * Get all of the items in the collection.
81
-     *
82
-     * @return array
83
-     */
84
-    public function all()
85
-    {
86
-        $this->initializeProxy();
87
-
88
-        return parent::all();
89
-    }
90
-
91
-    /**
92
-     * Get the average value of a given key.
93
-     *
94
-     * @param callable|string|null $callback
95
-     *
96
-     * @return mixed
97
-     */
98
-    public function avg($callback = null)
99
-    {
100
-        $this->initializeProxy();
101
-
102
-        return parent::avg($callback);
103
-    }
104
-
105
-    /**
106
-     * Get the median of a given key.
107
-     *
108
-     * @param null $key
109
-     *
110
-     * @return mixed|null
111
-     */
112
-    public function median($key = null)
113
-    {
114
-        $this->initializeProxy();
115
-
116
-        return parent::median($key);
117
-    }
118
-
119
-    /**
120
-     * Get the mode of a given key.
121
-     *
122
-     * @param mixed $key
123
-     *
124
-     * @return array
125
-     */
126
-    public function mode($key = null)
127
-    {
128
-        $this->initializeProxy();
129
-
130
-        return parent::mode($key);
131
-    }
132
-
133
-    /**
134
-     * Collapse the collection of items into a single array.
135
-     *
136
-     * @return static
137
-     */
138
-    public function collapse()
139
-    {
140
-        $this->initializeProxy();
141
-
142
-        return parent::collapse();
143
-    }
144
-
145
-    /**
146
-     * Determine if an item exists in the collection.
147
-     *
148
-     * @param mixed $key
149
-     * @param mixed $value
150
-     *
151
-     * @return bool
152
-     */
153
-    public function contains($key, $operator = null, $value = null)
154
-    {
155
-        $this->initializeProxy();
156
-
157
-        return parent::contains($key, $operator, $value);
158
-    }
159
-
160
-    /**
161
-     * Determine if an item exists in the collection using strict comparison.
162
-     *
163
-     * @param mixed $key
164
-     * @param mixed $value
165
-     *
166
-     * @return bool
167
-     */
168
-    public function containsStrict($key, $value = null)
169
-    {
170
-        $this->initializeProxy();
171
-
172
-        return parent::containsStrict($key, $value);
173
-    }
174
-
175
-    /**
176
-     * Get the items in the collection that are not present in the given items.
177
-     *
178
-     * @param mixed $items
179
-     *
180
-     * @return static
181
-     */
182
-    public function diff($items)
183
-    {
184
-        $this->initializeProxy();
185
-
186
-        return parent::diff($items);
187
-    }
188
-
189
-    /**
190
-     * Get the items in the collection whose keys are not present in the given items.
191
-     *
192
-     * @param mixed $items
193
-     *
194
-     * @return static
195
-     */
196
-    public function diffKeys($items)
197
-    {
198
-        $this->initializeProxy();
199
-
200
-        return parent::diffKeys($items);
201
-    }
202
-
203
-    /**
204
-     * Execute a callback over each item.
205
-     *
206
-     * @param callable $callback
207
-     *
208
-     * @return $this
209
-     */
210
-    public function each(callable $callback)
211
-    {
212
-        $this->initializeProxy();
213
-
214
-        return parent::each($callback);
215
-    }
216
-
217
-    /**
218
-     * Create a new collection consisting of every n-th element.
219
-     *
220
-     * @param int $step
221
-     * @param int $offset
222
-     *
223
-     * @return static
224
-     */
225
-    public function every($key, $operator = null, $value = null)
226
-    {
227
-        $this->initializeProxy();
228
-
229
-        return parent::every($key, $operator, $value);
230
-    }
231
-
232
-    /**
233
-     * Get all items except for those with the specified keys.
234
-     *
235
-     * @param mixed $keys
236
-     *
237
-     * @return static
238
-     */
239
-    public function except($keys)
240
-    {
241
-        $this->initializeProxy();
242
-
243
-        return parent::except($keys);
244
-    }
245
-
246
-    /**
247
-     * Run a filter over each of the items.
248
-     *
249
-     * @param callable|null $callback
250
-     *
251
-     * @return static
252
-     */
253
-    public function filter(callable $callback = null)
254
-    {
255
-        $this->initializeProxy();
256
-
257
-        return parent::filter($callback);
258
-    }
259
-
260
-    /**
261
-     * Filter items by the given key value pair.
262
-     *
263
-     * @param string $key
264
-     * @param mixed  $operator
265
-     * @param mixed  $value
266
-     *
267
-     * @return static
268
-     */
269
-    public function where($key, $operator, $value = null)
270
-    {
271
-        $this->initializeProxy();
272
-
273
-        return parent::where($key, $operator, $value);
274
-    }
275
-
276
-    /**
277
-     * Filter items by the given key value pair using strict comparison.
278
-     *
279
-     * @param string $key
280
-     * @param mixed  $value
281
-     *
282
-     * @return static
283
-     */
284
-    public function whereStrict($key, $value)
285
-    {
286
-        $this->initializeProxy();
287
-
288
-        return parent::whereStrict($key, $value);
289
-    }
290
-
291
-    /**
292
-     * Filter items by the given key value pair.
293
-     *
294
-     * @param string $key
295
-     * @param mixed  $values
296
-     * @param bool   $strict
297
-     *
298
-     * @return static
299
-     */
300
-    public function whereIn($key, $values, $strict = false)
301
-    {
302
-        $this->initializeProxy();
303
-
304
-        return parent::whereIn($key, $values, $strict);
305
-    }
306
-
307
-    /**
308
-     * Filter items by the given key value pair using strict comparison.
309
-     *
310
-     * @param string $key
311
-     * @param mixed  $values
312
-     *
313
-     * @return static
314
-     */
315
-    public function whereInStrict($key, $values)
316
-    {
317
-        $this->initializeProxy();
318
-
319
-        return parent::whereInStrict($key, $values);
320
-    }
321
-
322
-    /**
323
-     * Get the first item from the collection.
324
-     *
325
-     * @param callable|null $callback
326
-     * @param mixed         $default
327
-     *
328
-     * @return mixed
329
-     */
330
-    public function first(callable $callback = null, $default = null)
331
-    {
332
-        // TODO Consider partial loading
333
-        $this->initializeProxy();
334
-
335
-        return parent::first($callback, $default);
336
-    }
337
-
338
-    /**
339
-     * Get a flattened array of the items in the collection.
340
-     *
341
-     * @param int $depth
342
-     *
343
-     * @return static
344
-     */
345
-    public function flatten($depth = INF)
346
-    {
347
-        $this->initializeProxy();
348
-
349
-        return parent::flatten($depth);
350
-    }
351
-
352
-    /**
353
-     * Flip the items in the collection.
354
-     *
355
-     * @return static
356
-     */
357
-    public function flip()
358
-    {
359
-        $this->initializeProxy();
360
-
361
-        return parent::flip();
362
-    }
363
-
364
-    /**
365
-     * Remove an item from the collection by key.
366
-     *
367
-     * @param string|array $keys
368
-     *
369
-     * @return $this
370
-     */
371
-    public function forget($keys)
372
-    {
373
-        // TODO, we could consider these as
374
-        // 'pending deletion', the same way that
375
-        // we treat added items
376
-        $this->initializeProxy();
377
-
378
-        return parent::forget($keys);
379
-    }
380
-
381
-    /**
382
-     * Get an item from the collection by key.
383
-     *
384
-     * @param mixed $key
385
-     * @param mixed $default
386
-     *
387
-     * @return mixed
388
-     */
389
-    public function get($key, $default = null)
390
-    {
391
-        // TODO : We could also consider partial loading
392
-        // here
393
-        $this->initializeProxy();
394
-
395
-        return parent::get($key, $default);
396
-    }
397
-
398
-    /**
399
-     * Group an associative array by a field or using a callback.
400
-     *
401
-     * @param callable|string $groupBy
402
-     * @param bool            $preserveKeys
403
-     *
404
-     * @return static
405
-     */
406
-    public function groupBy($groupBy, $preserveKeys = false)
407
-    {
408
-        $this->initializeProxy();
409
-
410
-        return parent::groupBy($groupBy, $preserveKeys);
411
-    }
412
-
413
-    /**
414
-     * Key an associative array by a field or using a callback.
415
-     *
416
-     * @param callable|string $keyBy
417
-     *
418
-     * @return static
419
-     */
420
-    public function keyBy($keyBy)
421
-    {
422
-        $this->initializeProxy();
423
-
424
-        return parent::keyBy($keyBy);
425
-    }
426
-
427
-    /**
428
-     * Determine if an item exists in the collection by key.
429
-     *
430
-     * @param mixed $key
431
-     *
432
-     * @return bool
433
-     */
434
-    public function has($key)
435
-    {
436
-        // TODO : we could do automagic here by directly
437
-        // calling the database if the collection hasn't
438
-        // been initialized yet.
439
-        // Potential issue is that several calls to this
440
-        // could cause a lot queries vs a single get query.
441
-        $this->initializeProxy();
442
-
443
-        return parent::has($key);
444
-    }
445
-
446
-    /**
447
-     * Concatenate values of a given key as a string.
448
-     *
449
-     * @param string $value
450
-     * @param string $glue
451
-     *
452
-     * @return string
453
-     */
454
-    public function implode($value, $glue = null)
455
-    {
456
-        $this->initializeProxy();
457
-
458
-        return parent::implode($value, $glue);
459
-    }
460
-
461
-    /**
462
-     * Intersect the collection with the given items.
463
-     *
464
-     * @param mixed $items
465
-     *
466
-     * @return static
467
-     */
468
-    public function intersect($items)
469
-    {
470
-        $this->initializeProxy();
471
-
472
-        return parent::intersect($items);
473
-    }
474
-
475
-    /**
476
-     * Determine if the collection is empty or not.
477
-     *
478
-     * @return bool
479
-     */
480
-    public function isEmpty()
481
-    {
482
-        $this->initializeProxy();
483
-
484
-        return parent::isEmpty();
485
-    }
486
-
487
-    /**
488
-     * Get the keys of the collection items.
489
-     *
490
-     * @return static
491
-     */
492
-    public function keys()
493
-    {
494
-        $this->initializeProxy();
495
-
496
-        return parent::keys();
497
-    }
498
-
499
-    /**
500
-     * Get the last item from the collection.
501
-     *
502
-     * @param callable|null $callback
503
-     * @param mixed         $default
504
-     *
505
-     * @return mixed
506
-     */
507
-    public function last(callable $callback = null, $default = null)
508
-    {
509
-        // TODO : we could do partial loading there as well
510
-        $this->initializeProxy();
511
-
512
-        return parent::last($callback, $default);
513
-    }
514
-
515
-    /**
516
-     * Get the values of a given key.
517
-     *
518
-     * @param string      $value
519
-     * @param string|null $key
520
-     *
521
-     * @return static
522
-     */
523
-    public function pluck($value, $key = null)
524
-    {
525
-        // TODO : automagic call to QB if not initialized
526
-        $this->initializeProxy();
527
-
528
-        return parent::pluck($value, $key);
529
-    }
530
-
531
-    /**
532
-     * Run a map over each of the items.
533
-     *
534
-     * @param callable $callback
535
-     *
536
-     * @return static
537
-     */
538
-    public function map(callable $callback)
539
-    {
540
-        $this->initializeProxy();
541
-
542
-        return parent::map($callback);
543
-    }
544
-
545
-    /**
546
-     * Run an associative map over each of the items.
547
-     *
548
-     * The callback should return an associative array with a single key/value pair.
549
-     *
550
-     * @param callable $callback
551
-     *
552
-     * @return static
553
-     */
554
-    public function mapWithKeys(callable $callback)
555
-    {
556
-        $this->initializeProxy();
557
-
558
-        return parent::mapWithKeys($callback);
559
-    }
560
-
561
-    /**
562
-     * Map a collection and flatten the result by a single level.
563
-     *
564
-     * @param callable $callback
565
-     *
566
-     * @return static
567
-     */
568
-    public function flatMap(callable $callback)
569
-    {
570
-        $this->initializeProxy();
571
-
572
-        return parent::flatMap($callback);
573
-    }
574
-
575
-    /**
576
-     * Get the max value of a given key.
577
-     *
578
-     * @param callable|string|null $callback
579
-     *
580
-     * @return mixed
581
-     */
582
-    public function max($callback = null)
583
-    {
584
-        $this->initializeProxy();
585
-
586
-        return parent::max($callback);
587
-    }
588
-
589
-    /**
590
-     * Merge the collection with the given items.
591
-     *
592
-     * @param mixed $items
593
-     *
594
-     * @return static
595
-     */
596
-    public function merge($items)
597
-    {
598
-        // TODO : Check if the EntityCollection
599
-        // returns a native Collection, as it
600
-        // is what we want here
601
-        $this->initializeProxy();
602
-
603
-        return parent::merge($items);
604
-    }
605
-
606
-    /**
607
-     * Create a collection by using this collection for keys and another for its values.
608
-     *
609
-     * @param mixed $values
610
-     *
611
-     * @return static
612
-     */
613
-    public function combine($values)
614
-    {
615
-        // TODO : Check if the EntityCollection
616
-        // returns a native Collection, as it
617
-        // is what we want here
618
-        $this->initializeProxy();
619
-
620
-        return parent::combine($values);
621
-    }
622
-
623
-    /**
624
-     * Union the collection with the given items.
625
-     *
626
-     * @param mixed $items
627
-     *
628
-     * @return static
629
-     */
630
-    public function union($items)
631
-    {
632
-        // TODO : Check if the EntityCollection
633
-        // returns a native Collection, as it
634
-        // is what we want here
635
-        $this->initializeProxy();
636
-
637
-        return parent::union($items);
638
-    }
639
-
640
-    /**
641
-     * Get the min value of a given key.
642
-     *
643
-     * @param callable|string|null $callback
644
-     *
645
-     * @return mixed
646
-     */
647
-    public function min($callback = null)
648
-    {
649
-        // TODO : we could rely on the QB
650
-        // for thos, if initialization has not
651
-        // take place yet
652
-        $this->initializeProxy();
653
-
654
-        return parent::min($callback);
655
-    }
656
-
657
-    /**
658
-     * Create a new collection consisting of every n-th element.
659
-     *
660
-     * @param int $step
661
-     * @param int $offset
662
-     *
663
-     * @return static
664
-     */
665
-    public function nth($step, $offset = 0)
666
-    {
667
-        $this->initializeProxy();
668
-
669
-        return parent::nth($step, $offset);
670
-    }
671
-
672
-    /**
673
-     * Get the items with the specified keys.
674
-     *
675
-     * @param mixed $keys
676
-     *
677
-     * @return static
678
-     */
679
-    public function only($keys)
680
-    {
681
-        // TODO : we could rely on the QB if
682
-        // the collection hasn't been initialized yet
683
-        $this->initializeProxy();
684
-
685
-        return parent::only($keys);
686
-    }
687
-
688
-    /**
689
-     * "Paginate" the collection by slicing it into a smaller collection.
690
-     *
691
-     * @param int $page
692
-     * @param int $perPage
693
-     *
694
-     * @return static
695
-     */
696
-    public function forPage($page, $perPage)
697
-    {
698
-        // TODO : check possibility of partial loading
699
-        // if not initialized
700
-        $this->initializeProxy();
701
-
702
-        return parent::forPage($page, $perPage);
703
-    }
704
-
705
-    /**
706
-     * Partition the collection into two arrays using the given callback or key.
707
-     *
708
-     * @param callable|string $callback
709
-     *
710
-     * @return static
711
-     */
712
-    public function partition($callback)
713
-    {
714
-        $this->initializeProxy();
715
-
716
-        return parent::partition($callback);
717
-    }
718
-
719
-    /**
720
-     * Pass the collection to the given callback and return the result.
721
-     *
722
-     * @param callable $callback
723
-     *
724
-     * @return mixed
725
-     */
726
-    public function pipe(callable $callback)
727
-    {
728
-        $this->initializeProxy();
729
-
730
-        return parent::pipe($callback);
731
-    }
732
-
733
-    /**
734
-     * Get and remove the last item from the collection.
735
-     *
736
-     * @return mixed
737
-     */
738
-    public function pop()
739
-    {
740
-        $this->initializeProxy();
741
-
742
-        return parent::pop();
743
-    }
744
-
745
-    /**
746
-     * Push an item onto the beginning of the collection.
747
-     *
748
-     * @param mixed $value
749
-     * @param mixed $key
750
-     *
751
-     * @return $this
752
-     */
753
-    public function prepend($value, $key = null)
754
-    {
755
-        // TODO : partial adding of values.
756
-        // we could have a $prepended , and $pushed arrays
757
-        // which we would combine at full initialization
758
-
759
-        $this->initializeProxy();
760
-
761
-        return parent::prepend($value, $key);
762
-    }
763
-
764
-    /**
765
-     * Push an item onto the end of the collection.
766
-     *
767
-     * @param mixed $value
768
-     *
769
-     * @return $this
770
-     */
771
-    public function push($value)
772
-    {
773
-        // TODO : partial adding of values.
774
-        // we could have a $prepended , and $pushed arrays
775
-        // which we would combine at full initialization
776
-
777
-        $this->initializeProxy();
778
-
779
-        return parent::push($value);
780
-    }
781
-
782
-    /**
783
-     * Get and remove an item from the collection.
784
-     *
785
-     * @param mixed $key
786
-     * @param mixed $default
787
-     *
788
-     * @return mixed
789
-     */
790
-    public function pull($key, $default = null)
791
-    {
792
-        // TODO : QB query if the collection
793
-        // hasn't been initialized yet
794
-
795
-        $this->initializeProxy();
796
-
797
-        return parent::pull($key, $default);
798
-    }
799
-
800
-    /**
801
-     * Put an item in the collection by key.
802
-     *
803
-     * @param mixed $key
804
-     * @param mixed $value
805
-     *
806
-     * @return $this
807
-     */
808
-    public function put($key, $value)
809
-    {
810
-        // TODO : Partial loading ?
811
-
812
-        $this->initializeProxy();
813
-
814
-        return parent::put($key, $value);
815
-    }
816
-
817
-    /**
818
-     * Get one or more items randomly from the collection.
819
-     *
820
-     * @param int $amount
821
-     *
822
-     * @throws \InvalidArgumentException
823
-     *
824
-     * @return mixed
825
-     */
826
-    public function random($amount = 1)
827
-    {
828
-        // TODO : we could optimize this by only
829
-        // fetching the keys from the database
830
-        // and performing partial loading
831
-
832
-        $this->initializeProxy();
833
-
834
-        return parent::random($amount);
835
-    }
836
-
837
-    /**
838
-     * Reduce the collection to a single value.
839
-     *
840
-     * @param callable $callback
841
-     * @param mixed    $initial
842
-     *
843
-     * @return mixed
844
-     */
845
-    public function reduce(callable $callback, $initial = null)
846
-    {
847
-        $this->initializeProxy();
848
-
849
-        return parent::reduce($callback, $initial);
850
-    }
851
-
852
-    /**
853
-     * Create a collection of all elements that do not pass a given truth test.
854
-     *
855
-     * @param callable|mixed $callback
856
-     *
857
-     * @return static
858
-     */
859
-    public function reject($callback)
860
-    {
861
-        $this->initializeProxy();
862
-
863
-        return parent::reject($callback);
864
-    }
865
-
866
-    /**
867
-     * Reverse items order.
868
-     *
869
-     * @return static
870
-     */
871
-    public function reverse()
872
-    {
873
-        $this->initializeProxy();
874
-
875
-        return parent::reverse();
876
-    }
877
-
878
-    /**
879
-     * Search the collection for a given value and return the corresponding key if successful.
880
-     *
881
-     * @param mixed $value
882
-     * @param bool  $strict
883
-     *
884
-     * @return mixed
885
-     */
886
-    public function search($value, $strict = false)
887
-    {
888
-        $this->initializeProxy();
889
-
890
-        return parent::search($value, $strict);
891
-    }
892
-
893
-    /**
894
-     * Get and remove the first item from the collection.
895
-     *
896
-     * @return mixed
897
-     */
898
-    public function shift()
899
-    {
900
-        // Todo : Partial Removing
901
-        // we could have a pending removal array
902
-        $this->initializeProxy();
903
-
904
-        return parent::shift();
905
-    }
906
-
907
-    /**
908
-     * Shuffle the items in the collection.
909
-     *
910
-     * @param int $seed
911
-     *
912
-     * @return static
913
-     */
914
-    public function shuffle($seed = null)
915
-    {
916
-        $this->initializeProxy();
917
-
918
-        return parent::shuffle($seed);
919
-    }
920
-
921
-    /**
922
-     * Slice the underlying collection array.
923
-     *
924
-     * @param int $offset
925
-     * @param int $length
926
-     *
927
-     * @return static
928
-     */
929
-    public function slice($offset, $length = null)
930
-    {
931
-        $this->initializeProxy();
932
-
933
-        return parent::slice($offset, $length);
934
-    }
935
-
936
-    /**
937
-     * Split a collection into a certain number of groups.
938
-     *
939
-     * @param int $numberOfGroups
940
-     *
941
-     * @return static
942
-     */
943
-    public function split($numberOfGroups)
944
-    {
945
-        $this->initializeProxy();
946
-
947
-        return parent::split($numberOfGroups);
948
-    }
949
-
950
-    /**
951
-     * Chunk the underlying collection array.
952
-     *
953
-     * @param int $size
954
-     *
955
-     * @return static
956
-     */
957
-    public function chunk($size)
958
-    {
959
-        // TODO : partial loading ?
960
-        $this->initializeProxy();
961
-
962
-        return parent::chunk($size);
963
-    }
964
-
965
-    /**
966
-     * Sort through each item with a callback.
967
-     *
968
-     * @param callable|null $callback
969
-     *
970
-     * @return static
971
-     */
972
-    public function sort(callable $callback = null)
973
-    {
974
-        $this->initializeProxy();
975
-
976
-        return parent::sort($callback);
977
-    }
978
-
979
-    /**
980
-     * Sort the collection using the given callback.
981
-     *
982
-     * @param callable|string $callback
983
-     * @param int             $options
984
-     * @param bool            $descending
985
-     *
986
-     * @return static
987
-     */
988
-    public function sortBy($callback, $options = SORT_REGULAR, $descending = false)
989
-    {
990
-        $this->initializeProxy();
991
-
992
-        return parent::sort($callback, $options, $descending);
993
-    }
994
-
995
-    /**
996
-     * Splice a portion of the underlying collection array.
997
-     *
998
-     * @param int      $offset
999
-     * @param int|null $length
1000
-     * @param mixed    $replacement
1001
-     *
1002
-     * @return static
1003
-     */
1004
-    public function splice($offset, $length = null, $replacement = [])
1005
-    {
1006
-        $this->initializeProxy();
1007
-
1008
-        return parent::splice($offset, $length, $replacement);
1009
-    }
1010
-
1011
-    /**
1012
-     * Get the sum of the given values.
1013
-     *
1014
-     * @param callable|string|null $callback
1015
-     *
1016
-     * @return mixed
1017
-     */
1018
-    public function sum($callback = null)
1019
-    {
1020
-        $this->initializeProxy();
1021
-
1022
-        return parent::sum($callback);
1023
-    }
1024
-
1025
-    /**
1026
-     * Take the first or last {$limit} items.
1027
-     *
1028
-     * @param int $limit
1029
-     *
1030
-     * @return static
1031
-     */
1032
-    public function take($limit)
1033
-    {
1034
-        // TODO: partial loading
1035
-        $this->initializeProxy();
1036
-
1037
-        return parent::take($limit);
1038
-    }
1039
-
1040
-    /**
1041
-     * Pass the collection to the given callback and then return it.
1042
-     *
1043
-     * @param callable $callback
1044
-     *
1045
-     * @return $this
1046
-     */
1047
-    public function tap(callable $callback)
1048
-    {
1049
-        $this->initializeProxy();
1050
-
1051
-        return parent::tap($this);
1052
-    }
1053
-
1054
-    /**
1055
-     * Transform each item in the collection using a callback.
1056
-     *
1057
-     * @param callable $callback
1058
-     *
1059
-     * @return $this
1060
-     */
1061
-    public function transform(callable $callback)
1062
-    {
1063
-        $this->initializeProxy();
1064
-
1065
-        return parent::transform($callback);
1066
-    }
1067
-
1068
-    /**
1069
-     * Return only unique items from the collection array.
1070
-     *
1071
-     * @param string|callable|null $key
1072
-     * @param bool                 $strict
1073
-     *
1074
-     * @return static
1075
-     */
1076
-    public function unique($key = null, $strict = false)
1077
-    {
1078
-        $this->initializeProxy();
1079
-
1080
-        return parent::unique($key, $strict);
1081
-    }
1082
-
1083
-    /**
1084
-     * Reset the keys on the underlying array.
1085
-     *
1086
-     * @return static
1087
-     */
1088
-    public function values()
1089
-    {
1090
-        $this->initializeProxy();
1091
-
1092
-        return parent::values();
1093
-    }
1094
-
1095
-    /**
1096
-     * Apply the callback if the value is truthy.
1097
-     *
1098
-     * @param  bool  $value
1099
-     * @param  callable  $callback
1100
-     * @return mixed
1101
-     */
1102
-    public function when($value, callable $callback)
1103
-    {
1104
-        $this->initializeProxy();
1105
-
1106
-        return parent::when($value, $callback);
1107
-    }
1108
-
1109
-    /**
1110
-     * Zip the collection together with one or more arrays.
1111
-     *
1112
-     * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]);
1113
-     *      => [[1, 4], [2, 5], [3, 6]]
1114
-     *
1115
-     * @param mixed ...$items
1116
-     *
1117
-     * @return static
1118
-     */
1119
-    public function zip($items)
1120
-    {
1121
-        $this->initializeProxy();
1122
-
1123
-        return parent::zip($items);
1124
-    }
1125
-
1126
-    /**
1127
-     * Get the collection of items as a plain array.
1128
-     *
1129
-     * @return array
1130
-     */
1131
-    public function toArray()
1132
-    {
1133
-        // If this is called on all subsequent proxy,
1134
-        // this would eventually trigger all lazy loading,
1135
-        // which is NOT what we would expect...
1136
-        // TODO : must think of this.
1137
-        $this->initializeProxy();
1138
-
1139
-        return parent::toArray();
1140
-    }
1141
-
1142
-    /**
1143
-     * Convert the object into something JSON serializable.
1144
-     *
1145
-     * @return array
1146
-     */
1147
-    public function jsonSerialize()
1148
-    {
1149
-        // If this is called on all subsequent proxy,
1150
-        // this would eventually trigger all lazy loading,
1151
-        // which is NOT what we would expect...
1152
-        // TODO : must think of this.
1153
-        $this->initializeProxy();
1154
-
1155
-        return parent::jsonSerialize();
1156
-    }
1157
-
1158
-    /**
1159
-     * Get the collection of items as JSON.
1160
-     *
1161
-     * @param int $options
1162
-     *
1163
-     * @return string
1164
-     */
1165
-    public function toJson($options = 0)
1166
-    {
1167
-        // If this is called on all subsequent proxy,
1168
-        // this would eventually trigger all lazy loading,
1169
-        // which is NOT what we would expect...
1170
-        // TODO : must think of this.
1171
-        $this->initializeProxy();
1172
-
1173
-        return parent::toJson($options);
1174
-    }
1175
-
1176
-    /**
1177
-     * Get an iterator for the items.
1178
-     *
1179
-     * @return \ArrayIterator
1180
-     */
1181
-    public function getIterator()
1182
-    {
1183
-        $this->initializeProxy();
1184
-
1185
-        return parent::getIterator();
1186
-    }
1187
-
1188
-    /**
1189
-     * Get a CachingIterator instance.
1190
-     *
1191
-     * @param int $flags
1192
-     *
1193
-     * @return \CachingIterator
1194
-     */
1195
-    public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING)
1196
-    {
1197
-        $this->initializeProxy();
1198
-
1199
-        return parent::getCachingIterator($flags);
1200
-    }
1201
-
1202
-    /**
1203
-     * Count the number of items in the collection.
1204
-     *
1205
-     * @return int
1206
-     */
1207
-    public function count()
1208
-    {
1209
-        // TODO rely on QB if not initialized
1210
-        $this->initializeProxy();
1211
-
1212
-        return parent::count();
1213
-    }
1214
-
1215
-    /**
1216
-     * Get a base Support collection instance from this collection.
1217
-     *
1218
-     * @return \Illuminate\Support\Collection
1219
-     */
1220
-    public function toBase()
1221
-    {
1222
-        $this->initializeProxy();
1223
-
1224
-        return parent::toBase();
1225
-    }
1226
-
1227
-    /**
1228
-     * Determine if an item exists at an offset.
1229
-     *
1230
-     * @param mixed $key
1231
-     *
1232
-     * @return bool
1233
-     */
1234
-    public function offsetExists($key)
1235
-    {
1236
-        // TODO rely on QB if no collection
1237
-        // initialized
1238
-        $this->initializeProxy();
1239
-
1240
-        return parent::offsetExists($key);
1241
-    }
1242
-
1243
-    /**
1244
-     * Get an item at a given offset.
1245
-     *
1246
-     * @param mixed $key
1247
-     *
1248
-     * @return mixed
1249
-     */
1250
-    public function offsetGet($key)
1251
-    {
1252
-        // TODO rely on partial init if no collection
1253
-        // initialized
1254
-        $this->initializeProxy();
1255
-
1256
-        return parent::offsetGet($key);
1257
-    }
1258
-
1259
-    /**
1260
-     * Set the item at a given offset.
1261
-     *
1262
-     * @param mixed $key
1263
-     * @param mixed $value
1264
-     *
1265
-     * @return void
1266
-     */
1267
-    public function offsetSet($key, $value)
1268
-    {
1269
-        // TODO : think of the use of it into a ProxyCollection
1270
-        // context
1271
-        $this->initializeProxy();
1272
-
1273
-        return parent::offsetSet($key, $value);
1274
-    }
1275
-
1276
-    /**
1277
-     * Unset the item at a given offset.
1278
-     *
1279
-     * @param string $key
1280
-     *
1281
-     * @return void
1282
-     */
1283
-    public function offsetUnset($key)
1284
-    {
1285
-        // TODO : think of the use of it into a ProxyCollection
1286
-        // context
1287
-        $this->initializeProxy();
1288
-
1289
-        return parent::offsetUnset($key);
1290
-    }
1291
-
1292
-    /**
1293
-     * Dynamically access collection proxies.
1294
-     *
1295
-     * @param string $key
1296
-     *
1297
-     * @throws \Exception
1298
-     *
1299
-     * @return mixed
1300
-     */
1301
-    public function __get($key)
1302
-    {
1303
-        parent::__get($key);
1304
-    }
1305
-
1306
-    /**
1307
-     * Dynamically handle calls to the class.
1308
-     *
1309
-     * @param string $method
1310
-     * @param array  $parameters
1311
-     *
1312
-     * @throws \BadMethodCallException
1313
-     *
1314
-     * @return mixed
1315
-     */
1316
-    public function __call($method, $parameters)
1317
-    {
1318
-        $this->initializeProxy();
1319
-
1320
-        return parent::__call($method, $parameters);
1321
-    }
12
+	/**
13
+	 * Indicate if the relationship has been lazy loaded.
14
+	 *
15
+	 * @var bool
16
+	 */
17
+	protected $relationshipLoaded = false;
18
+
19
+	protected $addedItems = [];
20
+
21
+	/**
22
+	 * Create a new collection.
23
+	 *
24
+	 * @param mixed  $entity
25
+	 * @param string $relation
26
+	 *
27
+	 * @return void
28
+	 */
29
+	public function __construct($entity, $relation)
30
+	{
31
+		$this->parentEntity = $entity;
32
+		$this->relationshipMethod = $relation;
33
+	}
34
+
35
+	/**
36
+	 * Return Items that has been added without lady loading
37
+	 * the underlying collection.
38
+	 *
39
+	 * @return array
40
+	 */
41
+	public function getAddedItems()
42
+	{
43
+		return $this->addedItems;
44
+	}
45
+
46
+	/**
47
+	 * Force initialization of the proxy.
48
+	 *
49
+	 * @return bool true if the proxy could be initialized
50
+	 */
51
+	public function initializeProxy() : bool
52
+	{
53
+		if ($this->isProxyInitialized()) {
54
+			return true;
55
+		}
56
+
57
+		$relation = $this->relationshipMethod;
58
+		$entity = $this->parentEntity;
59
+
60
+		$entityMap = Manager::getMapper($entity)->getEntityMap();
61
+
62
+		$this->items = $entityMap->$relation($entity)->getResults($relation)->all() + $this->addedItems;
63
+
64
+		$this->relationshipLoaded = true;
65
+
66
+		return true;
67
+	}
68
+
69
+	/**
70
+	 * Retrieves current initialization status of the proxy.
71
+	 *
72
+	 * @return bool
73
+	 */
74
+	public function isProxyInitialized() : bool
75
+	{
76
+		return $this->relationshipLoaded;
77
+	}
78
+
79
+	/**
80
+	 * Get all of the items in the collection.
81
+	 *
82
+	 * @return array
83
+	 */
84
+	public function all()
85
+	{
86
+		$this->initializeProxy();
87
+
88
+		return parent::all();
89
+	}
90
+
91
+	/**
92
+	 * Get the average value of a given key.
93
+	 *
94
+	 * @param callable|string|null $callback
95
+	 *
96
+	 * @return mixed
97
+	 */
98
+	public function avg($callback = null)
99
+	{
100
+		$this->initializeProxy();
101
+
102
+		return parent::avg($callback);
103
+	}
104
+
105
+	/**
106
+	 * Get the median of a given key.
107
+	 *
108
+	 * @param null $key
109
+	 *
110
+	 * @return mixed|null
111
+	 */
112
+	public function median($key = null)
113
+	{
114
+		$this->initializeProxy();
115
+
116
+		return parent::median($key);
117
+	}
118
+
119
+	/**
120
+	 * Get the mode of a given key.
121
+	 *
122
+	 * @param mixed $key
123
+	 *
124
+	 * @return array
125
+	 */
126
+	public function mode($key = null)
127
+	{
128
+		$this->initializeProxy();
129
+
130
+		return parent::mode($key);
131
+	}
132
+
133
+	/**
134
+	 * Collapse the collection of items into a single array.
135
+	 *
136
+	 * @return static
137
+	 */
138
+	public function collapse()
139
+	{
140
+		$this->initializeProxy();
141
+
142
+		return parent::collapse();
143
+	}
144
+
145
+	/**
146
+	 * Determine if an item exists in the collection.
147
+	 *
148
+	 * @param mixed $key
149
+	 * @param mixed $value
150
+	 *
151
+	 * @return bool
152
+	 */
153
+	public function contains($key, $operator = null, $value = null)
154
+	{
155
+		$this->initializeProxy();
156
+
157
+		return parent::contains($key, $operator, $value);
158
+	}
159
+
160
+	/**
161
+	 * Determine if an item exists in the collection using strict comparison.
162
+	 *
163
+	 * @param mixed $key
164
+	 * @param mixed $value
165
+	 *
166
+	 * @return bool
167
+	 */
168
+	public function containsStrict($key, $value = null)
169
+	{
170
+		$this->initializeProxy();
171
+
172
+		return parent::containsStrict($key, $value);
173
+	}
174
+
175
+	/**
176
+	 * Get the items in the collection that are not present in the given items.
177
+	 *
178
+	 * @param mixed $items
179
+	 *
180
+	 * @return static
181
+	 */
182
+	public function diff($items)
183
+	{
184
+		$this->initializeProxy();
185
+
186
+		return parent::diff($items);
187
+	}
188
+
189
+	/**
190
+	 * Get the items in the collection whose keys are not present in the given items.
191
+	 *
192
+	 * @param mixed $items
193
+	 *
194
+	 * @return static
195
+	 */
196
+	public function diffKeys($items)
197
+	{
198
+		$this->initializeProxy();
199
+
200
+		return parent::diffKeys($items);
201
+	}
202
+
203
+	/**
204
+	 * Execute a callback over each item.
205
+	 *
206
+	 * @param callable $callback
207
+	 *
208
+	 * @return $this
209
+	 */
210
+	public function each(callable $callback)
211
+	{
212
+		$this->initializeProxy();
213
+
214
+		return parent::each($callback);
215
+	}
216
+
217
+	/**
218
+	 * Create a new collection consisting of every n-th element.
219
+	 *
220
+	 * @param int $step
221
+	 * @param int $offset
222
+	 *
223
+	 * @return static
224
+	 */
225
+	public function every($key, $operator = null, $value = null)
226
+	{
227
+		$this->initializeProxy();
228
+
229
+		return parent::every($key, $operator, $value);
230
+	}
231
+
232
+	/**
233
+	 * Get all items except for those with the specified keys.
234
+	 *
235
+	 * @param mixed $keys
236
+	 *
237
+	 * @return static
238
+	 */
239
+	public function except($keys)
240
+	{
241
+		$this->initializeProxy();
242
+
243
+		return parent::except($keys);
244
+	}
245
+
246
+	/**
247
+	 * Run a filter over each of the items.
248
+	 *
249
+	 * @param callable|null $callback
250
+	 *
251
+	 * @return static
252
+	 */
253
+	public function filter(callable $callback = null)
254
+	{
255
+		$this->initializeProxy();
256
+
257
+		return parent::filter($callback);
258
+	}
259
+
260
+	/**
261
+	 * Filter items by the given key value pair.
262
+	 *
263
+	 * @param string $key
264
+	 * @param mixed  $operator
265
+	 * @param mixed  $value
266
+	 *
267
+	 * @return static
268
+	 */
269
+	public function where($key, $operator, $value = null)
270
+	{
271
+		$this->initializeProxy();
272
+
273
+		return parent::where($key, $operator, $value);
274
+	}
275
+
276
+	/**
277
+	 * Filter items by the given key value pair using strict comparison.
278
+	 *
279
+	 * @param string $key
280
+	 * @param mixed  $value
281
+	 *
282
+	 * @return static
283
+	 */
284
+	public function whereStrict($key, $value)
285
+	{
286
+		$this->initializeProxy();
287
+
288
+		return parent::whereStrict($key, $value);
289
+	}
290
+
291
+	/**
292
+	 * Filter items by the given key value pair.
293
+	 *
294
+	 * @param string $key
295
+	 * @param mixed  $values
296
+	 * @param bool   $strict
297
+	 *
298
+	 * @return static
299
+	 */
300
+	public function whereIn($key, $values, $strict = false)
301
+	{
302
+		$this->initializeProxy();
303
+
304
+		return parent::whereIn($key, $values, $strict);
305
+	}
306
+
307
+	/**
308
+	 * Filter items by the given key value pair using strict comparison.
309
+	 *
310
+	 * @param string $key
311
+	 * @param mixed  $values
312
+	 *
313
+	 * @return static
314
+	 */
315
+	public function whereInStrict($key, $values)
316
+	{
317
+		$this->initializeProxy();
318
+
319
+		return parent::whereInStrict($key, $values);
320
+	}
321
+
322
+	/**
323
+	 * Get the first item from the collection.
324
+	 *
325
+	 * @param callable|null $callback
326
+	 * @param mixed         $default
327
+	 *
328
+	 * @return mixed
329
+	 */
330
+	public function first(callable $callback = null, $default = null)
331
+	{
332
+		// TODO Consider partial loading
333
+		$this->initializeProxy();
334
+
335
+		return parent::first($callback, $default);
336
+	}
337
+
338
+	/**
339
+	 * Get a flattened array of the items in the collection.
340
+	 *
341
+	 * @param int $depth
342
+	 *
343
+	 * @return static
344
+	 */
345
+	public function flatten($depth = INF)
346
+	{
347
+		$this->initializeProxy();
348
+
349
+		return parent::flatten($depth);
350
+	}
351
+
352
+	/**
353
+	 * Flip the items in the collection.
354
+	 *
355
+	 * @return static
356
+	 */
357
+	public function flip()
358
+	{
359
+		$this->initializeProxy();
360
+
361
+		return parent::flip();
362
+	}
363
+
364
+	/**
365
+	 * Remove an item from the collection by key.
366
+	 *
367
+	 * @param string|array $keys
368
+	 *
369
+	 * @return $this
370
+	 */
371
+	public function forget($keys)
372
+	{
373
+		// TODO, we could consider these as
374
+		// 'pending deletion', the same way that
375
+		// we treat added items
376
+		$this->initializeProxy();
377
+
378
+		return parent::forget($keys);
379
+	}
380
+
381
+	/**
382
+	 * Get an item from the collection by key.
383
+	 *
384
+	 * @param mixed $key
385
+	 * @param mixed $default
386
+	 *
387
+	 * @return mixed
388
+	 */
389
+	public function get($key, $default = null)
390
+	{
391
+		// TODO : We could also consider partial loading
392
+		// here
393
+		$this->initializeProxy();
394
+
395
+		return parent::get($key, $default);
396
+	}
397
+
398
+	/**
399
+	 * Group an associative array by a field or using a callback.
400
+	 *
401
+	 * @param callable|string $groupBy
402
+	 * @param bool            $preserveKeys
403
+	 *
404
+	 * @return static
405
+	 */
406
+	public function groupBy($groupBy, $preserveKeys = false)
407
+	{
408
+		$this->initializeProxy();
409
+
410
+		return parent::groupBy($groupBy, $preserveKeys);
411
+	}
412
+
413
+	/**
414
+	 * Key an associative array by a field or using a callback.
415
+	 *
416
+	 * @param callable|string $keyBy
417
+	 *
418
+	 * @return static
419
+	 */
420
+	public function keyBy($keyBy)
421
+	{
422
+		$this->initializeProxy();
423
+
424
+		return parent::keyBy($keyBy);
425
+	}
426
+
427
+	/**
428
+	 * Determine if an item exists in the collection by key.
429
+	 *
430
+	 * @param mixed $key
431
+	 *
432
+	 * @return bool
433
+	 */
434
+	public function has($key)
435
+	{
436
+		// TODO : we could do automagic here by directly
437
+		// calling the database if the collection hasn't
438
+		// been initialized yet.
439
+		// Potential issue is that several calls to this
440
+		// could cause a lot queries vs a single get query.
441
+		$this->initializeProxy();
442
+
443
+		return parent::has($key);
444
+	}
445
+
446
+	/**
447
+	 * Concatenate values of a given key as a string.
448
+	 *
449
+	 * @param string $value
450
+	 * @param string $glue
451
+	 *
452
+	 * @return string
453
+	 */
454
+	public function implode($value, $glue = null)
455
+	{
456
+		$this->initializeProxy();
457
+
458
+		return parent::implode($value, $glue);
459
+	}
460
+
461
+	/**
462
+	 * Intersect the collection with the given items.
463
+	 *
464
+	 * @param mixed $items
465
+	 *
466
+	 * @return static
467
+	 */
468
+	public function intersect($items)
469
+	{
470
+		$this->initializeProxy();
471
+
472
+		return parent::intersect($items);
473
+	}
474
+
475
+	/**
476
+	 * Determine if the collection is empty or not.
477
+	 *
478
+	 * @return bool
479
+	 */
480
+	public function isEmpty()
481
+	{
482
+		$this->initializeProxy();
483
+
484
+		return parent::isEmpty();
485
+	}
486
+
487
+	/**
488
+	 * Get the keys of the collection items.
489
+	 *
490
+	 * @return static
491
+	 */
492
+	public function keys()
493
+	{
494
+		$this->initializeProxy();
495
+
496
+		return parent::keys();
497
+	}
498
+
499
+	/**
500
+	 * Get the last item from the collection.
501
+	 *
502
+	 * @param callable|null $callback
503
+	 * @param mixed         $default
504
+	 *
505
+	 * @return mixed
506
+	 */
507
+	public function last(callable $callback = null, $default = null)
508
+	{
509
+		// TODO : we could do partial loading there as well
510
+		$this->initializeProxy();
511
+
512
+		return parent::last($callback, $default);
513
+	}
514
+
515
+	/**
516
+	 * Get the values of a given key.
517
+	 *
518
+	 * @param string      $value
519
+	 * @param string|null $key
520
+	 *
521
+	 * @return static
522
+	 */
523
+	public function pluck($value, $key = null)
524
+	{
525
+		// TODO : automagic call to QB if not initialized
526
+		$this->initializeProxy();
527
+
528
+		return parent::pluck($value, $key);
529
+	}
530
+
531
+	/**
532
+	 * Run a map over each of the items.
533
+	 *
534
+	 * @param callable $callback
535
+	 *
536
+	 * @return static
537
+	 */
538
+	public function map(callable $callback)
539
+	{
540
+		$this->initializeProxy();
541
+
542
+		return parent::map($callback);
543
+	}
544
+
545
+	/**
546
+	 * Run an associative map over each of the items.
547
+	 *
548
+	 * The callback should return an associative array with a single key/value pair.
549
+	 *
550
+	 * @param callable $callback
551
+	 *
552
+	 * @return static
553
+	 */
554
+	public function mapWithKeys(callable $callback)
555
+	{
556
+		$this->initializeProxy();
557
+
558
+		return parent::mapWithKeys($callback);
559
+	}
560
+
561
+	/**
562
+	 * Map a collection and flatten the result by a single level.
563
+	 *
564
+	 * @param callable $callback
565
+	 *
566
+	 * @return static
567
+	 */
568
+	public function flatMap(callable $callback)
569
+	{
570
+		$this->initializeProxy();
571
+
572
+		return parent::flatMap($callback);
573
+	}
574
+
575
+	/**
576
+	 * Get the max value of a given key.
577
+	 *
578
+	 * @param callable|string|null $callback
579
+	 *
580
+	 * @return mixed
581
+	 */
582
+	public function max($callback = null)
583
+	{
584
+		$this->initializeProxy();
585
+
586
+		return parent::max($callback);
587
+	}
588
+
589
+	/**
590
+	 * Merge the collection with the given items.
591
+	 *
592
+	 * @param mixed $items
593
+	 *
594
+	 * @return static
595
+	 */
596
+	public function merge($items)
597
+	{
598
+		// TODO : Check if the EntityCollection
599
+		// returns a native Collection, as it
600
+		// is what we want here
601
+		$this->initializeProxy();
602
+
603
+		return parent::merge($items);
604
+	}
605
+
606
+	/**
607
+	 * Create a collection by using this collection for keys and another for its values.
608
+	 *
609
+	 * @param mixed $values
610
+	 *
611
+	 * @return static
612
+	 */
613
+	public function combine($values)
614
+	{
615
+		// TODO : Check if the EntityCollection
616
+		// returns a native Collection, as it
617
+		// is what we want here
618
+		$this->initializeProxy();
619
+
620
+		return parent::combine($values);
621
+	}
622
+
623
+	/**
624
+	 * Union the collection with the given items.
625
+	 *
626
+	 * @param mixed $items
627
+	 *
628
+	 * @return static
629
+	 */
630
+	public function union($items)
631
+	{
632
+		// TODO : Check if the EntityCollection
633
+		// returns a native Collection, as it
634
+		// is what we want here
635
+		$this->initializeProxy();
636
+
637
+		return parent::union($items);
638
+	}
639
+
640
+	/**
641
+	 * Get the min value of a given key.
642
+	 *
643
+	 * @param callable|string|null $callback
644
+	 *
645
+	 * @return mixed
646
+	 */
647
+	public function min($callback = null)
648
+	{
649
+		// TODO : we could rely on the QB
650
+		// for thos, if initialization has not
651
+		// take place yet
652
+		$this->initializeProxy();
653
+
654
+		return parent::min($callback);
655
+	}
656
+
657
+	/**
658
+	 * Create a new collection consisting of every n-th element.
659
+	 *
660
+	 * @param int $step
661
+	 * @param int $offset
662
+	 *
663
+	 * @return static
664
+	 */
665
+	public function nth($step, $offset = 0)
666
+	{
667
+		$this->initializeProxy();
668
+
669
+		return parent::nth($step, $offset);
670
+	}
671
+
672
+	/**
673
+	 * Get the items with the specified keys.
674
+	 *
675
+	 * @param mixed $keys
676
+	 *
677
+	 * @return static
678
+	 */
679
+	public function only($keys)
680
+	{
681
+		// TODO : we could rely on the QB if
682
+		// the collection hasn't been initialized yet
683
+		$this->initializeProxy();
684
+
685
+		return parent::only($keys);
686
+	}
687
+
688
+	/**
689
+	 * "Paginate" the collection by slicing it into a smaller collection.
690
+	 *
691
+	 * @param int $page
692
+	 * @param int $perPage
693
+	 *
694
+	 * @return static
695
+	 */
696
+	public function forPage($page, $perPage)
697
+	{
698
+		// TODO : check possibility of partial loading
699
+		// if not initialized
700
+		$this->initializeProxy();
701
+
702
+		return parent::forPage($page, $perPage);
703
+	}
704
+
705
+	/**
706
+	 * Partition the collection into two arrays using the given callback or key.
707
+	 *
708
+	 * @param callable|string $callback
709
+	 *
710
+	 * @return static
711
+	 */
712
+	public function partition($callback)
713
+	{
714
+		$this->initializeProxy();
715
+
716
+		return parent::partition($callback);
717
+	}
718
+
719
+	/**
720
+	 * Pass the collection to the given callback and return the result.
721
+	 *
722
+	 * @param callable $callback
723
+	 *
724
+	 * @return mixed
725
+	 */
726
+	public function pipe(callable $callback)
727
+	{
728
+		$this->initializeProxy();
729
+
730
+		return parent::pipe($callback);
731
+	}
732
+
733
+	/**
734
+	 * Get and remove the last item from the collection.
735
+	 *
736
+	 * @return mixed
737
+	 */
738
+	public function pop()
739
+	{
740
+		$this->initializeProxy();
741
+
742
+		return parent::pop();
743
+	}
744
+
745
+	/**
746
+	 * Push an item onto the beginning of the collection.
747
+	 *
748
+	 * @param mixed $value
749
+	 * @param mixed $key
750
+	 *
751
+	 * @return $this
752
+	 */
753
+	public function prepend($value, $key = null)
754
+	{
755
+		// TODO : partial adding of values.
756
+		// we could have a $prepended , and $pushed arrays
757
+		// which we would combine at full initialization
758
+
759
+		$this->initializeProxy();
760
+
761
+		return parent::prepend($value, $key);
762
+	}
763
+
764
+	/**
765
+	 * Push an item onto the end of the collection.
766
+	 *
767
+	 * @param mixed $value
768
+	 *
769
+	 * @return $this
770
+	 */
771
+	public function push($value)
772
+	{
773
+		// TODO : partial adding of values.
774
+		// we could have a $prepended , and $pushed arrays
775
+		// which we would combine at full initialization
776
+
777
+		$this->initializeProxy();
778
+
779
+		return parent::push($value);
780
+	}
781
+
782
+	/**
783
+	 * Get and remove an item from the collection.
784
+	 *
785
+	 * @param mixed $key
786
+	 * @param mixed $default
787
+	 *
788
+	 * @return mixed
789
+	 */
790
+	public function pull($key, $default = null)
791
+	{
792
+		// TODO : QB query if the collection
793
+		// hasn't been initialized yet
794
+
795
+		$this->initializeProxy();
796
+
797
+		return parent::pull($key, $default);
798
+	}
799
+
800
+	/**
801
+	 * Put an item in the collection by key.
802
+	 *
803
+	 * @param mixed $key
804
+	 * @param mixed $value
805
+	 *
806
+	 * @return $this
807
+	 */
808
+	public function put($key, $value)
809
+	{
810
+		// TODO : Partial loading ?
811
+
812
+		$this->initializeProxy();
813
+
814
+		return parent::put($key, $value);
815
+	}
816
+
817
+	/**
818
+	 * Get one or more items randomly from the collection.
819
+	 *
820
+	 * @param int $amount
821
+	 *
822
+	 * @throws \InvalidArgumentException
823
+	 *
824
+	 * @return mixed
825
+	 */
826
+	public function random($amount = 1)
827
+	{
828
+		// TODO : we could optimize this by only
829
+		// fetching the keys from the database
830
+		// and performing partial loading
831
+
832
+		$this->initializeProxy();
833
+
834
+		return parent::random($amount);
835
+	}
836
+
837
+	/**
838
+	 * Reduce the collection to a single value.
839
+	 *
840
+	 * @param callable $callback
841
+	 * @param mixed    $initial
842
+	 *
843
+	 * @return mixed
844
+	 */
845
+	public function reduce(callable $callback, $initial = null)
846
+	{
847
+		$this->initializeProxy();
848
+
849
+		return parent::reduce($callback, $initial);
850
+	}
851
+
852
+	/**
853
+	 * Create a collection of all elements that do not pass a given truth test.
854
+	 *
855
+	 * @param callable|mixed $callback
856
+	 *
857
+	 * @return static
858
+	 */
859
+	public function reject($callback)
860
+	{
861
+		$this->initializeProxy();
862
+
863
+		return parent::reject($callback);
864
+	}
865
+
866
+	/**
867
+	 * Reverse items order.
868
+	 *
869
+	 * @return static
870
+	 */
871
+	public function reverse()
872
+	{
873
+		$this->initializeProxy();
874
+
875
+		return parent::reverse();
876
+	}
877
+
878
+	/**
879
+	 * Search the collection for a given value and return the corresponding key if successful.
880
+	 *
881
+	 * @param mixed $value
882
+	 * @param bool  $strict
883
+	 *
884
+	 * @return mixed
885
+	 */
886
+	public function search($value, $strict = false)
887
+	{
888
+		$this->initializeProxy();
889
+
890
+		return parent::search($value, $strict);
891
+	}
892
+
893
+	/**
894
+	 * Get and remove the first item from the collection.
895
+	 *
896
+	 * @return mixed
897
+	 */
898
+	public function shift()
899
+	{
900
+		// Todo : Partial Removing
901
+		// we could have a pending removal array
902
+		$this->initializeProxy();
903
+
904
+		return parent::shift();
905
+	}
906
+
907
+	/**
908
+	 * Shuffle the items in the collection.
909
+	 *
910
+	 * @param int $seed
911
+	 *
912
+	 * @return static
913
+	 */
914
+	public function shuffle($seed = null)
915
+	{
916
+		$this->initializeProxy();
917
+
918
+		return parent::shuffle($seed);
919
+	}
920
+
921
+	/**
922
+	 * Slice the underlying collection array.
923
+	 *
924
+	 * @param int $offset
925
+	 * @param int $length
926
+	 *
927
+	 * @return static
928
+	 */
929
+	public function slice($offset, $length = null)
930
+	{
931
+		$this->initializeProxy();
932
+
933
+		return parent::slice($offset, $length);
934
+	}
935
+
936
+	/**
937
+	 * Split a collection into a certain number of groups.
938
+	 *
939
+	 * @param int $numberOfGroups
940
+	 *
941
+	 * @return static
942
+	 */
943
+	public function split($numberOfGroups)
944
+	{
945
+		$this->initializeProxy();
946
+
947
+		return parent::split($numberOfGroups);
948
+	}
949
+
950
+	/**
951
+	 * Chunk the underlying collection array.
952
+	 *
953
+	 * @param int $size
954
+	 *
955
+	 * @return static
956
+	 */
957
+	public function chunk($size)
958
+	{
959
+		// TODO : partial loading ?
960
+		$this->initializeProxy();
961
+
962
+		return parent::chunk($size);
963
+	}
964
+
965
+	/**
966
+	 * Sort through each item with a callback.
967
+	 *
968
+	 * @param callable|null $callback
969
+	 *
970
+	 * @return static
971
+	 */
972
+	public function sort(callable $callback = null)
973
+	{
974
+		$this->initializeProxy();
975
+
976
+		return parent::sort($callback);
977
+	}
978
+
979
+	/**
980
+	 * Sort the collection using the given callback.
981
+	 *
982
+	 * @param callable|string $callback
983
+	 * @param int             $options
984
+	 * @param bool            $descending
985
+	 *
986
+	 * @return static
987
+	 */
988
+	public function sortBy($callback, $options = SORT_REGULAR, $descending = false)
989
+	{
990
+		$this->initializeProxy();
991
+
992
+		return parent::sort($callback, $options, $descending);
993
+	}
994
+
995
+	/**
996
+	 * Splice a portion of the underlying collection array.
997
+	 *
998
+	 * @param int      $offset
999
+	 * @param int|null $length
1000
+	 * @param mixed    $replacement
1001
+	 *
1002
+	 * @return static
1003
+	 */
1004
+	public function splice($offset, $length = null, $replacement = [])
1005
+	{
1006
+		$this->initializeProxy();
1007
+
1008
+		return parent::splice($offset, $length, $replacement);
1009
+	}
1010
+
1011
+	/**
1012
+	 * Get the sum of the given values.
1013
+	 *
1014
+	 * @param callable|string|null $callback
1015
+	 *
1016
+	 * @return mixed
1017
+	 */
1018
+	public function sum($callback = null)
1019
+	{
1020
+		$this->initializeProxy();
1021
+
1022
+		return parent::sum($callback);
1023
+	}
1024
+
1025
+	/**
1026
+	 * Take the first or last {$limit} items.
1027
+	 *
1028
+	 * @param int $limit
1029
+	 *
1030
+	 * @return static
1031
+	 */
1032
+	public function take($limit)
1033
+	{
1034
+		// TODO: partial loading
1035
+		$this->initializeProxy();
1036
+
1037
+		return parent::take($limit);
1038
+	}
1039
+
1040
+	/**
1041
+	 * Pass the collection to the given callback and then return it.
1042
+	 *
1043
+	 * @param callable $callback
1044
+	 *
1045
+	 * @return $this
1046
+	 */
1047
+	public function tap(callable $callback)
1048
+	{
1049
+		$this->initializeProxy();
1050
+
1051
+		return parent::tap($this);
1052
+	}
1053
+
1054
+	/**
1055
+	 * Transform each item in the collection using a callback.
1056
+	 *
1057
+	 * @param callable $callback
1058
+	 *
1059
+	 * @return $this
1060
+	 */
1061
+	public function transform(callable $callback)
1062
+	{
1063
+		$this->initializeProxy();
1064
+
1065
+		return parent::transform($callback);
1066
+	}
1067
+
1068
+	/**
1069
+	 * Return only unique items from the collection array.
1070
+	 *
1071
+	 * @param string|callable|null $key
1072
+	 * @param bool                 $strict
1073
+	 *
1074
+	 * @return static
1075
+	 */
1076
+	public function unique($key = null, $strict = false)
1077
+	{
1078
+		$this->initializeProxy();
1079
+
1080
+		return parent::unique($key, $strict);
1081
+	}
1082
+
1083
+	/**
1084
+	 * Reset the keys on the underlying array.
1085
+	 *
1086
+	 * @return static
1087
+	 */
1088
+	public function values()
1089
+	{
1090
+		$this->initializeProxy();
1091
+
1092
+		return parent::values();
1093
+	}
1094
+
1095
+	/**
1096
+	 * Apply the callback if the value is truthy.
1097
+	 *
1098
+	 * @param  bool  $value
1099
+	 * @param  callable  $callback
1100
+	 * @return mixed
1101
+	 */
1102
+	public function when($value, callable $callback)
1103
+	{
1104
+		$this->initializeProxy();
1105
+
1106
+		return parent::when($value, $callback);
1107
+	}
1108
+
1109
+	/**
1110
+	 * Zip the collection together with one or more arrays.
1111
+	 *
1112
+	 * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]);
1113
+	 *      => [[1, 4], [2, 5], [3, 6]]
1114
+	 *
1115
+	 * @param mixed ...$items
1116
+	 *
1117
+	 * @return static
1118
+	 */
1119
+	public function zip($items)
1120
+	{
1121
+		$this->initializeProxy();
1122
+
1123
+		return parent::zip($items);
1124
+	}
1125
+
1126
+	/**
1127
+	 * Get the collection of items as a plain array.
1128
+	 *
1129
+	 * @return array
1130
+	 */
1131
+	public function toArray()
1132
+	{
1133
+		// If this is called on all subsequent proxy,
1134
+		// this would eventually trigger all lazy loading,
1135
+		// which is NOT what we would expect...
1136
+		// TODO : must think of this.
1137
+		$this->initializeProxy();
1138
+
1139
+		return parent::toArray();
1140
+	}
1141
+
1142
+	/**
1143
+	 * Convert the object into something JSON serializable.
1144
+	 *
1145
+	 * @return array
1146
+	 */
1147
+	public function jsonSerialize()
1148
+	{
1149
+		// If this is called on all subsequent proxy,
1150
+		// this would eventually trigger all lazy loading,
1151
+		// which is NOT what we would expect...
1152
+		// TODO : must think of this.
1153
+		$this->initializeProxy();
1154
+
1155
+		return parent::jsonSerialize();
1156
+	}
1157
+
1158
+	/**
1159
+	 * Get the collection of items as JSON.
1160
+	 *
1161
+	 * @param int $options
1162
+	 *
1163
+	 * @return string
1164
+	 */
1165
+	public function toJson($options = 0)
1166
+	{
1167
+		// If this is called on all subsequent proxy,
1168
+		// this would eventually trigger all lazy loading,
1169
+		// which is NOT what we would expect...
1170
+		// TODO : must think of this.
1171
+		$this->initializeProxy();
1172
+
1173
+		return parent::toJson($options);
1174
+	}
1175
+
1176
+	/**
1177
+	 * Get an iterator for the items.
1178
+	 *
1179
+	 * @return \ArrayIterator
1180
+	 */
1181
+	public function getIterator()
1182
+	{
1183
+		$this->initializeProxy();
1184
+
1185
+		return parent::getIterator();
1186
+	}
1187
+
1188
+	/**
1189
+	 * Get a CachingIterator instance.
1190
+	 *
1191
+	 * @param int $flags
1192
+	 *
1193
+	 * @return \CachingIterator
1194
+	 */
1195
+	public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING)
1196
+	{
1197
+		$this->initializeProxy();
1198
+
1199
+		return parent::getCachingIterator($flags);
1200
+	}
1201
+
1202
+	/**
1203
+	 * Count the number of items in the collection.
1204
+	 *
1205
+	 * @return int
1206
+	 */
1207
+	public function count()
1208
+	{
1209
+		// TODO rely on QB if not initialized
1210
+		$this->initializeProxy();
1211
+
1212
+		return parent::count();
1213
+	}
1214
+
1215
+	/**
1216
+	 * Get a base Support collection instance from this collection.
1217
+	 *
1218
+	 * @return \Illuminate\Support\Collection
1219
+	 */
1220
+	public function toBase()
1221
+	{
1222
+		$this->initializeProxy();
1223
+
1224
+		return parent::toBase();
1225
+	}
1226
+
1227
+	/**
1228
+	 * Determine if an item exists at an offset.
1229
+	 *
1230
+	 * @param mixed $key
1231
+	 *
1232
+	 * @return bool
1233
+	 */
1234
+	public function offsetExists($key)
1235
+	{
1236
+		// TODO rely on QB if no collection
1237
+		// initialized
1238
+		$this->initializeProxy();
1239
+
1240
+		return parent::offsetExists($key);
1241
+	}
1242
+
1243
+	/**
1244
+	 * Get an item at a given offset.
1245
+	 *
1246
+	 * @param mixed $key
1247
+	 *
1248
+	 * @return mixed
1249
+	 */
1250
+	public function offsetGet($key)
1251
+	{
1252
+		// TODO rely on partial init if no collection
1253
+		// initialized
1254
+		$this->initializeProxy();
1255
+
1256
+		return parent::offsetGet($key);
1257
+	}
1258
+
1259
+	/**
1260
+	 * Set the item at a given offset.
1261
+	 *
1262
+	 * @param mixed $key
1263
+	 * @param mixed $value
1264
+	 *
1265
+	 * @return void
1266
+	 */
1267
+	public function offsetSet($key, $value)
1268
+	{
1269
+		// TODO : think of the use of it into a ProxyCollection
1270
+		// context
1271
+		$this->initializeProxy();
1272
+
1273
+		return parent::offsetSet($key, $value);
1274
+	}
1275
+
1276
+	/**
1277
+	 * Unset the item at a given offset.
1278
+	 *
1279
+	 * @param string $key
1280
+	 *
1281
+	 * @return void
1282
+	 */
1283
+	public function offsetUnset($key)
1284
+	{
1285
+		// TODO : think of the use of it into a ProxyCollection
1286
+		// context
1287
+		$this->initializeProxy();
1288
+
1289
+		return parent::offsetUnset($key);
1290
+	}
1291
+
1292
+	/**
1293
+	 * Dynamically access collection proxies.
1294
+	 *
1295
+	 * @param string $key
1296
+	 *
1297
+	 * @throws \Exception
1298
+	 *
1299
+	 * @return mixed
1300
+	 */
1301
+	public function __get($key)
1302
+	{
1303
+		parent::__get($key);
1304
+	}
1305
+
1306
+	/**
1307
+	 * Dynamically handle calls to the class.
1308
+	 *
1309
+	 * @param string $method
1310
+	 * @param array  $parameters
1311
+	 *
1312
+	 * @throws \BadMethodCallException
1313
+	 *
1314
+	 * @return mixed
1315
+	 */
1316
+	public function __call($method, $parameters)
1317
+	{
1318
+		$this->initializeProxy();
1319
+
1320
+		return parent::__call($method, $parameters);
1321
+	}
1322 1322
 }
Please login to merge, or discard this patch.
src/System/Proxies/ProxyFactory.php 1 patch
Indentation   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -14,47 +14,47 @@
 block discarded – undo
14 14
  */
15 15
 class ProxyFactory
16 16
 {
17
-    public function make($entity, $relation, $class)
18
-    {
19
-        $entityMap = Manager::getMapper($entity)->getEntityMap();
20
-
21
-        $singleRelations = $entityMap->getSingleRelationships();
22
-        $manyRelations = $entityMap->getManyRelationships();
23
-
24
-        if (in_array($relation, $singleRelations)) {
25
-            return $this->makeEntityProxy($entity, $relation, $class);
26
-        }
27
-
28
-        if (in_array($relation, $manyRelations)) {
29
-            return new CollectionProxy($entity, $relation);
30
-        }
31
-
32
-        throw new MappingException("Could not identify relation '$relation'");
33
-    }
34
-
35
-    /**
36
-     * Create an instance of a proxy object, extending the actual
37
-     * related class.
38
-     *
39
-     * @param mixed  $entity   parent object
40
-     * @param string $relation the name of the relationship method
41
-     * @param string $class    the class name of the related object
42
-     *
43
-     * @return mixed
44
-     */
45
-    protected function makeEntityProxy($entity, $relation, $class)
46
-    {
47
-        $factory = new LazyLoadingValueHolderFactory();
48
-
49
-        $initializer = function (&$wrappedObject, LazyLoadingInterface $proxy, $method, array $parameters, &$initializer) use ($entity, $relation) {
50
-            $entityMap = Manager::getMapper($entity)->getEntityMap();
51
-
52
-            $wrappedObject = $entityMap->$relation($entity)->getResults($relation);
53
-
54
-            $initializer = null; // disable initialization
55
-            return true; // confirm that initialization occurred correctly
56
-        };
57
-
58
-        return $factory->createProxy($class, $initializer);
59
-    }
17
+	public function make($entity, $relation, $class)
18
+	{
19
+		$entityMap = Manager::getMapper($entity)->getEntityMap();
20
+
21
+		$singleRelations = $entityMap->getSingleRelationships();
22
+		$manyRelations = $entityMap->getManyRelationships();
23
+
24
+		if (in_array($relation, $singleRelations)) {
25
+			return $this->makeEntityProxy($entity, $relation, $class);
26
+		}
27
+
28
+		if (in_array($relation, $manyRelations)) {
29
+			return new CollectionProxy($entity, $relation);
30
+		}
31
+
32
+		throw new MappingException("Could not identify relation '$relation'");
33
+	}
34
+
35
+	/**
36
+	 * Create an instance of a proxy object, extending the actual
37
+	 * related class.
38
+	 *
39
+	 * @param mixed  $entity   parent object
40
+	 * @param string $relation the name of the relationship method
41
+	 * @param string $class    the class name of the related object
42
+	 *
43
+	 * @return mixed
44
+	 */
45
+	protected function makeEntityProxy($entity, $relation, $class)
46
+	{
47
+		$factory = new LazyLoadingValueHolderFactory();
48
+
49
+		$initializer = function (&$wrappedObject, LazyLoadingInterface $proxy, $method, array $parameters, &$initializer) use ($entity, $relation) {
50
+			$entityMap = Manager::getMapper($entity)->getEntityMap();
51
+
52
+			$wrappedObject = $entityMap->$relation($entity)->getResults($relation);
53
+
54
+			$initializer = null; // disable initialization
55
+			return true; // confirm that initialization occurred correctly
56
+		};
57
+
58
+		return $factory->createProxy($class, $initializer);
59
+	}
60 60
 }
Please login to merge, or discard this patch.
src/System/Aggregate.php 1 patch
Indentation   +1155 added lines, -1155 removed lines patch added patch discarded remove patch
@@ -15,1159 +15,1159 @@
 block discarded – undo
15 15
  */
16 16
 class Aggregate implements InternallyMappable
17 17
 {
18
-    /**
19
-     * The Root Entity.
20
-     *
21
-     * @var \Analogue\ORM\System\Wrappers\Wrapper
22
-     */
23
-    protected $wrappedEntity;
24
-
25
-    /**
26
-     * Class of the entity being aggregated.
27
-     *
28
-     * @var string
29
-     */
30
-    protected $class;
31
-
32
-    /**
33
-     * Parent Root Aggregate.
34
-     *
35
-     * @var \Analogue\ORM\System\Aggregate
36
-     */
37
-    protected $parent;
38
-
39
-    /**
40
-     * Parent's relationship method.
41
-     *
42
-     * @var string
43
-     */
44
-    protected $parentRelationship;
45
-
46
-    /**
47
-     * Root Entity.
48
-     *
49
-     * @var \Analogue\ORM\System\Aggregate
50
-     */
51
-    protected $root;
52
-
53
-    /**
54
-     * An associative array containing entity's
55
-     * relationships converted to Aggregates.
56
-     *
57
-     * @var array
58
-     */
59
-    protected $relationships = [];
60
-
61
-    /**
62
-     * Relationship that need post-command synchronization.
63
-     *
64
-     * @var array
65
-     */
66
-    protected $needSync = [];
67
-
68
-    /**
69
-     * Mapper.
70
-     *
71
-     * @var \Analogue\ORM\System\Mapper;
72
-     */
73
-    protected $mapper;
74
-
75
-    /**
76
-     * Entity Map.
77
-     *
78
-     * @var \Analogue\ORM\EntityMap;
79
-     */
80
-    protected $entityMap;
81
-
82
-    /**
83
-     * Create a new Aggregated Entity instance.
84
-     *
85
-     * @param mixed          $entity
86
-     * @param Aggregate|null $parent
87
-     * @param string         $parentRelationship
88
-     * @param Aggregate|null $root
89
-     *
90
-     * @throws MappingException
91
-     */
92
-    public function __construct($entity, Aggregate $parent = null, $parentRelationship = null, Aggregate $root = null)
93
-    {
94
-        $factory = new Factory();
95
-
96
-        $this->class = get_class($entity);
97
-
98
-        $this->wrappedEntity = $factory->make($entity);
99
-
100
-        $this->parent = $parent;
101
-
102
-        $this->parentRelationship = $parentRelationship;
103
-
104
-        $this->root = $root;
105
-
106
-        $mapper = $this->getMapper($entity);
107
-
108
-        $this->entityMap = $mapper->getEntityMap();
109
-
110
-        $this->parseRelationships();
111
-    }
112
-
113
-    /**
114
-     * Parse Every relationships defined on the entity.
115
-     *
116
-     * @throws MappingException
117
-     *
118
-     * @return void
119
-     */
120
-    protected function parseRelationships()
121
-    {
122
-        foreach ($this->entityMap->getSingleRelationships() as $relation) {
123
-            $this->parseSingleRelationship($relation);
124
-        }
125
-
126
-        foreach ($this->entityMap->getManyRelationships() as $relation) {
127
-            $this->parseManyRelationship($relation);
128
-        }
129
-    }
130
-
131
-    /**
132
-     * Parse for values common to single & many relations.
133
-     *
134
-     * @param string $relation
135
-     *
136
-     * @throws MappingException
137
-     *
138
-     * @return mixed|bool
139
-     */
140
-    protected function parseForCommonValues($relation)
141
-    {
142
-        if (!$this->hasAttribute($relation)) {
143
-            // If no attribute exists for this relationships
144
-            // we'll make it a simple empty array. This will
145
-            // save us from constantly checking for the attributes
146
-            // actual existence.
147
-            $this->relationships[$relation] = [];
148
-
149
-            return false;
150
-        }
151
-
152
-        $value = $this->getRelationshipValue($relation);
153
-
154
-        if (is_null($value)) {
155
-            $this->relationships[$relation] = [];
156
-
157
-            // If the relationship's content is the null value
158
-            // and the Entity's exist in DB, we'll interpret this
159
-            // as the need to detach all related Entities,
160
-            // therefore a sync operation is needed.
161
-            $this->needSync[] = $relation;
162
-
163
-            return false;
164
-        }
165
-
166
-        return $value;
167
-    }
168
-
169
-    /**
170
-     * Parse a 'single' relationship.
171
-     *
172
-     * @param string $relation
173
-     *
174
-     * @throws MappingException
175
-     *
176
-     * @return bool
177
-     */
178
-    protected function parseSingleRelationship($relation)
179
-    {
180
-        if (!$value = $this->parseForCommonValues($relation)) {
181
-            return true;
182
-        }
183
-
184
-        if ($value instanceof Collection || is_array($value) || $value instanceof CollectionProxy) {
185
-            throw new MappingException("Entity's attribute $relation should not be array, or collection");
186
-        }
187
-
188
-        if ($value instanceof LazyLoadingInterface && !$value->isProxyInitialized()) {
189
-            $this->relationships[$relation] = [];
190
-
191
-            return true;
192
-        }
193
-
194
-        // If the attribute is a loaded proxy, swap it for its
195
-        // loaded entity.
196
-        if ($value instanceof LazyLoadingInterface && $value->isProxyInitialized()) {
197
-            $value = $value->getWrappedValueHolderValue();
198
-        }
199
-
200
-        if ($this->isParentOrRoot($value)) {
201
-            $this->relationships[$relation] = [];
202
-
203
-            return true;
204
-        }
205
-
206
-        // At this point, we can assume the attribute is an Entity instance
207
-        // so we'll treat it as such.
208
-        $subAggregate = $this->createSubAggregate($value, $relation);
209
-
210
-        // Even if it's a single entity, we'll store it as an array
211
-        // just for consistency with other relationships
212
-        $this->relationships[$relation] = [$subAggregate];
213
-
214
-        // We always need to check a loaded relation is in sync
215
-        // with its local key
216
-        $this->needSync[] = $relation;
217
-
218
-        return true;
219
-    }
220
-
221
-    /**
222
-     * Check if value isn't parent or root in the aggregate.
223
-     *
224
-     * @param  mixed
225
-     *
226
-     * @return bool|null
227
-     */
228
-    protected function isParentOrRoot($value)
229
-    {
230
-        if (!is_null($this->root)) {
231
-            $rootClass = get_class($this->root->getEntityObject());
232
-            if ($rootClass == get_class($value)) {
233
-                return true;
234
-            }
235
-        }
236
-
237
-        if (!is_null($this->parent)) {
238
-            $parentClass = get_class($this->parent->getEntityObject());
239
-            if ($parentClass == get_class($value)) {
240
-                return true;
241
-            }
242
-        }
243
-    }
244
-
245
-    /**
246
-     * Parse a 'many' relationship.
247
-     *
248
-     * @param string $relation
249
-     *
250
-     * @throws MappingException
251
-     *
252
-     * @return bool
253
-     */
254
-    protected function parseManyRelationship($relation)
255
-    {
256
-        if (!$value = $this->parseForCommonValues($relation)) {
257
-            return true;
258
-        }
259
-
260
-        if (is_array($value) || (!$value instanceof CollectionProxy && $value instanceof Collection)) {
261
-            $this->needSync[] = $relation;
262
-        }
263
-
264
-        // If the relation is a proxy, we test is the relation
265
-        // has been lazy loaded, otherwise we'll just treat
266
-        // the subset of newly added items.
267
-        if ($value instanceof CollectionProxy && $value->isProxyInitialized()) {
268
-            $this->needSync[] = $relation;
269
-            //$value = $value->getUnderlyingCollection();
270
-        }
271
-
272
-        if ($value instanceof CollectionProxy && !$value->isProxyInitialized()) {
273
-            $value = $value->getAddedItems();
274
-        }
275
-
276
-        // At this point $value should be either an array or an instance
277
-        // of a collection class.
278
-        if (!is_array($value) && !$value instanceof Collection) {
279
-            throw new MappingException("'$relation' attribute should be array() or Collection");
280
-        }
281
-
282
-        $this->relationships[$relation] = $this->createSubAggregates($value, $relation);
283
-
284
-        return true;
285
-    }
286
-
287
-    /**
288
-     * Return Entity's relationship attribute.
289
-     *
290
-     * @param string $relation
291
-     *
292
-     * @throws MappingException
293
-     *
294
-     * @return mixed
295
-     */
296
-    protected function getRelationshipValue($relation)
297
-    {
298
-        $value = $this->getEntityAttribute($relation);
299
-
300
-        if (is_bool($value) || is_float($value) || is_int($value) || is_string($value)) {
301
-            throw new MappingException("Entity's attribute $relation should be array, object, collection or null");
302
-        }
303
-
304
-        return $value;
305
-    }
306
-
307
-    /**
308
-     * Create a child, aggregated entity.
309
-     *
310
-     * @param mixed  $entities
311
-     * @param string $relation
312
-     *
313
-     * @return array
314
-     */
315
-    protected function createSubAggregates($entities, $relation)
316
-    {
317
-        $aggregates = [];
318
-
319
-        foreach ($entities as $entity) {
320
-            $aggregates[] = $this->createSubAggregate($entity, $relation);
321
-        }
322
-
323
-        return $aggregates;
324
-    }
325
-
326
-    /**
327
-     * Create a related subAggregate.
328
-     *
329
-     * @param mixed  $entity
330
-     * @param string $relation
331
-     *
332
-     * @throws MappingException
333
-     *
334
-     * @return self
335
-     */
336
-    protected function createSubAggregate($entity, $relation)
337
-    {
338
-        // If root isn't defined, then this is the Aggregate Root
339
-        if (is_null($this->root)) {
340
-            $root = $this;
341
-        } else {
342
-            $root = $this->root;
343
-        }
344
-
345
-        return new self($entity, $this, $relation, $root);
346
-    }
347
-
348
-    /**
349
-     * Get the Entity's primary key attribute.
350
-     *
351
-     * @return string|int
352
-     */
353
-    public function getEntityId()
354
-    {
355
-        return $this->wrappedEntity->getEntityKey();
356
-    }
357
-
358
-    /**
359
-     * Get the name of the primary key.
360
-     *
361
-     * @return string
362
-     */
363
-    public function getEntityKey()
364
-    {
365
-        return $this->entityMap->getKeyName();
366
-    }
367
-
368
-    /**
369
-     * Return the entity map for the current entity.
370
-     *
371
-     * @return \Analogue\ORM\EntityMap
372
-     */
373
-    public function getEntityMap()
374
-    {
375
-        return $this->entityMap;
376
-    }
377
-
378
-    /**
379
-     * Return the Entity's hash $class.$id.
380
-     *
381
-     * @return string
382
-     */
383
-    public function getEntityHash()
384
-    {
385
-        return $this->getEntityClass().'.'.$this->getEntityId();
386
-    }
387
-
388
-    /**
389
-     * Get wrapped entity class.
390
-     *
391
-     * @return string
392
-     */
393
-    public function getEntityClass()
394
-    {
395
-        return $this->entityMap->getClass();
396
-    }
397
-
398
-    /**
399
-     * Return the Mapper's entity cache.
400
-     *
401
-     * @return \Analogue\ORM\System\EntityCache
402
-     */
403
-    protected function getEntityCache()
404
-    {
405
-        return $this->getMapper()->getEntityCache();
406
-    }
407
-
408
-    /**
409
-     * Get a relationship as an aggregated entities' array.
410
-     *
411
-     * @param string $name
412
-     *
413
-     * @return array
414
-     */
415
-    public function getRelationship($name)
416
-    {
417
-        if (array_key_exists($name, $this->relationships)) {
418
-            return $this->relationships[$name];
419
-        } else {
420
-            return [];
421
-        }
422
-    }
423
-
424
-    /**
425
-     * [TO IMPLEMENT].
426
-     *
427
-     * @return array
428
-     */
429
-    public function getPivotAttributes()
430
-    {
431
-        return [];
432
-    }
433
-
434
-    /**
435
-     * Get Non existing related entities from several relationships.
436
-     *
437
-     * @param array $relationships
438
-     *
439
-     * @return array
440
-     */
441
-    public function getNonExistingRelated(array $relationships)
442
-    {
443
-        $nonExisting = [];
444
-
445
-        foreach ($relationships as $relation) {
446
-            if ($this->hasAttribute($relation) && array_key_exists($relation, $this->relationships)) {
447
-                $nonExisting = array_merge($nonExisting, $this->getNonExistingFromRelation($relation));
448
-            }
449
-        }
450
-
451
-        return $nonExisting;
452
-    }
453
-
454
-    /**
455
-     * Get non-existing related entities from a single relation.
456
-     *
457
-     * @param string $relation
458
-     *
459
-     * @return array
460
-     */
461
-    protected function getNonExistingFromRelation($relation)
462
-    {
463
-        $nonExisting = [];
464
-
465
-        foreach ($this->relationships[$relation] as $aggregate) {
466
-            if (!$aggregate->exists()) {
467
-                $nonExisting[] = $aggregate;
468
-            }
469
-        }
470
-
471
-        return $nonExisting;
472
-    }
473
-
474
-    /**
475
-     * Synchronize relationships if needed.
476
-     *
477
-     * @param array
478
-     *
479
-     * @return void
480
-     */
481
-    public function syncRelationships(array $relationships)
482
-    {
483
-        foreach ($relationships as $relation) {
484
-            if (in_array($relation, $this->needSync)) {
485
-                $this->synchronize($relation);
486
-            }
487
-        }
488
-    }
489
-
490
-    /**
491
-     * Synchronize a relationship attribute.
492
-     *
493
-     * @param $relation
494
-     *
495
-     * @return void
496
-     */
497
-    protected function synchronize($relation)
498
-    {
499
-        $actualContent = $this->relationships[$relation];
500
-
501
-        $relationshipObject = $this->entityMap->$relation($this->getEntityObject());
502
-        $relationshipObject->setParent($this->wrappedEntity);
503
-        $relationshipObject->sync($actualContent);
504
-    }
505
-
506
-    /**
507
-     * Returns an array of Missing related Entities for the
508
-     * given $relation.
509
-     *
510
-     * @param string $relation
511
-     *
512
-     * @return array
513
-     */
514
-    public function getMissingEntities($relation)
515
-    {
516
-        $cachedRelations = $this->getCachedAttribute($relation);
517
-
518
-        if (!is_null($cachedRelations)) {
519
-            $missing = [];
520
-
521
-            foreach ($cachedRelations as $hash) {
522
-                if (!$this->getRelatedAggregateFromHash($hash, $relation)) {
523
-                    $missing[] = $hash;
524
-                }
525
-            }
526
-
527
-            return $missing;
528
-        } else {
529
-            return [];
530
-        }
531
-    }
532
-
533
-    /**
534
-     * Get Relationships who have dirty attributes / dirty relationships.
535
-     *
536
-     * @return array
537
-     */
538
-    public function getDirtyRelationships()
539
-    {
540
-        $dirtyAggregates = [];
541
-
542
-        foreach ($this->relationships as $relation) {
543
-            foreach ($relation as $aggregate) {
544
-                if (!$aggregate->exists() || $aggregate->isDirty() || count($aggregate->getDirtyRelationships()) > 0) {
545
-                    $dirtyAggregates[] = $aggregate;
546
-                }
547
-            }
548
-        }
549
-
550
-        return $dirtyAggregates;
551
-    }
552
-
553
-    /**
554
-     * Compare the object's raw attributes with the record in cache.
555
-     *
556
-     * @return bool
557
-     */
558
-    public function isDirty()
559
-    {
560
-        if (count($this->getDirtyRawAttributes()) > 0) {
561
-            return true;
562
-        } else {
563
-            return false;
564
-        }
565
-    }
566
-
567
-    /**
568
-     * Get Raw Entity's attributes, as they are represented
569
-     * in the database, including value objects, foreign keys,
570
-     * and discriminator column.
571
-     *
572
-     * @return array
573
-     */
574
-    public function getRawAttributes()
575
-    {
576
-        $attributes = $this->wrappedEntity->getEntityAttributes();
577
-
578
-        foreach ($this->entityMap->getNonEmbeddedRelationships() as $relation) {
579
-            unset($attributes[$relation]);
580
-        }
581
-
582
-        if ($this->entityMap->getInheritanceType() == 'single_table') {
583
-            $attributes = $this->addDiscriminatorColumn($attributes);
584
-        }
585
-
586
-        $attributes = $this->flattenEmbeddables($attributes);
587
-
588
-        $foreignKeys = $this->getForeignKeyAttributes();
589
-
590
-        return $foreignKeys + $attributes;
591
-    }
592
-
593
-    /**
594
-     * Add Discriminator Column if it doesn't exist on the actual entity.
595
-     *
596
-     * @param array $attributes
597
-     *
598
-     * @return array
599
-     */
600
-    protected function addDiscriminatorColumn($attributes)
601
-    {
602
-        $discriminatorColumn = $this->entityMap->getDiscriminatorColumn();
603
-        $entityClass = $this->entityMap->getClass();
604
-
605
-        if (!array_key_exists($discriminatorColumn, $attributes)) {
606
-
607
-            // Use key if present in discriminatorMap
608
-            $map = $this->entityMap->getDiscriminatorColumnMap();
609
-
610
-            $type = array_search($entityClass, $map);
611
-
612
-            if ($type === false) {
613
-                // Use entity FQDN if no corresponding key is set
614
-                $attributes[$discriminatorColumn] = $entityClass;
615
-            } else {
616
-                $attributes[$discriminatorColumn] = $type;
617
-            }
618
-        }
619
-
620
-        return $attributes;
621
-    }
622
-
623
-    /**
624
-     * Convert Value Objects to raw db attributes.
625
-     *
626
-     * @param array $attributes
627
-     *
628
-     * @return array
629
-     */
630
-    protected function flattenEmbeddables($attributes)
631
-    {
632
-        // TODO : deprecate old implementation
633
-        $embeddables = $this->entityMap->getEmbeddables();
634
-
635
-        foreach ($embeddables as $localKey => $embed) {
636
-            // Retrieve the value object from the entity's attributes
637
-            $valueObject = $attributes[$localKey];
638
-
639
-            // Unset the corresponding key
640
-            unset($attributes[$localKey]);
641
-
642
-            // TODO Make wrapper object compatible with value objects
643
-            $valueObjectAttributes = $valueObject->getEntityAttributes();
644
-
645
-            // Now (if setup in the entity map) we prefix the value object's
646
-            // attributes with the snake_case name of the embedded class.
647
-            $prefix = snake_case(class_basename($embed));
648
-
649
-            foreach ($valueObjectAttributes as $key=>$value) {
650
-                $valueObjectAttributes[$prefix.'_'.$key] = $value;
651
-                unset($valueObjectAttributes[$key]);
652
-            }
653
-
654
-            $attributes = array_merge($attributes, $valueObjectAttributes);
655
-        }
656
-
657
-        //*********************
658
-        // New implementation
659
-        // *****************->
660
-
661
-        $embeddedRelations = $this->entityMap->getEmbeddedRelationships();
662
-
663
-        foreach ($embeddedRelations as $relation) {
664
-
665
-            // Spawn a new instance we can pass to the relationship methdod
666
-            $parentInstance = $this->getMapper()->newInstance();
667
-            $relationInstance = $this->entityMap->$relation($parentInstance);
668
-
669
-            // Extract the object from the attributes
670
-            $embeddedObject = $attributes[$relation];
671
-
672
-            unset($attributes[$relation]);
673
-
674
-            $attributes = $relationInstance->normalize($embeddedObject) + $attributes;
675
-        }
676
-
677
-        return $attributes;
678
-    }
679
-
680
-    /**
681
-     * Return's entity raw attributes in the state they were at last
682
-     * query.
683
-     *
684
-     * @param array|null $columns
685
-     *
686
-     * @return array
687
-     */
688
-    protected function getCachedRawAttributes(array $columns = null)
689
-    {
690
-        $cachedAttributes = $this->getCache()->get($this->getEntityId());
691
-
692
-        if (is_null($columns)) {
693
-            return $cachedAttributes;
694
-        } else {
695
-            return array_only($cachedAttributes, $columns);
696
-        }
697
-    }
698
-
699
-    /**
700
-     * Return a single attribute from the cache.
701
-     *
702
-     * @param string $key
703
-     *
704
-     * @return mixed
705
-     */
706
-    protected function getCachedAttribute($key)
707
-    {
708
-        $cachedAttributes = $this->getCache()->get($this->getEntityId());
709
-
710
-        if (!array_key_exists($key, $cachedAttributes)) {
711
-            return;
712
-        } else {
713
-            return $cachedAttributes[$key];
714
-        }
715
-    }
716
-
717
-    /**
718
-     * Convert related Entity's attributes to foreign keys.
719
-     *
720
-     * @return array
721
-     */
722
-    protected function getForeignKeyAttributes()
723
-    {
724
-        $foreignKeys = [];
725
-
726
-        foreach ($this->entityMap->getLocalRelationships() as $relation) {
727
-            // check if relationship has been parsed, meaning it has an actual object
728
-            // in the entity's attributes
729
-            if ($this->isActualRelationships($relation)) {
730
-                $foreignKeys = $foreignKeys + $this->getForeignKeyAttributesFromRelation($relation);
731
-            } else {
732
-                $foreignKeys = $foreignKeys + $this->getNullForeignKeyFromRelation($relation);
733
-            }
734
-        }
735
-
736
-        if (!is_null($this->parent)) {
737
-            $foreignKeys = $this->getForeignKeyAttributesFromParent() + $foreignKeys;
738
-        }
739
-
740
-        return $foreignKeys;
741
-    }
742
-
743
-    /**
744
-     * Get a null foreign key value pair for an empty relationship.
745
-     *
746
-     * @param [type] $relation [description]
747
-     *
748
-     * @return [type] [description]
749
-     */
750
-    protected function getNullForeignKeyFromRelation($relation) : array
751
-    {
752
-        $key = $this->entityMap->getLocalKeys($relation);
753
-
754
-        // If the relation is polymorphic, we'll set both key and type
755
-        // to null
756
-        if (is_array($key)) {
757
-            return [
758
-                $key['type'] => null,
759
-                $key['id']   => null,
760
-            ];
761
-        }
762
-
763
-        return [$key => null];
764
-    }
765
-
766
-    /**
767
-     * Return an associative array containing the key-value pair(s) from
768
-     * the related entity.
769
-     *
770
-     * @param string $relation
771
-     *
772
-     * @return array
773
-     */
774
-    protected function getForeignKeyAttributesFromRelation($relation)
775
-    {
776
-        // Call Relationship's method
777
-        $relationship = $this->entityMap->$relation($this->getEntityObject());
778
-
779
-        $relatedAggregate = $this->relationships[$relation][0];
780
-
781
-        return $relationship->getForeignKeyValuePair($relatedAggregate->getEntityObject());
782
-    }
783
-
784
-    /**
785
-     * Get foreign key attribute(s) from a parent entity in this
786
-     * aggregate context.
787
-     *
788
-     * @return array
789
-     */
790
-    protected function getForeignKeyAttributesFromParent()
791
-    {
792
-        $parentMap = $this->parent->getEntityMap();
793
-
794
-        $parentForeignRelations = $parentMap->getForeignRelationships();
795
-        $parentPivotRelations = $parentMap->getPivotRelationships();
796
-
797
-        // The parentRelation is the name of the relationship
798
-        // methods on the parent entity map
799
-        $parentRelation = $this->parentRelationship;
800
-
801
-        if (in_array($parentRelation, $parentForeignRelations)
802
-            && !in_array($parentRelation, $parentPivotRelations)
803
-        ) {
804
-            $parentObject = $this->parent->getEntityObject();
805
-
806
-            // Call Relationship's method on parent map
807
-            $relationship = $parentMap->$parentRelation($parentObject);
808
-
809
-            return $relationship->getForeignKeyValuePair($parentObject);
810
-        } else {
811
-            return [];
812
-        }
813
-    }
814
-
815
-    /**
816
-     * Update Pivot records on loaded relationships, by comparing the
817
-     * values from the Entity Cache to the actual relationship inside
818
-     * the aggregated entity.
819
-     *
820
-     * @return void
821
-     */
822
-    public function updatePivotRecords()
823
-    {
824
-        $pivots = $this->entityMap->getPivotRelationships();
825
-
826
-        foreach ($pivots as $pivot) {
827
-            if (array_key_exists($pivot, $this->relationships)) {
828
-                $this->updatePivotRelation($pivot);
829
-            }
830
-        }
831
-    }
832
-
833
-    /**
834
-     * Update Single pivot relationship.
835
-     *
836
-     * @param string $relation
837
-     *
838
-     * @return void
839
-     */
840
-    protected function updatePivotRelation($relation)
841
-    {
842
-        $hashes = $this->getEntityHashesFromRelation($relation);
843
-
844
-        $cachedAttributes = $this->getCachedRawAttributes();
845
-
846
-        if (array_key_exists($relation, $cachedAttributes)) {
847
-            // Compare the two array of hashes to find out existing
848
-            // pivot records, and the ones to be created.
849
-            $new = array_diff($hashes, array_keys($cachedAttributes[$relation]));
850
-            $existing = array_intersect($hashes, array_keys($cachedAttributes[$relation]));
851
-        } else {
852
-            $existing = [];
853
-            $new = $hashes;
854
-        }
855
-
856
-        if (count($new) > 0) {
857
-            $pivots = $this->getRelatedAggregatesFromHashes($new, $relation);
858
-
859
-            $this->entityMap->$relation($this->getEntityObject())->createPivots($pivots);
860
-        }
861
-
862
-        if (count($existing) > 0) {
863
-            foreach ($existing as $pivotHash) {
864
-                $this->updatePivotIfDirty($pivotHash, $relation);
865
-            }
866
-        }
867
-    }
868
-
869
-    /**
870
-     * Compare existing pivot record in cache and update it
871
-     * if the pivot attributes are dirty.
872
-     *
873
-     * @param string $pivotHash
874
-     * @param string $relation
875
-     *
876
-     * @return void
877
-     */
878
-    protected function updatePivotIfDirty($pivotHash, $relation)
879
-    {
880
-        $aggregate = $this->getRelatedAggregateFromHash($pivotHash, $relation);
881
-
882
-        if ($aggregate->hasAttribute('pivot')) {
883
-            $pivot = $aggregate->getEntityAttribute('pivot')->getEntityAttributes();
884
-
885
-            $cachedPivotAttributes = $this->getPivotAttributesFromCache($pivotHash, $relation);
886
-
887
-            $actualPivotAttributes = array_only($pivot, array_keys($cachedPivotAttributes));
888
-
889
-            $dirty = $this->getDirtyAttributes($actualPivotAttributes, $cachedPivotAttributes);
890
-
891
-            if (count($dirty) > 0) {
892
-                $id = $aggregate->getEntityId();
893
-
894
-                $this->entityMap->$relation($this->getEntityObject())->updateExistingPivot($id, $dirty);
895
-            }
896
-        }
897
-    }
898
-
899
-    /**
900
-     * Compare two attributes array and return dirty attributes.
901
-     *
902
-     * @param array $actual
903
-     * @param array $cached
904
-     *
905
-     * @return array
906
-     */
907
-    protected function getDirtyAttributes(array $actual, array $cached)
908
-    {
909
-        $dirty = [];
910
-
911
-        foreach ($actual as $key => $value) {
912
-            if (!$this->originalIsNumericallyEquivalent($value, $cached[$key])) {
913
-                $dirty[$key] = $actual[$key];
914
-            }
915
-        }
916
-
917
-        return $dirty;
918
-    }
919
-
920
-    /**
921
-     * @param string $pivotHash
922
-     * @param string $relation
923
-     *
924
-     * @return array
925
-     */
926
-    protected function getPivotAttributesFromCache($pivotHash, $relation)
927
-    {
928
-        $cachedAttributes = $this->getCachedRawAttributes();
929
-
930
-        $cachedRelations = $cachedAttributes[$relation];
931
-
932
-        foreach ($cachedRelations as $cachedRelation) {
933
-            if ($cachedRelation == $pivotHash) {
934
-                return $cachedRelation->getPivotAttributes();
935
-            }
936
-        }
937
-    }
938
-
939
-    /**
940
-     * Returns an array of related Aggregates from its entity hashes.
941
-     *
942
-     * @param array  $hashes
943
-     * @param string $relation
944
-     *
945
-     * @return array
946
-     */
947
-    protected function getRelatedAggregatesFromHashes(array $hashes, $relation)
948
-    {
949
-        $related = [];
950
-
951
-        foreach ($hashes as $hash) {
952
-            $aggregate = $this->getRelatedAggregateFromHash($hash, $relation);
953
-
954
-            if (!is_null($aggregate)) {
955
-                $related[] = $aggregate;
956
-            }
957
-        }
958
-
959
-        return $related;
960
-    }
961
-
962
-    /**
963
-     * Get related aggregate from its hash.
964
-     *
965
-     * @param string $hash
966
-     * @param string $relation
967
-     *
968
-     * @return \Analogue\ORM\System\Aggregate|null
969
-     */
970
-    protected function getRelatedAggregateFromHash($hash, $relation)
971
-    {
972
-        foreach ($this->relationships[$relation] as $aggregate) {
973
-            if ($aggregate->getEntityHash() == $hash) {
974
-                return $aggregate;
975
-            }
976
-        }
977
-    }
978
-
979
-    /**
980
-     * Return an array of Entity Hashes from a specific relation.
981
-     *
982
-     * @param string $relation
983
-     *
984
-     * @return array
985
-     */
986
-    protected function getEntityHashesFromRelation($relation)
987
-    {
988
-        return array_map(function ($aggregate) {
989
-            return $aggregate->getEntityHash();
990
-        }, $this->relationships[$relation]);
991
-    }
992
-
993
-    /**
994
-     * Check the existence of an actual relationship.
995
-     *
996
-     * @param string $relation
997
-     *
998
-     * @return bool
999
-     */
1000
-    protected function isActualRelationships($relation)
1001
-    {
1002
-        return array_key_exists($relation, $this->relationships)
1003
-            && count($this->relationships[$relation]) > 0;
1004
-    }
1005
-
1006
-    /**
1007
-     * Return cache instance for the current entity type.
1008
-     *
1009
-     * @return \Analogue\ORM\System\EntityCache
1010
-     */
1011
-    protected function getCache()
1012
-    {
1013
-        return $this->getMapper()->getEntityCache();
1014
-    }
1015
-
1016
-    /**
1017
-     * Get Only Raw Entiy's attributes which have been modified
1018
-     * since last query.
1019
-     *
1020
-     * @return array
1021
-     */
1022
-    public function getDirtyRawAttributes()
1023
-    {
1024
-        $attributes = $this->getRawAttributes();
1025
-        $cachedAttributes = $this->getCachedRawAttributes(array_keys($attributes));
1026
-
1027
-        $dirty = [];
1028
-
1029
-        foreach ($attributes as $key => $value) {
1030
-            if ($this->isRelation($key) || $key == 'pivot') {
1031
-                continue;
1032
-            }
1033
-
1034
-            if (!array_key_exists($key, $cachedAttributes) && !$value instanceof Pivot) {
1035
-                $dirty[$key] = $value;
1036
-            } elseif ($value !== $cachedAttributes[$key] &&
1037
-                !$this->originalIsNumericallyEquivalent($value, $cachedAttributes[$key])) {
1038
-                $dirty[$key] = $value;
1039
-            }
1040
-        }
1041
-
1042
-        return $dirty;
1043
-    }
1044
-
1045
-    /**
1046
-     * @param $key
1047
-     *
1048
-     * @return bool
1049
-     */
1050
-    protected function isRelation($key)
1051
-    {
1052
-        return in_array($key, $this->entityMap->getRelationships());
1053
-    }
1054
-
1055
-    /**
1056
-     * Determine if the new and old values for a given key are numerically equivalent.
1057
-     *
1058
-     * @param $current
1059
-     * @param $original
1060
-     *
1061
-     * @return bool
1062
-     */
1063
-    protected function originalIsNumericallyEquivalent($current, $original)
1064
-    {
1065
-        return is_numeric($current) && is_numeric($original) && strcmp((string) $current, (string) $original) === 0;
1066
-    }
1067
-
1068
-    /**
1069
-     * Get the underlying entity object.
1070
-     *
1071
-     * @return mixed
1072
-     */
1073
-    public function getEntityObject()
1074
-    {
1075
-        return $this->wrappedEntity->getObject();
1076
-    }
1077
-
1078
-    /**
1079
-     * Return the Mapper instance for the current Entity Type.
1080
-     *
1081
-     * @return \Analogue\ORM\System\Mapper
1082
-     */
1083
-    public function getMapper()
1084
-    {
1085
-        return Manager::getMapper($this->class);
1086
-    }
1087
-
1088
-    /**
1089
-     * Check that the entity already exists in the database, by checking
1090
-     * if it has an EntityCache record.
1091
-     *
1092
-     * @return bool
1093
-     */
1094
-    public function exists()
1095
-    {
1096
-        return $this->getCache()->has($this->getEntityId());
1097
-    }
1098
-
1099
-    /**
1100
-     * Set the object attribute raw values (hydration).
1101
-     *
1102
-     * @param array $attributes
1103
-     */
1104
-    public function setEntityAttributes(array $attributes)
1105
-    {
1106
-        $this->wrappedEntity->setEntityAttributes($attributes);
1107
-    }
1108
-
1109
-    /**
1110
-     * Get the raw object's values.
1111
-     *
1112
-     * @return array
1113
-     */
1114
-    public function getEntityAttributes()
1115
-    {
1116
-        return $this->wrappedEntity->getEntityAttributes();
1117
-    }
1118
-
1119
-    /**
1120
-     * Set the raw entity attributes.
1121
-     *
1122
-     * @param string $key
1123
-     * @param string $value
1124
-     */
1125
-    public function setEntityAttribute($key, $value)
1126
-    {
1127
-        $this->wrappedEntity->setEntityAttribute($key, $value);
1128
-    }
1129
-
1130
-    /**
1131
-     * Return the entity's attribute.
1132
-     *
1133
-     * @param string $key
1134
-     *
1135
-     * @return mixed
1136
-     */
1137
-    public function getEntityAttribute($key)
1138
-    {
1139
-        return $this->wrappedEntity->getEntityAttribute($key);
1140
-    }
1141
-
1142
-    /**
1143
-     * Does the attribute exists on the entity.
1144
-     *
1145
-     * @param string $key
1146
-     *
1147
-     * @return bool
1148
-     */
1149
-    public function hasAttribute($key)
1150
-    {
1151
-        return $this->wrappedEntity->hasAttribute($key);
1152
-    }
1153
-
1154
-    /**
1155
-     * Set the lazyloading proxies on the wrapped entity.
1156
-     *
1157
-     * @return void
1158
-     */
1159
-    public function setProxies()
1160
-    {
1161
-        $this->wrappedEntity->setProxies();
1162
-    }
1163
-
1164
-    /**
1165
-     * Hydrate the actual entity.
1166
-     *
1167
-     * @return void
1168
-     */
1169
-    public function hydrate()
1170
-    {
1171
-        $this->wrappedEntity->hydrate();
1172
-    }
18
+	/**
19
+	 * The Root Entity.
20
+	 *
21
+	 * @var \Analogue\ORM\System\Wrappers\Wrapper
22
+	 */
23
+	protected $wrappedEntity;
24
+
25
+	/**
26
+	 * Class of the entity being aggregated.
27
+	 *
28
+	 * @var string
29
+	 */
30
+	protected $class;
31
+
32
+	/**
33
+	 * Parent Root Aggregate.
34
+	 *
35
+	 * @var \Analogue\ORM\System\Aggregate
36
+	 */
37
+	protected $parent;
38
+
39
+	/**
40
+	 * Parent's relationship method.
41
+	 *
42
+	 * @var string
43
+	 */
44
+	protected $parentRelationship;
45
+
46
+	/**
47
+	 * Root Entity.
48
+	 *
49
+	 * @var \Analogue\ORM\System\Aggregate
50
+	 */
51
+	protected $root;
52
+
53
+	/**
54
+	 * An associative array containing entity's
55
+	 * relationships converted to Aggregates.
56
+	 *
57
+	 * @var array
58
+	 */
59
+	protected $relationships = [];
60
+
61
+	/**
62
+	 * Relationship that need post-command synchronization.
63
+	 *
64
+	 * @var array
65
+	 */
66
+	protected $needSync = [];
67
+
68
+	/**
69
+	 * Mapper.
70
+	 *
71
+	 * @var \Analogue\ORM\System\Mapper;
72
+	 */
73
+	protected $mapper;
74
+
75
+	/**
76
+	 * Entity Map.
77
+	 *
78
+	 * @var \Analogue\ORM\EntityMap;
79
+	 */
80
+	protected $entityMap;
81
+
82
+	/**
83
+	 * Create a new Aggregated Entity instance.
84
+	 *
85
+	 * @param mixed          $entity
86
+	 * @param Aggregate|null $parent
87
+	 * @param string         $parentRelationship
88
+	 * @param Aggregate|null $root
89
+	 *
90
+	 * @throws MappingException
91
+	 */
92
+	public function __construct($entity, Aggregate $parent = null, $parentRelationship = null, Aggregate $root = null)
93
+	{
94
+		$factory = new Factory();
95
+
96
+		$this->class = get_class($entity);
97
+
98
+		$this->wrappedEntity = $factory->make($entity);
99
+
100
+		$this->parent = $parent;
101
+
102
+		$this->parentRelationship = $parentRelationship;
103
+
104
+		$this->root = $root;
105
+
106
+		$mapper = $this->getMapper($entity);
107
+
108
+		$this->entityMap = $mapper->getEntityMap();
109
+
110
+		$this->parseRelationships();
111
+	}
112
+
113
+	/**
114
+	 * Parse Every relationships defined on the entity.
115
+	 *
116
+	 * @throws MappingException
117
+	 *
118
+	 * @return void
119
+	 */
120
+	protected function parseRelationships()
121
+	{
122
+		foreach ($this->entityMap->getSingleRelationships() as $relation) {
123
+			$this->parseSingleRelationship($relation);
124
+		}
125
+
126
+		foreach ($this->entityMap->getManyRelationships() as $relation) {
127
+			$this->parseManyRelationship($relation);
128
+		}
129
+	}
130
+
131
+	/**
132
+	 * Parse for values common to single & many relations.
133
+	 *
134
+	 * @param string $relation
135
+	 *
136
+	 * @throws MappingException
137
+	 *
138
+	 * @return mixed|bool
139
+	 */
140
+	protected function parseForCommonValues($relation)
141
+	{
142
+		if (!$this->hasAttribute($relation)) {
143
+			// If no attribute exists for this relationships
144
+			// we'll make it a simple empty array. This will
145
+			// save us from constantly checking for the attributes
146
+			// actual existence.
147
+			$this->relationships[$relation] = [];
148
+
149
+			return false;
150
+		}
151
+
152
+		$value = $this->getRelationshipValue($relation);
153
+
154
+		if (is_null($value)) {
155
+			$this->relationships[$relation] = [];
156
+
157
+			// If the relationship's content is the null value
158
+			// and the Entity's exist in DB, we'll interpret this
159
+			// as the need to detach all related Entities,
160
+			// therefore a sync operation is needed.
161
+			$this->needSync[] = $relation;
162
+
163
+			return false;
164
+		}
165
+
166
+		return $value;
167
+	}
168
+
169
+	/**
170
+	 * Parse a 'single' relationship.
171
+	 *
172
+	 * @param string $relation
173
+	 *
174
+	 * @throws MappingException
175
+	 *
176
+	 * @return bool
177
+	 */
178
+	protected function parseSingleRelationship($relation)
179
+	{
180
+		if (!$value = $this->parseForCommonValues($relation)) {
181
+			return true;
182
+		}
183
+
184
+		if ($value instanceof Collection || is_array($value) || $value instanceof CollectionProxy) {
185
+			throw new MappingException("Entity's attribute $relation should not be array, or collection");
186
+		}
187
+
188
+		if ($value instanceof LazyLoadingInterface && !$value->isProxyInitialized()) {
189
+			$this->relationships[$relation] = [];
190
+
191
+			return true;
192
+		}
193
+
194
+		// If the attribute is a loaded proxy, swap it for its
195
+		// loaded entity.
196
+		if ($value instanceof LazyLoadingInterface && $value->isProxyInitialized()) {
197
+			$value = $value->getWrappedValueHolderValue();
198
+		}
199
+
200
+		if ($this->isParentOrRoot($value)) {
201
+			$this->relationships[$relation] = [];
202
+
203
+			return true;
204
+		}
205
+
206
+		// At this point, we can assume the attribute is an Entity instance
207
+		// so we'll treat it as such.
208
+		$subAggregate = $this->createSubAggregate($value, $relation);
209
+
210
+		// Even if it's a single entity, we'll store it as an array
211
+		// just for consistency with other relationships
212
+		$this->relationships[$relation] = [$subAggregate];
213
+
214
+		// We always need to check a loaded relation is in sync
215
+		// with its local key
216
+		$this->needSync[] = $relation;
217
+
218
+		return true;
219
+	}
220
+
221
+	/**
222
+	 * Check if value isn't parent or root in the aggregate.
223
+	 *
224
+	 * @param  mixed
225
+	 *
226
+	 * @return bool|null
227
+	 */
228
+	protected function isParentOrRoot($value)
229
+	{
230
+		if (!is_null($this->root)) {
231
+			$rootClass = get_class($this->root->getEntityObject());
232
+			if ($rootClass == get_class($value)) {
233
+				return true;
234
+			}
235
+		}
236
+
237
+		if (!is_null($this->parent)) {
238
+			$parentClass = get_class($this->parent->getEntityObject());
239
+			if ($parentClass == get_class($value)) {
240
+				return true;
241
+			}
242
+		}
243
+	}
244
+
245
+	/**
246
+	 * Parse a 'many' relationship.
247
+	 *
248
+	 * @param string $relation
249
+	 *
250
+	 * @throws MappingException
251
+	 *
252
+	 * @return bool
253
+	 */
254
+	protected function parseManyRelationship($relation)
255
+	{
256
+		if (!$value = $this->parseForCommonValues($relation)) {
257
+			return true;
258
+		}
259
+
260
+		if (is_array($value) || (!$value instanceof CollectionProxy && $value instanceof Collection)) {
261
+			$this->needSync[] = $relation;
262
+		}
263
+
264
+		// If the relation is a proxy, we test is the relation
265
+		// has been lazy loaded, otherwise we'll just treat
266
+		// the subset of newly added items.
267
+		if ($value instanceof CollectionProxy && $value->isProxyInitialized()) {
268
+			$this->needSync[] = $relation;
269
+			//$value = $value->getUnderlyingCollection();
270
+		}
271
+
272
+		if ($value instanceof CollectionProxy && !$value->isProxyInitialized()) {
273
+			$value = $value->getAddedItems();
274
+		}
275
+
276
+		// At this point $value should be either an array or an instance
277
+		// of a collection class.
278
+		if (!is_array($value) && !$value instanceof Collection) {
279
+			throw new MappingException("'$relation' attribute should be array() or Collection");
280
+		}
281
+
282
+		$this->relationships[$relation] = $this->createSubAggregates($value, $relation);
283
+
284
+		return true;
285
+	}
286
+
287
+	/**
288
+	 * Return Entity's relationship attribute.
289
+	 *
290
+	 * @param string $relation
291
+	 *
292
+	 * @throws MappingException
293
+	 *
294
+	 * @return mixed
295
+	 */
296
+	protected function getRelationshipValue($relation)
297
+	{
298
+		$value = $this->getEntityAttribute($relation);
299
+
300
+		if (is_bool($value) || is_float($value) || is_int($value) || is_string($value)) {
301
+			throw new MappingException("Entity's attribute $relation should be array, object, collection or null");
302
+		}
303
+
304
+		return $value;
305
+	}
306
+
307
+	/**
308
+	 * Create a child, aggregated entity.
309
+	 *
310
+	 * @param mixed  $entities
311
+	 * @param string $relation
312
+	 *
313
+	 * @return array
314
+	 */
315
+	protected function createSubAggregates($entities, $relation)
316
+	{
317
+		$aggregates = [];
318
+
319
+		foreach ($entities as $entity) {
320
+			$aggregates[] = $this->createSubAggregate($entity, $relation);
321
+		}
322
+
323
+		return $aggregates;
324
+	}
325
+
326
+	/**
327
+	 * Create a related subAggregate.
328
+	 *
329
+	 * @param mixed  $entity
330
+	 * @param string $relation
331
+	 *
332
+	 * @throws MappingException
333
+	 *
334
+	 * @return self
335
+	 */
336
+	protected function createSubAggregate($entity, $relation)
337
+	{
338
+		// If root isn't defined, then this is the Aggregate Root
339
+		if (is_null($this->root)) {
340
+			$root = $this;
341
+		} else {
342
+			$root = $this->root;
343
+		}
344
+
345
+		return new self($entity, $this, $relation, $root);
346
+	}
347
+
348
+	/**
349
+	 * Get the Entity's primary key attribute.
350
+	 *
351
+	 * @return string|int
352
+	 */
353
+	public function getEntityId()
354
+	{
355
+		return $this->wrappedEntity->getEntityKey();
356
+	}
357
+
358
+	/**
359
+	 * Get the name of the primary key.
360
+	 *
361
+	 * @return string
362
+	 */
363
+	public function getEntityKey()
364
+	{
365
+		return $this->entityMap->getKeyName();
366
+	}
367
+
368
+	/**
369
+	 * Return the entity map for the current entity.
370
+	 *
371
+	 * @return \Analogue\ORM\EntityMap
372
+	 */
373
+	public function getEntityMap()
374
+	{
375
+		return $this->entityMap;
376
+	}
377
+
378
+	/**
379
+	 * Return the Entity's hash $class.$id.
380
+	 *
381
+	 * @return string
382
+	 */
383
+	public function getEntityHash()
384
+	{
385
+		return $this->getEntityClass().'.'.$this->getEntityId();
386
+	}
387
+
388
+	/**
389
+	 * Get wrapped entity class.
390
+	 *
391
+	 * @return string
392
+	 */
393
+	public function getEntityClass()
394
+	{
395
+		return $this->entityMap->getClass();
396
+	}
397
+
398
+	/**
399
+	 * Return the Mapper's entity cache.
400
+	 *
401
+	 * @return \Analogue\ORM\System\EntityCache
402
+	 */
403
+	protected function getEntityCache()
404
+	{
405
+		return $this->getMapper()->getEntityCache();
406
+	}
407
+
408
+	/**
409
+	 * Get a relationship as an aggregated entities' array.
410
+	 *
411
+	 * @param string $name
412
+	 *
413
+	 * @return array
414
+	 */
415
+	public function getRelationship($name)
416
+	{
417
+		if (array_key_exists($name, $this->relationships)) {
418
+			return $this->relationships[$name];
419
+		} else {
420
+			return [];
421
+		}
422
+	}
423
+
424
+	/**
425
+	 * [TO IMPLEMENT].
426
+	 *
427
+	 * @return array
428
+	 */
429
+	public function getPivotAttributes()
430
+	{
431
+		return [];
432
+	}
433
+
434
+	/**
435
+	 * Get Non existing related entities from several relationships.
436
+	 *
437
+	 * @param array $relationships
438
+	 *
439
+	 * @return array
440
+	 */
441
+	public function getNonExistingRelated(array $relationships)
442
+	{
443
+		$nonExisting = [];
444
+
445
+		foreach ($relationships as $relation) {
446
+			if ($this->hasAttribute($relation) && array_key_exists($relation, $this->relationships)) {
447
+				$nonExisting = array_merge($nonExisting, $this->getNonExistingFromRelation($relation));
448
+			}
449
+		}
450
+
451
+		return $nonExisting;
452
+	}
453
+
454
+	/**
455
+	 * Get non-existing related entities from a single relation.
456
+	 *
457
+	 * @param string $relation
458
+	 *
459
+	 * @return array
460
+	 */
461
+	protected function getNonExistingFromRelation($relation)
462
+	{
463
+		$nonExisting = [];
464
+
465
+		foreach ($this->relationships[$relation] as $aggregate) {
466
+			if (!$aggregate->exists()) {
467
+				$nonExisting[] = $aggregate;
468
+			}
469
+		}
470
+
471
+		return $nonExisting;
472
+	}
473
+
474
+	/**
475
+	 * Synchronize relationships if needed.
476
+	 *
477
+	 * @param array
478
+	 *
479
+	 * @return void
480
+	 */
481
+	public function syncRelationships(array $relationships)
482
+	{
483
+		foreach ($relationships as $relation) {
484
+			if (in_array($relation, $this->needSync)) {
485
+				$this->synchronize($relation);
486
+			}
487
+		}
488
+	}
489
+
490
+	/**
491
+	 * Synchronize a relationship attribute.
492
+	 *
493
+	 * @param $relation
494
+	 *
495
+	 * @return void
496
+	 */
497
+	protected function synchronize($relation)
498
+	{
499
+		$actualContent = $this->relationships[$relation];
500
+
501
+		$relationshipObject = $this->entityMap->$relation($this->getEntityObject());
502
+		$relationshipObject->setParent($this->wrappedEntity);
503
+		$relationshipObject->sync($actualContent);
504
+	}
505
+
506
+	/**
507
+	 * Returns an array of Missing related Entities for the
508
+	 * given $relation.
509
+	 *
510
+	 * @param string $relation
511
+	 *
512
+	 * @return array
513
+	 */
514
+	public function getMissingEntities($relation)
515
+	{
516
+		$cachedRelations = $this->getCachedAttribute($relation);
517
+
518
+		if (!is_null($cachedRelations)) {
519
+			$missing = [];
520
+
521
+			foreach ($cachedRelations as $hash) {
522
+				if (!$this->getRelatedAggregateFromHash($hash, $relation)) {
523
+					$missing[] = $hash;
524
+				}
525
+			}
526
+
527
+			return $missing;
528
+		} else {
529
+			return [];
530
+		}
531
+	}
532
+
533
+	/**
534
+	 * Get Relationships who have dirty attributes / dirty relationships.
535
+	 *
536
+	 * @return array
537
+	 */
538
+	public function getDirtyRelationships()
539
+	{
540
+		$dirtyAggregates = [];
541
+
542
+		foreach ($this->relationships as $relation) {
543
+			foreach ($relation as $aggregate) {
544
+				if (!$aggregate->exists() || $aggregate->isDirty() || count($aggregate->getDirtyRelationships()) > 0) {
545
+					$dirtyAggregates[] = $aggregate;
546
+				}
547
+			}
548
+		}
549
+
550
+		return $dirtyAggregates;
551
+	}
552
+
553
+	/**
554
+	 * Compare the object's raw attributes with the record in cache.
555
+	 *
556
+	 * @return bool
557
+	 */
558
+	public function isDirty()
559
+	{
560
+		if (count($this->getDirtyRawAttributes()) > 0) {
561
+			return true;
562
+		} else {
563
+			return false;
564
+		}
565
+	}
566
+
567
+	/**
568
+	 * Get Raw Entity's attributes, as they are represented
569
+	 * in the database, including value objects, foreign keys,
570
+	 * and discriminator column.
571
+	 *
572
+	 * @return array
573
+	 */
574
+	public function getRawAttributes()
575
+	{
576
+		$attributes = $this->wrappedEntity->getEntityAttributes();
577
+
578
+		foreach ($this->entityMap->getNonEmbeddedRelationships() as $relation) {
579
+			unset($attributes[$relation]);
580
+		}
581
+
582
+		if ($this->entityMap->getInheritanceType() == 'single_table') {
583
+			$attributes = $this->addDiscriminatorColumn($attributes);
584
+		}
585
+
586
+		$attributes = $this->flattenEmbeddables($attributes);
587
+
588
+		$foreignKeys = $this->getForeignKeyAttributes();
589
+
590
+		return $foreignKeys + $attributes;
591
+	}
592
+
593
+	/**
594
+	 * Add Discriminator Column if it doesn't exist on the actual entity.
595
+	 *
596
+	 * @param array $attributes
597
+	 *
598
+	 * @return array
599
+	 */
600
+	protected function addDiscriminatorColumn($attributes)
601
+	{
602
+		$discriminatorColumn = $this->entityMap->getDiscriminatorColumn();
603
+		$entityClass = $this->entityMap->getClass();
604
+
605
+		if (!array_key_exists($discriminatorColumn, $attributes)) {
606
+
607
+			// Use key if present in discriminatorMap
608
+			$map = $this->entityMap->getDiscriminatorColumnMap();
609
+
610
+			$type = array_search($entityClass, $map);
611
+
612
+			if ($type === false) {
613
+				// Use entity FQDN if no corresponding key is set
614
+				$attributes[$discriminatorColumn] = $entityClass;
615
+			} else {
616
+				$attributes[$discriminatorColumn] = $type;
617
+			}
618
+		}
619
+
620
+		return $attributes;
621
+	}
622
+
623
+	/**
624
+	 * Convert Value Objects to raw db attributes.
625
+	 *
626
+	 * @param array $attributes
627
+	 *
628
+	 * @return array
629
+	 */
630
+	protected function flattenEmbeddables($attributes)
631
+	{
632
+		// TODO : deprecate old implementation
633
+		$embeddables = $this->entityMap->getEmbeddables();
634
+
635
+		foreach ($embeddables as $localKey => $embed) {
636
+			// Retrieve the value object from the entity's attributes
637
+			$valueObject = $attributes[$localKey];
638
+
639
+			// Unset the corresponding key
640
+			unset($attributes[$localKey]);
641
+
642
+			// TODO Make wrapper object compatible with value objects
643
+			$valueObjectAttributes = $valueObject->getEntityAttributes();
644
+
645
+			// Now (if setup in the entity map) we prefix the value object's
646
+			// attributes with the snake_case name of the embedded class.
647
+			$prefix = snake_case(class_basename($embed));
648
+
649
+			foreach ($valueObjectAttributes as $key=>$value) {
650
+				$valueObjectAttributes[$prefix.'_'.$key] = $value;
651
+				unset($valueObjectAttributes[$key]);
652
+			}
653
+
654
+			$attributes = array_merge($attributes, $valueObjectAttributes);
655
+		}
656
+
657
+		//*********************
658
+		// New implementation
659
+		// *****************->
660
+
661
+		$embeddedRelations = $this->entityMap->getEmbeddedRelationships();
662
+
663
+		foreach ($embeddedRelations as $relation) {
664
+
665
+			// Spawn a new instance we can pass to the relationship methdod
666
+			$parentInstance = $this->getMapper()->newInstance();
667
+			$relationInstance = $this->entityMap->$relation($parentInstance);
668
+
669
+			// Extract the object from the attributes
670
+			$embeddedObject = $attributes[$relation];
671
+
672
+			unset($attributes[$relation]);
673
+
674
+			$attributes = $relationInstance->normalize($embeddedObject) + $attributes;
675
+		}
676
+
677
+		return $attributes;
678
+	}
679
+
680
+	/**
681
+	 * Return's entity raw attributes in the state they were at last
682
+	 * query.
683
+	 *
684
+	 * @param array|null $columns
685
+	 *
686
+	 * @return array
687
+	 */
688
+	protected function getCachedRawAttributes(array $columns = null)
689
+	{
690
+		$cachedAttributes = $this->getCache()->get($this->getEntityId());
691
+
692
+		if (is_null($columns)) {
693
+			return $cachedAttributes;
694
+		} else {
695
+			return array_only($cachedAttributes, $columns);
696
+		}
697
+	}
698
+
699
+	/**
700
+	 * Return a single attribute from the cache.
701
+	 *
702
+	 * @param string $key
703
+	 *
704
+	 * @return mixed
705
+	 */
706
+	protected function getCachedAttribute($key)
707
+	{
708
+		$cachedAttributes = $this->getCache()->get($this->getEntityId());
709
+
710
+		if (!array_key_exists($key, $cachedAttributes)) {
711
+			return;
712
+		} else {
713
+			return $cachedAttributes[$key];
714
+		}
715
+	}
716
+
717
+	/**
718
+	 * Convert related Entity's attributes to foreign keys.
719
+	 *
720
+	 * @return array
721
+	 */
722
+	protected function getForeignKeyAttributes()
723
+	{
724
+		$foreignKeys = [];
725
+
726
+		foreach ($this->entityMap->getLocalRelationships() as $relation) {
727
+			// check if relationship has been parsed, meaning it has an actual object
728
+			// in the entity's attributes
729
+			if ($this->isActualRelationships($relation)) {
730
+				$foreignKeys = $foreignKeys + $this->getForeignKeyAttributesFromRelation($relation);
731
+			} else {
732
+				$foreignKeys = $foreignKeys + $this->getNullForeignKeyFromRelation($relation);
733
+			}
734
+		}
735
+
736
+		if (!is_null($this->parent)) {
737
+			$foreignKeys = $this->getForeignKeyAttributesFromParent() + $foreignKeys;
738
+		}
739
+
740
+		return $foreignKeys;
741
+	}
742
+
743
+	/**
744
+	 * Get a null foreign key value pair for an empty relationship.
745
+	 *
746
+	 * @param [type] $relation [description]
747
+	 *
748
+	 * @return [type] [description]
749
+	 */
750
+	protected function getNullForeignKeyFromRelation($relation) : array
751
+	{
752
+		$key = $this->entityMap->getLocalKeys($relation);
753
+
754
+		// If the relation is polymorphic, we'll set both key and type
755
+		// to null
756
+		if (is_array($key)) {
757
+			return [
758
+				$key['type'] => null,
759
+				$key['id']   => null,
760
+			];
761
+		}
762
+
763
+		return [$key => null];
764
+	}
765
+
766
+	/**
767
+	 * Return an associative array containing the key-value pair(s) from
768
+	 * the related entity.
769
+	 *
770
+	 * @param string $relation
771
+	 *
772
+	 * @return array
773
+	 */
774
+	protected function getForeignKeyAttributesFromRelation($relation)
775
+	{
776
+		// Call Relationship's method
777
+		$relationship = $this->entityMap->$relation($this->getEntityObject());
778
+
779
+		$relatedAggregate = $this->relationships[$relation][0];
780
+
781
+		return $relationship->getForeignKeyValuePair($relatedAggregate->getEntityObject());
782
+	}
783
+
784
+	/**
785
+	 * Get foreign key attribute(s) from a parent entity in this
786
+	 * aggregate context.
787
+	 *
788
+	 * @return array
789
+	 */
790
+	protected function getForeignKeyAttributesFromParent()
791
+	{
792
+		$parentMap = $this->parent->getEntityMap();
793
+
794
+		$parentForeignRelations = $parentMap->getForeignRelationships();
795
+		$parentPivotRelations = $parentMap->getPivotRelationships();
796
+
797
+		// The parentRelation is the name of the relationship
798
+		// methods on the parent entity map
799
+		$parentRelation = $this->parentRelationship;
800
+
801
+		if (in_array($parentRelation, $parentForeignRelations)
802
+			&& !in_array($parentRelation, $parentPivotRelations)
803
+		) {
804
+			$parentObject = $this->parent->getEntityObject();
805
+
806
+			// Call Relationship's method on parent map
807
+			$relationship = $parentMap->$parentRelation($parentObject);
808
+
809
+			return $relationship->getForeignKeyValuePair($parentObject);
810
+		} else {
811
+			return [];
812
+		}
813
+	}
814
+
815
+	/**
816
+	 * Update Pivot records on loaded relationships, by comparing the
817
+	 * values from the Entity Cache to the actual relationship inside
818
+	 * the aggregated entity.
819
+	 *
820
+	 * @return void
821
+	 */
822
+	public function updatePivotRecords()
823
+	{
824
+		$pivots = $this->entityMap->getPivotRelationships();
825
+
826
+		foreach ($pivots as $pivot) {
827
+			if (array_key_exists($pivot, $this->relationships)) {
828
+				$this->updatePivotRelation($pivot);
829
+			}
830
+		}
831
+	}
832
+
833
+	/**
834
+	 * Update Single pivot relationship.
835
+	 *
836
+	 * @param string $relation
837
+	 *
838
+	 * @return void
839
+	 */
840
+	protected function updatePivotRelation($relation)
841
+	{
842
+		$hashes = $this->getEntityHashesFromRelation($relation);
843
+
844
+		$cachedAttributes = $this->getCachedRawAttributes();
845
+
846
+		if (array_key_exists($relation, $cachedAttributes)) {
847
+			// Compare the two array of hashes to find out existing
848
+			// pivot records, and the ones to be created.
849
+			$new = array_diff($hashes, array_keys($cachedAttributes[$relation]));
850
+			$existing = array_intersect($hashes, array_keys($cachedAttributes[$relation]));
851
+		} else {
852
+			$existing = [];
853
+			$new = $hashes;
854
+		}
855
+
856
+		if (count($new) > 0) {
857
+			$pivots = $this->getRelatedAggregatesFromHashes($new, $relation);
858
+
859
+			$this->entityMap->$relation($this->getEntityObject())->createPivots($pivots);
860
+		}
861
+
862
+		if (count($existing) > 0) {
863
+			foreach ($existing as $pivotHash) {
864
+				$this->updatePivotIfDirty($pivotHash, $relation);
865
+			}
866
+		}
867
+	}
868
+
869
+	/**
870
+	 * Compare existing pivot record in cache and update it
871
+	 * if the pivot attributes are dirty.
872
+	 *
873
+	 * @param string $pivotHash
874
+	 * @param string $relation
875
+	 *
876
+	 * @return void
877
+	 */
878
+	protected function updatePivotIfDirty($pivotHash, $relation)
879
+	{
880
+		$aggregate = $this->getRelatedAggregateFromHash($pivotHash, $relation);
881
+
882
+		if ($aggregate->hasAttribute('pivot')) {
883
+			$pivot = $aggregate->getEntityAttribute('pivot')->getEntityAttributes();
884
+
885
+			$cachedPivotAttributes = $this->getPivotAttributesFromCache($pivotHash, $relation);
886
+
887
+			$actualPivotAttributes = array_only($pivot, array_keys($cachedPivotAttributes));
888
+
889
+			$dirty = $this->getDirtyAttributes($actualPivotAttributes, $cachedPivotAttributes);
890
+
891
+			if (count($dirty) > 0) {
892
+				$id = $aggregate->getEntityId();
893
+
894
+				$this->entityMap->$relation($this->getEntityObject())->updateExistingPivot($id, $dirty);
895
+			}
896
+		}
897
+	}
898
+
899
+	/**
900
+	 * Compare two attributes array and return dirty attributes.
901
+	 *
902
+	 * @param array $actual
903
+	 * @param array $cached
904
+	 *
905
+	 * @return array
906
+	 */
907
+	protected function getDirtyAttributes(array $actual, array $cached)
908
+	{
909
+		$dirty = [];
910
+
911
+		foreach ($actual as $key => $value) {
912
+			if (!$this->originalIsNumericallyEquivalent($value, $cached[$key])) {
913
+				$dirty[$key] = $actual[$key];
914
+			}
915
+		}
916
+
917
+		return $dirty;
918
+	}
919
+
920
+	/**
921
+	 * @param string $pivotHash
922
+	 * @param string $relation
923
+	 *
924
+	 * @return array
925
+	 */
926
+	protected function getPivotAttributesFromCache($pivotHash, $relation)
927
+	{
928
+		$cachedAttributes = $this->getCachedRawAttributes();
929
+
930
+		$cachedRelations = $cachedAttributes[$relation];
931
+
932
+		foreach ($cachedRelations as $cachedRelation) {
933
+			if ($cachedRelation == $pivotHash) {
934
+				return $cachedRelation->getPivotAttributes();
935
+			}
936
+		}
937
+	}
938
+
939
+	/**
940
+	 * Returns an array of related Aggregates from its entity hashes.
941
+	 *
942
+	 * @param array  $hashes
943
+	 * @param string $relation
944
+	 *
945
+	 * @return array
946
+	 */
947
+	protected function getRelatedAggregatesFromHashes(array $hashes, $relation)
948
+	{
949
+		$related = [];
950
+
951
+		foreach ($hashes as $hash) {
952
+			$aggregate = $this->getRelatedAggregateFromHash($hash, $relation);
953
+
954
+			if (!is_null($aggregate)) {
955
+				$related[] = $aggregate;
956
+			}
957
+		}
958
+
959
+		return $related;
960
+	}
961
+
962
+	/**
963
+	 * Get related aggregate from its hash.
964
+	 *
965
+	 * @param string $hash
966
+	 * @param string $relation
967
+	 *
968
+	 * @return \Analogue\ORM\System\Aggregate|null
969
+	 */
970
+	protected function getRelatedAggregateFromHash($hash, $relation)
971
+	{
972
+		foreach ($this->relationships[$relation] as $aggregate) {
973
+			if ($aggregate->getEntityHash() == $hash) {
974
+				return $aggregate;
975
+			}
976
+		}
977
+	}
978
+
979
+	/**
980
+	 * Return an array of Entity Hashes from a specific relation.
981
+	 *
982
+	 * @param string $relation
983
+	 *
984
+	 * @return array
985
+	 */
986
+	protected function getEntityHashesFromRelation($relation)
987
+	{
988
+		return array_map(function ($aggregate) {
989
+			return $aggregate->getEntityHash();
990
+		}, $this->relationships[$relation]);
991
+	}
992
+
993
+	/**
994
+	 * Check the existence of an actual relationship.
995
+	 *
996
+	 * @param string $relation
997
+	 *
998
+	 * @return bool
999
+	 */
1000
+	protected function isActualRelationships($relation)
1001
+	{
1002
+		return array_key_exists($relation, $this->relationships)
1003
+			&& count($this->relationships[$relation]) > 0;
1004
+	}
1005
+
1006
+	/**
1007
+	 * Return cache instance for the current entity type.
1008
+	 *
1009
+	 * @return \Analogue\ORM\System\EntityCache
1010
+	 */
1011
+	protected function getCache()
1012
+	{
1013
+		return $this->getMapper()->getEntityCache();
1014
+	}
1015
+
1016
+	/**
1017
+	 * Get Only Raw Entiy's attributes which have been modified
1018
+	 * since last query.
1019
+	 *
1020
+	 * @return array
1021
+	 */
1022
+	public function getDirtyRawAttributes()
1023
+	{
1024
+		$attributes = $this->getRawAttributes();
1025
+		$cachedAttributes = $this->getCachedRawAttributes(array_keys($attributes));
1026
+
1027
+		$dirty = [];
1028
+
1029
+		foreach ($attributes as $key => $value) {
1030
+			if ($this->isRelation($key) || $key == 'pivot') {
1031
+				continue;
1032
+			}
1033
+
1034
+			if (!array_key_exists($key, $cachedAttributes) && !$value instanceof Pivot) {
1035
+				$dirty[$key] = $value;
1036
+			} elseif ($value !== $cachedAttributes[$key] &&
1037
+				!$this->originalIsNumericallyEquivalent($value, $cachedAttributes[$key])) {
1038
+				$dirty[$key] = $value;
1039
+			}
1040
+		}
1041
+
1042
+		return $dirty;
1043
+	}
1044
+
1045
+	/**
1046
+	 * @param $key
1047
+	 *
1048
+	 * @return bool
1049
+	 */
1050
+	protected function isRelation($key)
1051
+	{
1052
+		return in_array($key, $this->entityMap->getRelationships());
1053
+	}
1054
+
1055
+	/**
1056
+	 * Determine if the new and old values for a given key are numerically equivalent.
1057
+	 *
1058
+	 * @param $current
1059
+	 * @param $original
1060
+	 *
1061
+	 * @return bool
1062
+	 */
1063
+	protected function originalIsNumericallyEquivalent($current, $original)
1064
+	{
1065
+		return is_numeric($current) && is_numeric($original) && strcmp((string) $current, (string) $original) === 0;
1066
+	}
1067
+
1068
+	/**
1069
+	 * Get the underlying entity object.
1070
+	 *
1071
+	 * @return mixed
1072
+	 */
1073
+	public function getEntityObject()
1074
+	{
1075
+		return $this->wrappedEntity->getObject();
1076
+	}
1077
+
1078
+	/**
1079
+	 * Return the Mapper instance for the current Entity Type.
1080
+	 *
1081
+	 * @return \Analogue\ORM\System\Mapper
1082
+	 */
1083
+	public function getMapper()
1084
+	{
1085
+		return Manager::getMapper($this->class);
1086
+	}
1087
+
1088
+	/**
1089
+	 * Check that the entity already exists in the database, by checking
1090
+	 * if it has an EntityCache record.
1091
+	 *
1092
+	 * @return bool
1093
+	 */
1094
+	public function exists()
1095
+	{
1096
+		return $this->getCache()->has($this->getEntityId());
1097
+	}
1098
+
1099
+	/**
1100
+	 * Set the object attribute raw values (hydration).
1101
+	 *
1102
+	 * @param array $attributes
1103
+	 */
1104
+	public function setEntityAttributes(array $attributes)
1105
+	{
1106
+		$this->wrappedEntity->setEntityAttributes($attributes);
1107
+	}
1108
+
1109
+	/**
1110
+	 * Get the raw object's values.
1111
+	 *
1112
+	 * @return array
1113
+	 */
1114
+	public function getEntityAttributes()
1115
+	{
1116
+		return $this->wrappedEntity->getEntityAttributes();
1117
+	}
1118
+
1119
+	/**
1120
+	 * Set the raw entity attributes.
1121
+	 *
1122
+	 * @param string $key
1123
+	 * @param string $value
1124
+	 */
1125
+	public function setEntityAttribute($key, $value)
1126
+	{
1127
+		$this->wrappedEntity->setEntityAttribute($key, $value);
1128
+	}
1129
+
1130
+	/**
1131
+	 * Return the entity's attribute.
1132
+	 *
1133
+	 * @param string $key
1134
+	 *
1135
+	 * @return mixed
1136
+	 */
1137
+	public function getEntityAttribute($key)
1138
+	{
1139
+		return $this->wrappedEntity->getEntityAttribute($key);
1140
+	}
1141
+
1142
+	/**
1143
+	 * Does the attribute exists on the entity.
1144
+	 *
1145
+	 * @param string $key
1146
+	 *
1147
+	 * @return bool
1148
+	 */
1149
+	public function hasAttribute($key)
1150
+	{
1151
+		return $this->wrappedEntity->hasAttribute($key);
1152
+	}
1153
+
1154
+	/**
1155
+	 * Set the lazyloading proxies on the wrapped entity.
1156
+	 *
1157
+	 * @return void
1158
+	 */
1159
+	public function setProxies()
1160
+	{
1161
+		$this->wrappedEntity->setProxies();
1162
+	}
1163
+
1164
+	/**
1165
+	 * Hydrate the actual entity.
1166
+	 *
1167
+	 * @return void
1168
+	 */
1169
+	public function hydrate()
1170
+	{
1171
+		$this->wrappedEntity->hydrate();
1172
+	}
1173 1173
 }
Please login to merge, or discard this patch.
src/System/ResultBuilder.php 1 patch
Indentation   +340 added lines, -340 removed lines patch added patch discarded remove patch
@@ -7,344 +7,344 @@
 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
-     * @param Mapper $defaultMapper
41
-     * @param array  $eagerLoads
42
-     */
43
-    public function __construct(Mapper $defaultMapper)
44
-    {
45
-        $this->defaultMapper = $defaultMapper;
46
-        $this->entityMap = $defaultMapper->getEntityMap();
47
-    }
48
-
49
-    /**
50
-     * Convert a result set into an array of entities.
51
-     *
52
-     * @param array $results
53
-     * @param array $eagerLoads name of the relation to be eager loaded on the Entities
54
-     *
55
-     * @return \Illuminate\Support\Collection
56
-     */
57
-    public function build(array $results, array $eagerLoads)
58
-    {
59
-        // Parse embedded relations and build corresponding entities using the default
60
-        // mapper.
61
-        $results = $this->buildEmbeddedRelationships($results);
62
-
63
-        // Launch the queries related to eager loads, and match the
64
-        // current result set to these loaded relationships.
65
-        $results = $this->queryEagerLoadedRelationships($results, $eagerLoads);
66
-
67
-        // Note : Maybe we could use a PolymorphicResultBuilder, which would
68
-        // be shared by both STI and polymorphic relations, as they share the
69
-        // same process.
70
-
71
-        switch ($this->entityMap->getInheritanceType()) {
72
-            case 'single_table':
73
-                return $this->buildUsingSingleTableInheritance($results);
74
-                break;
75
-
76
-            default:
77
-                return $this->buildWithDefaultMapper($results);
78
-                break;
79
-        }
80
-    }
81
-
82
-    /**
83
-     * Build embedded objects and match them to the result set.
84
-     *
85
-     * @param array $results
86
-     *
87
-     * @return array
88
-     */
89
-    protected function buildEmbeddedRelationships(array $results) : array
90
-    {
91
-        $entityMap = $this->entityMap;
92
-        $instance = $this->defaultMapper->newInstance();
93
-        $embeddeds = $entityMap->getEmbeddedRelationships();
94
-
95
-        foreach ($embeddeds as $embedded) {
96
-            $results = $entityMap->$embedded($instance)->match($results, $embedded);
97
-        }
98
-
99
-        return $results;
100
-    }
101
-
102
-    /**
103
-     * Launch queries on eager loaded relationships.
104
-     *
105
-     * @return array
106
-     */
107
-    protected function queryEagerLoadedRelationships(array $results, array $eagerLoads) : array
108
-    {
109
-        $this->eagerLoads = $this->parseRelations($eagerLoads);
110
-
111
-        return $this->eagerLoadRelations($results);
112
-    }
113
-
114
-    /**
115
-     * Parse a list of relations into individuals.
116
-     *
117
-     * @param array $relations
118
-     *
119
-     * @return array
120
-     */
121
-    protected function parseRelations(array $relations)
122
-    {
123
-        $results = [];
124
-
125
-        foreach ($relations as $name => $constraints) {
126
-            // If the "relation" value is actually a numeric key, we can assume that no
127
-            // constraints have been specified for the eager load and we'll just put
128
-            // an empty Closure with the loader so that we can treat all the same.
129
-            if (is_numeric($name)) {
130
-                $f = function () {
131
-                };
132
-
133
-                list($name, $constraints) = [$constraints, $f];
134
-            }
135
-
136
-            // We need to separate out any nested includes. Which allows the developers
137
-            // to load deep relationships using "dots" without stating each level of
138
-            // the relationship with its own key in the array of eager load names.
139
-            $results = $this->parseNested($name, $results);
140
-
141
-            $results[$name] = $constraints;
142
-        }
143
-
144
-        return $results;
145
-    }
146
-
147
-    /**
148
-     * Parse the nested relationships in a relation.
149
-     *
150
-     * @param string $name
151
-     * @param array  $results
152
-     *
153
-     * @return array
154
-     */
155
-    protected function parseNested($name, $results)
156
-    {
157
-        $progress = [];
158
-
159
-        // If the relation has already been set on the result array, we will not set it
160
-        // again, since that would override any constraints that were already placed
161
-        // on the relationships. We will only set the ones that are not specified.
162
-        foreach (explode('.', $name) as $segment) {
163
-            $progress[] = $segment;
164
-
165
-            if (!isset($results[$last = implode('.', $progress)])) {
166
-                $results[$last] = function () {
167
-                };
168
-            }
169
-        }
170
-
171
-        return $results;
172
-    }
173
-
174
-    /**
175
-     * Eager load the relationships on a result set.
176
-     *
177
-     * @param array $results
178
-     *
179
-     * @return array
180
-     */
181
-    public function eagerLoadRelations(array $results)
182
-    {
183
-        foreach ($this->eagerLoads as $name => $constraints) {
184
-
185
-            // For nested eager loads we'll skip loading them here and they will be set as an
186
-            // eager load on the query to retrieve the relation so that they will be eager
187
-            // loaded on that query, because that is where they get hydrated as models.
188
-            if (strpos($name, '.') === false) {
189
-                $results = $this->loadRelation($results, $name, $constraints);
190
-            }
191
-        }
192
-
193
-        return $results;
194
-    }
195
-
196
-    /**
197
-     * Eagerly load the relationship on a set of entities.
198
-     *
199
-     * @param array    $results
200
-     * @param string   $name
201
-     * @param \Closure $constraints
202
-     *
203
-     * @return array
204
-     */
205
-    protected function loadRelation(array $results, $name, Closure $constraints) : array
206
-    {
207
-        // First we will "back up" the existing where conditions on the query so we can
208
-        // add our eager constraints. Then we will merge the wheres that were on the
209
-        // query back to it in order that any where conditions might be specified.
210
-        $relation = $this->getRelation($name);
211
-
212
-        $relation->addEagerConstraints($results);
213
-
214
-        call_user_func($constraints, $relation);
215
-
216
-        // Once we have the results, we just match those back up to their parent models
217
-        // using the relationship instance. Then we just return the finished arrays
218
-        // of models which have been eagerly hydrated and are readied for return.
219
-
220
-        return $relation->match($results, $name);
221
-    }
222
-
223
-    /**
224
-     * Get the relation instance for the given relation name.
225
-     *
226
-     * @param string $relation
227
-     *
228
-     * @return \Analogue\ORM\Relationships\Relationship
229
-     */
230
-    public function getRelation($relation)
231
-    {
232
-        // We want to run a relationship query without any constrains so that we will
233
-        // not have to remove these where clauses manually which gets really hacky
234
-        // and is error prone while we remove the developer's own where clauses.
235
-        $query = Relationship::noConstraints(function () use ($relation) {
236
-            return $this->entityMap->$relation($this->defaultMapper->newInstance());
237
-        });
238
-
239
-        $nested = $this->nestedRelations($relation);
240
-
241
-        // If there are nested relationships set on the query, we will put those onto
242
-        // the query instances so that they can be handled after this relationship
243
-        // is loaded. In this way they will all trickle down as they are loaded.
244
-        if (count($nested) > 0) {
245
-            $query->getQuery()->with($nested);
246
-        }
247
-
248
-        return $query;
249
-    }
250
-
251
-    /**
252
-     * Get the deeply nested relations for a given top-level relation.
253
-     *
254
-     * @param string $relation
255
-     *
256
-     * @return array
257
-     */
258
-    protected function nestedRelations($relation)
259
-    {
260
-        $nested = [];
261
-
262
-        // We are basically looking for any relationships that are nested deeper than
263
-        // the given top-level relationship. We will just check for any relations
264
-        // that start with the given top relations and adds them to our arrays.
265
-        foreach ($this->eagerLoads as $name => $constraints) {
266
-            if ($this->isNested($name, $relation)) {
267
-                $nested[substr($name, strlen($relation.'.'))] = $constraints;
268
-            }
269
-        }
270
-
271
-        return $nested;
272
-    }
273
-
274
-    /**
275
-     * Determine if the relationship is nested.
276
-     *
277
-     * @param string $name
278
-     * @param string $relation
279
-     *
280
-     * @return bool
281
-     */
282
-    protected function isNested($name, $relation)
283
-    {
284
-        $dots = str_contains($name, '.');
285
-
286
-        return $dots && starts_with($name, $relation.'.');
287
-    }
288
-
289
-    /**
290
-     * Build an entity from results, using the default mapper on this builder.
291
-     * This is the default build plan when no table inheritance is being used.
292
-     *
293
-     * @param array $results
294
-     *
295
-     * @return Collection
296
-     */
297
-    protected function buildWithDefaultMapper(array $results)
298
-    {
299
-        $builder = new EntityBuilder($this->defaultMapper, array_keys($this->eagerLoads));
300
-
301
-        return collect($results)->map(function ($item, $key) use ($builder) {
302
-            return $builder->build($item);
303
-        })->all();
304
-    }
305
-
306
-    /**
307
-     * Build an entity from results, using single table inheritance.
308
-     *
309
-     * @param array $results
310
-     *
311
-     * @return Collection
312
-     */
313
-    protected function buildUsingSingleTableInheritance(array $results)
314
-    {
315
-        return collect($results)->map(function ($item, $key) {
316
-            $builder = $this->builderForResult($item);
317
-
318
-            return $builder->build($item);
319
-        })->all();
320
-    }
321
-
322
-    /**
323
-     * Given a result array, return the entity builder needed to correctly
324
-     * build the result into an entity. If no getDiscriminatorColumnMap property
325
-     * has been defined on the EntityMap, we'll assume that the value stored in
326
-     * the $type column is the fully qualified class name of the entity and
327
-     * we'll use it instead.
328
-     *
329
-     * @param array $result
330
-     *
331
-     * @return EntityBuilder
332
-     */
333
-    protected function builderForResult(array $result)
334
-    {
335
-        $type = $result[$this->entityMap->getDiscriminatorColumn()];
336
-
337
-        $columnMap = $this->entityMap->getDiscriminatorColumnMap();
338
-
339
-        $class = isset($columnMap[$type]) ? $columnMap[$type] : $type;
340
-
341
-        if (!isset($this->builders[$type])) {
342
-            $this->builders[$type] = new EntityBuilder(
343
-                Manager::getInstance()->mapper($class),
344
-                array_keys($this->eagerLoads)
345
-            );
346
-        }
347
-
348
-        return $this->builders[$type];
349
-    }
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
+	 * @param Mapper $defaultMapper
41
+	 * @param array  $eagerLoads
42
+	 */
43
+	public function __construct(Mapper $defaultMapper)
44
+	{
45
+		$this->defaultMapper = $defaultMapper;
46
+		$this->entityMap = $defaultMapper->getEntityMap();
47
+	}
48
+
49
+	/**
50
+	 * Convert a result set into an array of entities.
51
+	 *
52
+	 * @param array $results
53
+	 * @param array $eagerLoads name of the relation to be eager loaded on the Entities
54
+	 *
55
+	 * @return \Illuminate\Support\Collection
56
+	 */
57
+	public function build(array $results, array $eagerLoads)
58
+	{
59
+		// Parse embedded relations and build corresponding entities using the default
60
+		// mapper.
61
+		$results = $this->buildEmbeddedRelationships($results);
62
+
63
+		// Launch the queries related to eager loads, and match the
64
+		// current result set to these loaded relationships.
65
+		$results = $this->queryEagerLoadedRelationships($results, $eagerLoads);
66
+
67
+		// Note : Maybe we could use a PolymorphicResultBuilder, which would
68
+		// be shared by both STI and polymorphic relations, as they share the
69
+		// same process.
70
+
71
+		switch ($this->entityMap->getInheritanceType()) {
72
+			case 'single_table':
73
+				return $this->buildUsingSingleTableInheritance($results);
74
+				break;
75
+
76
+			default:
77
+				return $this->buildWithDefaultMapper($results);
78
+				break;
79
+		}
80
+	}
81
+
82
+	/**
83
+	 * Build embedded objects and match them to the result set.
84
+	 *
85
+	 * @param array $results
86
+	 *
87
+	 * @return array
88
+	 */
89
+	protected function buildEmbeddedRelationships(array $results) : array
90
+	{
91
+		$entityMap = $this->entityMap;
92
+		$instance = $this->defaultMapper->newInstance();
93
+		$embeddeds = $entityMap->getEmbeddedRelationships();
94
+
95
+		foreach ($embeddeds as $embedded) {
96
+			$results = $entityMap->$embedded($instance)->match($results, $embedded);
97
+		}
98
+
99
+		return $results;
100
+	}
101
+
102
+	/**
103
+	 * Launch queries on eager loaded relationships.
104
+	 *
105
+	 * @return array
106
+	 */
107
+	protected function queryEagerLoadedRelationships(array $results, array $eagerLoads) : array
108
+	{
109
+		$this->eagerLoads = $this->parseRelations($eagerLoads);
110
+
111
+		return $this->eagerLoadRelations($results);
112
+	}
113
+
114
+	/**
115
+	 * Parse a list of relations into individuals.
116
+	 *
117
+	 * @param array $relations
118
+	 *
119
+	 * @return array
120
+	 */
121
+	protected function parseRelations(array $relations)
122
+	{
123
+		$results = [];
124
+
125
+		foreach ($relations as $name => $constraints) {
126
+			// If the "relation" value is actually a numeric key, we can assume that no
127
+			// constraints have been specified for the eager load and we'll just put
128
+			// an empty Closure with the loader so that we can treat all the same.
129
+			if (is_numeric($name)) {
130
+				$f = function () {
131
+				};
132
+
133
+				list($name, $constraints) = [$constraints, $f];
134
+			}
135
+
136
+			// We need to separate out any nested includes. Which allows the developers
137
+			// to load deep relationships using "dots" without stating each level of
138
+			// the relationship with its own key in the array of eager load names.
139
+			$results = $this->parseNested($name, $results);
140
+
141
+			$results[$name] = $constraints;
142
+		}
143
+
144
+		return $results;
145
+	}
146
+
147
+	/**
148
+	 * Parse the nested relationships in a relation.
149
+	 *
150
+	 * @param string $name
151
+	 * @param array  $results
152
+	 *
153
+	 * @return array
154
+	 */
155
+	protected function parseNested($name, $results)
156
+	{
157
+		$progress = [];
158
+
159
+		// If the relation has already been set on the result array, we will not set it
160
+		// again, since that would override any constraints that were already placed
161
+		// on the relationships. We will only set the ones that are not specified.
162
+		foreach (explode('.', $name) as $segment) {
163
+			$progress[] = $segment;
164
+
165
+			if (!isset($results[$last = implode('.', $progress)])) {
166
+				$results[$last] = function () {
167
+				};
168
+			}
169
+		}
170
+
171
+		return $results;
172
+	}
173
+
174
+	/**
175
+	 * Eager load the relationships on a result set.
176
+	 *
177
+	 * @param array $results
178
+	 *
179
+	 * @return array
180
+	 */
181
+	public function eagerLoadRelations(array $results)
182
+	{
183
+		foreach ($this->eagerLoads as $name => $constraints) {
184
+
185
+			// For nested eager loads we'll skip loading them here and they will be set as an
186
+			// eager load on the query to retrieve the relation so that they will be eager
187
+			// loaded on that query, because that is where they get hydrated as models.
188
+			if (strpos($name, '.') === false) {
189
+				$results = $this->loadRelation($results, $name, $constraints);
190
+			}
191
+		}
192
+
193
+		return $results;
194
+	}
195
+
196
+	/**
197
+	 * Eagerly load the relationship on a set of entities.
198
+	 *
199
+	 * @param array    $results
200
+	 * @param string   $name
201
+	 * @param \Closure $constraints
202
+	 *
203
+	 * @return array
204
+	 */
205
+	protected function loadRelation(array $results, $name, Closure $constraints) : array
206
+	{
207
+		// First we will "back up" the existing where conditions on the query so we can
208
+		// add our eager constraints. Then we will merge the wheres that were on the
209
+		// query back to it in order that any where conditions might be specified.
210
+		$relation = $this->getRelation($name);
211
+
212
+		$relation->addEagerConstraints($results);
213
+
214
+		call_user_func($constraints, $relation);
215
+
216
+		// Once we have the results, we just match those back up to their parent models
217
+		// using the relationship instance. Then we just return the finished arrays
218
+		// of models which have been eagerly hydrated and are readied for return.
219
+
220
+		return $relation->match($results, $name);
221
+	}
222
+
223
+	/**
224
+	 * Get the relation instance for the given relation name.
225
+	 *
226
+	 * @param string $relation
227
+	 *
228
+	 * @return \Analogue\ORM\Relationships\Relationship
229
+	 */
230
+	public function getRelation($relation)
231
+	{
232
+		// We want to run a relationship query without any constrains so that we will
233
+		// not have to remove these where clauses manually which gets really hacky
234
+		// and is error prone while we remove the developer's own where clauses.
235
+		$query = Relationship::noConstraints(function () use ($relation) {
236
+			return $this->entityMap->$relation($this->defaultMapper->newInstance());
237
+		});
238
+
239
+		$nested = $this->nestedRelations($relation);
240
+
241
+		// If there are nested relationships set on the query, we will put those onto
242
+		// the query instances so that they can be handled after this relationship
243
+		// is loaded. In this way they will all trickle down as they are loaded.
244
+		if (count($nested) > 0) {
245
+			$query->getQuery()->with($nested);
246
+		}
247
+
248
+		return $query;
249
+	}
250
+
251
+	/**
252
+	 * Get the deeply nested relations for a given top-level relation.
253
+	 *
254
+	 * @param string $relation
255
+	 *
256
+	 * @return array
257
+	 */
258
+	protected function nestedRelations($relation)
259
+	{
260
+		$nested = [];
261
+
262
+		// We are basically looking for any relationships that are nested deeper than
263
+		// the given top-level relationship. We will just check for any relations
264
+		// that start with the given top relations and adds them to our arrays.
265
+		foreach ($this->eagerLoads as $name => $constraints) {
266
+			if ($this->isNested($name, $relation)) {
267
+				$nested[substr($name, strlen($relation.'.'))] = $constraints;
268
+			}
269
+		}
270
+
271
+		return $nested;
272
+	}
273
+
274
+	/**
275
+	 * Determine if the relationship is nested.
276
+	 *
277
+	 * @param string $name
278
+	 * @param string $relation
279
+	 *
280
+	 * @return bool
281
+	 */
282
+	protected function isNested($name, $relation)
283
+	{
284
+		$dots = str_contains($name, '.');
285
+
286
+		return $dots && starts_with($name, $relation.'.');
287
+	}
288
+
289
+	/**
290
+	 * Build an entity from results, using the default mapper on this builder.
291
+	 * This is the default build plan when no table inheritance is being used.
292
+	 *
293
+	 * @param array $results
294
+	 *
295
+	 * @return Collection
296
+	 */
297
+	protected function buildWithDefaultMapper(array $results)
298
+	{
299
+		$builder = new EntityBuilder($this->defaultMapper, array_keys($this->eagerLoads));
300
+
301
+		return collect($results)->map(function ($item, $key) use ($builder) {
302
+			return $builder->build($item);
303
+		})->all();
304
+	}
305
+
306
+	/**
307
+	 * Build an entity from results, using single table inheritance.
308
+	 *
309
+	 * @param array $results
310
+	 *
311
+	 * @return Collection
312
+	 */
313
+	protected function buildUsingSingleTableInheritance(array $results)
314
+	{
315
+		return collect($results)->map(function ($item, $key) {
316
+			$builder = $this->builderForResult($item);
317
+
318
+			return $builder->build($item);
319
+		})->all();
320
+	}
321
+
322
+	/**
323
+	 * Given a result array, return the entity builder needed to correctly
324
+	 * build the result into an entity. If no getDiscriminatorColumnMap property
325
+	 * has been defined on the EntityMap, we'll assume that the value stored in
326
+	 * the $type column is the fully qualified class name of the entity and
327
+	 * we'll use it instead.
328
+	 *
329
+	 * @param array $result
330
+	 *
331
+	 * @return EntityBuilder
332
+	 */
333
+	protected function builderForResult(array $result)
334
+	{
335
+		$type = $result[$this->entityMap->getDiscriminatorColumn()];
336
+
337
+		$columnMap = $this->entityMap->getDiscriminatorColumnMap();
338
+
339
+		$class = isset($columnMap[$type]) ? $columnMap[$type] : $type;
340
+
341
+		if (!isset($this->builders[$type])) {
342
+			$this->builders[$type] = new EntityBuilder(
343
+				Manager::getInstance()->mapper($class),
344
+				array_keys($this->eagerLoads)
345
+			);
346
+		}
347
+
348
+		return $this->builders[$type];
349
+	}
350 350
 }
Please login to merge, or discard this patch.
src/System/EntityBuilder.php 1 patch
Indentation   +136 added lines, -136 removed lines patch added patch discarded remove patch
@@ -9,141 +9,141 @@
 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 values into an entity.
64
-     *
65
-     * @param array $result
66
-     *
67
-     * @return array
68
-     */
69
-    public function build(array $result)
70
-    {
71
-        $wrapper = $this->getWrapperInstance();
72
-
73
-        // Hydrate any embedded Value Object
74
-        //
75
-        // TODO Move this to the result builder instead,
76
-        // as we'll handle this the same way as they were
77
-        // eager loaded relationships.
78
-        $this->hydrateValueObjects($result);
79
-
80
-        $wrapper->setEntityAttributes($result);
81
-
82
-        $wrapper->setProxies();
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 values into an entity.
64
+	 *
65
+	 * @param array $result
66
+	 *
67
+	 * @return array
68
+	 */
69
+	public function build(array $result)
70
+	{
71
+		$wrapper = $this->getWrapperInstance();
72
+
73
+		// Hydrate any embedded Value Object
74
+		//
75
+		// TODO Move this to the result builder instead,
76
+		// as we'll handle this the same way as they were
77
+		// eager loaded relationships.
78
+		$this->hydrateValueObjects($result);
79
+
80
+		$wrapper->setEntityAttributes($result);
81
+
82
+		$wrapper->setProxies();
83 83
         
84
-        // Hydrate and return the instance
85
-        $wrapper->hydrate();
86
-        $entity = $wrapper->getObject();
87
-
88
-        return $entity;
89
-    }
90
-
91
-    /**
92
-     * Get the correct wrapper prototype corresponding to the object type.
93
-     *
94
-     * @throws \Analogue\ORM\Exceptions\MappingException
95
-     *
96
-     * @return InternallyMappable
97
-     */
98
-    protected function getWrapperInstance()
99
-    {
100
-        return $this->factory->make($this->mapper->newInstance());
101
-    }
102
-
103
-    /**
104
-     * Hydrate value object embedded in this entity.
105
-     *
106
-     * @param array $attributes
107
-     *
108
-     * @throws \Analogue\ORM\Exceptions\MappingException
109
-     *
110
-     * @return void
111
-     */
112
-    protected function hydrateValueObjects(&$attributes)
113
-    {
114
-        foreach ($this->entityMap->getEmbeddables() as $localKey => $valueClass) {
115
-            $this->hydrateValueObject($attributes, $localKey, $valueClass);
116
-        }
117
-    }
118
-
119
-    /**
120
-     * Hydrate a single value object.
121
-     *
122
-     * @param array  $attributes
123
-     * @param string $localKey
124
-     * @param string $valueClass
125
-     *
126
-     * @throws \Analogue\ORM\Exceptions\MappingException
127
-     *
128
-     * @return void
129
-     */
130
-    protected function hydrateValueObject(&$attributes, $localKey, $valueClass)
131
-    {
132
-        $map = $this->mapper->getManager()->getValueMap($valueClass);
133
-
134
-        $embeddedAttributes = $map->getAttributes();
135
-
136
-        $valueObject = $this->mapper->getManager()->getValueObjectInstance($valueClass);
137
-        $voWrapper = $this->factory->make($valueObject);
138
-
139
-        foreach ($embeddedAttributes as $key) {
140
-            $prefix = snake_case(class_basename($valueClass)).'_';
141
-
142
-            $voWrapper->setEntityAttribute($key, $attributes[$prefix.$key]);
143
-
144
-            unset($attributes[$prefix.$key]);
145
-        }
146
-
147
-        $attributes[$localKey] = $voWrapper->getObject();
148
-    }
84
+		// Hydrate and return the instance
85
+		$wrapper->hydrate();
86
+		$entity = $wrapper->getObject();
87
+
88
+		return $entity;
89
+	}
90
+
91
+	/**
92
+	 * Get the correct wrapper prototype corresponding to the object type.
93
+	 *
94
+	 * @throws \Analogue\ORM\Exceptions\MappingException
95
+	 *
96
+	 * @return InternallyMappable
97
+	 */
98
+	protected function getWrapperInstance()
99
+	{
100
+		return $this->factory->make($this->mapper->newInstance());
101
+	}
102
+
103
+	/**
104
+	 * Hydrate value object embedded in this entity.
105
+	 *
106
+	 * @param array $attributes
107
+	 *
108
+	 * @throws \Analogue\ORM\Exceptions\MappingException
109
+	 *
110
+	 * @return void
111
+	 */
112
+	protected function hydrateValueObjects(&$attributes)
113
+	{
114
+		foreach ($this->entityMap->getEmbeddables() as $localKey => $valueClass) {
115
+			$this->hydrateValueObject($attributes, $localKey, $valueClass);
116
+		}
117
+	}
118
+
119
+	/**
120
+	 * Hydrate a single value object.
121
+	 *
122
+	 * @param array  $attributes
123
+	 * @param string $localKey
124
+	 * @param string $valueClass
125
+	 *
126
+	 * @throws \Analogue\ORM\Exceptions\MappingException
127
+	 *
128
+	 * @return void
129
+	 */
130
+	protected function hydrateValueObject(&$attributes, $localKey, $valueClass)
131
+	{
132
+		$map = $this->mapper->getManager()->getValueMap($valueClass);
133
+
134
+		$embeddedAttributes = $map->getAttributes();
135
+
136
+		$valueObject = $this->mapper->getManager()->getValueObjectInstance($valueClass);
137
+		$voWrapper = $this->factory->make($valueObject);
138
+
139
+		foreach ($embeddedAttributes as $key) {
140
+			$prefix = snake_case(class_basename($valueClass)).'_';
141
+
142
+			$voWrapper->setEntityAttribute($key, $attributes[$prefix.$key]);
143
+
144
+			unset($attributes[$prefix.$key]);
145
+		}
146
+
147
+		$attributes[$localKey] = $voWrapper->getObject();
148
+	}
149 149
 }
Please login to merge, or discard this patch.