GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Relation   C
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 515
Duplicated Lines 3.11 %

Coupling/Cohesion

Components 2
Dependencies 8

Test Coverage

Coverage 58.97%

Importance

Changes 0
Metric Value
dl 16
loc 515
ccs 92
cts 156
cp 0.5897
rs 6.0975
c 0
b 0
f 0
wmc 60
lcom 2
cbo 8

48 Methods

Rating   Name   Duplication   Size   Complexity  
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 4 1
A setName() 0 4 1
A setWithClass() 0 11 3
A getManager() 0 8 2
A setManager() 0 4 1
A initManager() 0 4 1
A getItem() 0 4 1
A setItem() 0 6 1
A populateQuerySpecific() 0 3 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 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 isPopulated() 0 4 1
initResults() 0 1 ?
A getEagerResults() 0 9 2
A getEagerQuery() 8 8 1
A getEagerFkList() 8 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

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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 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;
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...
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;
0 ignored issues
show
Documentation Bug introduced by
It seems like $manager can also be of type object<Nip\Records\Trait...sRelationsRecordsTrait>. However, the property $manager 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...
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) {
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...
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;
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...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
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...
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