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 ( aeb17c...371354 )
by Pavel
05:22
created

MaterializedPathBehavior::preDeleteWithChildren()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
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
        $nodes[] = $this->owner;
379 3
        foreach ($nodes as $node) {
380 3
            $key = $node->getAttribute($this->itemAttribute);
381 3
            if (isset($relates[$key])) {
382 3
                $node->populateRelation('children', $relates[$key]);
383 3
            } elseif ($depth === null) {
384 3
                $node->populateRelation('children', []);
385 3
            }
386 3
        }
387
388 3
        return $this->owner;
389
    }
390
391
    /**
392
     * @return bool
393
     */
394 72
    public function isRoot()
395
    {
396 72
        return count(explode($this->delimiter, $this->owner->getAttribute($this->pathAttribute))) === 1;
397
    }
398
399
    /**
400
     * @param ActiveRecord $node
401
     * @return bool
402
     */
403 96
    public function isChildOf($node)
404
    {
405 96
        if ($node->getIsNewRecord()) {
406 30
            return false;
407
        }
408 66
        $nodePath  = $node->getAttribute($this->pathAttribute) . $this->delimiter;
409 66
        $result = substr($this->owner->getAttribute($this->pathAttribute), 0, strlen($nodePath)) === $nodePath;
410
411 66
        if ($result && $this->treeAttribute !== null) {
412 9
            $result = $this->owner->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute);
413 9
        }
414
415 66
        return $result;
416
    }
417
418
    /**
419
     * @return bool
420
     */
421 3
    public function isLeaf()
422
    {
423 3
        return count($this->owner->children) === 0;
424
    }
425
426
    /**
427
     * @return ActiveRecord
428
     */
429 6
    public function makeRoot()
430
    {
431 6
        $this->operation = self::OPERATION_MAKE_ROOT;
432 6
        return $this->owner;
433
    }
434
435
    /**
436
     * @param ActiveRecord $node
437
     * @return ActiveRecord
438
     */
439 33
    public function prependTo($node)
440
    {
441 33
        $this->operation = self::OPERATION_PREPEND_TO;
442 33
        $this->node = $node;
443 33
        return $this->owner;
444
    }
445
446
    /**
447
     * @param ActiveRecord $node
448
     * @return ActiveRecord
449
     */
450 33
    public function appendTo($node)
451
    {
452 33
        $this->operation = self::OPERATION_APPEND_TO;
453 33
        $this->node = $node;
454 33
        return $this->owner;
455
    }
456
457
    /**
458
     * @param ActiveRecord $node
459
     * @return ActiveRecord
460
     */
461 30
    public function insertBefore($node)
462
    {
463 30
        $this->operation = self::OPERATION_INSERT_BEFORE;
464 30
        $this->node = $node;
465 30
        return $this->owner;
466
    }
467
468
    /**
469
     * @param ActiveRecord $node
470
     * @return ActiveRecord
471
     */
472 30
    public function insertAfter($node)
473
    {
474 30
        $this->operation = self::OPERATION_INSERT_AFTER;
475 30
        $this->node = $node;
476 30
        return $this->owner;
477
    }
478
479
    /**
480
     * Need for paulzi/auto-tree
481
     */
482
    public function preDeleteWithChildren()
483
    {
484
        $this->operation = self::OPERATION_DELETE_ALL;
485
    }
486
487
    /**
488
     * @return bool|int
489
     * @throws \Exception
490
     * @throws \yii\db\Exception
491
     */
492 9
    public function deleteWithChildren()
493
    {
494 9
        $this->operation = self::OPERATION_DELETE_ALL;
495 9
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
496
            $transaction = $this->owner->getDb()->beginTransaction();
497
            try {
498
                $result = $this->deleteWithChildrenInternal();
499
                if ($result === false) {
500
                    $transaction->rollBack();
501
                } else {
502
                    $transaction->commit();
503
                }
504
                return $result;
505
            } catch (\Exception $e) {
506
                $transaction->rollBack();
507
                throw $e;
508
            }
509
        } else {
510 9
            $result = $this->deleteWithChildrenInternal();
511
        }
512 6
        return $result;
513
    }
514
515
    /**
516
     * @param bool $middle
517
     * @return int
518
     */
519 3
    public function reorderChildren($middle = true)
520
    {
521
        /** @var ActiveRecord|SortableBehavior $item */
522 3
        $item = count($this->owner->children) > 0 ? $this->owner->children[0] : null;
523 3
        if ($item) {
524 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...
525
        } else {
526
            return 0;
527
        }
528
    }
529
530
    /**
531
     * @throws Exception
532
     * @throws NotSupportedException
533
     */
534 138
    public function beforeSave()
