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.
Completed
Push — master ( 371354...5fbd66 )
by Pavel
03:40
created

MaterializedPathBehavior::beforeDelete()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7.0422

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 19
cts 21
cp 0.9048
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 17
nc 5
nop 1
crap 7.0422
1
<?php
2
/**
3
 * @link https://github.com/paulzi/yii2-materialized-path
4
 * @copyright Copyright (c) 2015 PaulZi <[email protected]>
5
 * @license MIT (https://github.com/paulzi/yii2-materialized-path/blob/master/LICENSE)
6
 */
7
8
namespace paulzi\materializedPath;
9
10
use paulzi\sortable\SortableBehavior;
11
use Yii;
12
use yii\base\Behavior;
13
use yii\base\Exception;
14
use yii\base\NotSupportedException;
15
use yii\db\ActiveRecord;
16
use yii\db\Expression;
17
18
/**
19
 * Materialized Path Behavior for Yii2
20
 * @author PaulZi <[email protected]>
21
 *
22
 * @property ActiveRecord $owner
23
 */
24
class MaterializedPathBehavior extends Behavior
25
{
26
    const OPERATION_MAKE_ROOT       = 1;
27
    const OPERATION_PREPEND_TO      = 2;
28
    const OPERATION_APPEND_TO       = 3;
29
    const OPERATION_INSERT_BEFORE   = 4;
30
    const OPERATION_INSERT_AFTER    = 5;
31
    const OPERATION_DELETE_ALL      = 6;
32
33
34
    /**
35
     * @var string
36
     */
37
    public $pathAttribute = 'path';
38
39
    /**
40
     * @var string
41
     */
42
    public $depthAttribute = 'depth';
43
44
    /**
45
     * @var string
46
     */
47
    public $itemAttribute;
48
49
    /**
50
     * @var string|null
51
     */
52
    public $treeAttribute;
53
54
    /**
55
     * @var array|false SortableBehavior config
56
     */
57
    public $sortable = [];
58
59
    /**
60
     * @var string
61
     */
62
    public $delimiter = '/';
63
64
    /**
65
     * @var int Value of $depthAttribute for root node.
66
     */
67
    public $rootDepthValue = 0;
68
    
69
    /**
70
     * @var int|null
71
     */
72
    protected $operation;
73
74
    /**
75
     * @var ActiveRecord|self|null
76
     */
77
    protected $node;
78
79
    /**
80
     * @var SortableBehavior
81
     */
82
    protected $behavior;
83
84
    /**
85
     * @var bool
86
     */
87
    protected $primaryKeyMode = false;
88
89
90
    /**
91
     * @inheritdoc
92
     */
93 201
    public function events()
94
    {
95
        return [
96 201
            ActiveRecord::EVENT_BEFORE_INSERT   => 'beforeSave',
97 201
            ActiveRecord::EVENT_AFTER_INSERT    => 'afterInsert',
98 201
            ActiveRecord::EVENT_BEFORE_UPDATE   => 'beforeSave',
99 201
            ActiveRecord::EVENT_AFTER_UPDATE    => 'afterUpdate',
100 201
            ActiveRecord::EVENT_BEFORE_DELETE   => 'beforeDelete',
101 201
            ActiveRecord::EVENT_AFTER_DELETE    => 'afterDelete',
102 201
        ];
103
    }
104
105
    /**
106
     * @param ActiveRecord $owner
107
     * @throws Exception
108
     */
109 201
    public function attach($owner)
110
    {
111 201
        parent::attach($owner);
112 201
        if ($this->itemAttribute === null) {
113 201
            $primaryKey = $owner->primaryKey();
114 201
            if (!isset($primaryKey[0])) {
115
                throw new Exception('"' . $owner->className() . '" must have a primary key.');
116
            }
117 201
            $this->itemAttribute = $primaryKey[0];
118 201
            $this->primaryKeyMode = true;
119 201
        }
120 201
        if ($this->sortable !== false) {
121 201
            $this->behavior = Yii::createObject(array_merge(
122
                [
123 201
                    'class' => SortableBehavior::className(),
124
                    'query' => function () {
125 87
                        return $this->getSortableQuery();
126 201
                    },
127 201
                ],
128 201
                $this->sortable
129 201
            ));
130 201
            $owner->attachBehavior('materialized-path-sortable', $this->behavior);
131 201
        }
132 201
    }
133
134
    /**
135
     * @param int|null $depth
136
     * @return \yii\db\ActiveQuery
137
     */
138 9
    public function getParents($depth = null)
139
    {
140 9
        $path  = $this->getParentPath();
141 9
        if ($path !== null) {
142 9
            $paths = explode($this->delimiter, $path);
143 9
            if (!$this->primaryKeyMode) {
144 9
                $path  = null;
145 9
                $paths = array_map(
146 9
                    function ($value) use (&$path) {
147 9
                        return $path = ($path !== null ? $path . $this->delimiter : '') . $value;
148 9
                    },
149
                    $paths
150 9
                );
151 9
            }
152 9
            if ($depth !== null) {
153 9
                $paths = array_slice($paths, -$depth);
154 9
            }
155 9
        } else {
156 3
            $paths = [];
157
        }
158
159 9
        $tableName = $this->owner->tableName();
160 9
        $condition = ['and'];
161 9
        if ($this->primaryKeyMode) {
162 6
            $condition[] = ["{$tableName}.[[{$this->itemAttribute}]]" => $paths];
163 6
        } else {
164 9
            $condition[] = ["{$tableName}.[[{$this->pathAttribute}]]" => $paths];
165
        }
166
167 9
        $query = $this->owner->find()
168 9
            ->andWhere($condition)
169 9
            ->andWhere($this->treeCondition())
170 9
            ->addOrderBy(["{$tableName}.[[{$this->pathAttribute}]]" => SORT_ASC]);
171 9
        $query->multiple = true;
172
173 9
        return $query;
174
    }
175
176
    /**
177
     * @return \yii\db\ActiveQuery
178
     */
179 6
    public function getParent()
180
    {
181 6
        $query = $this->getParents(1)->limit(1);
182 6
        $query->multiple = false;
183 6
        return $query;
184
    }
185
186
    /**
187
     * @return \yii\db\ActiveQuery
188
     */
189 3
    public function getRoot()
190
    {
191 3
        $path = explode($this->delimiter, $this->owner->getAttribute($this->pathAttribute));
192 3
        $path = array_shift($path);
193 3
        $tableName = $this->owner->tableName();
194 3
        $query = $this->owner->find()
195 3
            ->andWhere(["{$tableName}.[[{$this->pathAttribute}]]" => $path])
196 3
            ->andWhere($this->treeCondition())
197 3
            ->limit(1);
198 3
        $query->multiple = false;
199 3
        return $query;
200
    }
201
202
    /**
203
     * @param int|null $depth
204
     * @param bool $andSelf
205
     * @return \yii\db\ActiveQuery
206
     */
207 27
    public function getDescendants($depth = null, $andSelf = false)
208
    {
209 27
        $tableName = $this->owner->tableName();
210 27
        $path = $this->owner->getAttribute($this->pathAttribute);
211 27
        $query = $this->owner->find()
212 27
            ->andWhere(['like', "{$tableName}.[[{$this->pathAttribute}]]", $this->getLike($path), false]);
213
214 27
        if ($andSelf) {
215 9
            $query->orWhere(["{$tableName}.[[{$this->pathAttribute}]]" => $path]);
216 9
        }
217
218 27
        if ($depth !== null) {
219 21
            $query->andWhere(['<=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) + $depth]);
220 21
        }
221
222 27
        $orderBy = [];
223 27
        $orderBy["{$tableName}.[[{$this->depthAttribute}]]"] = SORT_ASC;
224 27 View Code Duplication
        if ($this->sortable !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
225 27
            $orderBy["{$tableName}.[[{$this->behavior->sortAttribute}]]"] = SORT_ASC;
226 27
        }
227 27
        $orderBy["{$tableName}.[[{$this->itemAttribute}]]"]  = SORT_ASC;
228
229
        $query
230 27
            ->andWhere($this->treeCondition())
231 27
            ->addOrderBy($orderBy);
232 27
        $query->multiple = true;
233
234 27
        return $query;
235
    }
236
237
    /**
238
     * @return \yii\db\ActiveQuery
239
     */
240 12
    public function getChildren()
241
    {
242 12
        return $this->getDescendants(1);
243
    }
244
245
    /**
246
     * @param int|null $depth
247
     * @return \yii\db\ActiveQuery
248
     */
249 3
    public function getLeaves($depth = null)
250
    {
251 3
        $tableName = $this->owner->tableName();
252
        $condition = [
253 3
            'and',
254 3
            ['like', "leaves.[[{$this->pathAttribute}]]",  new Expression($this->concatExpression(["{$tableName}.[[{$this->pathAttribute}]]", ':delimiter']), [':delimiter' => $this->delimiter . '%'])],
255 3
        ];
256
257 3
        if ($this->treeAttribute !== null) {
258 3
            $condition[] = ["leaves.[[{$this->treeAttribute}]]" => new Expression("{$tableName}.[[{$this->treeAttribute}]]")];
259 3
        }
260
261 3
        $query = $this->getDescendants($depth)
262 3
            ->leftJoin("{$tableName} leaves", $condition)
263 3
            ->andWhere(["leaves.[[{$this->pathAttribute}]]" => null]);
264 3
        $query->multiple = true;
265 3
        return $query;
266
    }
267
268
    /**
269
     * @return \yii\db\ActiveQuery
270
     * @throws NotSupportedException
271
     */
272 3 View Code Duplication
    public function getPrev()
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...
273
    {
274 3
        if ($this->sortable === false) {
275
            throw new NotSupportedException('prev() not allow if not set sortable');
276
        }
277 3
        $tableName = $this->owner->tableName();
278 3
        $query = $this->owner->find()
279 3
            ->andWhere(['like', "{$tableName}.[[{$this->pathAttribute}]]", $this->getLike($this->getParentPath()), false])
0 ignored issues
show
Bug introduced by
It seems like $this->getParentPath() targeting paulzi\materializedPath\...havior::getParentPath() can also be of type array or null; however, paulzi\materializedPath\...PathBehavior::getLike() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
280 3
            ->andWhere(["{$tableName}.[[{$this->depthAttribute}]]" => $this->owner->getAttribute($this->depthAttribute)])
281 3
            ->andWhere(['<', "{$tableName}.[[{$this->behavior->sortAttribute}]]", $this->owner->getSortablePosition()])
282 3
            ->andWhere($this->treeCondition())
283 3
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_DESC])
284 3
            ->limit(1);
285 3
        $query->multiple = false;
286 3
        return $query;
287
    }
288
289
    /**
290
     * @return \yii\db\ActiveQuery
291
     * @throws NotSupportedException
292
     */
293 6 View Code Duplication
    public function getNext()
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...
294
    {
295 6
        if ($this->sortable === false) {
296
            throw new NotSupportedException('prev() not allow if not set sortable');
297
        }
298 6
        $tableName = $this->owner->tableName();
299 6
        $query = $this->owner->find()
300 6
            ->andWhere(['like', "{$tableName}.[[{$this->pathAttribute}]]", $this->getLike($this->getParentPath()), false])
0 ignored issues
show
Bug introduced by
It seems like $this->getParentPath() targeting paulzi\materializedPath\...havior::getParentPath() can also be of type array or null; however, paulzi\materializedPath\...PathBehavior::getLike() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
301 6
            ->andWhere(["{$tableName}.[[{$this->depthAttribute}]]" => $this->owner->getAttribute($this->depthAttribute)])
302 6
            ->andWhere(['>', "{$tableName}.[[{$this->behavior->sortAttribute}]]", $this->owner->getSortablePosition()])
303 6
            ->andWhere($this->treeCondition())
304 6
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_ASC])
305 6
            ->limit(1);
