Completed
Push — master ( 470d43...3f2648 )
by Gabriel
03:36
created

Relation::checkParamClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.5

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
ccs 2
cts 4
cp 0.5
crap 2.5
rs 10
c 0
b 0
f 0
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\AbstractModels\Record;
15
use Nip\Records\AbstractModels\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
     */
113 3
    public function newQuery()
114
    {
115 3
        return $this->getWith()->paramsToQuery();
116
    }
117
118
    /** @noinspection PhpDocMissingThrowsInspection
119
     * @return RecordManager
120
     */
121 9
    public function getWith()
122
    {
123 9
        if ($this->with == null) {
124
            /** @noinspection PhpUnhandledExceptionInspection */
125 4
            $this->initWith();
126
        }
127
128 9
        return $this->with;
129
    }
130
131
    /**
132
     * @param RecordManager|HasRelationsRecordsTrait $object
133
     * @return $this
134
     */
135 9
    public function setWith($object)
136
    {
137 9
        $this->with = $object;
0 ignored issues
show
Documentation Bug introduced by
It seems like $object can also be of type Nip\Records\Traits\Relat...asRelationsRecordsTrait. However, the property $with is declared as type Nip\Records\AbstractModels\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 9
        return $this;
140
    }
141
142
    /**
143
     * @throws Exception
144
     */
145 4
    public function initWith()
146
    {
147 4
        $className = $this->getWithClass();
148 4
        $this->setWithClass($className);
149 4
    }
150
151
    /** @noinspection PhpDocMissingThrowsInspection
152
     * @return string
153
     */
154 3
    public function getWithClass()
