Completed
Push — master ( 4ac004...470d43 )
by Gabriel
06:41
created

Relation::checkParamFk()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.2559

Importance

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