306 6
        $query->multiple = false;
307 6
        return $query;
308
    }
309
310
    /**
311
     * Returns all sibilings of node.
312
     * 
313
     * @param bool $andSelf = false Include self node into result.
314
     * @return \yii\db\ActiveQuery
315
     */
316
    public function getSiblings($andSelf = false)
317
    {
318
        $tableName = $this->owner->tableName();
319
        $path = $this->getParentPath();
320
        $like = strtr($path . $this->delimiter, ['%' => '\%', '_' => '\_', '\\' => '\\\\']);
321
322
        $query = $this->owner->find()
323
            ->andWhere(['like', "{$tableName}.[[{$this->pathAttribute}]]", $like . '%', false])
324
            ->andWhere(['<=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->{$this->depthAttribute}]);
325
326
        if (!$andSelf) {
327
            $query->andWhere(["!=", "{$tableName}.[[{$this->itemAttribute}]]", $this->owner->{$this->itemAttribute}]);
328
        }
329
330
        $orderBy = [];
331
        $orderBy["{$tableName}.[[{$this->depthAttribute}]]"] = SORT_ASC;
332 View Code Duplication
        if ($this->sortable !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
333
            $orderBy["{$tableName}.[[{$this->behavior->sortAttribute}]]"] = SORT_ASC;
334
        }
335
        $orderBy["{$tableName}.[[{$this->itemAttribute}]]"] = SORT_ASC;
336
337
        $query
338
            ->andWhere($this->treeCondition())
339
            ->addOrderBy($orderBy);
340
        $query->multiple = true;
341
        return $query;
342
    }
343
344
    /**
345
     * @param bool $asArray = false
346
     * @return null|string|array
347
     */
348 66
    public function getParentPath($asArray = false)
349
    {
350 66
        return static::getParentPathInternal($this->owner->getAttribute($this->pathAttribute), $this->delimiter, $asArray);
351
    }
352
353
    /**
354
     * Populate children relations for self and all descendants
355
     *
356
     * @param int $depth = null
357
     * @return static
358
     */
359 3
    public function populateTree($depth = null)
360
    {
361
        /** @var ActiveRecord[]|static[] $nodes */
362 3
        if ($depth === null) {
363 3
            $nodes = $this->owner->descendants;
364 3
        } else {
365 3
            $nodes = $this->getDescendants($depth)->all();
366
        }
367
368 3
        $relates = [];
369 3
        foreach ($nodes as $node) {
370 3
            $path = $node->getParentPath(true);
371 3
            $key = array_pop($path);
372 3
            if (!isset($relates[$key])) {
373 3
                $relates[$key] = [];
374 3
            }
375 3
            $relates[$key][] = $node;
376 3
        }
377
378 3
        $ownerDepth = $this->owner->getAttribute($this->depthAttribute);
379 3
        $nodes[] = $this->owner;
380 3
        foreach ($nodes as $node) {
381 3
            $key = $node->getAttribute($this->itemAttribute);
382 3
            if (isset($relates[$key])) {
383 3
                $node->populateRelation('children', $relates[$key]);
384 3
            } elseif ($depth === null || $ownerDepth + $depth > $node->getAttribute($this->depthAttribute)) {
385 3
                $node->populateRelation('children', []);
386 3
            }
387 3
        }
388
389 3
        return $this->owner;
390
    }
391
392
    /**
393
     * @return bool
394
     */
395 72
    public function isRoot()
396
    {
397 72
        return count(explode($this->delimiter, $this->owner->getAttribute($this->pathAttribute))) === 1;
398
    }
399
400
    /**
401
     * @param ActiveRecord $node
402
     * @return bool
403
     */
404 96
    public function isChildOf($node)
405
    {
406 96
        if ($node->getIsNewRecord()) {
407 30
            return false;
408
        }
409 66
        $nodePath  = $node->getAttribute($this->pathAttribute) . $this->delimiter;
410 66
        $result = substr($this->owner->getAttribute($this->pathAttribute), 0, strlen($nodePath)) === $nodePath;
411
412 66
        if ($result && $this->treeAttribute !== null) {
413 9
            $result = $this->owner->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute);
414 9
        }
415
416 66
        return $result;
417
    }
418
419
    /**
420
     * @return bool
421
     */
422 3
    public function isLeaf()
423
    {
424 3
        return count($this->owner->children) === 0;
425
    }
426
427
    /**
428
     * @return ActiveRecord
429
     */
430 6
    public function makeRoot()
431
    {
432 6
        $this->operation = self::OPERATION_MAKE_ROOT;
433 6
        return $this->owner;
434
    }
435
436
    /**
437
     * @param ActiveRecord $node
438
     * @return ActiveRecord
439
     */
440 33
    public function prependTo($node)
441
    {
442 33
        $this->operation = self::OPERATION_PREPEND_TO;
443 33
        $this->node = $node;
444 33
        return $this->owner;
445
    }
446
447
    /**
448
     * @param ActiveRecord $node
449
     * @return ActiveRecord
450
     */
451 33
    public function appendTo($node)
452
    {
453 33
        $this->operation = self::OPERATION_APPEND_TO;
454 33
        $this->node = $node;
455 33
        return $this->owner;
456
    }
457
458
    /**
459
     * @param ActiveRecord $node
460
     * @return ActiveRecord
461
     */
462 30
    public function insertBefore($node)
463
    {
464 30
        $this->operation = self::OPERATION_INSERT_BEFORE;
465 30
        $this->node = $node;
466 30
        return $this->owner;
467
    }
468
469
    /**
470
     * @param ActiveRecord $node
471
     * @return ActiveRecord
472
     */
473 30
    public function insertAfter($node)
474
    {
475 30
        $this->operation = self::OPERATION_INSERT_AFTER;
476 30
        $this->node = $node;
477 30
        return $this->owner;
478
    }
479
480
    /**
481
     * Need for paulzi/auto-tree
482
     */
483
    public function preDeleteWithChildren()
484
    {
485
        $this->operation = self::OPERATION_DELETE_ALL;
486
    }
487
488
    /**
489
     * @return bool|int
490
     * @throws \Exception
491
     * @throws \yii\db\Exception
492
     */
493 9
    public function deleteWithChildren()
494
    {
495 9
        $this->operation = self::OPERATION_DELETE_ALL;
496 9
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
497
            $transaction = $this->owner->getDb()->beginTransaction();
498
            try {
499
                $result = $this->deleteWithChildrenInternal();
500
                if ($result === false) {
501
                    $transaction->rollBack();
502
                } else {
503
                    $transaction->commit();
504
                }
505
                return $result;
506
            } catch (\Exception $e) {
507
                $transaction->rollBack();
508
                throw $e;
509
            }
510
        } else {
511 9
            $result = $this->deleteWithChildrenInternal();
512
        }
513 6
        return $result;
514
    }
