Completed
Push — master ( c5047e...3a177f )
by Gabriel
03:52
created

Relation::getQuery()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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