Test Setup Failed
Push — master ( 3bfa70...333906 )
by Gabriel
13:19
created

Relation   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 553
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 11

Test Coverage

Coverage 75.63%

Importance

Changes 0
Metric Value
wmc 61
lcom 2
cbo 11
dl 0
loc 553
ccs 121
cts 160
cp 0.7563
rs 3.52
c 0
b 0
f 0

50 Methods

Rating   Name   Duplication   Size   Complexity  
populateQuerySpecific() 0 1 ?
A getQuery() 0 8 2
A setQuery() 0 6 1
A initQuery() 0 7 1
A newQuery() 0 4 1
A getWith() 0 8 2
A setWith() 0 6 1
A initWith() 0 5 1
A getWithClass() 0 4 1
A getName() 0 7 2
A setName() 0 4 1
A setWithClass() 0 12 2
A getModelManagerInstance() 0 4 1
A getItem() 0 4 1
A setItem() 0 6 1
A getDeleteQuery() 0 7 1
A getDB() 0 4 1
A getParam() 0 4 2
A hasParam() 0 4 1
A addParams() 0 8 1
A checkParamClass() 0 7 2
A checkParamWith() 0 7 2
A checkParamTable() 0 7 2
A checkParamFk() 0 7 2
A getParams() 0 4 1
A setParams() 0 4 1
A getTable() 0 8 2
A setTable() 0 4 1
A initTable() 0 4 1
A generateTable() 0 4 1
A getResults() 0 8 2
A setResults() 0 7 1
A isPopulatable() 0 5 1
A isPopulated() 0 4 1
initResults() 0 1 ?
A getEagerResults() 0 9 2
A getEagerQuery() 0 6 1
A populateEagerQueryFromFkList() 0 6 1
A getEagerFkList() 0 8 1
A getFK() 0 8 2
A setFK() 0 4 1
A initFK() 0 4 1
A generateFK() 0 4 1
A getWithPK() 0 4 1
A match() 0 12 2
buildDictionary() 0 1 ?
getResultsFromCollectionDictionary() 0 1 ?
A save() 0 3 1
A getType() 0 4 1
A debugString() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like Relation often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Relation, and based on these observations, apply Extract Interface, too.

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 9
    public function getWith()
129
    {
130 9
        if ($this->with == null) {
131 4
            $this->initWith();
132
        }
133
134 9
        return $this->with;
135
    }
136
137
    /**
138
     * @param RecordManager|HasRelationsRecordsTrait $object
139
     * @return $this
140
     */
141 9
    public function setWith($object)