515
516
    /**
517
     * @param bool $middle
518
     * @return int
519
     */
520 3
    public function reorderChildren($middle = true)
521
    {
522
        /** @var ActiveRecord|SortableBehavior $item */
523 3
        $item = count($this->owner->children) > 0 ? $this->owner->children[0] : null;
524 3
        if ($item) {
525 3
            return $item->reorder($middle);
0 ignored issues
show
Bug introduced by
The method reorder does only exist in paulzi\sortable\SortableBehavior, but not in yii\db\ActiveRecord.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
526
        } else {
527
            return 0;
528
        }
529
    }
530
531
    /**
532
     * @throws Exception
533
     * @throws NotSupportedException
534
     */
535 138
    public function beforeSave()
536
    {
537 138
        if ($this->node !== null && !$this->node->getIsNewRecord()) {
0 ignored issues
show
Bug introduced by
The method getIsNewRecord does only exist in yii\db\ActiveRecord, but not in paulzi\materializedPath\MaterializedPathBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
538 108
            $this->node->refresh();
0 ignored issues
show
Bug introduced by
The method refresh does only exist in yii\db\ActiveRecord, but not in paulzi\materializedPath\MaterializedPathBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
539 108
        }
540
541 138
        switch ($this->operation) {
542 138
            case self::OPERATION_MAKE_ROOT:
543 6
                $this->makeRootInternal();
544
545 6
                break;
546 132
            case self::OPERATION_PREPEND_TO:
547 33
                $this->insertIntoInternal(false);
548
549 21
                break;
550 99
            case self::OPERATION_APPEND_TO:
551 33
                $this->insertIntoInternal(true);
552
553 21
                break;
554 66
            case self::OPERATION_INSERT_BEFORE:
555 30
                $this->insertNearInternal(false);
556
557 21
                break;
558
559 36
            case self::OPERATION_INSERT_AFTER:
560 30
                $this->insertNearInternal(true);
561
562 18
                break;
563
564 6
            default:
565 6
                if ($this->owner->getIsNewRecord()) {
566 3
                    throw new NotSupportedException('Method "' . $this->owner->className() . '::insert" is not supported for inserting new nodes.');
567
                }
568
569 3
                $item = $this->owner->getAttribute($this->itemAttribute);
570 3
                $path = $this->getParentPath();
571 3
                $this->owner->setAttribute($this->pathAttribute, ($path !== null ? $path . $this->delimiter : null) . $item);
572 93
        }
573 90
    }
