Skip to content

Commit bcf99b4

Browse files
authored
Merge pull request #7 from nevadskiy/dev
Release 0.5.0
2 parents 374ee2f + 44e5d8a commit bcf99b4

19 files changed

+619
-384
lines changed

CHANGELOG.md

+23
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- Possibility to create model in the middle of the sequence
14+
- Possibility to create model in the beginning of the sequence
15+
- Extra argument for shift amount in `shiftToStart` and `shiftToEnd` methods
16+
- Possibility to update positions without shifting other models
17+
18+
### Changed
19+
20+
- Rename method `getInitPosition` to `startPosition`
21+
- Models now are shifted after the model update
22+
23+
## [0.4.1] - 2023-02-08
24+
25+
### Added
26+
27+
- Possibility to update `position` attribute along with other attributes
28+
1129
## [0.4.0] - 2022-05-08
1230

1331
### Added
32+
1433
- Laravel 9 support
1534

1635
## [0.3.0] - 2021-06-24
1736

1837
### Fixed
38+
1939
- Fix position query scoping for relations
2040

2141
## [0.2.0] - 2021-06-19
2242

2343
### Added
44+
2445
- Documentation
2546
- `OrderByPosition` global scope
2647
- Support for models delete
2748
- `swap` method
2849
- Add PHP 8 support
2950

3051
### Changed
52+
3153
- Rename `arrangeByIds` into `arrangeByKeys`
3254
- Extract `arrangeByKeys` method into query builder
3355
- Extract shift methods into query builder
3456

3557
## [0.1.0] - 2021-06-13
3658

3759
### Added
60+
3861
- Base ordering features

README.md

