, (*1)
ActiveRecord Position Extension for Yii2
    
This extension provides support for ActiveRecord custom records order setup., (*2)
For license information check the LICENSE-file., (*3)
, (*4)
Installation
The preferred way to install this extension is through composer., (*5)
Either run, (*6)
php composer.phar require --prefer-dist yii2tech/ar-position
or add, (*7)
"yii2tech/ar-position": "*"
to the require section of your composer.json., (*8)
Usage
This extension provides support for custom records order setup via column-based position index., (*9)
This extension provides \yii2tech\ar\position\PositionBehavior ActiveRecord behavior for such solution
support in Yii2. You may attach it to your model class in the following way:, (*10)
<?php
use yii\db\ActiveRecord;
use yii2tech\ar\position\PositionBehavior;
class Item extends ActiveRecord
{
    public function behaviors()
    {
        return [
            'positionBehavior' => [
                'class' => PositionBehavior::className(),
                'positionAttribute' => 'position',
            ],
        ];
    }
}
Behavior uses the specific integer field of the database entity to set up position index.
Due to this the database entity, which the model refers to, must contain field positionAttribute., (*11)
In order to display custom list in correct order you should sort it by positionAttribute in ascending mode:, (*12)
<?php
$records = Item::find()->orderBy(['position' => SORT_ASC])->all();
foreach ($records as $record) {
    echo $record->position . ', ';
}
// outputs: 1, 2, 3, 4, 5,...
Position saving 
Being attached, behavior automatically fills up positionAttribute value for the new record, placing it to the end
of the list:, (*13)
<?php
echo Item::find()->count(); // outputs: 4
$item = new Item();
$item->save();
echo $item->position; // outputs: 5
However, you may setup position for the new record explicitly:, (*14)
<?php
echo Item::find()->count(); // outputs: 4
$item = new Item();
$item->position = 2; // enforce position '2'
$item->save();
echo $item->position; // outputs: 2 !!!
Position switching 
Existing record can be moved to another position using following methods:, (*15)
- 
movePrev() - moves record by one position towards the start of the list. 
- 
moveNext() - moves record by one position towards the end of the list. 
- 
moveFirst() - moves record to the start of the list. 
- 
moveLast() - moves record to the end of the list. 
- 
moveToPosition() - moves owner record to the specific position. 
You may as well change record position through the attribute, provided to positionAttribute directly:, (*16)
<?php
$item = Item::find()->andWhere(['position' => 3])->one();
$item->position = 5; // switch position to '5'
$item->save();
Position in group 
Sometimes single database entity contains several listings, which require custom ordering, separated logically
by grouping attributes. For example: FAQ questions may be grouped by categories, while inside single category
questions should be ordered manually. For this case \yii2tech\ar\position\PositionBehavior::$groupAttributes
can be used:, (*17)
<?php
use yii\db\ActiveRecord;
use yii2tech\ar\position\PositionBehavior;
class FaqQuestion extends ActiveRecord
{
    public function behaviors()
    {
        return [
            'positionBehavior' => [
                'class' => PositionBehavior::className(),
                'positionAttribute' => 'position',
                'groupAttributes' => [
                    'categoryId' // multiple lists varying by 'categoryId'
                ],
            ],
        ];
    }
}
In this case behavior will use owner values of groupAttributes as additional condition for position
calculation and changing:, (*18)
<?php
echo FaqQuestion::find()->andWhere(['categoryId' => 1])->count(); // outputs: '4'
echo FaqQuestion::find()->andWhere(['categoryId' => 2])->count(); // outputs: '7'
$record = new FaqQuestion();
$record->categoryId = 1;
$record->save();
echo $record->position; // outputs: '5'
$record = new FaqQuestion();
$record->categoryId = 2;
$record->save();
echo $record->position; // outputs: '8'
List navigation 
Records with custom position order applied make a chained list, which you may navigate if necessary.
You may use \yii2tech\ar\position\PositionBehavior::getIsFirst() and \yii2tech\ar\position\PositionBehavior::getIsLast()
methods to determine if particular record is the first or last one in the list. For example:, (*19)
<?php
echo Item::find()->count(); // outputs: 10
$firstItem = Item::find()->andWhere(['position' => 1])->one();
echo $firstItem->getIsFirst(); // outputs: true
echo $firstItem->getIsLast(); // outputs: false
$lastItem = Item::find()->andWhere(['position' => 10])->one();
echo $lastItem->getIsFirst(); // outputs: false
echo $lastItem->getIsLast(); // outputs: true
Having a particular record instance, you can always find record, which is located at next or previous position to it,
using \yii2tech\ar\position\PositionBehavior::getNext() or \yii2tech\ar\position\PositionBehavior::getPrev() method.
For example:, (*20)
<?php
$item = Item::find()->andWhere(['position' => 5])->one();
$nextItem = $item->findNext();
echo $nextItem->position; // outputs: 6
$prevItem = $item->findPrev();
echo $prevItem->position; // outputs: 4
You may as well get the first and the last records in the list. For example:, (*21)
<?php
echo Item::find()->count(); // outputs: 10
$item = Item::find()->andWhere(['position' => 5])->one();
$firstItem = $item->findFirst();
echo $firstItem->position; // outputs: 1
$lastItem = $item->findLast();
echo $lastItem->position; // outputs: 10