Completed
Branch master (411345)
by Rémi
11:20
created

Relationship::ownForeignKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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  mixed    $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 = $mapper->newInstance();
119
120
        $this->relatedMap = $mapper->getEntityMap();
121
122
        $this->addConstraints();
123
    }
124
125
    /**
126
     * Indicate if the parent entity hold the foreign key for relation.
127
     *
128
     * @return boolean
129
     */
130
    public function ownForeignKey()
131
    {
132
        return static::$ownForeignKey;
133
    }
134
135
    /**
136
     * Indicate if the relationship uses a pivot table
137
     *
138
     * @return boolean
139
     */
140
    public function hasPivot()
141
    {
142
        return static::$hasPivot;
143
    }
144
145
    /**
146
     * Set the base constraints on the relation query.
147
     *
148
     * @return void
149
     */
150
    abstract public function addConstraints();
151
152
    /**
153
     * Set the constraints for an eager load of the relation.
154
     *
155
     * @param  array $results
156
     * @return void
157
     */
158
    abstract public function addEagerConstraints(array $results);
159
160
    /**
161
     * Match the eagerly loaded results to their parents, then return
162
     * updated results
163
     *
164
     * @param  array            $results
165
     * @param  string           $relation
166
     * @return array
167
     */
168
    abstract public function match(array $results, $relation);
169
170
    /**
171
     * Get the results of the relationship.
172
     *
173
     * @param string $relation relation name in parent's entity map
174
     * @return mixed
175
     */
176
    abstract public function getResults($relation);
177
178
    /**
179
     * Get the relationship for eager loading.
180
     *
181
     * @return EntityCollection
182
     */
183
    public function getEager()
184
    {
185
        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...
186
    }
187
188
    /**
189
     * Add the constraints for a relationship count query.
190
     *
191
     * @param  Query $query
192
     * @param  Query $parent
193
     * @return Query
194
     */
195
    public function getRelationCountQuery(Query $query, Query $parent)
196
    {
197
        $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...
198
199
        $key = $this->wrap($this->getQualifiedParentKeyName());
200
201
        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...
202
    }
203
204
    /**
205
     * Run a callback with constraints disabled on the relation.
206
     *
207
     * @param  Closure $callback
208
     * @return mixed
209
     */
210
    public static function noConstraints(Closure $callback)
211
    {
212
        static::$constraints = false;
213
214
        // When resetting the relation where clause, we want to shift the first element
215
        // off of the bindings, leaving only the constraints that the developers put
216
        // as "extra" on the relationships, and not original relation constraints.
217
        $results = call_user_func($callback);
218
219
        static::$constraints = true;
220
221
        return $results;
222
    }
223
224
    /**
225
     * Get all of the primary keys for an array of entities.
226
     *
227
     * @param  array  $entities
228
     * @param  string $key
229
     * @return array
230
     */
231
    protected function getKeys(array $entities, $key = null)
232
    {
233
        if (is_null($key)) {
234
            $key = $this->relatedMap->getKeyName();
235
        }
236
237
        $host = $this;
238
239
        return array_unique(array_values(array_map(function ($value) use ($key, $host) {
240
            if (!$value instanceof InternallyMappable) {
241
                $value = $host->factory->make($value);
242
            }
243
244
            return $value->getEntityAttribute($key);
245
246
        }, $entities)));
247
    }
248
249
    /**  
250
     * Get all the keys from a result set
251
     * 
252
     * @param  array  $results 
253
     * @param  string  $key    
254
     * @return array          
255
     */
256
    protected function getKeysFromResults(array $results, $key = null)
257
    {
258
        if (is_null($key)) {
259
            $key = $this->relatedMap->getKeyName();
260
        }
261
262
        return array_unique(array_values(array_map(function ($value) use ($key) {
263
            return $value[$key];
264
        }, $results)));
265
    }
266
267
    /**
268
     * Get the underlying query for the relation.
269
     *
270
     * @return Query
271
     */
272
    public function getQuery()
273
    {
274
        return $this->query;
275
    }
276
277
    /**
278
     * Get the base query builder
279
     *
280
     * @return \Analogue\ORM\Drivers\QueryAdapter
281
     */