535
    {
536 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...
537 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...
538 108
        }
539
540 138
        switch ($this->operation) {
541 138
            case self::OPERATION_MAKE_ROOT:
542 6
                $this->makeRootInternal();
543
544 6
                break;
545 132
            case self::OPERATION_PREPEND_TO:
546 33
                $this->insertIntoInternal(false);
547
548 21
                break;
549 99
            case self::OPERATION_APPEND_TO:
550 33
                $this->insertIntoInternal(true);
551
552 21
                break;
553 66
            case self::OPERATION_INSERT_BEFORE:
554 30
                $this->insertNearInternal(false);
555
556 21
                break;
557
558 36
            case self::OPERATION_INSERT_AFTER:
559 30
                $this->insertNearInternal(true);
560
561 18
                break;
562
563 6
            default:
564 6
                if ($this->owner->getIsNewRecord()) {
565 3
                    throw new NotSupportedException('Method "' . $this->owner->className() . '::insert" is not supported for inserting new nodes.');
566
                }
567
568 3
                $item = $this->owner->getAttribute($this->itemAttribute);
569 3
                $path = $this->getParentPath();
570 3
                $this->owner->setAttribute($this->pathAttribute, ($path !== null ? $path . $this->delimiter : null) . $item);
571 93
        }
572 90
    }
573
574
    /**
575
     * @throws Exception
576
     */
577 33
    public function afterInsert()
578
    {
579 33
        if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== null && $this->owner->getAttribute($this->treeAttribute) === null) {
580 3
            $id = $this->getPrimaryKeyValue();
581 3
            $this->owner->setAttribute($this->treeAttribute, $id);
582
583 3
            $primaryKey = $this->owner->primaryKey();
584 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...
585
                throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
586
            }
587
588 3
            $this->owner->updateAll([$this->treeAttribute => $id], [$primaryKey[0] => $id]);
589 3
        }
590 33
        if ($this->owner->getAttribute($this->pathAttribute) === null) {
591 33
            $primaryKey = $this->owner->primaryKey();
592 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...
593
                throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
594
            }
595 33
            $id = $this->getPrimaryKeyValue();
596 33
            if ($this->operation === self::OPERATION_MAKE_ROOT) {
597 3
                $path = $id;
598 3
            } else {
599 30
                if ($this->operation === self::OPERATION_INSERT_BEFORE || $this->operation === self::OPERATION_INSERT_AFTER) {
600 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...
601 18
                } else {
602 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...
603
                }
604 30
                $path = $path . $this->delimiter . $id;
605
            }
606 33
            $this->owner->setAttribute($this->pathAttribute, $path);
607 33
            $this->owner->updateAll([$this->pathAttribute => $path], [$primaryKey[0] => $id]);
608 33
        }
609 33
        $this->operation = null;
610 33
        $this->node      = null;
611 33
    }
612
613
    /**
614
     * @param \yii\db\AfterSaveEvent $event
615
     */
616 57
    public function afterUpdate($event)
617
    {
618 57
        $this->moveNode($event->changedAttributes);
619 57
        $this->operation = null;
620 57
        $this->node      = null;
621 57
    }
622
623
    /**
624
     * @param \yii\base\ModelEvent $event
625
     * @throws Exception
626
     */
627 120
    public function beforeDelete($event)
628
    {
629 120
        if ($this->owner->getIsNewRecord()) {
630 6
            throw new Exception('Can not delete a node when it is new record.');
631
        }
632 12
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
633 3
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
634
        }
635 9
        $this->owner->refresh();
636 9
        if ($this->operation !== static::OPERATION_DELETE_ALL && !$this->primaryKeyMode) {
637
            /** @var self $parent */
638 3
            $parent =$this->getParent()->one();
639 3
            $slugs1 = $parent->getChildren()
640 3
                ->andWhere(['<>', $this->itemAttribute, $this->owner->getAttribute($this->itemAttribute)])
641 3
                ->select([$this->itemAttribute])
642 3
                ->column();
643 3
            $slugs2 = $this->getChildren()
644 3
                ->select([$this->itemAttribute])
645 3
                ->column();
646 3
            if (array_intersect($slugs1, $slugs2)) {
647
                $event->isValid = false;
648
            }
649 3
        }
650 9
    }
651
652
    /**
653
     *
654
     */
655 9
    public function afterDelete()
656
    {
657 9
        if ($this->operation !== static::OPERATION_DELETE_ALL) {
658 3
            foreach ($this->owner->children as $child) {
659
                /** @var self $child */
660 3
                if ($this->owner->next === null) {
661
                    $child->appendTo($this->owner->parent)->save();
662
                } else {
663 3
                    $child->insertBefore($this->owner->next)->save();
664
                }
665 3
            }
666 3
        }
667 9
        $this->operation = null;
668 9
        $this->node      = null;
669 9
    }
