Completed
Push — 5.1 ( 9aabac...dea3be )
by Rémi
10s
created

HasManyThrough::sync()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 1
c 1
b 0
f 1
nc 1
nop 1
dl 0
loc 4
rs 10
1
<?php
2
3
namespace Analogue\ORM\Relationships;
4
5
use Analogue\ORM\System\Query;
6
use Analogue\ORM\System\Mapper;
7
use Analogue\ORM\EntityCollection;
8
use Illuminate\Database\Query\Expression;
9
10
class HasManyThrough extends Relationship
11
{
12
    /**
13
     * The distance parent Entity instance.
14
     *
15
     * @var \Analogue\ORM\Entity
16
     */
17
    protected $farParent;
18
19
    /**
20
     * The far parent map instance
21
     *
22
     * @var \Analogue\ORM\EntityMap
23
     */
24
    protected $farParentMap;
25
26
    /**
27
     * The near key on the relationship.
28
     *
29
     * @var string
30
     */
31
    protected $firstKey;
32
33
    /**
34
     * The far key on the relationship.
35
     *
36
     * @var string
37
     */
38
    protected $secondKey;
39
40
    /**
41
     * Create a new has many relationship instance.
42
     *
43
     * @param Mapper                  $mapper
44
     * @param \Analogue\ORM\Mappable  $farParent
45
     * @param \Analogue\ORM\EntityMap $parentMap
46
     * @param string                  $firstKey
47
     * @param string                  $secondKey
48
     * @throws \Analogue\ORM\Exceptions\MappingException
49
     */
50
    public function __construct(Mapper $mapper, $farParent, $parentMap, $firstKey, $secondKey)
51
    {
52
        $this->firstKey = $firstKey;
53
        $this->secondKey = $secondKey;
54
        $this->farParent = $farParent;
0 ignored issues
show
Documentation Bug introduced by
$farParent is of type object<Analogue\ORM\Mappable>, but the property $farParent was declared to be of type object<Analogue\ORM\Entity>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
55
56
        $this->farParentMap = $this->relatedMapper->getManager()->mapper($farParent)->getEntityMap();
57
58
        $parentInstance = $this->relatedMapper->getManager()->mapper($parentMap->getClass())->newInstance();
59
60
        parent::__construct($mapper, $parentInstance);
61
    }
62
63
    /**
64
     * @param $related
65
     * @return mixed
66
     */
67
    public function attachTo($related)
68
    {
69
        // N/A
70
    }
71
72
    /**
73
     * @param $related
74
     * @return mixed
75
     */
76
    public function detachFrom($related)
77
    {
78
        // N/A
79
    }
80
81
    /**
82
     * Set the base constraints on the relation query.
83
     *
84
     * @return void
85
     */
86
    public function addConstraints()
87
    {
88
        $parentTable = $this->parentMap->getTable();
89
90
        $this->setJoin();
91
92
        if (static::$constraints) {
93
            $farParentKeyName = $this->farParentMap->getKeyName();
94
95
            $this->query->where(
96
                $parentTable . '.' . $this->firstKey,
97
                '=',
98
                $this->farParent->getEntityAttribute($farParentKeyName)
99
            );
100
        }
101
    }
102
103
    /**
104
     * Add the constraints for a relationship count query.
105
     *
106
     * @param  Query $query
107
     * @param  Query $parent
108
     * @return Query
109
     */
110
    public function getRelationCountQuery(Query $query, Query $parent)
111
    {
112
        $parentTable = $this->parentMap->getTable();
113
114
        $this->setJoin($query);
115
116
        $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...
117
118
        $key = $this->wrap($parentTable . '.' . $this->firstKey);
119
120
        return $query->where($this->getHasCompareKey(), '=', new Expression($key));
121
    }
122
123
    /**
124
     * Set the join clause on the query.
125
     *
126
     * @param  null|Query $query
127
     * @return void
128
     */
129
    protected function setJoin(Query $query = null)
130
    {
131
        $query = $query ?: $this->query;
132
133
        $foreignKey = $this->relatedMap->getTable() . '.' . $this->secondKey;
134
135
        $query->join($this->parentMap->getTable(), $this->getQualifiedParentKeyName(), '=', $foreignKey);
0 ignored issues
show
Documentation Bug introduced by
The method join 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...
136
    }
137
138
    /**
139
     * Set the constraints for an eager load of the relation.
140
     *
141
     * @param  array $entities
142
     * @return void
143
     */
144
    public function addEagerConstraints(array $entities)
145
    {
146
        $table = $this->parentMap->getTable();
147
148
        $this->query->whereIn($table . '.' . $this->firstKey, $this->getKeys($entities));
0 ignored issues
show
Documentation Bug introduced by
The method whereIn 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...
149
    }
150
151
    /**
152
     * Initialize the relation on a set of entities.
153
     *
154
     * @param  \Analogue\ORM\Entity[] $entities
155
     * @param  string                 $relation
156
     * @return \Analogue\ORM\Entity[]
157
     */
158
    public function initRelation(array $entities, $relation)
159
    {
160
        foreach ($entities as $entity) {
161
            $entity->setEntityAttribute($relation, $this->relatedMap->newCollection());
162
        }
163
164
        return $entities;
165
    }
166
167
    /**
168
     * Match the eagerly loaded results to their parents.
169
     *
170
     * @param  \Analogue\ORM\Entity[] $entities
171
     * @param  EntityCollection       $results
172
     * @param  string                 $relation
173
     * @return \Analogue\ORM\Entity[]
174
     */
175
    public function match(array $entities, EntityCollection $results, $relation)
176
    {
177
        $dictionary = $this->buildDictionary($results);
178
179
        $relatedKey = $this->relatedMap->getKeyName();
180
181
        $cache = $this->parentMapper->getEntityCache();
182
183
        // Once we have the dictionary we can simply spin through the parent entities to
184
        // link them up with their children using the keyed dictionary to make the
185
        // matching very convenient and easy work. Then we'll just return them.
186
        foreach ($entities as $entity) {
187
            $key = $entity->getEntityAttribute($relatedKey);
188
189
            if (isset($dictionary[$key])) {
190
                $value = $this->relatedMap->newCollection($dictionary[$key]);
191
192
                $entity->setEntityAttribute($relation, $value);
193
194
                $cache->cacheLoadedRelationResult($entity, $relation, $value, $this);
195
            }
196
        }
197
198
        return $entities;
199
    }
200
201
    /**
202
     * Build model dictionary keyed by the relation's foreign key.
203
     *
204
     * @param  EntityCollection $results
205
     * @return array
206
     */
207 View Code Duplication
    protected function buildDictionary(EntityCollection $results)
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...
208
    {
209
        $dictionary = [];
210
211
        $foreign = $this->firstKey;
212
213
        // First we will create a dictionary of entities keyed by the foreign key of the
214
        // relationship as this will allow us to quickly access all of the related
215
        // entities without having to do nested looping which will be quite slow.
216
        foreach ($results as $result) {
217
            $dictionary[$result->{$foreign}][] = $result;
218
        }
219
220
        return $dictionary;
221
    }
222
223
    /**
224
     * Get the results of the relationship.
225
     *
226
     * @param  $relation
227
     * @return EntityCollection
228
     */
229
    public function getResults($relation)
230
    {
231
        $results = $this->query->get();
232
233
        $this->cacheRelation($results, $relation);
234
235
        return $results;
236
    }
237
238
    /**
239
     * Execute the query as a "select" statement.
240
     *
241
     * @param  array $columns
242
     * @return EntityCollection
243
     */
244
    public function get($columns = ['*'])
245
    {
246
        // First we'll add the proper select columns onto the query so it is run with
247
        // the proper columns. Then, we will get the results and hydrate out pivot
248
        // entities with the result of those columns as a separate model relation.
249
        $select = $this->getSelectColumns($columns);
250
251
        $entities = $this->query->addSelect($select)->getEntities();
0 ignored issues
show
Documentation Bug introduced by
The method addSelect 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...
252
253
        // If we actually found entities we will also eager load any relationships that
254
        // have been specified as needing to be eager loaded. This will solve the
255
        // n + 1 query problem for the developer and also increase performance.
256
        if (count($entities) > 0) {
257
            $entities = $this->query->eagerLoadRelations($entities);
258
        }
259
260
        return $this->relatedMap->newCollection($entities);
261
    }
262
263
    /**
264
     * Set the select clause for the relation query.
265
     *
266
     * @param  array $columns
267
     * @return BelongsToMany
268
     */
269
    protected function getSelectColumns(array $columns = ['*'])
270
    {
271
        if ($columns == ['*']) {
272
            $columns = [$this->relatedMap->getTable() . '.*'];
273
        }
274
275
        return array_merge($columns, [$this->parentMap->getTable() . '.' . $this->firstKey]);
276
    }
277
278
    /**
279
     * Get a paginator for the "select" statement.
280
     *
281
     * @param  int   $perPage
282
     * @param  array $columns
283
     * @return \Illuminate\Pagination\LengthAwarePaginator
284
     */
285
    public function paginate($perPage = null, $columns = ['*'])
286
    {
287
        $this->query->addSelect($this->getSelectColumns($columns));
0 ignored issues
show
Documentation Bug introduced by
The method addSelect 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...
288
289
        return $this->query->paginate($perPage, $columns);
290
    }
291
292
    /**
293
     * Get the key name of the parent model.
294
     *
295
     * @return string
296
     */
297
    protected function getQualifiedParentKeyName()
298
    {
299
        return $this->parentMap->getQualifiedKeyName();
300
    }
301
302
    /**
303
     * Get the key for comparing against the parent key in "has" query.
304
     *
305
     * @return string
306
     */
307
    public function getHasCompareKey()
308
    {
309
        return $this->farParentMap->getQualifiedKeyName();
310
    }
311
312
    /**
313
     * Run synchronization content if needed by the
314
     * relation type.
315
     *
316
     * @param  array $actualContent
0 ignored issues
show
Bug introduced by
There is no parameter named $actualContent. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
317
     * @return void
318
     */
319
    public function sync(array $entities)
320
    {
321
        // N/A
322
    }
323
}
324