574
575
    /**
576
     * @throws Exception
577
     */
578 33
    public function afterInsert()
579
    {
580 33
        if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== null && $this->owner->getAttribute($this->treeAttribute) === null) {
581 3
            $id = $this->getPrimaryKeyValue();
582 3
            $this->owner->setAttribute($this->treeAttribute, $id);
583
584 3
            $primaryKey = $this->owner->primaryKey();
585 3 View Code Duplication
            if (!isset($primaryKey[0])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
586
                throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
587
            }
588
589 3
            $this->owner->updateAll([$this->treeAttribute => $id], [$primaryKey[0] => $id]);
590 3
        }
591 33
        if ($this->owner->getAttribute($this->pathAttribute) === null) {
592 33
            $primaryKey = $this->owner->primaryKey();
593 33 View Code Duplication
            if (!isset($primaryKey[0])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
594
                throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
595
            }
596 33
            $id = $this->getPrimaryKeyValue();
597 33
            if ($this->operation === self::OPERATION_MAKE_ROOT) {
598 3
                $path = $id;
599 3
            } else {
600 30
                if ($this->operation === self::OPERATION_INSERT_BEFORE || $this->operation === self::OPERATION_INSERT_AFTER) {
601 18
                    $path = $this->node->getParentPath();
0 ignored issues
show
Bug introduced by
The method getParentPath does only exist in paulzi\materializedPath\MaterializedPathBehavior, but not in yii\db\ActiveRecord.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
602 18
                } else {
603 12
                    $path = $this->node->getAttribute($this->pathAttribute);
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\materializedPath\MaterializedPathBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
604
                }
605 30
                $path = $path . $this->delimiter . $id;
606
            }
607 33
            $this->owner->setAttribute($this->pathAttribute, $path);
608 33
            $this->owner->updateAll([$this->pathAttribute => $path], [$primaryKey[0] => $id]);
609 33
        }
