Position::afterSave()   F
last analyzed

Complexity

Conditions 19
Paths 291

Size

Total Lines 72
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 206.5483

Importance

Changes 0
Metric Value
cc 19
eloc 50
c 0
b 0
f 0
nc 291
nop 3
dl 0
loc 72
ccs 10
cts 51
cp 0.1961
crap 206.5483
rs 2.5458

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of the Zemit Framework.
5
 *
6
 * (c) Zemit Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Zemit\Mvc\Model\Behavior;
13
14
use Phalcon\Db\RawValue;
15
use Phalcon\Mvc\EntityInterface;
16
use Phalcon\Mvc\Model\Behavior;
17
use Phalcon\Mvc\ModelInterface;
18
use Zemit\Mvc\Model;
19
use Zemit\Mvc\Model\Behavior\Traits\ProgressTrait;
20
use Zemit\Mvc\Model\Behavior\Traits\SkippableTrait;
21
22
class Position extends Behavior
23
{
24
    use ProgressTrait;
25
    use SkippableTrait;
26
    
27
    public bool $progress = false;
28
    
29 56
    public function setField(string $field): void
30
    {
31 56
        $this->options['field'] = $field;
32
    }
33
    
34 3
    public function getField(): string
35
    {
36 3
        return $this->options['field'];
37
    }
38
    
39 56
    public function setRawSql(bool $rawSql): void
40
    {
41 56
        $this->options['rawSql'] = $rawSql;
42
    }
43
    
44 3
    public function getRawSql(): bool
45
    {
46 3
        return $this->options['rawSql'];
47
    }
48
    
49 3
    public function hasProperty(ModelInterface $model, string $field): bool
50
    {
51 3
        return property_exists($model, $field);
52
    }
53
    
54 56
    public function __construct(array $options = [])
55
    {
56 56
        parent::__construct($options);
57 56
        $this->setField($options['field'] ?? 'position');
58 56
        $this->setRawSql($options['rawSql'] ?? true);
59
    }
60
    
61
    /**
62
     * Set the default position field value before validation
63
     * Shift position+1 and position-1 to other records after save
64
     */
65 3
    public function notify(string $type, ModelInterface $model): ?bool
66
    {
67 3
        if (!$this->isEnabled()) {
68
            return null;
69
        }
70
        
71 3
        $field = $this->getField();
72 3
        $rawSql = $this->getRawSql();
73
        
74
        // skip if the current model doesn't have the position property defined
75 3
        if (!$this->hasProperty($model, $field)) {
76 3
            return null;
77
        }
78
        
79
        switch ($type) {
80 2
            case 'beforeValidation':
81 2
                $this->beforeValidation($model, $field);
82 2
                break;
83
            
84 2
            case 'afterSave':
85 2
                $this->afterSave($model, $field, $rawSql);
86 2
                break;
87
        }
88
        
89 2
        return true;
90
    }
91
    
92
    /**
93
     * Force the current position to max(position)+1 if it's empty
94
     * will only happen if the position field is present on the current model
95
     */
96 2
    public function beforeValidation(ModelInterface $model, string $field): void
97
    {
98 2
        if (property_exists($model, $field) && $model instanceof EntityInterface) {
99
            
100 2
            $positionValue = $model->readAttribute($field);
101 2
            if (is_null($positionValue)) {
102
                
103
                // if position field is empty, force current max(position)+1
104
                $lastRecord = $model::findFirst(['order' => $field . ' DESC']);
105
                if ($lastRecord && assert($lastRecord instanceof $model)) {
106
                    $lastPosition = (int)$lastRecord->readAttribute($field);
107
                    $model->writeAttribute($field, $lastPosition + 1);
108
                }
109
            }
110
        }
111
    }
112
    
113 2
    public function afterSave(ModelInterface $model, string $field, bool $rawSql): void
114
    {
115 2
        assert($model instanceof Model);
116 2
        if (!$this->inProgress() && $model->hasSnapshotData() && $model->hasUpdated($field)) {
117 2
            self::staticStart();
118
            
119 2
            $snapshot = $model->getOldSnapshotData() ?: $model->getSnapshotData();
120 2
            $modelPosition = $model->readAttribute($field);
121 2
            $modelPrimaryKeys = $model->getPrimaryKeysValues();
122
            
123 2
            if ($modelPosition instanceof RawValue) {
124
                $modelPosition = $modelPosition->getValue();
125
            }
126
            
127 2
            if (!empty($modelPosition) || $modelPosition === '0') {
128
                $modelPosition = (int)$modelPosition;
129
                
130
                $positionField = $field;
131
                if (ini_get('phalcon.orm.column_renaming')) {
132
                    $columnMap = $model->getModelsMetaData()->getReverseColumnMap($model);
133
                    $positionFieldRaw = $columnMap[$field] ?? $field;
134
                } else {
135
                    $positionFieldRaw = $field;
136
                }
137
                
138
                $primaryKeyCondition = $rawSql
139
                    ? implode_sprintf($modelPrimaryKeys, ' and ', '`' . $model->getSource() . '`.`%2$s` <> ?')
140
                    : implode_sprintf($modelPrimaryKeys, ' and ', '[' . get_class($model) . '].[%2$s] <> :%2$s:');
141
                
142
                $positionKey = uniqid('_position_') . '_';
143
                $oldPositionKey = uniqid('_oldPosition_') . '_';
144
                
145
                $updatePositionQuery = null;
146
                $updatePositionParams = [$positionKey => $modelPosition, $oldPositionKey => (int)$snapshot[$field]];
147
                if ($snapshot[$field] > $modelPosition) {
148
                    $updatePositionQuery = $rawSql
149
                        ? 'UPDATE `' . $model->getSource() . '` SET `' . $positionFieldRaw . '` = `' . $positionFieldRaw . '`+1 WHERE `' . $positionFieldRaw . '` >= ? and `' . $positionFieldRaw . '` < ? and ' . $primaryKeyCondition
150
                        : 'UPDATE [' . get_class($model) . '] SET [' . $positionField . '] = [' . $positionField . ']+1 WHERE [' . $positionField . '] >= :' . $positionKey . ': and [' . $positionField . '] < :' . $oldPositionKey . ': and ' . $primaryKeyCondition;
151
                    $updatePositionParams = $rawSql
152
                        ? [$modelPosition, $snapshot[$field]]
153
                        : $updatePositionParams;
154
                }
155
                elseif ($snapshot[$field] < $modelPosition) {
156
                    $updatePositionQuery = $rawSql
157
                        ? 'UPDATE `' . $model->getSource() . '` SET `' . $positionFieldRaw . '` = `' . $positionFieldRaw . '`-1 WHERE `' . $positionFieldRaw . '` > ? and `' . $positionFieldRaw . '` <= ? and ' . $primaryKeyCondition
158
                        : 'UPDATE [' . get_class($model) . '] SET [' . $positionField . '] = [' . $positionField . ']-1 WHERE [' . $positionField . '] > :' . $oldPositionKey . ': and [' . $positionField . '] <= :' . $positionKey . ': and ' . $primaryKeyCondition;
159
                    $updatePositionParams = $rawSql
160
                        ? [$snapshot[$field], $modelPosition]
161
                        : $updatePositionParams;
162
                }
163
                
164
                if (!empty($updatePositionQuery)) {
165
                    if ($rawSql) {
166
                        $model->getWriteConnection()->query($updatePositionQuery, [
167
                            ...$updatePositionParams,
168
                            ...array_values($modelPrimaryKeys),
169
                        ]);
170
                    }
171
                    else {
172
                        $save = $model->getModelsManager()->executeQuery($updatePositionQuery, array_merge(
173
                            $updatePositionParams,
174
                            $modelPrimaryKeys,
175
                        ));
176
                        $messages = $save->getMessages();
177
                        if (!empty($messages)) {
178
                            $model->appendMessages($messages, 'afterSave');
179
                        }
180
                    }
181
                }
182
            }
183
            
184 2
            self::staticStop();
185
        }
186
    }
187
}
188