155
    {
156
        /** @noinspection PhpUnhandledExceptionInspection */
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
    /** @noinspection PhpDocMissingThrowsInspection
181
     * @param string $name
182
     */
183 4
    public function setWithClass($name)
184
    {
185
        try {
186 4
            $manager = $this->getModelManagerInstance($name);
187 4
            $this->setWith($manager);
188
        } catch (InvalidModelException $exception) {
189
            /** @noinspection PhpUnhandledExceptionInspection */
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 \Nip\Records\AbstractModels\RecordManager
200
     */
201 4
    public function getModelManagerInstance($name)
202
    {
203 4
        return ModelLocator::get($name);
204
    }
205
206
207
    /**
208
     * @param AbstractQuery $query
209
     */
210
    abstract public function populateQuerySpecific(AbstractQuery $query);
211
212
    /**
213
     * @return \Nip\Database\Query\Delete
214
     * @throws Exception
215
     */
216
    public function getDeleteQuery()
217
    {
218
        $query = $this->getWith()->newDeleteQuery();
219
        $this->populateQuerySpecific($query);
220
221
        return $query;
222
    }
223
224
    /**
225
     * @return Connection
226
     */
227 1
    public function getDB()
228
    {
229 1
        return $this->getManager()->getDB();
230
    }
231
232
    /**
233
     * @param $key
234
     * @return mixed
235
     */
236 2
    public function getParam($key)
237
    {
238 2
        return $this->hasParam($key) ? $this->params[$key] : null;
239
    }
240
241
    /**
242
     * @param $key
243
     * @return boolean
244
     */
245 2
    public function hasParam($key)
246
    {
247 2
        return isset($this->params[$key]);
248
    }
249
250
    /**
251
     * @param $params
252
     */
253 3
    public function addParams($params)
254
    {
255 3
        $this->checkParamClass($params);
256 3
        $this->checkParamWith($params);
257 3
        $this->checkParamTable($params);
258 3
        $this->checkParamFk($params);
259 3
        $this->checkParamPrimaryKey($params);
260 3
        $this->setParams($params);
261 3
    }
262
263
    /**
264
     * @param $params
265
     */
266 3
    public function checkParamClass($params)
267
    {
268 3
        if (isset($params['class'])) {
269
            /** @noinspection PhpUnhandledExceptionInspection */
270
            $this->setWithClass($params['class']);
271
            unset($params['class']);
272
        }
273 3
    }
274
275
    /**
276
     * @param $params
277
     */
278 3
    public function checkParamWith($params)
279
    {
280 3
        if (isset($params['with'])) {
281
            $this->setWith($params['with']);
282
            unset($params['with']);
283
        }
284 3
    }
285
286
    /**
287
     * @param $params
288
     */
289 3
    public function checkParamTable($params)
290
    {
291 3
        if (isset($params['table'])) {
292
            $this->setTable($params['table']);
293
            unset($params['table']);
294
        }
295 3
    }
296
297
    /**
298
     * @return array
299
     */
300 1
    public function getParams(): array
301
    {
302 1
        return $this->params;
303
    }
304
305
    /**
306
     * @param $params
307
     */
308 3
    public function setParams($params)
309
    {
310 3
        $this->params = $params;
311 3
    }
312
313
    /**
314
     * @return string
315
     */
316 2
    public function getTable()
317
    {
318 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...
319 2
            $this->initTable();
320
        }
321
322 2
        return $this->table;
323
    }
324
325
    /**
326
     * @param $name
327
     */
328 2
    public function setTable($name)
329
    {
330 2
        $this->table = $name;
331 2
    }
332
333 2
    protected function initTable()
334
    {
335 2
        $this->setTable($this->generateTable());
336 2
    }
337
338
    /**
339
     * @return string
340
     */
341
    protected function generateTable()
342
    {
343
        return $this->getWith()->getTable();
344
    }
345
346
    /**
347
     * Get the results of the relationship.
348
     * @return Record|RecordCollection
349
     */
350 3
    public function getResults()
351
    {
352 3
        if (!$this->isPopulated()) {
353 3
            $this->initResults();
354
        }
355
356 3
        return $this->results;
357
    }
358
359
    /**
360
     * @param $results
361
     * @return null
362
     */
363 3
    public function setResults($results)
364
    {
365 3
        $this->results = $results;
366 3
        $this->populated = true;
367
368 3
        return $this->results;
369
    }
370
371
    /**
372
     * @return bool
373
     */
374 1
    public function isPopulatable()
375
    {
376 1
        return !empty($this->getItemRelationPrimaryKey());
377
    }
378
379
    /**
380
     * @return bool
381
     */
382 3
    public function isPopulated()
383
    {
384 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...
385
    }
386
387
    abstract public function initResults();
388
389
    /**
390
     * @param RecordCollection $collection
391
     * @return RecordCollection
392
     * @throws Exception
393
     */
394
    public function getEagerResults($collection)
395
    {
396
        if ($collection->count() < 1) {
397
            return $this->getWith()->newCollection();
398
        }
399
        $query = $this->getEagerQuery($collection);
400
401
        return $this->getWith()->findByQuery($query);
402
    }
403
404
    /**
405
     * @param RecordCollection $collection
406
     * @return Query
407
     */
408 2
    public function getEagerQuery(RecordCollection $collection)
409
    {
410 2
        $fkList = $this->getEagerFkList($collection);
411 2
        $query = $this->populateEagerQueryFromFkList($this->newQuery(), $fkList);
412 2
        return $query;
413
    }
414
415
    /**
416
     * @param Query $query
417
     * @param array $fkList
418
     * @return Query
419
     */
420 1
    protected function populateEagerQueryFromFkList($query, $fkList)
421
    {
422 1
        $query->where($this->getWithPK() . ' IN ?', $fkList);
423
424 1
        return $query;
425
    }
426
427
    /**
428
     * @param RecordCollection $collection
429
     * @return array
430
     */
431 2
    public function getEagerFkList(RecordCollection $collection)
432
    {
433
        /** @var ArraysHelper $arrayHelper */
434 2
        $arrayHelper = HelperBroker::get('Arrays');
435 2
        $return = $arrayHelper->pluck($collection, $this->getFK());
436
437 2
        return array_unique($return);
438
    }
439
440
    /**
441
     * @return string
442
     */
443 1
    public function getWithPK()
444
    {
445 1
        return $this->getWith()->getPrimaryKey();
446
    }
447
448
    /**
449
     * @param RecordCollection $collection
450
     * @param RecordCollection $recordsLoaded
451
     *
452
     * @return RecordCollection
453
     */
454
    public function match(RecordCollection $collection, RecordCollection $recordsLoaded)
455
    {
456
        $dictionary = $this->buildDictionary($recordsLoaded);
457
458
        foreach ($collection as $record) {
459
            /** @var Record $record */
460
            $results = $this->getResultsFromCollectionDictionary($dictionary, $collection, $record);
461
            /** @noinspection PhpUnhandledExceptionInspection */
462
            $record->getRelation($this->getName())->setResults($results);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $record->getRelation($this->getName()) targeting Nip\Records\AbstractModels\Record::__call() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
The method getRelation() does not exist on Nip\Records\AbstractModels\Record. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

462
            $record->/** @scrutinizer ignore-call */ 
463
                     getRelation($this->getName())->setResults($results);
Loading history...
463
        }
464
465
        return $recordsLoaded;
466
    }
467
468
    /**
469
     * Build model dictionary keyed by the relation's foreign key.
470
     *
471
     * @param RecordCollection $collection
472
     * @return array
473
     */
474
    abstract protected function buildDictionary(RecordCollection $collection);
475
476
    /**
477
     * @param $dictionary
478
     * @param Collection $collection
479
     * @param Record $record
480
     * @return mixed
481
     */
482
    abstract public function getResultsFromCollectionDictionary($dictionary, $collection, $record);
483
484
    public function save()
485
    {
486
    }
487
488
    /**
489
     * @return string
490
     */
491
    public function getType()
492
    {
493
        return $this->type;
494
    }
495
496
    /** @noinspection PhpDocMissingThrowsInspection
497
     * @return string
498
     */
499 1
    protected function debugString()
500
    {
501
        return 'Relation'
502 1
            . ' Manager:[' . ($this->hasManager() ? $this->getManager()->getClassName() : '') . ']'
503 1
            . ' name:[' . $this->getName() . '] '
504 1
            . ' params:[' . serialize($this->getParams()) . ']';
505
    }
506
}
507