610 33
        $this->operation = null;
611 33
        $this->node      = null;
612 33
    }
613
614
    /**
615
     * @param \yii\db\AfterSaveEvent $event
616
     */
617 57
    public function afterUpdate($event)
618
    {
619 57
        $this->moveNode($event->changedAttributes);
620 57
        $this->operation = null;
621 57
        $this->node      = null;
622 57
    }
623
624
    /**
625
     * @param \yii\base\ModelEvent $event
626
     * @throws Exception
627
     */
628 105
    public function beforeDelete($event)
629 105
    {
630 18
        if ($this->owner->getIsNewRecord()) {
631 6
            throw new Exception('Can not delete a node when it is new record.');
632
        }
633 12
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
634 3
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
635
        }
636 9
        $this->owner->refresh();
637 9
        if ($this->operation !== static::OPERATION_DELETE_ALL && !$this->primaryKeyMode) {
638
            /** @var self $parent */
639 3
            $parent =$this->getParent()->one();
640 3
            $slugs1 = $parent->getChildren()
641 3
                ->andWhere(['<>', $this->itemAttribute, $this->owner->getAttribute($this->itemAttribute)])
642 3
                ->select([$this->itemAttribute])
643 3
                ->column();
644 3
            $slugs2 = $this->getChildren()
645 3
                ->select([$this->itemAttribute])
646 3
                ->column();
647 3
            if (array_intersect($slugs1, $slugs2)) {
648
                $event->isValid = false;
649
            }
650 3
        }
