Skip to content

Commit eb378c0

Browse files
authored
Merge pull request #24 from josegonzalez/patch-1
Add ability to move up/down in list
2 parents bb827fe + 6ccbe01 commit eb378c0

File tree

3 files changed

+222
-18
lines changed

3 files changed

+222
-18
lines changed

.travis.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ matrix:
2525
- php: 7.0
2626
env: PHPCS=1 DEFAULT=0
2727

28+
- php: 7.0
29+
env: PHPSTAN=1 DEFAULT=0
30+
2831
before_script:
2932
- if [[ $TRAVIS_PHP_VERSION != 7.0 ]]; then phpenv config-rm xdebug.ini; fi
3033

@@ -39,8 +42,11 @@ before_script:
3942
script:
4043
- if [[ $DEFAULT = 1 && $TRAVIS_PHP_VERSION != 7.0 ]]; then vendor/bin/phpunit; fi
4144
- if [[ $DEFAULT = 1 && $TRAVIS_PHP_VERSION = 7.0 ]]; then vendor/bin/phpunit --coverage-clover=clover.xml; fi
45+
4246
- if [[ $PHPCS = 1 ]]; then vendor/bin/phpcs -n -p --extensions=php --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests; fi
4347

48+
- if [[ $PHPSTAN = 1 ]]; then composer require --dev phpstan/phpstan:^0.8 && vendor/bin/phpstan analyse -l 4 src; fi
49+
4450
after_success:
4551
- if [[ $DEFAULT = 1 && $TRAVIS_PHP_VERSION = 7.0 ]]; then bash <(curl -s https://codecov.io/bash); fi
4652

src/Model/Behavior/SequenceBehavior.php

Lines changed: 128 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use ArrayObject;
55
use Cake\Database\Expression\IdentifierExpression;
6+
use Cake\Datasource\EntityInterface;
67
use Cake\Event\Event;
78
use Cake\ORM\Behavior;
89
use Cake\ORM\Entity;
@@ -127,23 +128,9 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options)
127128
$config = $this->config();
128129

129130
$newOrder = null;
130-
$newScope = [];
131-
132-
// If scope are specified and data for all scope fields is not
133-
// provided we cannot calculate new order
134-
if ($config['scope']) {
135-
$newScope = $entity->extract($config['scope']);
136-
if (count($newScope) !== count($config['scope'])) {
137-
return;
138-
}
139-
140-
// Modify where clauses when NULL values are used
141-
foreach ($newScope as $field => $value) {
142-
if (is_null($value)) {
143-
$newScope[$field . ' IS'] = $value;
144-
unset($newScope[$field]);
145-
}
146-
}
131+
$newScope = $this->_getScope($entity);
132+
if ($newScope === false) {
133+
return;
147134
}
148135

149136
$orderField = $config['order'];
@@ -258,6 +245,97 @@ public function beforeDelete(Event $event, Entity $entity)
258245
);
259246
}
260247