142
    {
143 9
        $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 9
        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 8
    public function getItem()
216
    {
217 8
        return $this->item;
218
    }
219
220
    /**
221
     * @param Record|HasRelationsRecordTrait $item
222
     * @return $this
223
     */
224 9
    public function setItem(Record $item)
225
    {
226 9
        $this->item = $item;
227
228 9
        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 2
    public function getParam($key)
261
    {
262 2
        return $this->hasParam($key) ? $this->params[$key] : null;
263
    }
264
265
    /**
266
     * @param $key
267
     * @return boolean
268
     */
269 2
    public function hasParam($key)
270
    {
271 2
        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 3
    public function getResults()
387
    {
388 3
        if (!$this->isPopulated()) {
389 3
            $this->initResults();
390
        }
391
392 3
        return $this->results;
393
    }
394
395
    /**
396
     * @param $results
397
     * @return null
398
     */
399 3
    public function setResults($results)
400
    {
401 3
        $this->results = $results;
402 3
        $this->populated = true;
403
404 3
        return $this->results;
405
    }
406
407
    /**
408
     * @return bool
409
     */
410 1
    public function isPopulatable()
411
    {
412 1
        $pk1 = $this->getManager()->getPrimaryKey();
413 1
        return !empty($this->getItem()->{$pk1});
414
    }
415
416
    /**
417
     * @return bool
418
     */
419 3
    public function isPopulated()
420
    {
421 3
        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...
422
    }
423
424
    abstract public function initResults();
425
426
    /**
427
     * @param RecordCollection $collection
428
     * @return RecordCollection
429
     * @throws Exception
430
     */
431
    public function getEagerResults($collection)
432
    {
433
        if ($collection->count() < 1) {
434
            return $this->getWith()->newCollection();
435
        }
436
        $query = $this->getEagerQuery($collection);
437
438
        return $this->getWith()->findByQuery($query);
439
    }
440
441
    /**
442
     * @param RecordCollection $collection
443
     * @return Query
444
     * @throws Exception
445
     */
446 2
    public function getEagerQuery(RecordCollection $collection)
447
    {
448 2
        $fkList = $this->getEagerFkList($collection);
449 2
        $query = $this->populateEagerQueryFromFkList($this->newQuery(), $fkList);
450 2
        return $query;
451
    }
452
453
    /**
454
     * @param Query $query
455
     * @param array $fkList
456
     * @return Query
457
     * @throws Exception
458
     */
459 1
    protected function populateEagerQueryFromFkList($query, $fkList)
460
    {
461 1
        $query->where($this->getWithPK() . ' IN ?', $fkList);
462
463 1
        return $query;
464
    }
465
466
    /**
467
     * @param RecordCollection $collection
468
     * @return array
469
     */
470 2
    public function getEagerFkList(RecordCollection $collection)
471
    {
472
        /** @var ArraysHelper $arrayHelper */
473 2
        $arrayHelper = HelperBroker::get('Arrays');
474 2
        $return = $arrayHelper->pluck($collection, $this->getFK());
475
476 2
        return array_unique($return);
477
    }
478
479
    /**
480
     * @return string
481
     */
482 12
    public function getFK()
483
    {
484 12
        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...
485 12
            $this->initFK();
486
        }
487
488 12
        return $this->fk;
489
    }
490
491
    /**
492
     * @param $name
493
     */
494 12
    public function setFK($name)
495
    {
496 12
        $this->fk = $name;
497 12
    }
498
499 12
    protected function initFK()
500
    {
501 12
        $this->setFK($this->generateFK());
502 12
    }
503
504
    /**
505
     * @return string
506
     */
507
    protected function generateFK()
508
    {
509
        return $this->getManager()->getPrimaryFK();
510
    }
511
512
    /**
513
     * @return string
514
     * @throws Exception
515
     */
516 1
    public function getWithPK()
517
    {
518 1
        return $this->getWith()->getPrimaryKey();
519
    }
520
521
    /**
522
     * @param RecordCollection $collection
523
     * @param RecordCollection $recordsLoaded
524
     *
525
     * @return RecordCollection
526
     */
527
    public function match(RecordCollection $collection, RecordCollection $recordsLoaded)
528
    {
529
        $dictionary = $this->buildDictionary($recordsLoaded);
530
531
        foreach ($collection as $record) {
532
            /** @var Record $record */
533
            $results = $this->getResultsFromCollectionDictionary($dictionary, $collection, $record);
534
            $record->getRelation($this->getName())->setResults($results);
535
        }
536
537
        return $recordsLoaded;
538
    }
539
540
    /**
541
     * Build model dictionary keyed by the relation's foreign key.
542
     *
543
     * @param RecordCollection $collection
544
     * @return array
545
     */
546
    abstract protected function buildDictionary(RecordCollection $collection);
547
548
    /**
549
     * @param $dictionary
550
     * @param Collection $collection
551
     * @param Record $record
552
     * @return mixed
553
     */
554
    abstract public function getResultsFromCollectionDictionary($dictionary, $collection, $record);
555
556
    public function save()
557
    {
558
    }
559
560
    /**
561
     * @return string
562
     */
563
    public function getType()
564
    {
565
        return $this->type;
566
    }
567
568
    /**
569
     * @return string
570
     */
571 1
    protected function debugString()
572
    {
573
        return 'Relation'
574 1
            . ' Manager:[' . ($this->hasManager() ? $this->getManager()->getClassName() : '') . ']'
575 1
            . ' name:[' . $this->getName() . '] '
576 1
            . ' params:[' . serialize($this->getParams()) . ']';
577
    }
578
}
579