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