Completed
Push — master ( 0ba58c...dd341e )
by Gabriel
07:12
created

RecordManager::initUniqueFields()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 0
cts 9
cp 0
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 9
nc 5
nop 0
crap 30
1
<?php
2
3
namespace Nip\Records\AbstractModels;
4
5
use Nip\Collections\Registry;
6
use Nip\Database\Query\Insert as InsertQuery;
7
use Nip\HelperBroker;
8
use Nip\Records\Collections\Collection as RecordCollection;
9
use Nip\Records\Traits\ActiveRecord\ActiveRecordsTrait;
10
use Nip\Utility\Traits\NameWorksTrait;
11
12
/**
13
 * Class Table
14
 * @package Nip\Records\_Abstract
15
 *
16
 * @method \Nip_Helper_Url Url()
17
 */
18
abstract class RecordManager
19
{
20
    use NameWorksTrait;
21
    use ActiveRecordsTrait;
22
23
    /**
24
     * Collection class for current record manager
25
     *
26
     * @var string
27
     */
28
    protected $collectionClass = null;
29
30
    protected $helpers = [];
31
32
    /**
33
     * @var null|string
34
     */
35
    protected $urlPK = null;
36
37
    /**
38
     * Model class name
39
     * @var null|string
40
     */
41
    protected $model = null;
42
43
    /**
44
     * @var null|string
45
     */
46
    protected $controller = null;
47
48
    /**
49
     * @var null|string
50
     */
51
    protected $modelNamespacePath = null;
52
53
    protected $registry = null;
54
55
    /**
56
     * Overloads findByRecord, findByField, deleteByRecord, deleteByField, countByRecord, countByField
57
     *
58
     * @example findByCategory(Category $item)
59
     * @example deleteByProduct(Product $item)
60
     * @example findByIdUser(2)
61
     * @example deleteByTitle(array('Lorem ipsum', 'like'))
62
     * @example countByIdCategory(1)
63
     *
64
     * @param string $name
65
     * @param array $arguments
66
     *
67
     * @return mixed
68
     */
69
    public function __call($name, $arguments)
70
    {
71
        $return = $this->isCallDatabaseOperation($name, $arguments);
72
        if ($return !== null) {
73
            return $return;
74
        }
75
76
        /** @noinspection PhpAssignmentInConditionInspection */
77
        if ($return = $this->isCallUrl($name, $arguments)) {
78
            return $return;
79
        }
80
81
        if ($name === ucfirst($name)) {
82
            return $this->getHelper($name);
83
        }
84
85
        trigger_error("Call to undefined method $name", E_USER_ERROR);
86
87
        return $this;
88
    }
89
90
    /**
91
     * @param string $name
92
     * @param $arguments
93
     * @return bool
94
     */
95
    protected function isCallUrl($name, $arguments)
96
    {
97
        if (substr($name, 0, 3) == "get" && substr($name, -3) == "URL") {
98
            $action = substr($name, 3, -3);
99
            $params = isset($arguments[0]) ? $arguments[0] : [];
100
            $module = isset($arguments[1]) ? $arguments[1] : null;
101
102
            return $this->compileURL($action, $params, $module);
103
        }
104
105
        return false;
106
    }
107
108
    /**
109
     * @param string $action
110
     * @param array $params
111
     * @param null $module
112
     * @return string|null
113
     */
114
    public function compileURL($action, $params = [], $module = null)
115
    {
116
        $controller = $this->getController();
117
118
        if (substr($action, 0, 5) == 'Async') {
119
            $controller = 'async-' . $controller;
120
            $action = substr($action, 5);
121
        }
122
123
        if (substr($action, 0, 5) == 'Modal') {
124
            $controller = 'modal-' . $controller;
125
            $action = substr($action, 5);
126
        }
127
128
        $params['action'] = (!empty($action)) ? $action : 'index';
129
        $params['controller'] = $controller;
130
131
        $params['action'] = inflector()->unclassify($params['action']);
132
        $params['action'] = ($params['action'] == 'index') ? false : $params['action'];
133
134
        $params['controller'] = $controller ? $controller : $this->getController();
135
        $params['module'] = $module ? $module : request()->getModuleName();
136
137
        $routeName = $params['module'] . '.' . $params['controller'] . '.' . $params['action'];
138
        if ($this->Url()->getRouter()->hasRoute($routeName)) {
139
            unset($params['module'], $params['controller'], $params['action']);
140
        } else {
141
            $routeName = $params['module'] . '.default';
142
        }
143
144
        return $this->Url()->assemble($routeName, $params);
0 ignored issues
show
Documentation introduced by
$params is of type array<string,?,{"module":"?"}>, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
145
    }
146
147
    /**
148
     * @return string
149
     */
150 13
    public function getController()
151
    {
152 13
        if ($this->controller === null) {
153 13
            $this->initController();
154
        }
155
156 13
        return $this->controller;
157
    }
158
159
    /**
160
     * @param null|string $controller
161
     */
162 13
    public function setController($controller)
163
    {
164 13
        $this->controller = $controller;
165 13
    }
166
167 13
    protected function initController()
168
    {
169 13
        if ($this->isNamespaced()) {
170 8
            $controller = $this->generateControllerNamespaced();
171
        } else {
172 5
            $controller = $this->generateControllerGeneric();
173
        }
174 13
        $this->setController($controller);
175 13
    }
176
177
    /**
178
     * @return string
179
     */
180 8
    protected function generateControllerNamespaced()
181
    {
182 8
        $class = $this->getModelNamespacePath();
183 8
        $class = trim($class, '\\');
184
185 8
        return inflector()->unclassify($class);
186
    }
187
188
    /**
189
     * @return string
190
     */
191 8
    public function getModelNamespacePath()
192
    {
193 8
        if ($this->modelNamespacePath == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $this->modelNamespacePath of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
194 8
            $this->initModelNamespacePath();
195
        }
196
197 8
        return $this->modelNamespacePath;
198
    }
199
200 8
    public function initModelNamespacePath()
201
    {
202 8
        if ($this->isNamespaced()) {
203 8
            $path = $this->generateModelNamespacePathFromClassName() . '\\';
204
        } else {
205
            $controller = $this->generateControllerGeneric();
206
            $path = inflector()->classify($controller) . '\\';
207
        }
208 8
        $this->modelNamespacePath = $path;
209 8
    }
210
211
    /**
212
     * @return string
213
     */
214 8
    protected function generateModelNamespacePathFromClassName()
215
    {
216 8
        $className = $this->getClassName();
217 8
        $rootNamespace = $this->getRootNamespace();
218 8
        $path = str_replace($rootNamespace, '', $className);
219
220 8
        $nsParts = explode('\\', $path);
221 8
        array_pop($nsParts);
222
223 8
        return implode($nsParts, '\\');
224
    }
225
226
    /**
227
     * @return string
228
     */
229 8
    public function getRootNamespace()
230
    {
231 8
        if (function_exists('app')) {
232
            return app('app')->getRootNamespace() . 'Models\\';
233
        }
234 8
        return 'App\\Models\\';
235
    }
236
237
    /**
238
     * @return string
239
     */
240 5
    protected function generateControllerGeneric()
241
    {
242 5
        $class = $this->getClassName();
243
244 5
        return inflector()->unclassify($class);
245
    }
246
247
    /**
248
     * @param string $name
249
     * @return \Nip\Helpers\AbstractHelper
250
     */
251
    public function getHelper($name)
252
    {
253
        return HelperBroker::get($name);
254
    }
255
256
    /**
257
     * @return string
258
     */
259
    public function getModelNamespace()
260
    {
261
        return $this->getRootNamespace() . $this->getModelNamespacePath();
262
    }
263
264
    /**
265
     * When searching by primary key, look for items in current registry before
266
     * fetching them from the database
267
     *
268
     * @param array $pk_list
269
     * @return RecordCollection
270
     */
271
    public function findByPrimary($pk_list = [])
272
    {
273
        $pk = $this->getPrimaryKey();
274
        $return = $this->newCollection();
275
276
        if ($pk_list) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pk_list of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
277
            $pk_list = array_unique($pk_list);
278
            foreach ($pk_list as $key => $value) {
279
                $item = $this->getRegistry()->get($value);
280
                if ($item) {
281
                    unset($pk_list[$key]);
282
                    $return[$item->{$pk}] = $item;
283
                }
284
            }
285
            if ($pk_list) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pk_list of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
286
                $query = $this->paramsToQuery();
287
                $query->where("$pk IN ?", $pk_list);
288
                $items = $this->findByQuery($query);
289
290
                if (count($items)) {
291
                    foreach ($items as $item) {
292
                        $this->getRegistry()->set($item->{$pk}, $item);
293
                        $return[$item->{$pk}] = $item;
294
                    }
295
                }
296
            }
297
        }
298
299
        return $return;
300
    }
301
302
    /**
303
     * @return RecordCollection
304
     */
305 1
    public function newCollection()
306
    {
307 1
        $class = $this->getCollectionClass();
308
        /** @var RecordCollection $collection */
309 1
        $collection = new $class();
310 1
        $collection->setManager($this);
311
312 1
        return $collection;
313
    }
314
315
    /**
316
     * @return string
317
     */
318 2
    public function getCollectionClass()
319
    {
320 2
        if ($this->collectionClass === null) {
321 2
            $this->initCollectionClass();
322
        }
323
324 2
        return $this->collectionClass;
325
    }
326
327
    /**
328
     * @param string $collectionClass
329
     */
330 2
    public function setCollectionClass($collectionClass)
331
    {
332 2
        $this->collectionClass = $collectionClass;
333 2
    }
334
335 2
    protected function initCollectionClass()
336
    {
337 2
        $this->setCollectionClass($this->generateCollectionClass());
338 2
    }
339
340
    /**
341
     * @return string
342
     */
343 2
    protected function generateCollectionClass()
344
    {
345 2
        return RecordCollection::class;
346
    }
347
348
    /**
349
     * @return \Nip\Collections\Registry
350
     */
351
    public function getRegistry()
352
    {
353
        if (!$this->registry) {
354
            $this->registry = new Registry();
355
        }
356
357
        return $this->registry;
358
    }