651 9
    }
652
653
    /**
654
     *
655
     */
656 9
    public function afterDelete()
657
    {
658 9
        if ($this->operation !== static::OPERATION_DELETE_ALL) {
659 3
            foreach ($this->owner->children as $child) {
660
                /** @var self $child */
661 3
                if ($this->owner->next === null) {
662
                    $child->appendTo($this->owner->parent)->save();
663
                } else {
664 3
                    $child->insertBefore($this->owner->next)->save();
665
                }
666 3
            }
667 3
        }
668 9
        $this->operation = null;
669 9
        $this->node      = null;
670 9
    }
671
672
673
    /**
674
     * @return string
675
     */
676 36
    protected function getPrimaryKeyValue()
677
    {
678 36
        $result = $this->owner->getPrimaryKey(true);
679 36
        return reset($result);
680
    }
681
682
    /**
683
     * @param bool $forInsertNear
684
     * @throws Exception
685
     */
686 126
    protected function checkNode($forInsertNear = false)
687
    {
688 126
        if ($forInsertNear && $this->node->isRoot()) {
0 ignored issues
show
Bug introduced by
The method isRoot does only exist in paulzi\materializedPath\MaterializedPathBehavior, but not in yii\db\ActiveRecord.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
689 9
            throw new Exception('Can not move a node before/after root.');
690
        }
691 117
        if ($this->node->getIsNewRecord()) {
0 ignored issues
show
Bug introduced by
The method getIsNewRecord does only exist in yii\db\ActiveRecord, but not in paulzi\materializedPath\MaterializedPathBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
692 12
            throw new Exception('Can not move a node when the target node is new record.');
693
        }
694
695 105
        if ($this->owner->equals($this->node)) {
0 ignored issues
show
Bug introduced by
It seems like $this->node can also be of type null or object<paulzi\materializ...terializedPathBehavior>; however, yii\db\ActiveRecord::equals() does only seem to accept object<yii\db\ActiveRecord>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
696 12
            throw new Exception('Can not move a node when the target node is same.');
697
        }
698
699 93
        if ($this->node->isChildOf($this->owner)) {
0 ignored issues
show
Bug introduced by
The method isChildOf does only exist in paulzi\materializedPath\MaterializedPathBehavior, but not in yii\db\ActiveRecord.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
700 12
            throw new Exception('Can not move a node when the target node is child.');
701
        }
702 81
    }
703
704
    /**
705
     * Make root operation internal handler
706
     */
707 6
    protected function makeRootInternal()
