Relationship::initRelation()
last analyzed

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
nc 1
dl 0
loc 1
1
<?php
2
3
namespace Analogue\ORM\Relationships;
4
5
use Closure;
6
use Carbon\Carbon;
7
use Analogue\ORM\Mappable;
8
use Analogue\ORM\System\Query;
9
use Analogue\ORM\System\Mapper;
10
use Analogue\ORM\EntityCollection;
11
use Analogue\ORM\System\InternallyMappable;
12
use Analogue\ORM\System\Wrappers\Factory;
13
use Illuminate\Database\Query\Expression;
14
15
/**
16
 * Class Relationship
17
 *
18
 * @mixin Query
19
 */
20
abstract class Relationship
21
{
22
    /**
23
     * The mapper instance for the related entity
24
     *
25
     * @var Mapper
26
     */
27
    protected $relatedMapper;
28
29
    /**
30
     * The Analogue Query Builder instance.
31
     *
32
     * @var Query
33
     */
34
    protected $query;
35
36
    /**
37
     * The parent entity proxy instance.
38
     *
39
     * @var InternallyMappable
40
     */
41
    protected $parent;
42
43
    /**
44
     * The parent entity map
45
     * @var \Analogue\ORM\EntityMap
46
     */
47
    protected $parentMap;
48
49
    /**
50
     * The Parent Mapper instance
51
     *
52
     * @var Mapper
53
     */
54
    protected $parentMapper;
55
56
    /**
57
     * The related entity instance.
58
     *
59
     * @var object
60
     */
61
    protected $related;
62
63
    /**
64
     * The related entity Map
65
     * @var \Analogue\ORM\EntityMap
66
     */
67
    protected $relatedMap;
68
69
    /**
70
     * Indicate if the parent entity hold the key for the relation.
71
     *
72
     * @var boolean
73
     */
74
    protected static $ownForeignKey = false;
75
76
    /**
77
     * Indicate if the relationships use a pivot table.*
78
     *
79
     * @var boolean
80
     */
81
    protected static $hasPivot = false;
82
83
    /**
84
     * Indicates if the relation is adding constraints.
85
     *
86
     * @var bool
87
     */
88
    protected static $constraints = true;
89
90
    /**
91
     * Wrapper factory
92
     *
93
     * @var \Analogue\ORM\System\Wrappers\Factory
94
     */
95
    protected $factory;
96
97
    /**
98
     * Create a new relation instance.
99
     *
100
     * @param  Mapper   $mapper
101
     * @param  Mappable $parent
102
     * @throws \Analogue\ORM\Exceptions\MappingException
103
     */
104
    public function __construct(Mapper $mapper, $parent)
105
    {
106
        $this->relatedMapper = $mapper;
107
108
        $this->query = $mapper->getQuery();
109
110
        $this->factory = new Factory;
111
112
        $this->parent = $this->factory->make($parent);
113
114
        $this->parentMapper = $mapper->getManager()->getMapper($parent);
115
116
        $this->parentMap = $this->parentMapper->getEntityMap();
117
118
        $this->related = $this->query->getEntityInstance();
119
120
        $this->relatedMap = $mapper->getEntityMap();
121
122
        $this->addConstraints();
123
    }
124
125
    /**
126
     * @param $related
127
     * @return mixed
128
     */
129
    abstract public function attachTo($related);
130
131
    /**
132
     * @param $related
133
     * @return mixed
134
     */
135
    abstract public function detachFrom($related);
136
137
    /**
138
     * Indicate if the parent entity hold the foreign key for relation.
139
     *
140
     * @return boolean
141
     */
142
    public function ownForeignKey()
143
    {
144
        return static::$ownForeignKey;
145
    }
146
147
    /**
148
     * Indicate if the relationship uses a pivot table
149
     *
150
     * @return boolean
151
     */
152
    public function hasPivot()
153
    {
154
        return static::$hasPivot;
155
    }
156
157
    /**
158
     * Set the base constraints on the relation query.
159
     *
160
     * @return void
161
     */
162
    abstract public function addConstraints();
163
164
    /**
165
     * Set the constraints for an eager load of the relation.
166
     *
167
     * @param  array $models
168
     * @return void
169
     */
170
    abstract public function addEagerConstraints(array $models);
171
172
    /**
173
     * Initialize the relation on a set of models.
174
     *
175
     * @param  array  $models
176
     * @param  string $relation
177
     * @return array
178
     */
179
    abstract public function initRelation(array $models, $relation);
180
181
    /**
182
     * Match the eagerly loaded results to their parents.
183
     *
184
     * @param  array            $entities
185
     * @param  EntityCollection $results
186
     * @param  string           $relation
187
     * @return array
188
     */
189
    abstract public function match(array $entities, EntityCollection $results, $relation);
190
191
    /**
192
     * Get the results of the relationship.
193
     *
194
     * @param string $relation relation name in parent's entity map
195
     * @return mixed
196
     */
197
    abstract public function getResults($relation);
198
199
    /**
200
     * Get the relationship for eager loading.
201
     *
202
     * @return EntityCollection
203
     */
204
    public function getEager()
205
    {
206
        return $this->get();
0 ignored issues
show
Documentation Bug introduced by
The method get does not exist on object<Analogue\ORM\Relationships\Relationship>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
207
    }
208
209
    /**
210
     * Add the constraints for a relationship count query.
211
     *
212
     * @param  Query $query
213
     * @param  Query $parent
214
     * @return Query
215
     */
216
    public function getRelationCountQuery(Query $query, Query $parent)
217
    {
218
        $query->select(new Expression('count(*)'));
0 ignored issues
show
Documentation Bug introduced by
The method select does not exist on object<Analogue\ORM\System\Query>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
219
220
        $key = $this->wrap($this->getQualifiedParentKeyName());
221
222
        return $query->where($this->getHasCompareKey(), '=', new Expression($key));
0 ignored issues
show
Documentation Bug introduced by
The method getHasCompareKey does not exist on object<Analogue\ORM\Relationships\Relationship>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
223
    }
224
225
    /**
226
     * Run a callback with constraints disabled on the relation.
227
     *
228
     * @param  Closure $callback
229
     * @return mixed
230
     */
231
    public static function noConstraints(Closure $callback)
232
    {
233
        static::$constraints = false;
234
235
        // When resetting the relation where clause, we want to shift the first element
236
        // off of the bindings, leaving only the constraints that the developers put
237
        // as "extra" on the relationships, and not original relation constraints.
238
        $results = call_user_func($callback);
239
240
        static::$constraints = true;
241
242
        return $results;
243
    }
244
245
    /**
246
     * Get all of the primary keys for an array of entities.
247
     *
248
     * @param  array  $entities
249
     * @param  string $key
250
     * @return array
251
     */
252
    protected function getKeys(array $entities, $key = null)
253
    {
254
        if (is_null($key)) {
255
            $key = $this->relatedMap->getKeyName();
256
        }
257
258
        $host = $this;
259
260
        return array_unique(array_values(array_map(function ($value) use ($key, $host) {
261
            if (!$value instanceof InternallyMappable) {
262
                $value = $host->factory->make($value);
263
            }
264
265
            return $value->getEntityAttribute($key);
266
267
        }, $entities)));
268
    }
269
270
    /**
271
     * Get the underlying query for the relation.
272
     *
273
     * @return Query
274
     */
275
    public function getQuery()
276
    {
277
        return $this->query;
278
    }
279
280
    /**
281
     * Get the base query builder
282
     *
283
     * @return \Analogue\ORM\Drivers\QueryAdapter
284
     */
285
    public function getBaseQuery()
286
    {
287
        return $this->query->getQuery();
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->query->getQuery(); of type Analogue\ORM\Drivers\Que...\IlluminateQueryAdapter adds the type Analogue\ORM\Drivers\IlluminateQueryAdapter to the return on line 287 which is incompatible with the return type documented by Analogue\ORM\Relationshi...ationship::getBaseQuery of type Analogue\ORM\Drivers\QueryAdapter.
Loading history...
288
    }
289
290
    /**
291
     * Get the parent model of the relation.
292
     *
293
     * @return InternallyMappable
294
     */
295
    public function getParent()
296
    {
297
        return $this->parent;
298
    }
299
300
    /**
301
     * Get the fully qualified parent key name.
302
     *
303
     * @return string
304
     */
305
    protected function getQualifiedParentKeyName()
306
    {
307
        return $this->parent->getQualifiedKeyName();
0 ignored issues
show
Bug introduced by
The method getQualifiedKeyName() does not seem to exist on object<Analogue\ORM\System\InternallyMappable>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
308
    }
309
310
    /**
311
     * Get the related entity of the relation.
312
     *
313
     * @return \Analogue\ORM\Entity
314
     */
315
    public function getRelated()
316
    {
317
        return $this->related;
318
    }
319
320
    /**
321
     * Get the related mapper for the relation
322
     *
323
     * @return Mapper
324
     */
325
    public function getRelatedMapper()
326
    {
327
        return $this->relatedMapper;
328
    }
329
330
331
    /**
332
     * Get the name of the "created at" column.
333
     *
334
     * @return string
335
     */
336
    public function createdAt()
337
    {
338
        return $this->parentMap->getCreatedAtColumn();
339
    }
340
341
    /**
342
     * Get the name of the "updated at" column.
343
     *
344
     * @return string
345
     */
346
    public function updatedAt()
347
    {
348
        return $this->parentMap->getUpdatedAtColumn();
349
    }
350
351
    /**
352
     * Get the name of the related model's "updated at" column.
353
     *
354
     * @return string
355
     */
356
    public function relatedUpdatedAt()
357
    {
358
        return $this->related->getUpdatedAtColumn();
359
    }
360
361
    /**
362
     * Wrap the given value with the parent query's grammar.
363
     *
364
     * @param  string $value
365
     * @return string
366
     */
367
    public function wrap($value)
368
    {
369
        return $this->parentMapper->getQuery()->getQuery()->getGrammar()->wrap($value);
370
    }
371
372
    /**
373
     * Get a fresh timestamp
374
     *
375
     * @return Carbon
376
     */
377
    protected function freshTimestamp()
378
    {
379
        return new Carbon;
380
    }
381
382
    /**
383
     * Cache the link between parent and related
384
     * into the mapper's Entity Cache.
385
     *
386
     * @param  EntityCollection|Mappable $results  result of the relation query
387
     * @param  string                    $relation name of the relation method on the parent entity
388
     * @return void
389
     */
390
    protected function cacheRelation($results, $relation)
391
    {
392
        $cache = $this->parentMapper->getEntityCache();
393
394
        $cache->cacheLoadedRelationResult($this->parent, $relation, $results, $this);
395
    }
396
397
    /**
398
     * Return Pivot attributes when available on a relationship
399
     *
400
     * @return array
401
     */
402
    public function getPivotAttributes()
403
    {
404
        return [];
405
    }
406
407
    /**
408
     * Get a combo type.primaryKey
409
     *
410
     * @param  Mappable $entity
411
     * @return string
412
     */
413 View Code Duplication
    protected function getEntityHash(Mappable $entity)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
414
    {
415
        $class = get_class($entity);
416
417
        $keyName = Mapper::getMapper($class)->getEntityMap()->getKeyName();
0 ignored issues
show
Bug introduced by
The method getMapper() does not seem to exist on object<Analogue\ORM\System\Mapper>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
418
419
        return $class . '.' . $entity->getEntityAttribute($keyName);
0 ignored issues
show
Bug introduced by
The method getEntityAttribute() does not exist on Analogue\ORM\Mappable. Did you maybe mean getEntityAttributes()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
420
    }
421
422
    /**
423
     * Run synchronization content if needed by the
424
     * relation type.
425
     *
426
     * @param  array $actualContent
427
     * @return void
428
     */
429
    abstract public function sync(array $actualContent);
430
    
431
    /**
432
     * Handle dynamic method calls to the relationship.
433
     *
434
     * @param  string $method
435
     * @param  array  $parameters
436
     * @return mixed
437
     */
438
    public function __call($method, $parameters)
439
    {
440
        $result = call_user_func_array([$this->query, $method], $parameters);
441
442
        if ($result === $this->query) {
443
            return $this;
444
        }
445
446
        return $result;
447
    }
448
}
449