359
360
    /**
361
     * Factory
362
     *
363
     * @return Record
364
     * @param array $data [optional]
365
     */
366
    public function getNew($data = [])
367
    {
368
        $pk = $this->getPrimaryKey();
369
        if (is_string($pk) && isset($data[$pk]) && $this->getRegistry()->has($data[$pk])) {
370
            $return = $this->getRegistry()->get($data[$pk]);
371
            $return->writeData($data);
372
            $return->writeDBData($data);
373
374
            return $return;
375
        }
376
377
        $record = $this->getNewRecordFromDB($data);
378
379
        return $record;
380
    }
381
382
    /**
383
     * @param array $data
384
     * @return Record
385
     */
386
    public function getNewRecordFromDB($data = [])
387
    {
388
        $record = $this->getNewRecord($data);
389
        $record->writeDBData($data);
390
391
        return $record;
392
    }
393
394
    /**
395
     * @param array $data
396
     * @return Record
397
     */
398
    public function getNewRecord($data = [])
399
    {
400
        $model = $this->getModel();
401
        /** @var Record $record */
402
        $record = new $model();
403
        $record->setManager($this);
404
        $record->writeData($data);
405
406
        return $record;
407
    }
408
409
    /**
410
     * @return string
411
     */
412 1
    public function getModel()
413
    {
414 1
        if ($this->model == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $this->model of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
415
            $this->inflectModel();
416
        }
417
418 1
        return $this->model;
419
    }
420
421
    /**
422
     * @param null $model
423
     */
424 1
    public function setModel($model)
425
    {
426 1
        $this->model = $model;
427 1
    }
428
429
    protected function inflectModel()
430
    {
431
        $class = $this->getClassName();
432
        if ($this->model == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $this->model of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
433
            $this->model = $this->generateModelClass($class);
434
        }
435
    }
