Test Failed
Push — master ( 513669...a14d6b )
by Julien
09:38 queued 04:28
created

Position::beforeValidation()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 3
Bugs 2 Features 0
Metric Value
eloc 7
dl 0
loc 12
ccs 0
cts 8
cp 0
rs 9.2222
c 3
b 2
f 0
cc 6
nc 4
nop 2
crap 42
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
20
class Position extends Behavior
21
{
22
    use ProgressTrait;
23
    use SkippableTrait;
24
    
25
    public bool $progress = false;
26
    
27 2
    public function setField(string $field): void
28
    {
29 2
        $this->options['field'] = $field;
30
    }
31
    
32 2
    public function getField(): string
33
    {
34 2
        return $this->options['field'];
35
    }
36
    
37 2
    public function setRawSql(bool $rawSql): void
38
    {
39 2
        $this->options['rawSql'] = $rawSql;
40
    }
41
    
42 2
    public function getRawSql(): bool
43
    {
44 2
        return $this->options['rawSql'];
45
    }
46
    
47 2
    public function hasProperty(ModelInterface $model, string $field): bool
48
    {
49 2
        return property_exists($model, $field);
50
    }
51
    
52 2
    public function __construct(array $options = [])
53
    {
54 2
        parent::__construct($options);
55 2
        $this->setField($options['field'] ?? 'position');
56 2
        $this->setRawSql($options['rawSql'] ?? true);
57
    }
58
    
59
    /**
60
     * Set the default position field value before validation
61
     * Shift position+1 and position-1 to other records after save
62
     */
63 2
    public function notify(string $type, ModelInterface $model): ?bool
64
    {
65 2
        if (!$this->isEnabled()) {
66
            return null;
67
        }
68
        
69 2
        $field = $this->getField();
70 2
        $rawSql = $this->getRawSql();
71
        
72
        // skip if the current model doesn't have the position property defined
73 2
        if (!$this->hasProperty($model, $field)) {
74 2
            return null;
75
        }
76
        
77
        switch ($type) {
78 1
            case 'beforeValidation':
79
                $this->beforeValidation($model, $field);
80
                break;
81
            
82 1
            case 'afterSave':
83
                $this->afterSave($model, $field, $rawSql);
84
                break;
85
        }
86
        
87 1
        return true;
88
    }
89
    
90
    /**
91
     * Force the current position to max(position)+1 if it's empty
92
     * will only happen if the position field is present on the current model
93
     */
94
    public function beforeValidation(ModelInterface $model, string $field): void
95
    {
96
        if (property_exists($model, $field) && $model instanceof EntityInterface) {
97
            
98
            $positionValue = $model->readAttribute($field);
99
            if (is_null($positionValue)) {
100
                
101
                // if position field is empty, force current max(position)+1
102
                $lastRecord = $model::findFirst(['order' => $field . ' DESC']);
103
                if ($lastRecord && assert($lastRecord instanceof $model)) {
104
                    $lastPosition = (int)$lastRecord->readAttribute($field);
105
                    $model->writeAttribute($field, $lastPosition + 1);
106
                }
107
            }
108
        }
109
    }
110
    
111
    public function afterSave(ModelInterface $model, string $field, bool $rawSql): void
112
    {
113
        assert($model instanceof Model);
114
        if (!$this->getProgress() && $model->hasSnapshotData() && $model->hasUpdated($field)) {
115
            self::staticStart();
116
            
117
            $snapshot = $model->getOldSnapshotData() ?: $model->getSnapshotData();
118
            $modelPosition = $model->readAttribute($field);
119
            $modelPrimaryKeys = $model->getPrimaryKeysValues();
120
            
121
            if (!($modelPosition instanceof RawValue)) {
122
                
123
                $positionField = $field;
124
                if (ini_get('phalcon.orm.column_renaming')) {
125
                    $columnMap = $model->getModelsMetaData()->getReverseColumnMap($model);
126
                    $positionFieldRaw = $columnMap[$field] ?? $field;
127
                } else {
128
                    $positionFieldRaw = $field;
129
                }
130
                
131
                $primaryKeyConditionRaw = implode_mb_sprintf($modelPrimaryKeys, ' and ', '`' . $model->getSource() . '`.`%s` <> ?%s');
132
                $primaryKeyCondition = implode_mb_sprintf($modelPrimaryKeys, ' and ', '[' . get_class($model) . '].[%s] <> ?%s');
133
                
134
                $updatePositionQuery = null;
135
                if ($snapshot[$field] > $modelPosition) {
136
                    $updatePositionQuery = $rawSql
137
                        ? 'UPDATE `' . $model->getSource() . '` SET `' . $positionFieldRaw . '` = `' . $positionFieldRaw . '`+1 WHERE `' . $positionFieldRaw . '` >= :position and `' . $positionFieldRaw . '` < :oldPosition and ' . $primaryKeyConditionRaw
138
                        : 'UPDATE [' . get_class($model) . '] SET [' . $positionField . '] = [' . $positionField . ']+1 WHERE [' . $positionField . '] >= ?1 and [' . $positionField . '] < ?2 and ' . $primaryKeyCondition;
139
                }
140
                elseif ($snapshot[$field] < $modelPosition) {
141
                    $updatePositionQuery = $rawSql
142
                        ? 'UPDATE `' . $model->getSource() . '` SET `' . $positionFieldRaw . '` = `' . $positionFieldRaw . '`-1 WHERE `' . $positionFieldRaw . '` > :oldPosition and `' . $positionFieldRaw . '` <= :position and ' . $primaryKeyConditionRaw
143
                        : 'UPDATE [' . get_class($model) . '] SET [' . $positionField . '] = [' . $positionField . ']-1 WHERE [' . $positionField . '] > ?2 and [' . $positionField . '] <= ?1 and ' . $primaryKeyCondition;
144
                }
145
                
146
                if (!empty($updatePositionQuery)) {
147
                    if ($rawSql) {
148
                        $model->getWriteConnection()->query($updatePositionQuery, [
149
                            'position' => $modelPosition,
150
                            'oldPosition' => $snapshot[$field],
151
                        ]);
152
                    }
153
                    else {
154
                        $model->getModelsManager()->executeQuery($updatePositionQuery, [
155
                            $modelPosition,
156
                            $snapshot[$field]
157
                        ]);
158
                    }
159
                }
160
            }
161
            
162
            self::staticStop();
163
        }
164
    }
165
}
166