248+
/**
249+
* Decrease the position of the entity on the list
250+
*
251+
* If a "higher" entity exists, this will also swap positions with it
252+
*
253+
* @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved.
254+
* @return bool
255+
*/
256+
public function moveUp(EntityInterface $entity)
257+
{
258+
return $this->_movePosition($entity, $direction = '-');
259+
}
260+
261+
/**
262+
* Increase the position of the entity on the list
263+
*
264+
* If a "lower" entity exists, this will also swap positions with it
265+
*
266+
* @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved.
267+
* @return bool
268+
*/
269+
public function moveDown(EntityInterface $entity)
270+
{
271+
return $this->_movePosition($entity, $direction = '+');
272+
}
273+
274+
/**
275+
* Change the position of the entity on the list by a single position
276+
*
277+
* If an entity that conflicts with the new position already exists, this
278+
* will also swap positions with it
279+
*
280+
* @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved.
281+
* @param string $direction Whether to increment or decrement the field.
282+
*
283+
* @return bool
284+
*/
285+
protected function _movePosition(EntityInterface $entity, $direction = '+')
286+
{
287+
if ($entity->isNew()) {
288+
return false;
289+
}
290+
291+
$scope = $this->_getScope($entity);
292+
if ($scope === false) {
293+
return false;
294+
}
295+
296+
$config = $this->config();
297+
$table = $this->_table;
298+
299+
$table->removeBehavior('Sequence');
300+
301+
$return = $table->connection()->transactional(
302+
function ($connection) use ($table, $entity, $config, $scope, $direction) {
303+
$orderField = $config['order'];
304+
// Nothing to do if trying to move up entity already at first position
305+
if ($direction === '-' && $entity->get($orderField) === $config['start']) {
306+
return true;
307+
}
308+
309+
$oldOrder = $entity->get($orderField);
310+
$newOrder = $entity->get($orderField) - 1;
311+
if ($direction === '+') {
312+
$newOrder = $entity->get($orderField) + 1;
313+
}
314+
315+
$previousEntity = $table->find()
316+
->where(array_merge($scope, [$orderField => $newOrder]))
317+
->first();
318+
if (!empty($previousEntity)) {
319+
$previousEntity->set($orderField, $oldOrder);
320+
if (!$table->save($previousEntity, ['atomic' => false, 'checkRules' => false])) {
321+
return false;
322+
}
323+
// Nothing to do if trying to move down entity already at last position
324+
} elseif ($direction === '+') {
325+
return true;
326+
}
327+
328+
$entity->set($orderField, $newOrder);
329+
330+
return $table->save($entity, ['atomic' => false, 'checkRules' => false]);
331+
}
332+
);
333+
334+
$table->addBehavior('ADmad/Sequence.Sequence', $config);
335+
336+
return (bool)$return;
337+
}
338+
261339
/**
262340
* Set order for list of records provided.
263341
*
@@ -277,7 +355,7 @@ public function setOrder(array $records)
277355
$table->removeBehavior('Sequence');
278356

279357
$return = $table->connection()->transactional(
280-
function ($connection) use ($table, $records, $config) {
358+
function ($connection) use ($table, $records) {
281359
$order = $this->_config['start'];
282360
$field = $this->_config['order'];
283361

@@ -352,6 +430,38 @@ protected function _getOldValues(Entity $entity)
352430
return [$order, $values];
353431
}
354432

433+
/**
434+
* Get scope values.
435+
*
436+
* @param \Cake\Datasource\EntityInterface $entity Entity.
437+
*
438+
* @return array|bool
439+
*/
440+
protected function _getScope(EntityInterface $entity)
441+
{
442+
$scope = [];
443+
$config = $this->config();
444+
445+
// If scope are specified and data for all scope fields is not
446+
// provided we cannot calculate new order
447+
if ($config['scope']) {
448+
$scope = $entity->extract($config['scope']);
449+
if (count($scope) !== count($config['scope'])) {
450+
return false;
451+
}
452+
453+
// Modify where clauses when NULL values are used
454+
foreach ($scope as $field => $value) {
455+
if (is_null($value)) {
456+
$scope[$field . ' IS'] = $value;
457+
unset($scope[$field]);
458+
}
459+
}
460+
}
461+
462+
return $scope;
463+
}
464+
355465
/**
356466
* Returns the current highest order of all records in the set. When a new
357467
* record is added to the set, it is added at the current highest order, plus

tests/TestCase/Model/Behavior/SequenceBehaviorTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,94 @@ public function testSetOrder()
310310
$this->assertOrder([6, 7, 8, 9, 10], $GroupedItems, ['group_field' => 2]);
311311
}
312312

313+
/**
314+
* testMoveUp
315+
*
316+
* @return void
317+
*/
318+
public function testMoveUp()
319+
{
320+
$Items = TableRegistry::get('Items', [
321+
'className' => 'ADmad\Sequence\Test\TestCase\Model\Behavior\Items',
322+
]);
323+
324+
$entity = $Items->get(4);
325+
$result = $Items->moveUp($entity);
326+
327+
$this->assertTrue($result);
328+
$this->assertOrder([1, 2, 4, 3, 5], $Items);
329+
330+
// Move up entity already at first position
331+
$entity = $Items->get(1);
332+
$result = $Items->moveUp($entity);
333+
334+
$this->assertTrue($result);
335+
$this->assertOrder([1, 2, 4, 3, 5], $Items);
336+
337+
$GroupedItems = TableRegistry::get('GroupedItems', [
338+
'table' => 'grouped_items',
339+
'alias' => 'GroupedItems',
340+
'className' => 'ADmad\Sequence\Test\TestCase\Model\Behavior\GroupedItems',
341+
]);
342+
343+
$entity = $GroupedItems->get(4);
344+
$result = $GroupedItems->moveUp($entity);
345+
346+
$this->assertTrue($result);
347+
$this->assertOrder([1, 2, 4, 3, 5], $GroupedItems, ['group_field' => 1]);
348+
349+
// Move up entity already at first position
350+
$entity = $GroupedItems->get(1);
351+
$result = $GroupedItems->moveUp($entity);
352+
353+
$this->assertTrue($result);
354+
$this->assertOrder([1, 2, 4, 3, 5], $GroupedItems, ['group_field' => 1]);
355+
}
356+
357+
/**
358+
* moveDown
359+
*
360+
* @return void
361+
*/
362+
public function testMoveDown()
363+
{
364+
$Items = TableRegistry::get('Items', [
365+
'className' => 'ADmad\Sequence\Test\TestCase\Model\Behavior\Items',
366+
]);
367+
368+
$entity = $Items->get(2);
369+
$result = $Items->moveDown($entity);
370+
371+
$this->assertTrue($result);
372+
$this->assertOrder([1, 3, 2, 4, 5], $Items);
373+
374+
// Move down entity already at last position
375+
$entity = $Items->get(5);
376+
$result = $Items->moveDown($entity);
377+
378+
$this->assertTrue($result);
379+
$this->assertOrder([1, 3, 2, 4, 5], $Items);
380+
381+
$GroupedItems = TableRegistry::get('GroupedItems', [
382+
'table' => 'grouped_items',
383+
'alias' => 'GroupedItems',
384+
'className' => 'ADmad\Sequence\Test\TestCase\Model\Behavior\GroupedItems',
385+
]);
386+
387+
$entity = $GroupedItems->get(2);
388+
$result = $GroupedItems->moveDown($entity);
389+
390+
$this->assertTrue($result);
391+
$this->assertOrder([1, 3, 2, 4, 5], $GroupedItems, ['group_field' => 1]);
392+
393+
// Move down entity already at last position
394+
$entity = $GroupedItems->get(5);
395+
$result = $GroupedItems->moveDown($entity);
396+
397+
$this->assertTrue($result);
398+
$this->assertOrder([1, 3, 2, 4, 5], $GroupedItems, ['group_field' => 1]);
399+
}
400+
313401
/**
314402
* [assertOrder description].
315403
*

0 commit comments

Comments
 (0)