708
    {
709 6
        $item = $this->owner->getAttribute($this->itemAttribute);
710
711 6
        if ($item !== null) {
712 6
            $this->owner->setAttribute($this->pathAttribute, $item);
713 6
        }
714
715 6
        if ($this->sortable !== false) {
716 6
            $this->owner->setAttribute($this->behavior->sortAttribute, 0);
717 6
        }
718
719 6
        if ($this->treeAttribute !== null && !$this->owner->getDirtyAttributes([$this->treeAttribute]) && !$this->owner->getIsNewRecord()) {
720 3
            $this->owner->setAttribute($this->treeAttribute, $this->getPrimaryKeyValue());
721 3
        }
722
723 6
        $this->owner->setAttribute($this->depthAttribute, $this->rootDepthValue);
724 6
    }
725
726
    /**
727
     * Append to operation internal handler
728
     * @param bool $append
729
     * @throws Exception
730
     */
731 66 View Code Duplication
    protected function insertIntoInternal($append)
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...
732
    {
733 66
        $this->checkNode(false);
734 42
        $item = $this->owner->getAttribute($this->itemAttribute);
735
736 42
        if ($item !== null) {
737 42
            $path = $this->node->getAttribute($this->pathAttribute);
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\materializedPath\MaterializedPathBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
738 42
            $this->owner->setAttribute($this->pathAttribute, $path . $this->delimiter . $item);
739 42
        }
740
741 42
        $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute) + 1);
742
743 42
        if ($this->treeAttribute !== null) {
744 42
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
745 42
        }
746
747 42
        if ($this->sortable !== false) {
748 42
            if ($append) {
749 21
                $this->owner->moveLast();
750 21
            } else {
751 21
                $this->owner->moveFirst();
752
            }
753 42
        }
754 42
    }
755
756
    /**
757
     * Insert operation internal handler
758
     * @param bool $forward
759
     * @throws Exception
760
     */
761 60 View Code Duplication
    protected function insertNearInternal($forward)
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...
762
    {
763 60
        $this->checkNode(true);
764 39
        $item = $this->owner->getAttribute($this->itemAttribute);
765
766 39
        if ($item !== null) {
767 39
            $path = $this->node->getParentPath();
0 ignored issues
show
Bug introduced by
The method getParentPath does only exist in paulzi\materializedPath\MaterializedPathBehavior, but not in yii\db\ActiveRecord.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
768 39
            $this->owner->setAttribute($this->pathAttribute, $path . $this->delimiter . $item);
769 39
        }
770
771 39
        $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute));
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\materializedPath\MaterializedPathBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
772
773 39
        if ($this->treeAttribute !== null) {
774 39
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
775 39
        }
776
777 39
        if ($this->sortable !== false) {
778 39
            if ($forward) {
779 18
                $this->owner->moveAfter($this->node);
780 18
            } else {
781 21
                $this->owner->moveBefore($this->node);
782
            }
783 39
        }
784 39
    }
785
786
    /**
787
     * @return int
788
     */
789 9
    protected function deleteWithChildrenInternal()
790
    {
791 9
        if (!$this->owner->beforeDelete()) {
792
            return false;
793
        }
794 6
        $result = $this->owner->deleteAll($this->getDescendants(null, true)->where);
795 6
        $this->owner->setOldAttributes(null);
796 6
        $this->owner->afterDelete();
797 6
        return $result;
798
    }
799
800
    /**
801
     * @param array $changedAttributes
802
     * @throws Exception
803
     */
804 57
    protected function moveNode($changedAttributes)
805
    {
806 57
        $path = isset($changedAttributes[$this->pathAttribute]) ? $changedAttributes[$this->pathAttribute] : $this->owner->getAttribute($this->pathAttribute);
807 57
        $update = [];
808
        $condition = [
809 57
            'and',
810 57
            ['like', "[[{$this->pathAttribute}]]", $this->getLike($path), false],
811 57
        ];
812 57
        if ($this->treeAttribute !== null) {
813 54
            $tree = isset($changedAttributes[$this->treeAttribute]) ? $changedAttributes[$this->treeAttribute] : $this->owner->getAttribute($this->treeAttribute);
814 54
            $condition[] = [$this->treeAttribute => $tree];
815 54
        }
816 57
        $params = [];
817
818 57
        if (isset($changedAttributes[$this->pathAttribute])) {
819 30
            $substringExpr = $this->substringExpression(
820 30
                "[[{$this->pathAttribute}]]",
821 30
                'LENGTH(:pathOld) + 1',
822 30
                "LENGTH([[{$this->pathAttribute}]]) - LENGTH(:pathOld)"
823 30
            );
824 30
            $update[$this->pathAttribute] = new Expression($this->concatExpression([':pathNew', $substringExpr]));
825 30
            $params[':pathOld'] = $path;
826 30
            $params[':pathNew'] = $this->owner->getAttribute($this->pathAttribute);
827 30
        }
828
829 57
        if ($this->treeAttribute !== null && isset($changedAttributes[$this->treeAttribute])) {
830 15
            $update[$this->treeAttribute] = $this->owner->getAttribute($this->treeAttribute);
831 15
        }
832
833 57
        if ($this->depthAttribute !== null && isset($changedAttributes[$this->depthAttribute])) {
834 30
            $delta = $this->owner->getAttribute($this->depthAttribute) - $changedAttributes[$this->depthAttribute];
835 30
            $update[$this->depthAttribute] = new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', $delta));
836 30
        }
837
838 57
        if (!empty($update)) {
839 30
            $this->owner->updateAll($update, $condition, $params);
840 30
        }
841 57
    }
