简体   繁体   中英

Doctrine 2 Sortable

The customer needs to change the order of items as he wants, which means I need some "order" or "sequence" column to save actual position of each item.

How can I implement this using Doctrine 2?

I would implement it using Doctrine's event system . To add behaviors, I will typically write an event subscriber and enforce the rules with an interface implemented by my entity classes. The actual logic I keep in a service object somewhere (you're on your own for that).

use Doctrine\Common\EventSubscriber,
    Doctrine\ORM\Events,
    Doctrine\ORM\Event\LifecycleEventArgs,
    Doctrine\ORM\Event\PreUpdateEventArgs;


class SortableBehavior implements EventSubscriber
{
   public function getSubscribedEvents()
   {
      return array(Events::prePersist, Events::preUpdate);
   }

   public function prePersist(LifeCycleEventsArgs $args)
   {
     // the entity being persisted
     $entity = $args->getEntity();
     if ($entity instanceof SortableInterface) {
        //perform sorting magic
     }
   }

   public function preUpdate(preUpdateEventsArgs $args)
   {
     // the entity being updated
     $entity = $args->getEntity();
     if ($entity instanceof SortableInterface) {
        //perform sorting magic
     }
   }
}

Don't forget to register the subscribers when bootstrapping your app:

$eventManager = $entityManager->getEventManager();
$eventManager->addEventSubscriber(new SortableBehavior());

There is a nice repository on GitHub with various extensions for Doctrine, and one of them is just a Sortable feature.

Check it out: https://github.com/l3pp4rd/DoctrineExtensions

Sortable documentation

Add another column:

sort_order:
      type: integer(4)
      default: 10

Now changing that entry from 10 to anything else moves it in the resultset which would be retrieved by something like:

$this->result = Doctrine_Core::getTable('CustomerTable')
  ->createQuery('a')->orderBy("a.sort_order asc, a.id desc")->execute();

You can automatically retrieve collections ordered by a given field:

http://www.doctrine-project.org/projects/orm/2.0/docs/reference/association-mapping/en#ordering-to-many-collections

That way you can define the order using the "sort_order" column. Then you need some methods to work with this that update the sort columns correctly using DQL Update statements for performance reasons, ie "moveTo($pos, $entity)" and issuing the relevant number of updates.

But this solves half of your issue if you have to iterate the collection in the same request in that the order is changed. This is however rare a use-case so it can be often ignored.

I did some tests with the EventSubscriber but couldn't get it to work properly so I decided on just directly adding the code to my class (q&d). I use Zend Framework so change the fetching of the entity manager if you have a different solution.

It works as follows:

  1. When creating the entity use the getLast() and add the new entity last ($last_entity->getSort() + 1)
  2. Add the sortUp and sortDown functions and their subroutines to the entity code
  3. Add "sort ASC" in all your DQL queries where you want it sorted

The entity code is as follows:

/**
 * Searches for the element above and switches them
 * with eachother. Performs a EntityManager::flush()
 * to save the results after the switch.
 */
public function sortUp() {

    try {
        $em = \Zend_Registry::get("entitymanager");

        $class_name = get_class($this);
        $dql = "SELECT ut FROM $class_name ut WHERE ut.inactive IS NULL AND ut.inactive IS NULL AND ut.sort < '" . $this->getSort() . "' ORDER BY ut.sort DESC";
        $query = $em->createQuery($dql);
        $query->setMaxResults(1);
        $ut = $query->getResult();
    } catch (Exception $exc) {
        throw new Exception("Error when looking for sortable partner: " . $exc->getMessage());
    }

    if (count($ut)) {
        $this->_switchSortAndSave($ut[0]);
    }
}

/**
 * Searches for the element below and switches them
 * with eachother. Performs a EntityManager::flush()
 * to save the results after the switch.
 */
public function sortDown() {
    try {
        $em = \Zend_Registry::get("entitymanager");

        $class_name = get_class($this);
        $dql = "SELECT ut FROM $class_name ut WHERE ut.inactive IS NULL AND ut.sort > '" . $this->getSort() . "' ORDER BY ut.sort ASC";
        $query = $em->createQuery($dql);
        $query->setMaxResults(1);
        $ut = $query->getResult();
    } catch (Exception $exc) {
        throw new Exception("Error when looking for sortable partner: " . $exc->getMessage());
    }


    if (count($ut)) {
        $this->_switchSortAndSave($ut[0]);
    }
}

private function _switchSortAndSave(\Entities\Usertype $switch_entity) {
    $new_sort = $switch_entity->getSort();
    $switch_entity->setSort($this->getSort());
    $this->setSort($new_sort);

    $em = \Zend_Registry::get("entitymanager");
    $em->persist($switch_entity);
    $em->persist($this);
    $em->flush();
}

/**
 * Looks for the last entry according to sort order
 * and returns that if found.
 * 
 * @return Entity|null
 */
public static function findLast() {
    try {
        $em = \Zend_Registry::get("entitymanager");

        $class_name = get_called_class();
        $dql = "SELECT ut FROM $class_name ut ORDER BY ut.sort DESC";
        $query = $em->createQuery($dql);
        $query->setMaxResults(1);
        $ut = $query->getResult();
    } catch (Exception $exc) {
        throw new Exception("Error when searching for last $class_name: " . $exc->getMessage());
    }

    if (count($ut))
        return $ut[0];
    else
        return null;
}

I'm not so pleased with this solution so if someone comes up with a nice Sortable for Doctrine 2 please share :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM