Completed
Pull Request — master (#64)
by
unknown
02:16
created

SortableTrait::getOrderNumberAtPosition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Spatie\EloquentSortable;
4
5
use ArrayAccess;
6
use InvalidArgumentException;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Eloquent\SoftDeletingScope;
9
10
trait SortableTrait
11
{
12
    public static function bootSortableTrait()
13
    {
14
        static::creating(function ($model) {
0 ignored issues
show
Bug introduced by
The method creating() does not exist on Spatie\EloquentSortable\SortableTrait. Did you maybe mean shouldSortWhenCreating()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
15
            if ($model instanceof Sortable && $model->shouldSortWhenCreating()) {
16
                $model->setHighestOrderNumber();
17
            }
18
        });
19
    }
20
21
    /**
22
     * Modify the order column value.
23
     */
24
    public function setHighestOrderNumber()
25
    {
26
        $orderColumnName = $this->determineOrderColumnName();
27
28
        $this->$orderColumnName = $this->getHighestOrderNumber() + 1;
29
    }
30
31
    /**
32
     * Determine the order value for the new record.
33
     */
34
    public function getHighestOrderNumber(): int
35
    {
36
        return (int) $this->buildSortQuery()->max($this->determineOrderColumnName());
37
    }
38
39
    /**
40
     * Determine the order value of a model at a specified Nth position.
41
     *
42
     *  @param int $position The position of the model. Positions start at 1.
43
     *
44
     * @return int
45
     */
46
    public function getOrderNumberAtPosition(int $position): int
47
    {
48
        $position--;
49
        $position = max($position,0);
50
51
        return (int) $this->buildSortQuery()->orderBy($this->determineOrderColumnName())->skip($position)->limit(1)->value($this->determineOrderColumnName());
0 ignored issues
show
Bug introduced by
The method orderBy() does not exist on Illuminate\Database\Eloquent\Builder. Did you maybe mean enforceOrderBy()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
52
    }
53
54
    /**
55
     * Let's be nice and provide an ordered scope.
56
     *
57
     * @param \Illuminate\Database\Eloquent\Builder $query
58
     * @param string $direction
59
     *
60
     * @return \Illuminate\Database\Query\Builder
61
     */
62
    public function scopeOrdered(Builder $query, string $direction = 'asc')
63
    {
64
        return $query->orderBy($this->determineOrderColumnName(), $direction);
0 ignored issues
show
Bug introduced by
The method orderBy() does not exist on Illuminate\Database\Eloquent\Builder. Did you maybe mean enforceOrderBy()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
65
    }
66
67
    /**
68
     * This function reorders the records: the record with the first id in the array
69
     * will get order 1, the record with the second it will get order 2, ...
70
     *
71
     * A starting order number can be optionally supplied (defaults to 1).
72
     *
73
     * @param array|\ArrayAccess $ids
74
     * @param int $startOrder
75
     */
76
    public static function setNewOrder($ids, int $startOrder = 1)
77
    {
78
        if (! is_array($ids) && ! $ids instanceof ArrayAccess) {
79
            throw new InvalidArgumentException('You must pass an array or ArrayAccess object to setNewOrder');
80
        }
81
82
        $model = new static;
83
84
        $orderColumnName = $model->determineOrderColumnName();
85
        $primaryKeyColumn = $model->getKeyName();
0 ignored issues
show
Bug introduced by
It seems like getKeyName() 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...
86
87
        foreach ($ids as $id) {
88
            static::withoutGlobalScope(SoftDeletingScope::class)
89
                ->where($primaryKeyColumn, $id)
90
                ->update([$orderColumnName => $startOrder++]);
91
        }
92
    }
93
94
    /*
95
     * Determine the column name of the order column.
96
     */
97
    protected function determineOrderColumnName(): string
98
    {
99
        if (
100
            isset($this->sortable['order_column_name']) &&
101
            ! empty($this->sortable['order_column_name'])
102
        ) {
103
            return $this->sortable['order_column_name'];
104
        }
105
106
        return 'order_column';
107
    }
108
109
    /**
110
     * Determine if the order column should be set when saving a new model instance.
111
     */
112
    public function shouldSortWhenCreating(): bool
113
    {
114
        return $this->sortable['sort_when_creating'] ?? true;
115
    }
116
117
    /**
118
     * Swaps the order of this model with the model 'below' this model.
119
     *
120
     * @return $this
121
     */
122 View Code Duplication
    public function moveOrderDown()
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...
123
    {
124
        $orderColumnName = $this->determineOrderColumnName();
125
126
        $swapWithModel = $this->buildSortQuery()->limit(1)
127
            ->ordered()
128
            ->where($orderColumnName, '>', $this->$orderColumnName)
129
            ->first();
130
131
        if (! $swapWithModel) {
132
            return $this;
133
        }
134
135
        return $this->swapOrderWithModel($swapWithModel);
136
    }
137
138
    /**
139
     * Swaps the order of this model with the model 'above' this model.
140
     *
141
     * @return $this
142
     */
143 View Code Duplication
    public function moveOrderUp()
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...
144
    {
145
        $orderColumnName = $this->determineOrderColumnName();
146
147
        $swapWithModel = $this->buildSortQuery()->limit(1)
148
            ->ordered('desc')
149
            ->where($orderColumnName, '<', $this->$orderColumnName)
150
            ->first();
151
152
        if (! $swapWithModel) {
153
            return $this;
154
        }
155
156
        return $this->swapOrderWithModel($swapWithModel);
157
    }
158
159
    /**
160
     * Swap the order of this model with the order of another model.
161
     *
162
     * @param \Spatie\EloquentSortable\Sortable $otherModel
163
     *
164
     * @return $this
165
     */
166
    public function swapOrderWithModel(Sortable $otherModel)
167
    {
168
        $orderColumnName = $this->determineOrderColumnName();
169
170
        $oldOrderOfOtherModel = $otherModel->$orderColumnName;
171
172
        $otherModel->$orderColumnName = $this->$orderColumnName;
173
        $otherModel->save();
174
175
        $this->$orderColumnName = $oldOrderOfOtherModel;
176
        $this->save();
0 ignored issues
show
Bug introduced by
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...
177
178
        return $this;
179
    }
180
181
    /**
182
     * Swap the order of two models.
183
     *
184
     * @param \Spatie\EloquentSortable\Sortable $model
185
     * @param \Spatie\EloquentSortable\Sortable $otherModel
186
     */
187
    public static function swapOrder(Sortable $model, Sortable $otherModel)
188
    {
189
        $model->swapOrderWithModel($otherModel);
190
    }
191
192
    /**
193
     * Moves this model to the first position.
194
     *
195
     * @return $this
196
     */
197
    public function moveToStart()
198
    {
199
        $firstModel = $this->buildSortQuery()->limit(1)
200
            ->ordered()
201
            ->first();
202
203
        if ($firstModel->id === $this->id) {
0 ignored issues
show
Bug introduced by
The property id does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
204
            return $this;
205
        }
206
207
        $orderColumnName = $this->determineOrderColumnName();
208
209
        $this->$orderColumnName = $firstModel->$orderColumnName;
210
        $this->save();
0 ignored issues
show
Bug introduced by
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...
211
212
        $this->buildSortQuery()->where($this->getKeyName(), '!=', $this->id)->increment($orderColumnName);
0 ignored issues
show
Bug introduced by
It seems like getKeyName() 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...
213
214
        return $this;
215
    }
216
217
    /**
218
     * Moves this model to the last position.
219
     *
220
     * @return $this
221
     */
222
    public function moveToEnd()
223
    {
224
        $maxOrder = $this->getHighestOrderNumber();
225
226
        $orderColumnName = $this->determineOrderColumnName();
227
228
        if ($this->$orderColumnName === $maxOrder) {
229
            return $this;
230
        }
231
232
        $oldOrder = $this->$orderColumnName;
233
234
        $this->$orderColumnName = $maxOrder;
235
        $this->save();
0 ignored issues
show
Bug introduced by
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...
236
237
        $this->buildSortQuery()->where($this->getKeyName(), '!=', $this->id)
0 ignored issues
show
Bug introduced by
It seems like getKeyName() 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...
238
            ->where($orderColumnName, '>', $oldOrder)
239
            ->decrement($orderColumnName);
240
241
        return $this;
242
    }
243
244
    /**
245
     * Move a model into a specified position
246
     * Positions starts at 1. 0 would be the same as start.
247
     *
248
     * @param int $newPosition
249
     *
250
     * @return $this
251
     */
252
    public function moveToPosition( int $newPosition ): self
253
    {
254
        $orderColumnName = $this->determineOrderColumnName();
255
256
        $newPosition = max($newPosition, 0);
257
258
        $currentPosition = (int) $this->$orderColumnName;
259
        $orderAtPosition = $this->getOrderNumberAtPosition($newPosition);
260
261
        // No need to do anything, it is already in the correct position
262
        if ($currentPosition === $newPosition){
263
            return $this;
264
        }
265
266
        if ( $newPosition > $currentPosition ){
267
            // The model is moving up
268
            $this->buildSortQuery()->where([[$this->getKeyName(), '!=', $this->id], [$orderColumnName, '>', $currentPosition], [$orderColumnName, '<=', $orderAtPosition]])->decrement($orderColumnName);
0 ignored issues
show
Bug introduced by
It seems like getKeyName() 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...
269
        } else {
270
            // The model is moving down
271
            $this->buildSortQuery()->where([[$this->getKeyName(), '!=', $this->id], [$orderColumnName, '<', $currentPosition], [$orderColumnName, '>=', $orderAtPosition]])->increment($orderColumnName);
0 ignored issues
show
Bug introduced by
It seems like getKeyName() 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...
272
        }
273
274
        $this->$orderColumnName = $orderAtPosition;
275
        $this->save();
0 ignored issues
show
Bug introduced by
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...
276
277
        return $this;
278
279
    }
280
281
    /**
282
     * Build eloquent builder of sortable.
283
     *
284
     * @return \Illuminate\Database\Eloquent\Builder
285
     */
286
    public function buildSortQuery()
287
    {
288
        return static::query();
289
    }
290
}
291