Completed
Push — master ( 0ba58c...dd341e )
by Gabriel
07:12
created

Relation::setWithClass()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.5

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 4
cts 8
cp 0.5
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 3
nop 1
crap 2.5
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\HasManagerTrait;
18
use Nip\Records\Traits\Relations\HasRelationsRecordsTrait;
19
use Nip\Records\Traits\Relations\HasRelationsRecordTrait;
20
use Nip_Helper_Arrays as ArraysHelper;
21
22
/**
23
 * Class Relation
24
 * @package Nip\Records\Relations
25
 */
26
abstract class Relation
27
{
28
    use HasManagerTrait;
29
30
    /**
31
     * @var
32
     */
33
    protected $name = null;
34
35
    /**
36
     * @var string
37
     */
38
    protected $type = 'relation';
39
40
    /**
41
     * @var Record
42
     */
43
    protected $item;
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
     * @throws Exception
83
     */
84 1
    public function getQuery()
85
    {
86 1
        if ($this->query == null) {
87 1
            $this->initQuery();
88
        }
89
90 1
        return $this->query;
91
    }
92
93
    /**
94
     * @param $query
95
     * @return static
96
     */
97
    public function setQuery($query)
98
    {
99
        $this->query = $query;
100
101
        return $this;
102
    }
103
104
    /**
105
     * @throws Exception
106
     */
107 1
    public function initQuery()
108
    {
109 1
        $query = $this->newQuery();
110 1
        $this->populateQuerySpecific($query);
111
112 1
        $this->query = $query;
113 1
    }
114
115
    /**
116
     * @return Query
117
     * @throws Exception
118
     */
119 3
    public function newQuery()
120
    {
121 3
        return $this->getWith()->paramsToQuery();
122
    }
123
124
    /**
125
     * @return RecordManager
126
     * @throws Exception
127
     */
128 8
    public function getWith()
129
    {
130 8
        if ($this->with == null) {
131 4
            $this->initWith();
132
        }
133
134 8
        return $this->with;
135
    }
136
137
    /**
138
     * @param RecordManager|HasRelationsRecordsTrait $object
139
     * @return $this
140
     */
141 8
    public function setWith($object)
142
    {
143 8
        $this->with = $object;
0 ignored issues
show
Documentation Bug introduced by
It seems like $object can also be of type object<Nip\Records\Trait...sRelationsRecordsTrait>. However, the property $with is declared as type object<Nip\Records\RecordManager>. Maybe add an additional type 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 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 the id property of an instance of the Account 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
144
145 8
        return $this;
146
    }
147
148
    /**
149
     * @throws Exception
150
     */
151 4
    public function initWith()
152
    {
153 4
        $className = $this->getWithClass();
154 4
        $this->setWithClass($className);
155 4
    }
156
157
    /**
158
     * @return string
159
     */
160 3
    public function getWithClass()
161
    {
162 3
        return inflector()->pluralize($this->getName());
163
    }
164
165
    /**
166
     * @return mixed
167
     * @throws RelationsNeedsAName
168
     */
169 4
    public function getName()
170
    {
171 4
        if ($this->name === null) {
172
            throw new RelationsNeedsAName();
173
        }
174 4
        return $this->name;
175
    }
176
177
    /**
178
     * @param string $name
179
     */
180 9
    public function setName($name)
181
    {
182 9
        $this->name = $name;
183 9
    }
184
185
    /**
186
     * @param string $name
187
     * @throws Exception
188
     */
189 4
    public function setWithClass($name)
190
    {
191
        try {
192 4
            $manager = $this->getModelManagerInstance($name);
193 4
            $this->setWith($manager);
194
        } catch (InvalidModelException $exception) {
195
            throw new Exception(
196
                'Cannot instance records [' . $name . '] in ' . $this->debugString()
197
                .'|| with message ' . $exception->getMessage()
198
            );
199
        }
200 4
    }
201
202
    /**
203
     * @param $name
204
     * @return RecordManager
205
     * @throws InvalidModelException
206
     */
207 4
    public function getModelManagerInstance($name)
208
    {
209 4
        return ModelLocator::get($name);
210
    }
211
212
    /**
213
     * @return Record
214
     */
215 7
    public function getItem()
216
    {
217 7
        return $this->item;
218
    }
219
220
    /**
221
     * @param Record|HasRelationsRecordTrait $item
222
     * @return $this
223
     */
224 8
    public function setItem(Record $item)
225
    {
226 8
        $this->item = $item;
227
228 8
        return $this;
229
    }
230
231
    /**
232
     * @param AbstractQuery $query
233
     */
234
    abstract public function populateQuerySpecific(AbstractQuery $query);
235
236
    /**
237
     * @return \Nip\Database\Query\Delete
238
     * @throws Exception
239
     */
240
    public function getDeleteQuery()
241
    {
242
        $query = $this->getWith()->newDeleteQuery();
243
        $this->populateQuerySpecific($query);
244
245
        return $query;
246
    }
247
248
    /**
249
     * @return Connection
250
     */
251 1
    public function getDB()
252
    {
253 1
        return $this->getManager()->getDB();
254
    }
255
256
    /**
257
     * @param $key
258
     * @return mixed
259
     */
260 1
    public function getParam($key)
261
    {
262 1
        return $this->hasParam($key) ? $this->params[$key] : null;
263
    }
264
265
    /**
266
     * @param $key
267
     * @return boolean
268
     */
269 1
    public function hasParam($key)
270
    {
271 1
        return isset($this->params[$key]);
272
    }
273
274
    /**
275
     * @param $params
276
     * @throws Exception
277
     */
278 3
    public function addParams($params)
279
    {
280 3
        $this->checkParamClass($params);
281 3
        $this->checkParamWith($params);
282 3
        $this->checkParamTable($params);
283 3
        $this->checkParamFk($params);
284 3
        $this->setParams($params);
285 3
    }
286
287
    /**
288
     * @param $params
289
     * @throws Exception
290
     */
291 3
    public function checkParamClass($params)
292
    {
293 3
        if (isset($params['class'])) {
294
            $this->setWithClass($params['class']);
295
            unset($params['class']);
296
        }
297 3
    }
298
299
    /**
300
     * @param $params
301
     */
302 3
    public function checkParamWith($params)
303
    {
304 3
        if (isset($params['with'])) {
305
            $this->setWith($params['with']);
306
            unset($params['with']);
307
        }
308 3
    }
309
310
    /**
311
     * @param $params
312
     */
313 3
    public function checkParamTable($params)
314
    {
315 3
        if (isset($params['table'])) {
316
            $this->setTable($params['table']);
317
            unset($params['table']);
318
        }
319 3
    }
320
321
    /**
322
     * @param $params
323
     */
324 3
    public function checkParamFk($params)
325
    {
326 3
        if (isset($params['fk'])) {
327
            $this->setFK($params['fk']);
328
            unset($params['fk']);
329
        }
330 3
    }
331
332
    /**
333
     * @return array
334
     */
335 1
    public function getParams(): array
336
    {
337 1
        return $this->params;
338
    }
339
340
    /**
341
     * @param $params
342
     */
343 3
    public function setParams($params)
344
    {
345 3
        $this->params = $params;
346 3
    }
347
348
    /**
349
     * @return string
350
     */
351 2
    public function getTable()
352
    {
353 2
        if ($this->table == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $this->table of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
354 2
            $this->initTable();
355
        }
356
357 2
        return $this->table;
358
    }
359
360
    /**
361
     * @param $name
362
     */
363 2
    public function setTable($name)
364
    {
365 2
        $this->table = $name;
366 2
    }
367
368 2
    protected function initTable()
369
    {
370 2
        $this->setTable($this->generateTable());
371 2
    }
372
373
    /**
374
     * @return string
375
     * @throws Exception
376
     */
377
    protected function generateTable()
378
    {
379
        return $this->getWith()->getTable();
380
    }
381
382
    /**
383
     * Get the results of the relationship.
384
     * @return Record|RecordCollection
385
     */
386 2
    public function getResults()
387
    {
388 2
        if (!$this->isPopulated()) {
389 2
            $this->initResults();
390
        }
391
392 2
        return $this->results;
393
    }
394
395
    /**
396
     * @param $results
397
     * @return null
398
     */
399 2
    public function setResults($results)
400
    {
401 2
        $this->results = $results;
402 2
        $this->populated = true;
403
404 2
        return $this->results;
405
    }
406
407
    /**
408
     * @return bool
409
     */
410 2
    public function isPopulated()
411
    {
412 2
        return $this->populated == true;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
413
    }
414
415
    abstract public function initResults();
416
417
    /**
418
     * @param RecordCollection $collection
419
     * @return RecordCollection
420
     * @throws Exception
421
     */
422
    public function getEagerResults($collection)
423
    {
424
        if ($collection->count() < 1) {
425
            return $this->getWith()->newCollection();
426
        }
427
        $query = $this->getEagerQuery($collection);
428
429
        return $this->getWith()->findByQuery($query);
430
    }
431
432
    /**
433
     * @param RecordCollection $collection
434
     * @return Query
435
     * @throws Exception
436
     */
437 2
    public function getEagerQuery(RecordCollection $collection)
438
    {
439 2
        $fkList = $this->getEagerFkList($collection);
440 2
        $query = $this->populateEagerQueryFromFkList($this->newQuery(), $fkList);
441 2
        return $query;
442
    }
443
444
    /**
445
     * @param Query $query
446
     * @param array $fkList
447
     * @return Query
448
     * @throws Exception
449
     */
450 1
    protected function populateEagerQueryFromFkList($query, $fkList)
451
    {
452 1
        $query->where($this->getWithPK() . ' IN ?', $fkList);
453
454 1
        return $query;
455
    }
456
457
    /**
458
     * @param RecordCollection $collection
459
     * @return array
460
     */
461 2
    public function getEagerFkList(RecordCollection $collection)
462
    {
463
        /** @var ArraysHelper $arrayHelper */
464 2
        $arrayHelper = HelperBroker::get('Arrays');
465 2
        $return = $arrayHelper->pluck($collection, $this->getFK());
466
467 2
        return array_unique($return);
468
    }
469
470
    /**
471
     * @return string
472
     */
473 11
    public function getFK()
474
    {
475 11
        if ($this->fk == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $this->fk of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
476 11
            $this->initFK();
477
        }
478
479 11
        return $this->fk;
480
    }
481
482
    /**
483
     * @param $name
484
     */
485 11
    public function setFK($name)
486
    {
487 11
        $this->fk = $name;
488 11
    }
489
490 11
    protected function initFK()
491
    {
492 11
        $this->setFK($this->generateFK());
493 11
    }
494
495
    /**
496
     * @return string
497
     */
498
    protected function generateFK()
499
    {
500
        return $this->getManager()->getPrimaryFK();
501
    }
502
503
    /**
504
     * @return string
505
     * @throws Exception
506
     */
507 1
    public function getWithPK()
508
    {
509 1
        return $this->getWith()->getPrimaryKey();
510
    }
511
512
    /**
513
     * @param RecordCollection $collection
514
     * @param RecordCollection $records
515
     *
516
     * @return RecordCollection
517
     */
518
    public function match(RecordCollection $collection, RecordCollection $records)
519
    {
520
        $dictionary = $this->buildDictionary($records);
521
522
        foreach ($collection as $record) {
523
            /** @var Record $record */
524
            $results = $this->getResultsFromCollectionDictionary($dictionary, $collection, $record);
525
            $record->getRelation($this->getName())->setResults($results);
526
        }
527
528
        return $records;
529
    }
530
531
    /**
532
     * Build model dictionary keyed by the relation's foreign key.
533
     *
534
     * @param RecordCollection $collection
535
     * @return array
536
     */
537
    abstract protected function buildDictionary(RecordCollection $collection);
538
539
    /**
540
     * @param $dictionary
541
     * @param Collection $collection
542
     * @param Record $record
543
     * @return mixed
544
     */
545
    abstract public function getResultsFromCollectionDictionary($dictionary, $collection, $record);
546
547
    public function save()
548
    {
549
    }
550
551
    /**
552
     * @return string
553
     */
554
    public function getType()
555
    {
556
        return $this->type;
557
    }
558
559
    /**
560
     * @return string
561
     */
562 1
    protected function debugString()
563
    {
564
        return 'Relation'
565 1
        . ' Manager:[' . ($this->hasManager() ? $this->getManager()->getClassName() : '') . ']'
566 1
            . ' name:[' . $this->getName() . '] '
567 1
            . ' params:[' . serialize($this->getParams()) . ']';
568
    }
569
}
570