670
671
672
    /**
673
     * @return string
674
     */
675 36
    protected function getPrimaryKeyValue()
676
    {
677 36
        $result = $this->owner->getPrimaryKey(true);
678 36
        return reset($result);
679
    }
680
681
    /**
682
     * @param bool $forInsertNear
683
     * @throws Exception
684
     */
685 126
    protected function checkNode($forInsertNear = false)
686
    {
687 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...
688 9
            throw new Exception('Can not move a node before/after root.');
689
        }
690 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...
691 12
            throw new Exception('Can not move a node when the target node is new record.');
692
        }
693
694 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...
695 12
            throw new Exception('Can not move a node when the target node is same.');
696
        }
697
698 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...
699 12
            throw new Exception('Can not move a node when the target node is child.');
700
        }
701 81
    }
702
703
    /**
704
     * Make root operation internal handler
705
     */
706 6
    protected function makeRootInternal()
707
    {
708 6
        $item = $this->owner->getAttribute($this->itemAttribute);
709
710 6
        if ($item !== null) {
711 6
            $this->owner->setAttribute($this->pathAttribute, $item);
712 6
        }
713
714 6
        if ($this->sortable !== false) {
715 6
            $this->owner->setAttribute($this->behavior->sortAttribute, 0);
716 6
        }
717
718 6
        if ($this->treeAttribute !== null && !$this->owner->getDirtyAttributes([$this->treeAttribute]) && !$this->owner->getIsNewRecord()) {
719 3
            $this->owner->setAttribute($this->treeAttribute, $this->getPrimaryKeyValue());
720 3
        }
721
722 6
        $this->owner->setAttribute($this->depthAttribute, $this->rootDepthValue);
723 6
    }
724
725
    /**
726
     * Append to operation internal handler
727
     * @param bool $append
728
     * @throws Exception
729
     */
730 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...
731
    {
732 66
        $this->checkNode(false);
733 42
        $item = $this->owner->getAttribute($this->itemAttribute);
734
735 42
        if ($item !== null) {
736 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...
737 42
            $this->owner->setAttribute($this->pathAttribute, $path . $this->delimiter . $item);
738 42
        }
739
740 42
        $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute) + 1);
741
742 42
        if ($this->treeAttribute !== null) {
743 42
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
744 42
        }
745
746 42
        if ($this->sortable !== false) {
747 42
            if ($append) {
748 21
                $this->owner->moveLast();
749 21
            } else {
750 21
                $this->owner->moveFirst();
751
            }
752 42
        }
753 42
    }
754
755
    /**
756
     * Insert operation internal handler
757
     * @param bool $forward
758
     * @throws Exception
759
     */
760 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...
761
    {
762 60
        $this->checkNode(true);
763 39
        $item = $this->owner->getAttribute($this->itemAttribute);
764
765 39
        if ($item !== null) {
766 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...
767 39
            $this->owner->setAttribute($this->pathAttribute, $path . $this->delimiter . $item);
768 39
        }
769
770 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...
771
772 39
        if ($this->treeAttribute !== null) {
773 39
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
774 39
        }
775
776 39
        if ($this->sortable !== false) {
777 39
            if ($forward) {
778 18
                $this->owner->moveAfter($this->node);
779 18
            } else {
780 21
                $this->owner->moveBefore($this->node);
781
            }
782 39
        }
783 39
    }
784
785
    /**
786
     * @return int
787
     */
788 9
    protected function deleteWithChildrenInternal()
789
    {
790 9
        if (!$this->owner->beforeDelete()) {
791
            return false;
792
        }
793 6
        $result = $this->owner->deleteAll($this->getDescendants(null, true)->where);
794 6
        $this->owner->setOldAttributes(null);
795 6
        $this->owner->afterDelete();
796 6
        return $result;
797
    }
798
799
    /**
800
     * @param array $changedAttributes
801
     * @throws Exception
802
     */
803 57
    protected function moveNode($changedAttributes)
