Completed
Push — master ( 26d428...cbcdc3 )
by Freek
02:38 queued 27s
created

src/SortableTrait.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Spatie\EloquentSortable;
4
5
use ArrayAccess;
6
use Illuminate\Database\Eloquent\Builder;
7
use InvalidArgumentException;
8
9
trait SortableTrait
10
{
11
    public static function bootSortableTrait()
12
    {
13
        static::creating(function ($model) {
14
            if ($model instanceof Sortable && $model->shouldSortWhenCreating()) {
15
                $model->setHighestOrderNumber();
16
            }
17
        });
18
    }
19
20
    /**
21
     * Modify the order column value.
22
     */
23
    public function setHighestOrderNumber()
24
    {
25
        $orderColumnName = $this->determineOrderColumnName();
26
27
        $this->$orderColumnName = $this->getHighestOrderNumber() + 1;
28
    }
29
30
    /**
31
     * Determine the order value for the new record.
32
     */
33
    public function getHighestOrderNumber(): int
34
    {
35
        return (int) static::max($this->determineOrderColumnName());
36
    }
37
38
    /**
39
     * Let's be nice and provide an ordered scope.
40
     *
41
     * @param \Illuminate\Database\Eloquent\Builder $query
42
     * @param string $direction
43
     *
44
     * @return \Illuminate\Database\Query\Builder
45
     */
46
    public function scopeOrdered(Builder $query, string $direction = 'asc')
47
    {
48
        return $query->orderBy($this->determineOrderColumnName(), $direction);
49
    }
50
51
    /**
52
     * This function reorders the records: the record with the first id in the array
53
     * will get order 1, the record with the second it will get order 2, ...
54
     *
55
     * A starting order number can be optionally supplied (defaults to 1).
56
     *
57
     * @param array|\ArrayAccess $ids
58
     * @param int $startOrder
59
     */
60
    public static function setNewOrder($ids, int $startOrder = 1)
61
    {
62
        if (! is_array($ids) && ! $ids instanceof ArrayAccess) {
63
            throw new InvalidArgumentException('You must pass an array or ArrayAccess object to setNewOrder');
64
        }
65
66
        $model = new static;
67
68
        $orderColumnName = $model->determineOrderColumnName();
69
        $primaryKeyColumn = $model->getKeyName();
70
71
        foreach ($ids as $id) {
72
            static::where($primaryKeyColumn, $id)->update([$orderColumnName => $startOrder++]);
73
        }
74
    }
75
76
    /*
77
     * Determine the column name of the order column.
78
     */
79
    protected function determineOrderColumnName(): string
80
    {
81
        if (
82
            isset($this->sortable['order_column_name']) &&
83
            ! empty($this->sortable['order_column_name'])
84
        ) {
85
            return $this->sortable['order_column_name'];
86
        }
87
88
        return 'order_column';
89
    }
90
91
    /**
92
     * Determine if the order column should be set when saving a new model instance.
93
     */
94
    public function shouldSortWhenCreating(): bool
95
    {
96
        if (! isset($this->sortable)) {
97
            return true;
98
        }
99
100
        if (! isset($this->sortable['sort_when_creating'])) {
101
            return true;
102
        }
103
104
        return $this->sortable['sort_when_creating'];
105
    }
106
107
    /**
108
     * Swaps the order of this model with the model 'below' this model.
109
     *
110
     * @return $this
111
     */
112 View Code Duplication
    public function moveOrderDown()
113
    {
114
        $orderColumnName = $this->determineOrderColumnName();
115
116
        $swapWithModel = static::limit(1)
117
            ->ordered()
118
            ->where($orderColumnName, '>', $this->$orderColumnName)
119
            ->first();
120
121
        if (! $swapWithModel) {
122
            return $this;
123
        }
124
125
        return $this->swapOrderWithModel($swapWithModel);
126
    }
127
128
    /**
129
     * Swaps the order of this model with the model 'above' this model.
130
     *
131
     * @return $this
132
     */
133 View Code Duplication
    public function moveOrderUp()
134
    {
135
        $orderColumnName = $this->determineOrderColumnName();
136
137
        $swapWithModel = static::limit(1)
138
            ->ordered('desc')
139
            ->where($orderColumnName, '<', $this->$orderColumnName)
140
            ->first();
141
142
        if (! $swapWithModel) {
143
            return $this;
144
        }
145
146
        return $this->swapOrderWithModel($swapWithModel);
147
    }
148
149
    /**
150
     * Swap the order of this model with the order of another model.
151
     *
152
     * @param \Spatie\EloquentSortable\Sortable $otherModel
153
     *
154
     * @return $this
155
     */
156
    public function swapOrderWithModel(Sortable $otherModel)
157
    {
158
        $orderColumnName = $this->determineOrderColumnName();
159
160
        $oldOrderOfOtherModel = $otherModel->$orderColumnName;
161
162
        $otherModel->$orderColumnName = $this->$orderColumnName;
163
        $otherModel->save();
164
165
        $this->$orderColumnName = $oldOrderOfOtherModel;
166
        $this->save();
167
168
        return $this;
169
    }
170
171
    /**
172
     * Swap the order of two models.
173
     *
174
     * @param \Spatie\EloquentSortable\Sortable $model
175
     * @param \Spatie\EloquentSortable\Sortable $otherModel
176
     */
177
    public static function swapOrder(Sortable $model, Sortable $otherModel)
178
    {
179
        $model->swapOrderWithModel($otherModel);
180
    }
181
182
    /**
183
     * Moves this model to the first position.
184
     *
185
     * @return $this
186
     */
187
    public function moveToStart()
188
    {
189
        $firstModel = static::limit(1)
190
            ->ordered()
191
            ->first();
192
193
        if ($firstModel->id === $this->id) {
194
            return $this;
195
        }
196
197
        $orderColumnName = $this->determineOrderColumnName();
198
199
        $this->$orderColumnName = $firstModel->$orderColumnName;
200
        $this->save();
0 ignored issues
show
It seems like save() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
201
202
        static::where($this->getKeyName(), '!=', $this->id)->increment($orderColumnName);
203
204
        return $this;
205
    }
206
207
    /**
208
     * Moves this model to the last position.
209
     *
210
     * @return $this
211
     */
212
    public function moveToEnd()
213
    {
214
        $maxOrder = $this->getHighestOrderNumber();
215
216
        $orderColumnName = $this->determineOrderColumnName();
217
218
        if ($this->$orderColumnName === $maxOrder) {
219
            return $this;
220
        }
221
222
        $oldOrder = $this->$orderColumnName;
223
224
        $this->$orderColumnName = $maxOrder;
225
        $this->save();
226
227
        static::where($this->getKeyName(), '!=', $this->id)
228
            ->where($orderColumnName, '>', $oldOrder)
229
            ->decrement($orderColumnName);
230
231
        return $this;
232
    }
233
}
234