123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- <?php
- namespace common\behaviors;
- use yii\base\Behavior;
- use yii\base\ModelEvent;
- use yii\db\BaseActiveRecord;
- /**
- * PositionBehavior allows managing custom order for the records in the database.
- * 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]].
- *
- * ```php
- * class Item extends ActiveRecord
- * {
- * public function behaviors()
- * {
- * return [
- * 'positionBehavior' => [
- * 'class' => PositionBehavior::className(),
- * 'positionAttribute' => 'position',
- * ],
- * ];
- * }
- * }
- * ```
- *
- * @property BaseActiveRecord $owner owner ActiveRecord instance.
- *
- * @author Paul Klimov <klimov.paul@gmail.com>
- * @since 1.0
- */
- class PositionBehavior extends Behavior
- {
- /**
- * @var string name owner attribute, which will store position value.
- * This attribute should be an integer.
- */
- public $positionAttribute = 'position';
- /**
- * @var array list of owner attribute names, which values split records into the groups,
- * which should have their own positioning.
- * Example: `['group_id', 'category_id']`
- */
- public $groupAttributes = [];
- /**
- * @var integer position value, which should be applied to the model on its save.
- * Internal usage only.
- */
- private $positionOnSave;
- /**
- * Moves owner record by one position towards the start of the list.
- * @return boolean movement successful.
- */
- public function movePrev()
- {
- $positionAttribute = $this->positionAttribute;
- /* @var $previousRecord BaseActiveRecord */
- $previousRecord = $this->owner->find()
- ->andWhere($this->createGroupConditionAttributes())
- ->andWhere([$positionAttribute => ($this->owner->$positionAttribute - 1)])
- ->one();
- if (empty($previousRecord)) {
- return false;
- }
- $previousRecord->updateAttributes([
- $positionAttribute => $this->owner->$positionAttribute
- ]);
- $this->owner->updateAttributes([
- $positionAttribute => $this->owner->$positionAttribute - 1
- ]);
- return true;
- }
- /**
- * Moves owner record by one position towards the end of the list.
- * @return boolean movement successful.
- */
- public function moveNext()
- {
- $positionAttribute = $this->positionAttribute;
- /* @var $nextRecord BaseActiveRecord */
- $nextRecord = $this->owner->find()
- ->andWhere($this->createGroupConditionAttributes())
- ->andWhere([$positionAttribute => ($this->owner->$positionAttribute + 1)])
- ->one();
- if (empty($nextRecord)) {
- return false;
- }
- $nextRecord->updateAttributes([
- $positionAttribute => $this->owner->$positionAttribute
- ]);
- $this->owner->updateAttributes([
- $positionAttribute => $this->owner->getAttribute($positionAttribute) + 1
- ]);
- return true;
- }
- /**
- * Moves owner record to the start of the list.
- * @return boolean movement successful.
- */
- public function moveFirst()
- {
- $positionAttribute = $this->positionAttribute;
- if ($this->owner->$positionAttribute == 1) {
- return false;
- }
- $this->owner->updateAllCounters(
- [
- $positionAttribute => +1
- ],
- [
- 'and',
- $this->createGroupConditionAttributes(),
- ['<', $positionAttribute, $this->owner->$positionAttribute]
- ]
- );
- $this->owner->updateAttributes([
- $positionAttribute => 1
- ]);
- return true;
- }
- /**
- * Moves owner record to the end of the list.
- * @return boolean movement successful.
- */
- public function moveLast()
- {
- $positionAttribute = $this->positionAttribute;
- $recordsCount = $this->countGroupRecords();
- if ($this->owner->getAttribute($positionAttribute) == $recordsCount) {
- return false;
- }
- $this->owner->updateAllCounters(
- [
- $positionAttribute => -1
- ],
- [
- 'and',
- $this->createGroupConditionAttributes(),
- ['>', $positionAttribute, $this->owner->$positionAttribute]
- ]
- );
- $this->owner->updateAttributes([
- $positionAttribute => $recordsCount
- ]);
- return true;
- }
- /**
- * Moves owner record to the specific position.
- * If specified position exceeds the total number of records,
- * owner will be moved to the end of the list.
- * @param integer $position number of the new position.
- * @return boolean movement successful.
- */
- public function moveToPosition($position)
- {
- if (!is_numeric($position) || $position < 1) {
- return false;
- }
- $positionAttribute = $this->positionAttribute;
- $oldRecord = $this->owner->findOne($this->owner->getPrimaryKey());
- $oldRecordPosition = $oldRecord->$positionAttribute;
- if ($oldRecordPosition == $position) {
- return true;
- }
- if ($position < $oldRecordPosition) {
- // Move Up:
- $this->owner->updateAllCounters(
- [
- $positionAttribute => +1
- ],
- [
- 'and',
- $this->createGroupConditionAttributes(),
- ['>=', $positionAttribute, $position],
- ['<', $positionAttribute, $oldRecord->$positionAttribute],
- ]
- );
- $this->owner->updateAttributes([
- $positionAttribute => $position
- ]);
- return true;
- } else {
- // Move Down:
- $recordsCount = $this->countGroupRecords();
- if ($position >= $recordsCount) {
- return $this->moveLast();
- }
- $this->owner->updateAllCounters(
- [
- $positionAttribute => -1
- ],
- [
- 'and',
- $this->createGroupConditionAttributes(),
- ['>', $positionAttribute, $oldRecord->$positionAttribute],
- ['<=', $positionAttribute, $position],
- ]
- );
- $this->owner->updateAttributes([
- $positionAttribute => $position
- ]);
- return true;
- }
- }
- /**
- * Creates array of group attributes with their values.
- * @see groupAttributes
- * @return array attribute conditions.
- */
- protected function createGroupConditionAttributes()
- {
- $condition = [];
- if (!empty($this->groupAttributes)) {
- foreach ($this->groupAttributes as $attribute) {
- $condition[$attribute] = $this->owner->$attribute;
- }
- }
- return $condition;
- }
- /**
- * Finds the number of records which belongs to the group of the owner.
- * @see groupAttributes
- * @return integer records count.
- */
- protected function countGroupRecords()
- {
- $query = $this->owner->find();
- if (!empty($this->groupAttributes)) {
- $query->andWhere($this->createGroupConditionAttributes());
- }
- return $query->count();
- }
- // Events :
- /**
- * @inheritdoc
- */
- public function events()
- {
- return [
- BaseActiveRecord::EVENT_BEFORE_INSERT => 'beforeInsert',
- BaseActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate',
- BaseActiveRecord::EVENT_AFTER_INSERT => 'afterSave',
- BaseActiveRecord::EVENT_AFTER_UPDATE => 'afterSave',
- BaseActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
- ];
- }
- /**
- * Handles owner 'beforeInsert' owner event, preparing its positioning.
- * @param ModelEvent $event event instance.
- */
- public function beforeInsert($event)
- {
- $positionAttribute = $this->positionAttribute;
- if ($this->owner->$positionAttribute > 0) {
- $this->positionOnSave = $this->owner->$positionAttribute;
- }
- $this->owner->$positionAttribute = $this->countGroupRecords() + 1;
- }
- /**
- * Handles owner 'beforeInsert' owner event, preparing its possible re-positioning.
- * @param ModelEvent $event event instance.
- */
- public function beforeUpdate($event)
- {
- $positionAttribute = $this->positionAttribute;
- $isNewGroup = false;
- foreach ($this->groupAttributes as $groupAttribute) {
- if ($this->owner->isAttributeChanged($groupAttribute, false)) {
- $isNewGroup = true;
- break;
- }
- }
- if ($isNewGroup) {
- $oldRecord = $this->owner->findOne($this->owner->getPrimaryKey());
- $oldRecord->moveLast();
- $this->positionOnSave = $this->owner->$positionAttribute;
- $this->owner->$positionAttribute = $this->countGroupRecords() + 1;
- } else {
- if ($this->owner->isAttributeChanged($positionAttribute, false)) {
- $this->positionOnSave = $this->owner->$positionAttribute;
- $this->owner->$positionAttribute = $this->owner->getOldAttribute($positionAttribute);
- }
- }
- }
- /**
- * This event raises after owner inserted or updated.
- * It applies previously set [[positionOnSave]].
- * This event supports other functionality.
- * @param ModelEvent $event event instance.
- */
- public function afterSave($event)
- {
- if ($this->positionOnSave !== null) {
- $this->moveToPosition($this->positionOnSave);
- }
- $this->positionOnSave = null;
- }
- /**
- * Handles owner 'beforeDelete' owner event, moving it to the end of the list before deleting.
- * @param ModelEvent $event event instance.
- */
- public function beforeDelete($event)
- {
- $this->moveLast();
- }
- }
|