1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Nip\Records\Relations; |
4
|
|
|
|
5
|
|
|
use Nip\Database\Connections\Connection; |
6
|
|
|
use Nip\Database\Query\AbstractQuery; |
7
|
|
|
use Nip\Database\Query\Select as Query; |
8
|
|
|
use Nip\HelperBroker; |
9
|
|
|
use Exception; |
10
|
|
|
use Nip\Records\Collections\Collection; |
11
|
|
|
use Nip\Records\Collections\Collection as RecordCollection; |
12
|
|
|
use Nip\Records\Locator\Exceptions\InvalidModelException; |
13
|
|
|
use Nip\Records\Locator\ModelLocator; |
14
|
|
|
use Nip\Records\Record; |
15
|
|
|
use Nip\Records\RecordManager; |
16
|
|
|
use Nip\Records\Relations\Exceptions\RelationsNeedsAName; |
17
|
|
|
use Nip\Records\Relations\Traits\HasForeignKeyTrait; |
18
|
|
|
use Nip\Records\Relations\Traits\HasItemTrait; |
19
|
|
|
use Nip\Records\Relations\Traits\HasManagerTrait; |
20
|
|
|
use Nip\Records\Relations\Traits\HasPrimaryKeyTrait; |
21
|
|
|
use Nip\Records\Traits\Relations\HasRelationsRecordsTrait; |
22
|
|
|
use Nip_Helper_Arrays as ArraysHelper; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Class Relation |
26
|
|
|
* @package Nip\Records\Relations |
27
|
|
|
*/ |
28
|
|
|
abstract class Relation |
29
|
|
|
{ |
30
|
|
|
use HasManagerTrait; |
31
|
|
|
use HasForeignKeyTrait; |
32
|
|
|
use HasPrimaryKeyTrait; |
33
|
|
|
use HasItemTrait; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var |
37
|
|
|
*/ |
38
|
|
|
protected $name = null; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var string |
42
|
|
|
*/ |
43
|
|
|
protected $type = 'relation'; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var RecordManager |
47
|
|
|
*/ |
48
|
|
|
protected $with = null; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var null|string |
52
|
|
|
*/ |
53
|
|
|
protected $table = null; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @var Query |
57
|
|
|
*/ |
58
|
|
|
protected $query; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* @var bool |
62
|
|
|
*/ |
63
|
|
|
protected $populated = false; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @var array |
67
|
|
|
*/ |
68
|
|
|
protected $params = []; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @var null|Collection|Record |
72
|
|
|
*/ |
73
|
|
|
protected $results = null; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* @return Query |
77
|
|
|
* @throws Exception |
78
|
|
|
*/ |
79
|
1 |
|
public function getQuery() |
80
|
|
|
{ |
81
|
1 |
|
if ($this->query == null) { |
82
|
1 |
|
$this->initQuery(); |
83
|
|
|
} |
84
|
|
|
|
85
|
1 |
|
return $this->query; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* @param $query |
90
|
|
|
* @return static |
91
|
|
|
*/ |
92
|
|
|
public function setQuery($query) |
93
|
|
|
{ |
94
|
|
|
$this->query = $query; |
95
|
|
|
|
96
|
|
|
return $this; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* @throws Exception |
101
|
|
|
*/ |
102
|
1 |
|
public function initQuery() |
103
|
|
|
{ |
104
|
1 |
|
$query = $this->newQuery(); |
105
|
1 |
|
$this->populateQuerySpecific($query); |
106
|
|
|
|
107
|
1 |
|
$this->query = $query; |
108
|
1 |
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* @return Query |
112
|
|
|
* @throws Exception |
113
|
|
|
*/ |
114
|
3 |
|
public function newQuery() |
115
|
|
|
{ |
116
|
3 |
|
return $this->getWith()->paramsToQuery(); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** @noinspection PhpDocMissingThrowsInspection |
120
|
|
|
* @return RecordManager |
121
|
|
|
*/ |
122
|
9 |
|
public function getWith() |
123
|
|
|
{ |
124
|
9 |
|
if ($this->with == null) { |
125
|
|
|
/** @noinspection PhpUnhandledExceptionInspection */ |
126
|
4 |
|
$this->initWith(); |
127
|
|
|
} |
128
|
|
|
|
129
|
9 |
|
return $this->with; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @param RecordManager|HasRelationsRecordsTrait $object |
134
|
|
|
* @return $this |
135
|
|
|
*/ |
136
|
9 |
|
public function setWith($object) |
137
|
|
|
{ |
138
|
9 |
|
$this->with = $object; |
|
|
|
|
139
|
|
|
|
140
|
9 |
|
return $this; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* @throws Exception |
145
|
|
|
*/ |
146
|
4 |
|
public function initWith() |
147
|
|
|
{ |
148
|
4 |
|
$className = $this->getWithClass(); |
149
|
4 |
|
$this->setWithClass($className); |
150
|
4 |
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* @return string |
154
|
|
|
*/ |
155
|
3 |
|
public function getWithClass() |
156
|
|
|
{ |
157
|
3 |
|
return inflector()->pluralize($this->getName()); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @return mixed |
162
|
|
|
* @throws RelationsNeedsAName |
163
|
|
|
*/ |
164
|
4 |
|
public function getName() |
165
|
|
|
{ |
166
|
4 |
|
if ($this->name === null) { |
167
|
|
|
throw new RelationsNeedsAName(); |
168
|
|
|
} |
169
|
4 |
|
return $this->name; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* @param string $name |
174
|
|
|
*/ |
175
|
10 |
|
public function setName($name) |
176
|
|
|
{ |
177
|
10 |
|
$this->name = $name; |
178
|
10 |
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* @param string $name |
182
|
|
|
* @throws Exception |
183
|
|
|
*/ |
184
|
4 |
|
public function setWithClass($name) |
185
|
|
|
{ |
186
|
|
|
try { |
187
|
4 |
|
$manager = $this->getModelManagerInstance($name); |
188
|
4 |
|
$this->setWith($manager); |
189
|
|
|
} catch (InvalidModelException $exception) { |
190
|
|
|
throw new Exception( |
191
|
|
|
'Cannot instance records [' . $name . '] in ' . $this->debugString() |
192
|
|
|
. '|| with message ' . $exception->getMessage() |
193
|
|
|
); |
194
|
|
|
} |
195
|
4 |
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* @param $name |
199
|
|
|
* @return RecordManager |
200
|
|
|
* @throws InvalidModelException |
201
|
|
|
*/ |
202
|
4 |
|
public function getModelManagerInstance($name) |
203
|
|
|
{ |
204
|
4 |
|
return ModelLocator::get($name); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
|
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* @param AbstractQuery $query |
211
|
|
|
*/ |
212
|
|
|
abstract public function populateQuerySpecific(AbstractQuery $query); |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* @return \Nip\Database\Query\Delete |
216
|
|
|
* @throws Exception |
217
|
|
|
*/ |
218
|
|
|
public function getDeleteQuery() |
219
|
|
|
{ |
220
|
|
|
$query = $this->getWith()->newDeleteQuery(); |
221
|
|
|
$this->populateQuerySpecific($query); |
222
|
|
|
|
223
|
|
|
return $query; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* @return Connection |
228
|
|
|
*/ |
229
|
1 |
|
public function getDB() |
230
|
|
|
{ |
231
|
1 |
|
return $this->getManager()->getDB(); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* @param $key |
236
|
|
|
* @return mixed |
237
|
|
|
*/ |
238
|
2 |
|
public function getParam($key) |
239
|
|
|
{ |
240
|
2 |
|
return $this->hasParam($key) ? $this->params[$key] : null; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* @param $key |
245
|
|
|
* @return boolean |
246
|
|
|
*/ |
247
|
2 |
|
public function hasParam($key) |
248
|
|
|
{ |
249
|
2 |
|
return isset($this->params[$key]); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* @param $params |
254
|
|
|
* @throws Exception |
255
|
|
|
*/ |
256
|
3 |
|
public function addParams($params) |
257
|
|
|
{ |
258
|
3 |
|
$this->checkParamClass($params); |
259
|
3 |
|
$this->checkParamWith($params); |
260
|
3 |
|
$this->checkParamTable($params); |
261
|
3 |
|
$this->checkParamFk($params); |
262
|
3 |
|
$this->checkParamPrimaryKey($params); |
263
|
3 |
|
$this->setParams($params); |
264
|
3 |
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* @param $params |
268
|
|
|
* @throws Exception |
269
|
|
|
*/ |
270
|
3 |
|
public function checkParamClass($params) |
271
|
|
|
{ |
272
|
3 |
|
if (isset($params['class'])) { |
273
|
|
|
$this->setWithClass($params['class']); |
274
|
|
|
unset($params['class']); |
275
|
|
|
} |
276
|
3 |
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* @param $params |
280
|
|
|
*/ |
281
|
3 |
|
public function checkParamWith($params) |
282
|
|
|
{ |
283
|
3 |
|
if (isset($params['with'])) { |
284
|
|
|
$this->setWith($params['with']); |
285
|
|
|
unset($params['with']); |
286
|
|
|
} |
287
|
3 |
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* @param $params |
291
|
|
|
*/ |
292
|
3 |
|
public function checkParamTable($params) |
293
|
|
|
{ |
294
|
3 |
|
if (isset($params['table'])) { |
295
|
|
|
$this->setTable($params['table']); |
296
|
|
|
unset($params['table']); |
297
|
|
|
} |
298
|
3 |
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* @return array |
302
|
|
|
*/ |
303
|
1 |
|
public function getParams(): array |
304
|
|
|
{ |
305
|
1 |
|
return $this->params; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* @param $params |
310
|
|
|
*/ |
311
|
3 |
|
public function setParams($params) |
312
|
|
|
{ |
313
|
3 |
|
$this->params = $params; |
314
|
3 |
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* @return string |
318
|
|
|
*/ |
319
|
2 |
|
public function getTable() |
320
|
|
|
{ |
321
|
2 |
|
if ($this->table == null) { |
|
|
|
|
322
|
2 |
|
$this->initTable(); |
323
|
|
|
} |
324
|
|
|
|
325
|
2 |
|
return $this->table; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* @param $name |
330
|
|
|
*/ |
331
|
2 |
|
public function setTable($name) |
332
|
|
|
{ |
333
|
2 |
|
$this->table = $name; |
334
|
2 |
|
} |
335
|
|
|
|
336
|
2 |
|
protected function initTable() |
337
|
|
|
{ |
338
|
2 |
|
$this->setTable($this->generateTable()); |
339
|
2 |
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* @return string |
343
|
|
|
* @throws Exception |
344
|
|
|
*/ |
345
|
|
|
protected function generateTable() |
346
|
|
|
{ |
347
|
|
|
return $this->getWith()->getTable(); |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
/** |
351
|
|
|
* Get the results of the relationship. |
352
|
|
|
* @return Record|RecordCollection |
353
|
|
|
*/ |
354
|
3 |
|
public function getResults() |
355
|
|
|
{ |
356
|
3 |
|
if (!$this->isPopulated()) { |
357
|
3 |
|
$this->initResults(); |
358
|
|
|
} |
359
|
|
|
|
360
|
3 |
|
return $this->results; |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* @param $results |
365
|
|
|
* @return null |
366
|
|
|
*/ |
367
|
3 |
|
public function setResults($results) |
368
|
|
|
{ |
369
|
3 |
|
$this->results = $results; |
370
|
3 |
|
$this->populated = true; |
371
|
|
|
|
372
|
3 |
|
return $this->results; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* @return bool |
377
|
|
|
*/ |
378
|
1 |
|
public function isPopulatable() |
379
|
|
|
{ |
380
|
1 |
|
return !empty($this->getItemRelationPrimaryKey()); |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* @return bool |
385
|
|
|
*/ |
386
|
3 |
|
public function isPopulated() |
387
|
|
|
{ |
388
|
3 |
|
return $this->populated == true; |
|
|
|
|
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
abstract public function initResults(); |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* @param RecordCollection $collection |
395
|
|
|
* @return RecordCollection |
396
|
|
|
* @throws Exception |
397
|
|
|
*/ |
398
|
|
|
public function getEagerResults($collection) |
399
|
|
|
{ |
400
|
|
|
if ($collection->count() < 1) { |
401
|
|
|
return $this->getWith()->newCollection(); |
402
|
|
|
} |
403
|
|
|
$query = $this->getEagerQuery($collection); |
404
|
|
|
|
405
|
|
|
return $this->getWith()->findByQuery($query); |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* @param RecordCollection $collection |
410
|
|
|
* @return Query |
411
|
|
|
*/ |
412
|
2 |
|
public function getEagerQuery(RecordCollection $collection) |
413
|
|
|
{ |
414
|
2 |
|
$fkList = $this->getEagerFkList($collection); |
415
|
2 |
|
$query = $this->populateEagerQueryFromFkList($this->newQuery(), $fkList); |
416
|
2 |
|
return $query; |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
/** |
420
|
|
|
* @param Query $query |
421
|
|
|
* @param array $fkList |
422
|
|
|
* @return Query |
423
|
|
|
*/ |
424
|
1 |
|
protected function populateEagerQueryFromFkList($query, $fkList) |
425
|
|
|
{ |
426
|
1 |
|
$query->where($this->getWithPK() . ' IN ?', $fkList); |
427
|
|
|
|
428
|
1 |
|
return $query; |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
/** |
432
|
|
|
* @param RecordCollection $collection |
433
|
|
|
* @return array |
434
|
|
|
*/ |
435
|
2 |
|
public function getEagerFkList(RecordCollection $collection) |
436
|
|
|
{ |
437
|
|
|
/** @var ArraysHelper $arrayHelper */ |
438
|
2 |
|
$arrayHelper = HelperBroker::get('Arrays'); |
439
|
2 |
|
$return = $arrayHelper->pluck($collection, $this->getFK()); |
440
|
|
|
|
441
|
2 |
|
return array_unique($return); |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
/** |
445
|
|
|
* @return string |
446
|
|
|
*/ |
447
|
1 |
|
public function getWithPK() |
448
|
|
|
{ |
449
|
1 |
|
return $this->getWith()->getPrimaryKey(); |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
/** |
453
|
|
|
* @param RecordCollection $collection |
454
|
|
|
* @param RecordCollection $recordsLoaded |
455
|
|
|
* |
456
|
|
|
* @return RecordCollection |
457
|
|
|
*/ |
458
|
|
|
public function match(RecordCollection $collection, RecordCollection $recordsLoaded) |
459
|
|
|
{ |
460
|
|
|
$dictionary = $this->buildDictionary($recordsLoaded); |
461
|
|
|
|
462
|
|
|
foreach ($collection as $record) { |
463
|
|
|
/** @var Record $record */ |
464
|
|
|
$results = $this->getResultsFromCollectionDictionary($dictionary, $collection, $record); |
465
|
|
|
$record->getRelation($this->getName())->setResults($results); |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
return $recordsLoaded; |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
/** |
472
|
|
|
* Build model dictionary keyed by the relation's foreign key. |
473
|
|
|
* |
474
|
|
|
* @param RecordCollection $collection |
475
|
|
|
* @return array |
476
|
|
|
*/ |
477
|
|
|
abstract protected function buildDictionary(RecordCollection $collection); |
478
|
|
|
|
479
|
|
|
/** |
480
|
|
|
* @param $dictionary |
481
|
|
|
* @param Collection $collection |
482
|
|
|
* @param Record $record |
483
|
|
|
* @return mixed |
484
|
|
|
*/ |
485
|
|
|
abstract public function getResultsFromCollectionDictionary($dictionary, $collection, $record); |
486
|
|
|
|
487
|
|
|
public function save() |
488
|
|
|
{ |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* @return string |
493
|
|
|
*/ |
494
|
|
|
public function getType() |
495
|
|
|
{ |
496
|
|
|
return $this->type; |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
/** |
500
|
|
|
* @return string |
501
|
|
|
*/ |
502
|
1 |
|
protected function debugString() |
503
|
|
|
{ |
504
|
|
|
return 'Relation' |
505
|
1 |
|
. ' Manager:[' . ($this->hasManager() ? $this->getManager()->getClassName() : '') . ']' |
506
|
1 |
|
. ' name:[' . $this->getName() . '] ' |
507
|
1 |
|
. ' params:[' . serialize($this->getParams()) . ']'; |
508
|
|
|
} |
509
|
|
|
} |
510
|
|
|
|
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 mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.