842
843
    /**
844
     * @param string $path
845
     * @param string $delimiter
846
     * @param bool $asArray = false
847
     * @return null|string|array
848
     */
849 66
    protected static function getParentPathInternal($path, $delimiter, $asArray = false)
850
    {
851 66
        $path = explode($delimiter, $path);
852 66
        array_pop($path);
853 66
        if ($asArray) {
854 6
            return $path;
855
        }
856 63
        return count($path) > 0 ? implode($delimiter, $path) : null;
857
    }
858
859
    /**
860
     * @return array
861
     */
862 123
    protected function treeCondition()
863
    {
864 123
        $tableName = $this->owner->tableName();
865 123
        if ($this->treeAttribute === null) {
866 123
            return [];
867
        } else {
868 123
            return ["{$tableName}.[[{$this->treeAttribute}]]" => $this->owner->getAttribute($this->treeAttribute)];
869
        }
870
    }
871
872
    /**
873
     * @return \yii\db\ActiveQuery
874
     */
875 87
    protected function getSortableQuery()
876
    {
877 87
        switch ($this->operation) {
878 87
            case self::OPERATION_PREPEND_TO:
879 87
            case self::OPERATION_APPEND_TO:
880 42
                $path  = $this->node->getAttribute($this->pathAttribute);
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\materializedPath\MaterializedPathBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
881 42
                $depth = $this->node->getAttribute($this->depthAttribute) + 1;
882 42
                break;
883
884 45
            case self::OPERATION_INSERT_BEFORE:
885 45
            case self::OPERATION_INSERT_AFTER:
886 39
                $path  = $this->node->getParentPath();
0 ignored issues
show
Bug introduced by
The method getParentPath does only exist in paulzi\materializedPath\MaterializedPathBehavior, but not in yii\db\ActiveRecord.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
887 39
                $depth = $this->node->getAttribute($this->depthAttribute);
888 39
                break;
889
890 6
            default:
891 6
                $path  = $this->getParentPath();
892 6
                $depth = $this->owner->getAttribute($this->depthAttribute);
893 87
        }
894 87
        $tableName = $this->owner->tableName();
895
896 87
        return $this->owner->find()
897 87
            ->andWhere($this->treeCondition())
898 87
            ->andWhere($path !== null ? ['like', "{$tableName}.[[{$this->pathAttribute}]]", $this->getLike($path), false] : '1=0')
899 87
            ->andWhere(["{$tableName}.[[{$this->depthAttribute}]]" => $depth]);
900
    }
901
902
    /**
903
     * @param string $path
904
     * @return string
905
     */
906 117
    protected function getLike($path)
907
    {
908 117
        return strtr($path . $this->delimiter, ['%' => '\%', '_' => '\_', '\\' => '\\\\']) . '%';
909
    }
910
911
    /**
912
     * @param array $items
913
     * @return string
914
     */
915 33
    protected function concatExpression($items)
916
    {
917 33
        if ($this->owner->getDb()->driverName === 'sqlite' || $this->owner->getDb()->driverName === 'pgsql') {
918 22
            return implode(' || ', $items);
919
        }
920 11
        return 'CONCAT(' . implode(',', $items) . ')';
921
    }
922
923 30
    protected function substringExpression($string, $from, $length)
924
    {
925 30
        if ($this->owner->getDb()->driverName === 'sqlite') {
926 10
            return "SUBSTR({$string}, {$from}, {$length})";
927
        }
928 20
        return "SUBSTRING({$string}, {$from}, {$length})";
929
    }
930
}
931