282
    public function getBaseQuery()
283
    {
284
        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 284 which is incompatible with the return type documented by Analogue\ORM\Relationshi...ationship::getBaseQuery of type Analogue\ORM\Drivers\QueryAdapter.
Loading history...
285
    }
286
287
    /**
288
     * Get the parent model of the relation.
289
     *
290
     * @return InternallyMappable
291
     */
292
    public function getParent()
293
    {
294
        return $this->parent;
295
    }
296
297
    /**
298
     * Set the parent model of the relation
299
     * 
300
     * @param InternallyMappable $parent 
301
     * @return void
302
     */
303
    public function setParent(InternallyMappable $parent)
304
    {
305
        $this->parent = $parent;
306
    }
307
308
    /**
309
     * Get the fully qualified parent key name.
310
     *
311
     * @return string
312
     */
313
    protected function getQualifiedParentKeyName()
314
    {
315
        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...
316
    }
317
318
    /**
319
     * Get the related entity of the relation.
320
     *
321
     * @return \Analogue\ORM\Entity
322
     */
323
    public function getRelated()
324
    {
325
        return $this->related;
326
    }
327
328
    /**
329
     * Get the related mapper for the relation
330
     *
331
     * @return Mapper
332
     */
333
    public function getRelatedMapper()
334
    {
335
        return $this->relatedMapper;
336
    }
337
338
339
    /**
340
     * Get the name of the "created at" column.
341
     *
342
     * @return string
343
     */
344
    public function createdAt()
345
    {
346
        return $this->parentMap->getCreatedAtColumn();
347
    }
348
349
    /**
350
     * Get the name of the "updated at" column.
351
     *
352
     * @return string
353
     */
354
    public function updatedAt()
355
    {
356
        return $this->parentMap->getUpdatedAtColumn();
357
    }
358
359
    /**
360
     * Get the name of the related model's "updated at" column.
361
     *
362
     * @return string
363
     */
364
    public function relatedUpdatedAt()
365
    {
366
        return $this->related->getUpdatedAtColumn();
367
    }
368
369
    /**
370
     * Wrap the given value with the parent query's grammar.
371
     *
372
     * @param  string $value
373
     * @return string
374
     */
375
    public function wrap($value)
376
    {
377
        return $this->parentMapper->getQuery()->getQuery()->getGrammar()->wrap($value);
378
    }
379
380
    /**
381
     * Get a fresh timestamp
382
     *
383
     * @return Carbon
384
     */
385
    protected function freshTimestamp()
386
    {
387
        return new Carbon;
388
    }
389
390
    /**
391
     * Cache the link between parent and related
392
     * into the mapper's Entity Cache.
393
     *
394
     * @param  EntityCollection|Mappable $results  result of the relation query
395
     * @param  string                    $relation name of the relation method on the parent entity
396
     * @return void
397
     */
398
    protected function cacheRelation($results, $relation)
399
    {
400
        $cache = $this->parentMapper->getEntityCache();
401
402
        $cache->cacheLoadedRelationResult($this->parent->getEntityKey(), $relation, $results, $this);
403
    }
404
405
    /**
406
     * Return Pivot attributes when available on a relationship
407
     *
408
     * @return array
409
     */
410
    public function getPivotAttributes()
411
    {
412
        return [];
413
    }
414
415
    /**
416
     * Get a combo type.primaryKey
417
     *
418
     * @param  Mappable $entity
419
     * @return string
420
     */
421 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...
422
    {
423
        $class = get_class($entity);
424
425
        $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...
426
427
        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...
428
    }
429
430
    /**
431
     * Run synchronization content if needed by the
432
     * relation type.
433
     *
434
     * @param  array $actualContent
435
     * @return void
436
     */
437
    abstract public function sync(array $actualContent);
438
    
439
    /**
440
     * Handle dynamic method calls to the relationship.
441
     *
442
     * @param  string $method
443
     * @param  array  $parameters
444
     * @return mixed
445
     */
446
    public function __call($method, $parameters)
447
    {
448
        $result = call_user_func_array([$this->query, $method], $parameters);
449
450
        if ($result === $this->query) {
451
            return $this;
452
        }
453
454
        return $result;
455
    }
456
}
457