1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Analogue\ORM\Relationships; |
4
|
|
|
|
5
|
|
|
use Analogue\ORM\Mappable; |
6
|
|
|
use Analogue\ORM\System\Query; |
7
|
|
|
use Analogue\ORM\System\Mapper; |
8
|
|
|
use Analogue\ORM\EntityCollection; |
9
|
|
|
use Illuminate\Database\Query\Expression; |
10
|
|
|
|
11
|
|
|
class BelongsTo extends Relationship |
12
|
|
|
{ |
13
|
|
|
/** |
14
|
|
|
* The foreign key of the parent model. |
15
|
|
|
* |
16
|
|
|
* @var string |
17
|
|
|
*/ |
18
|
|
|
protected $foreignKey; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* The associated key on the parent model. |
22
|
|
|
* |
23
|
|
|
* @var string |
24
|
|
|
*/ |
25
|
|
|
protected $otherKey; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* The name of the relationship. |
29
|
|
|
* |
30
|
|
|
* @var string |
31
|
|
|
*/ |
32
|
|
|
protected $relation; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Indicate if the parent entity hold the key for the relation. |
36
|
|
|
* |
37
|
|
|
* @var boolean |
38
|
|
|
*/ |
39
|
|
|
protected static $ownForeignKey = true; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Create a new belongs to relationship instance. |
43
|
|
|
* |
44
|
|
|
* @param Mapper $mapper |
45
|
|
|
* @param Mappable $parent |
46
|
|
|
* @param string $foreignKey |
47
|
|
|
* @param string $otherKey |
48
|
|
|
* @param string $relation |
49
|
|
|
*/ |
50
|
|
|
public function __construct(Mapper $mapper, $parent, $foreignKey, $otherKey, $relation) |
51
|
|
|
{ |
52
|
|
|
$this->otherKey = $otherKey; |
53
|
|
|
$this->relation = $relation; |
54
|
|
|
$this->foreignKey = $foreignKey; |
55
|
|
|
|
56
|
|
|
parent::__construct($mapper, $parent); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @param $related |
61
|
|
|
* @return mixed |
62
|
|
|
*/ |
63
|
|
|
public function attachTo($related) |
64
|
|
|
{ |
65
|
|
|
$this->associate($related); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @param $related |
70
|
|
|
* @return Mappable |
71
|
|
|
*/ |
72
|
|
|
public function detachFrom($related) |
73
|
|
|
{ |
74
|
|
|
return $this->dissociate($related); //todo |
|
|
|
|
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Get the results of the relationship. |
79
|
|
|
* |
80
|
|
|
* @param $relation |
81
|
|
|
* |
82
|
|
|
* @return \Analogue\ORM\Entity |
83
|
|
|
*/ |
84
|
|
|
public function getResults($relation) |
85
|
|
|
{ |
86
|
|
|
$result = $this->query->first(); |
87
|
|
|
|
88
|
|
|
$this->cacheRelation($result, $relation); |
89
|
|
|
|
90
|
|
|
return $result; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Set the base constraints on the relation query. |
95
|
|
|
* |
96
|
|
|
* @return void |
97
|
|
|
*/ |
98
|
|
|
public function addConstraints() |
99
|
|
|
{ |
100
|
|
|
if (static::$constraints) { |
101
|
|
|
// For belongs to relationships, which are essentially the inverse of has one |
102
|
|
|
// or has many relationships, we need to actually query on the primary key |
103
|
|
|
// of the related models matching on the foreign key that's on a parent. |
104
|
|
|
$table = $this->relatedMap->getTable(); |
105
|
|
|
|
106
|
|
|
$this->query->where($table . '.' . $this->otherKey, '=', $this->parent->getEntityAttribute($this->foreignKey)); |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Add the constraints for a relationship count query. |
112
|
|
|
* |
113
|
|
|
* @param Query $query |
114
|
|
|
* @param Query $parent |
115
|
|
|
* @return Query |
116
|
|
|
*/ |
117
|
|
|
public function getRelationCountQuery(Query $query, Query $parent) |
118
|
|
|
{ |
119
|
|
|
$query->select(new Expression('count(*)')); |
|
|
|
|
120
|
|
|
|
121
|
|
|
$otherKey = $this->wrap($query->getTable() . '.' . $this->otherKey); |
122
|
|
|
|
123
|
|
|
return $query->where($this->getQualifiedForeignKey(), '=', new Expression($otherKey)); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Set the constraints for an eager load of the relation. |
128
|
|
|
* |
129
|
|
|
* @param array $entities |
130
|
|
|
* @return void |
131
|
|
|
*/ |
132
|
|
|
public function addEagerConstraints(array $entities) |
133
|
|
|
{ |
134
|
|
|
// We'll grab the primary key name of the related models since it could be set to |
135
|
|
|
// a non-standard name and not "id". We will then construct the constraint for |
136
|
|
|
// our eagerly loading query so it returns the proper models from execution. |
137
|
|
|
$key = $this->relatedMap->getTable() . '.' . $this->otherKey; |
138
|
|
|
|
139
|
|
|
$this->query->whereIn($key, $this->getEagerModelKeys($entities)); |
|
|
|
|
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Gather the keys from an array of related models. |
144
|
|
|
* |
145
|
|
|
* @param array $entities |
146
|
|
|
* @return array |
147
|
|
|
*/ |
148
|
|
|
protected function getEagerModelKeys(array $entities) |
149
|
|
|
{ |
150
|
|
|
$keys = []; |
151
|
|
|
|
152
|
|
|
// First we need to gather all of the keys from the parent models so we know what |
153
|
|
|
// to query for via the eager loading query. We will add them to an array then |
154
|
|
|
// execute a "where in" statement to gather up all of those related records. |
155
|
|
|
foreach ($entities as $entity) { |
156
|
|
|
$entity = $this->factory->make($entity); |
157
|
|
|
|
158
|
|
|
if (!is_null($value = $entity->getEntityAttribute($this->foreignKey))) { |
159
|
|
|
$keys[] = $value; |
160
|
|
|
} |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
// If there are no keys that were not null we will just return an array with 0 in |
164
|
|
|
// it so the query doesn't fail, but will not return any results, which should |
165
|
|
|
// be what this developer is expecting in a case where this happens to them. |
166
|
|
|
if (count($keys) == 0) { |
167
|
|
|
return [0]; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
return array_values(array_unique($keys)); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Initialize the relation on a set of models. |
175
|
|
|
* |
176
|
|
|
* @param array $entities |
177
|
|
|
* @param string $relation |
178
|
|
|
* @return array |
179
|
|
|
*/ |
180
|
|
|
public function initRelation(array $entities, $relation) |
181
|
|
|
{ |
182
|
|
|
foreach ($entities as $entity) { |
183
|
|
|
$entity = $this->factory->make($entity); |
184
|
|
|
$entity->setEntityAttribute($relation, null); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
return $entities; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Match the eagerly loaded results to their parents. |
192
|
|
|
* |
193
|
|
|
* @param array $entities |
194
|
|
|
* @param EntityCollection $results |
195
|
|
|
* @param string $relation |
196
|
|
|
* @return array |
197
|
|
|
*/ |
198
|
|
|
public function match(array $entities, EntityCollection $results, $relation) |
199
|
|
|
{ |
200
|
|
|
$foreign = $this->foreignKey; |
201
|
|
|
|
202
|
|
|
$other = $this->otherKey; |
203
|
|
|
|
204
|
|
|
// First we will get to build a dictionary of the child models by their primary |
205
|
|
|
// key of the relationship, then we can easily match the children back onto |
206
|
|
|
// the parents using that dictionary and the primary key of the children. |
207
|
|
|
$dictionary = []; |
208
|
|
|
|
209
|
|
|
foreach ($results as $result) { |
210
|
|
|
$result = $this->factory->make($result); |
211
|
|
|
$dictionary[$result->getEntityAttribute($other)] = $result->getObject(); |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
// Once we have the dictionary constructed, we can loop through all the parents |
215
|
|
|
// and match back onto their children using these keys of the dictionary and |
216
|
|
|
// the primary key of the children to map them onto the correct instances. |
217
|
|
|
foreach ($entities as $entity) { |
218
|
|
|
$entity = $this->factory->make($entity); |
219
|
|
|
|
220
|
|
|
if (isset($dictionary[$entity->getEntityAttribute($foreign)])) { |
221
|
|
|
$entity->setEntityAttribute($relation, $dictionary[$entity->getEntityAttribute($foreign)]); |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
return $entities; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
public function sync(array $entities) |
229
|
|
|
{ |
230
|
|
|
if (count($entities) > 1) { |
231
|
|
|
throw new MappingException("Single Relationship shouldn't be synced with more than one entity"); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
if (count($entities) == 1) { |
235
|
|
|
return $this->associate($entities[0]); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
return false; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Associate the model instance to the given parent. |
243
|
|
|
* |
244
|
|
|
* @param mixed $entity |
245
|
|
|
* @return void |
246
|
|
|
*/ |
247
|
|
|
public function associate($entity) |
248
|
|
|
{ |
249
|
|
|
$this->parent->setEntityAttribute($this->foreignKey, $entity->getEntityAttribute($this->otherKey)); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Dissociate previously associated model from the given parent. |
254
|
|
|
* |
255
|
|
|
* @return Mappable |
256
|
|
|
*/ |
257
|
|
|
public function dissociate() |
258
|
|
|
{ |
259
|
|
|
// The Mapper will retrieve this association within the object model, we won't be using |
260
|
|
|
// the foreign key attribute inside the parent Entity. |
261
|
|
|
// |
262
|
|
|
//$this->parent->setEntityAttribute($this->foreignKey, null); |
|
|
|
|
263
|
|
|
|
264
|
|
|
$this->parent->setEntityAttribute($this->relation, null); |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* Get the foreign key of the relationship. |
269
|
|
|
* |
270
|
|
|
* @return string |
271
|
|
|
*/ |
272
|
|
|
public function getForeignKey() |
273
|
|
|
{ |
274
|
|
|
return $this->foreignKey; |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* Get the foreign key value pair for a related object |
279
|
|
|
* |
280
|
|
|
* @param mixed $related |
281
|
|
|
* |
282
|
|
|
* @return array |
283
|
|
|
*/ |
284
|
|
View Code Duplication |
public function getForeignKeyValuePair($related) |
|
|
|
|
285
|
|
|
{ |
286
|
|
|
$foreignKey = $this->getForeignKey(); |
287
|
|
|
|
288
|
|
|
if ($related) { |
289
|
|
|
$wrapper = $this->factory->make($related); |
290
|
|
|
|
291
|
|
|
$relatedKey = $this->relatedMap->getKeyName(); |
292
|
|
|
|
293
|
|
|
return [$foreignKey => $wrapper->getEntityAttribute($relatedKey)]; |
294
|
|
|
} else { |
295
|
|
|
return [$foreignKey => null]; |
296
|
|
|
} |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Get the fully qualified foreign key of the relationship. |
301
|
|
|
* |
302
|
|
|
* @return string |
303
|
|
|
*/ |
304
|
|
|
public function getQualifiedForeignKey() |
305
|
|
|
{ |
306
|
|
|
return $this->parentMap->getTable() . '.' . $this->foreignKey; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* Get the associated key of the relationship. |
311
|
|
|
* |
312
|
|
|
* @return string |
313
|
|
|
*/ |
314
|
|
|
public function getOtherKey() |
315
|
|
|
{ |
316
|
|
|
return $this->otherKey; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* Get the fully qualified associated key of the relationship. |
321
|
|
|
* |
322
|
|
|
* @return string |
323
|
|
|
*/ |
324
|
|
|
public function getQualifiedOtherKeyName() |
325
|
|
|
{ |
326
|
|
|
return $this->relatedMap->getTable() . '.' . $this->otherKey; |
327
|
|
|
} |
328
|
|
|
} |
329
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.