436
437
    /**
438
     * @param string $class
439
     * @return string
440
     */
441 1
    public function generateModelClass($class = null)
442
    {
443 1
        $class = $class ? $class : get_class($this);
444
445 1
        if (strpos($class, '\\')) {
446 1
            $nsParts = explode('\\', $class);
447 1
            $class = array_pop($nsParts);
448
449 1
            if ($class == 'Table') {
450 1
                $class = 'Row';
451
            } else {
452
                $class = ucfirst(inflector()->singularize($class));
453
            }
454
455 1
            return implode($nsParts, '\\') . '\\' . $class;
456
        }
457
458 1
        return ucfirst(inflector()->singularize($class));
459
    }
460
461
    /**
462
     * @return \Nip\Request
463
     */
464
    public function getRequest()
465
    {
466
        return request();
467
    }
468
469
    public function __wakeup()
470
    {
471
        $this->initDB();
472
    }
473
474
    /**
475
     * @param Record $item
476
     * @return bool|false|Record
477
     */
478
    public function exists(Record $item)
479
    {
480
        $params = [];
481
        $params['where'] = [];
482
483
        $fields = $this->getUniqueFields();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $fields is correct as $this->getUniqueFields() (which targets Nip\Records\AbstractMode...ager::getUniqueFields()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

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

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

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

Loading history...
484
485
        if (!$fields) {
486
            return false;
487
        }
488
489
        foreach ($fields as $field) {
490
            $params['where'][$field . '-UNQ'] = ["$field = ?", $item->{$field}];
491
        }
492
493
        $pk = $this->getPrimaryKey();
494
        if ($item->getPrimaryKey()) {
495
            $params['where'][] = ["$pk != ?", $item->getPrimaryKey()];
496
        }
497
498
        return $this->findOneByParams($params);
499
    }
500
501
    /**
502
     * @return null
503
     */
504
    public function getUniqueFields()
505
    {
506
        if ($this->uniqueFields === null) {
507
            $this->initUniqueFields();
508
        }
509
510
        return $this->uniqueFields;
511
    }
512
513
    /**
514
     * @return array|null
515
     */
516
    public function initUniqueFields()
517
    {
518
        $this->uniqueFields = [];
519
        $structure = $this->getTableStructure();
520
        foreach ($structure['indexes'] as $name => $index) {
521
            if ($index['unique']) {
522
                foreach ($index['fields'] as $field) {
523
                    if ($field != $this->getPrimaryKey()) {
524
                        $this->uniqueFields[] = $field;
525
                    }
526
                }
527
            }
528
        }
529
530
        return $this->uniqueFields;
531
    }
532
533
    /**
534
     * Finds one Record using params array
535
     *
536
     * @param array $params
537
     * @return Record|null
538
     */
539
    public function findOneByParams(array $params = [])
540
    {
541
        $params['limit'] = 1;
542
        $records = $this->findByParams($params);
543
        if (count($records) > 0) {
544
            return $records->rewind();
545
        }
546
547
        return null;
548
    }
549
550
    /**
551
     * Finds Records using params array
552
     *
553
     * @param array $params
554
     * @return RecordCollection
555
     */
556
    public function findByParams($params = [])
557
    {
558
        $query = $this->paramsToQuery($params);
559
560
        return $this->findByQuery($query, $params);
561
    }
562
563
    /**
564
     * @return RecordCollection
565
     */
566
    public function getAll()
567
    {
568
        if (!$this->getRegistry()->has("all")) {
569
            $this->getRegistry()->set("all", $this->findAll());
570
        }
571
572
        return $this->getRegistry()->get("all");
573
    }
574
575
    /**
576
     * @return RecordCollection
577
     */
578
    public function findAll()
579
    {
580
        return $this->findByParams();
581
    }
582
583
    /**
584
     * @param int $count
585
     * @return RecordCollection
586
     */
587
    public function findLast($count = 9)
588
    {
589
        return $this->findByParams([
590
            'limit' => $count,
591
        ]);
592
    }
593
594
    /**
595
     * Inserts a Record into the database
596
     * @param Record $model
597
     * @param array|bool $onDuplicate
598
     * @return integer
599
     */
600
    public function insert($model, $onDuplicate = false)
601
    {
602
        $query = $this->insertQuery($model, $onDuplicate);
603
        $query->execute();
604
605
        return $this->getDB()->lastInsertID();
606
    }
607
608
    /**
609
     * @param Record $model
610
     * @param $onDuplicate
611
     * @return InsertQuery
612
     */
613
    public function insertQuery($model, $onDuplicate)
614
    {
615
        $inserts = $this->getQueryModelData($model);
616
617
        $query = $this->newInsertQuery();
618
        $query->data($inserts);
0 ignored issues
show
Unused Code introduced by
The call to Insert::data() has too many arguments starting with $inserts.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
619
620
        if ($onDuplicate !== false) {
621
            $query->onDuplicate($onDuplicate);
622
        }
623
624
        return $query;
625
    }
626
627
    /**
628
     * @param Record $model
629
     * @return array
630
     */
631
    public function getQueryModelData($model)
632
    {
633
        $data = [];
634
635
        $fields = $this->getFields();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $fields is correct as $this->getFields() (which targets Nip\Records\Traits\Activ...cordsTrait::getFields()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

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

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

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

Loading history...
636
        foreach ($fields as $field) {
0 ignored issues
show
Bug introduced by
The expression $fields of type null is not traversable.
Loading history...
637
            if (isset($model->{$field})) {
638
                $data[$field] = $model->{$field};
639
            }
640
        }
641
642
        return $data;
643
    }
644
645
    /**
646
     * The name of the field used as a foreign key in other tables
647
     * @return string
648
     */
649 8
    public function getPrimaryFK()
650
    {
651 8
        if ($this->foreignKey == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $this->foreignKey of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
652 6
            $this->initPrimaryFK();
653
        }
654
655 8
        return $this->foreignKey;
656
    }
657
658 6
    public function initPrimaryFK()
659
    {
660 6
        $this->setForeignKey($this->generatePrimaryFK());
661 6
    }
662
663
    /**
664
     * @param string $foreignKey
665
     */
666 6
    public function setForeignKey($foreignKey)
667
    {
668 6
        $this->foreignKey = $foreignKey;
669 6
    }
670
671
    /**
672
     * @return string
673
     */
674 5
    public function generatePrimaryFK()
675
    {
676 5
        $singularize = inflector()->singularize($this->getController());
677
678 5
        return $this->getPrimaryKey() . "_" . inflector()->underscore($singularize);
679
    }
680
681
    /**
682
     * @param $fk
683
     */
684 3
    public function setPrimaryFK($fk)
685
    {
686 3
        $this->foreignKey = $fk;
687 3
    }
688
689
    /**
690
     * The name of the field used as a foreign key in other tables
691
     * @return string
692
     */
693
    public function getUrlPK()
694
    {
695
        if ($this->urlPK == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $this->urlPK of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
696
            $this->urlPK = $this->getPrimaryKey();
697
        }
698
699
        return $this->urlPK;
700
    }
701
702
    /**
703
     * @param $name
704
     * @return bool
705
     */
706
    public function hasField($name)
707
    {
708
        $fields = $this->getFields();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $fields is correct as $this->getFields() (which targets Nip\Records\Traits\Activ...cordsTrait::getFields()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

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

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

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

Loading history...
709
        if (is_array($fields) && in_array($name, $fields)) {
710
            return true;
711
        }
712
713
        return false;
714
    }
715
716
    /**
717
     * @return array
718
     */
719
    public function getFullTextFields()
720
    {
721
        $return = [];
722
        $structure = $this->getTableStructure();
723
        foreach ($structure['indexes'] as $name => $index) {
724
            if ($index['fulltext']) {
725
                $return[$name] = $index['fields'];
726
            }
727
        }
728
729
        return $return;
730
    }
731
732
    /**
733
     * Sets model and database table from the class name
734
     */
735
    protected function inflect()
736
    {
737
        $this->initController();
738
    }
739
}
740