804
    {
805 57
        $path = isset($changedAttributes[$this->pathAttribute]) ? $changedAttributes[$this->pathAttribute] : $this->owner->getAttribute($this->pathAttribute);
806 57
        $update = [];
807
        $condition = [
808 57
            'and',
809 57
            ['like', "[[{$this->pathAttribute}]]", $this->getLike($path), false],
810 57
        ];
811 57
        if ($this->treeAttribute !== null) {
812 54
            $tree = isset($changedAttributes[$this->treeAttribute]) ? $changedAttributes[$this->treeAttribute] : $this->owner->getAttribute($this->treeAttribute);
813 54
            $condition[] = [$this->treeAttribute => $tree];
814 54
        }
815 57
        $params = [];
816
817 57
        if (isset($changedAttributes[$this->pathAttribute])) {
818 30
            $substringExpr = $this->substringExpression(
819 30
                "[[{$this->pathAttribute}]]",
820 30
                'LENGTH(:pathOld) + 1',
821 30
                "LENGTH([[{$this->pathAttribute}]]) - LENGTH(:pathOld)"
822 30
            );
823 30
            $update[$this->pathAttribute] = new Expression($this->concatExpression([':pathNew', $substringExpr]));
824 30
            $params[':pathOld'] = $path;
825 30
            $params[':pathNew'] = $this->owner->getAttribute($this->pathAttribute);
826 30
        }
827
828 57
        if ($this->treeAttribute !== null && isset($changedAttributes[$this->treeAttribute])) {
829 15
            $update[$this->treeAttribute] = $this->owner->getAttribute($this->treeAttribute);
830 15
        }
831
832 57
        if ($this->depthAttribute !== null && isset($changedAttributes[$this->depthAttribute])) {
833 30
            $delta = $this->owner->getAttribute($this->depthAttribute) - $changedAttributes[$this->depthAttribute];
834 30
            $update[$this->depthAttribute] = new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', $delta));
835 30
        }
836
837 57
        if (!empty($update)) {
838 30
            $this->owner->updateAll($update, $condition, $params);
839 30
        }
840 57
    }
841
842
    /**
843
     * @param string $path
844
     * @param string $delimiter
845
     * @param bool $asArray = false
846
     * @return null|string|array
847
     */
848 66
    protected static function getParentPathInternal($path, $delimiter, $asArray = false)
849
    {
850 66
        $path = explode($delimiter, $path);
851 66
        array_pop($path);
852 66
        if ($asArray) {
853 6
            return $path;
854
        }
855 63
        return count($path) > 0 ? implode($delimiter, $path) : null;
856
    }
857
858
    /**
859
     * @return array
860
     */
861 123
    protected function treeCondition()
862
    {
863 123
        $tableName = $this->owner->tableName();
864 123
        if ($this->treeAttribute === null) {
865 123
            return [];
866
        } else {
867 123
            return ["{$tableName}.[[{$this->treeAttribute}]]" => $this->owner->getAttribute($this->treeAttribute)];
868
        }
869
    }
870
871
    /**
872
     * @return \yii\db\ActiveQuery
873
     */
874 87
    protected function getSortableQuery()
875
    {
876 87
        switch ($this->operation) {
877 87
            case self::OPERATION_PREPEND_TO:
878 87
            case self::OPERATION_APPEND_TO:
879 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...
880 42
                $depth = $this->node->getAttribute($this->depthAttribute) + 1;
881 42
                break;
882
883 45
            case self::OPERATION_INSERT_BEFORE:
884 45
            case self::OPERATION_INSERT_AFTER:
885 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...
886 39
                $depth = $this->node->getAttribute($this->depthAttribute);
887 39
                break;
888
889 6
            default:
890 6
                $path  = $this->getParentPath();
891 6
                $depth = $this->owner->getAttribute($this->depthAttribute);
892 87
        }
893 87
        $tableName = $this->owner->tableName();
894
895 87
        return $this->owner->find()
896 87
            ->andWhere($this->treeCondition())
897 87
            ->andWhere($path !== null ? ['like', "{$tableName}.[[{$this->pathAttribute}]]", $this->getLike($path), false] : '1=0')
898 87
            ->andWhere(["{$tableName}.[[{$this->depthAttribute}]]" => $depth]);
899
    }
900
901
    /**
902
     * @param string $path
903
     * @return string
904
     */
905 117
    protected function getLike($path)
906
    {
907 117
        return strtr($path . $this->delimiter, ['%' => '\%', '_' => '\_', '\\' => '\\\\']) . '%';
908
    }
909
910
    /**
911
     * @param array $items
912
     * @return string
913
     */
914 33
    protected function concatExpression($items)
915
    {
916 33
        if ($this->owner->getDb()->driverName === 'sqlite' || $this->owner->getDb()->driverName === 'pgsql') {
917 22
            return implode(' || ', $items);
918
        }
919 11
        return 'CONCAT(' . implode(',', $items) . ')';
920
    }
921
922 30
    protected function substringExpression($string, $from, $length)
923
    {
924 30
        if ($this->owner->getDb()->driverName === 'sqlite') {
925 10
            return "SUBSTR({$string}, {$from}, {$length})";
926
        }
927 20
        return "SUBSTRING({$string}, {$from}, {$length})";
928
    }
929
}
930