+28-7
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,26 @@ Models simply have an integer `position` attribute corresponding to the model's
5858
5959
The `position` attribute is a kind of array index and is automatically inserted when a new model is created.
6060
61-
By default, the model takes a position at the very end of the sequence.
62-
63-
The initial position gets a `0` value by default. To change that, override the `getInitPosition` method in the model.
61+
The starting position gets a `0` value by default. To change that, override the `startPosition` method in the model:
6462
6563
```php
66-
public function getInitPosition(): int
64+
public function startPosition(): int
6765
{
6866
return 0;
6967
}
7068
```
7169
70+
By default, the created model takes a position at the very end of the sequence. If you need to customize that behaviour, you can override the `nextPosition` method:
71+
72+
```php
73+
public function nextPosition(): ?int
74+
{
75+
return $this->startPosition();
76+
}
77+
```
78+
79+
In that example, a new model will be created in the beginning of the sequence.
80+
7281
### Deleting models
7382
7483
When a model is deleted, the positions of other records in the sequence are updated automatically.
@@ -110,12 +119,12 @@ $category->update([
110119
111120
The positions of other models will be automatically recalculated as well.
112121
113-
#### Move
122+
#### Shift / Move
114123
115-
You can also use the `move` method that sets a new position value and updates the model immediately:
124+
You can also use the `shift method that sets a new position value and updates the model immediately:
116125
117126
```php
118-
$category->move(3);
127+
$category->shift(3);
119128
```
120129
121130
#### Swap
@@ -126,6 +135,18 @@ The `swap` method swaps the position of two models.
126135
$category->swap($anotherCategory);
127136
```
128137
138+
#### Without shifting
139+
140+
By default, the package automatically updates the position of other models when the model position is updated.
141+
142+
If you want to update the model position without shifting the positions of other models, you can use the `withoutShifting` method:
143+
144+
```php
145+
Category::withoutShifting(function () {
146+
$category->move(5);
147+
})
148+
```
149+
129150
#### Arrange
130151
131152
It is also possible to arrange models by their IDs.

src/HasPosition.php

+88-30
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,58 @@
1212
trait HasPosition
1313
{
1414
/**
15-
* Boot the position trait.
15+
* Indicates if the model should shift position of other models in the sequence.
16+
*
17+
* @var bool
18+
*/
19+
protected static $shiftPosition = true;
20+
21+
/**
22+
* Boot the trait.
1623
*/
1724
public static function bootHasPosition(): void
1825
{
1926
static::addGlobalScope(new PositioningScope());
2027

2128
static::creating(static function (self $model) {
22-
$model->assignPositionIfMissing();
29+
$model->assignPosition();
2330
});
2431

25-
static::updating(static function (self $model) {
26-
if ($model->isDirty($model->getPositionColumn())) {
27-
$model->shiftBeforeMove($model->getPosition(), $model->getOriginal($model->getPositionColumn()));
32+
static::created(static function (self $model) {
33+
if (static::shouldShiftPosition() && $model->isMoving()) {
34+
$model->newPositionQuery()->whereKeyNot($model->getKey())->shiftToEnd($model->getPosition());
35+
}
36+
});
37+
38+
static::updated(static function (self $model) {
39+
if (static::shouldShiftPosition() && $model->isMoving()) {
40+
[$currentPosition, $previousPosition] = [$model->getPosition(), $model->getOriginal($model->getPositionColumn())];
41+
42+
if ($currentPosition < $previousPosition) {
43+
$model->newPositionQuery()->whereKeyNot($model->getKey())->shiftToEnd($currentPosition, $previousPosition);
44+
} elseif ($currentPosition > $previousPosition) {
45+
$model->newPositionQuery()->whereKeyNot($model->getKey())->shiftToStart($previousPosition, $currentPosition);
46+
}
2847
}
2948
});
3049

3150
static::deleted(static function (self $model) {
32-
$model->newPositionQuery()->shiftToStart($model->getPosition());
51+
if (static::shouldShiftPosition()) {
52+
$model->newPositionQuery()->shiftToStart($model->getPosition());
53+
}
3354
});
3455
}
3556

57+
/**
58+
* Initialize the trait.
59+
*/
60+
public function initializeHasPosition(): void
61+
{
62+
$this->mergeCasts([
63+
$this->getPositionColumn() => 'int',
64+
]);
65+
}
66+
3667
/**
3768
* Get the name of the "position" column.
3869
*/
@@ -44,7 +75,7 @@ public function getPositionColumn(): string
4475
/**
4576
* Get a value of the starting position.
4677
*/
47-
public function getInitPosition(): int
78+
public function startPosition(): int
4879
{
4980
return 0;
5081
}
@@ -57,6 +88,30 @@ public function alwaysOrderByPosition(): bool
5788
return false;
5889
}
5990

91+
/**
92+
* Execute a callback without shifting position of models.
93+
*/
94+
public static function withoutShiftingPosition(callable $callback)
95+
{
96+
$shifting = static::$shiftPosition;
97+
98+
static::$shiftPosition = false;
99+
100+
$result = $callback();
101+
102+
static::$shiftPosition = $shifting;
103+
104+
return $result;
105+
}
106+
107+
/**
108+
* Determine if the model should shift position of other models in the sequence.
109+
*/
110+
public static function shouldShiftPosition(): bool
111+
{
112+
return static::$shiftPosition;
113+
}
114+
60115
/**
61116
* Get the position value of the model.
62117
*/
@@ -68,7 +123,7 @@ public function getPosition(): ?int
68123
/**
69124
* Set the position to the given value.
70125
*/
71-
public function setPosition(int $position): Model
126+
public function setPosition(?int $position): Model
72127
{
73128
return $this->setAttribute($this->getPositionColumn(), $position);
74129
}
@@ -103,12 +158,20 @@ public function move(int $newPosition): bool
103158
return $this->setPosition($newPosition)->save();
104159
}
105160

161+
/**
162+
* Determine if the model is currently moving to a new position.
163+
*/
164+
public function isMoving(): bool
165+
{
166+
return $this->isDirty($this->getPositionColumn());
167+
}
168+
106169
/**
107170
* Swap the model position with another model.
108171
*/
109172
public function swap(self $that): void
110173
{
111-
static::withoutEvents(function () use ($that) {
174+
static::withoutShiftingPosition(function () use ($that) {
112175
$thisPosition = $this->getPosition();
113176
$thatPosition = $that->getPosition();
114177

@@ -129,32 +192,39 @@ protected function newPositionQuery(): Builder
129192
}
130193

131194
/**
132-
* Assign the next position value to the model if it is missing.
195+
* Assign the next position value to the model.
133196
*/
134-
protected function assignPositionIfMissing(): void
197+
protected function assignPosition(): void
135198
{
136-
if (null === $this->getPosition()) {
137-
$this->assignNextPosition();
199+
if ($this->getPosition() === null) {
200+
$this->setPosition($this->nextPosition());
201+
}
202+
203+
if ($this->getPosition() === null) {
204+
$this->setPosition($this->getEndPosition());
205+
206+
// Sync original attribute to not shift other models when the model will be created
207+
$this->syncOriginalAttribute($this->getPositionColumn());
138208
}
139209
}
140210

141211
/**
142-
* Assign the next position value to the model.
212+
* Get the next position in the sequence for the model.
143213
*/
144-
protected function assignNextPosition(): Model
214+
protected function nextPosition(): ?int
145215
{
146-
return $this->setPosition($this->getNextPosition());
216+
return null;
147217
}
148218

149219
/**
150220
* Determine the next position value in the model sequence.
151221
*/
152-
protected function getNextPosition(): int
222+
protected function getEndPosition(): int
153223
{
154224
$maxPosition = $this->getMaxPosition();
155225

156226
if (null === $maxPosition) {
157-
return $this->getInitPosition();
227+
return $this->startPosition();
158228
}
159229

160230
return $maxPosition + 1;
@@ -167,16 +237,4 @@ protected function getMaxPosition(): ?int
167237
{
168238
return $this->newPositionQuery()->max($this->getPositionColumn());
169239
}
170-
171-
/**
172-
* Shift models in a sequence before the move to a new position.
173-
*/
174-
protected function shiftBeforeMove(int $newPosition, int $oldPosition): void
175-
{
176-
if ($newPosition < $oldPosition) {
177-
$this->newPositionQuery()->shiftToEnd($newPosition, $oldPosition);
178-
} elseif ($newPosition > $oldPosition) {
179-
$this->newPositionQuery()->shiftToStart($oldPosition, $newPosition);
180-
}
181-
}
182240
}

0 commit comments

Comments
 (0)