Position::setField()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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