Search code examples
phpsymfonysortingdoctrinearraycollection

Doctrine embeded document maintain order by a key


I have a mongodb document in symfony2 with an embeded document:

/** 
 * @MongoDB\EmbedMany(targetDocument="Restriction")
 */
private $restrictions = array();

public function __construct()
{
    $this->restrictions = new \Doctrine\Common\Collections\ArrayCollection();
}

The restriction document has two properties. from_pos and length. I want this array to be always sorted by from_pos, so in the whole application I will be always sure that this list is sorted.

Is there an easy way to do this automatically? I mean, to call addRestriction function and this will be automatically saved in the mongodb database sorted by the key I want.

Currently the addRestriction function just adds the new document to the end of the list.

/**
 * Add restriction
 *
 * @param MyBundle\Document\Restriction $restriction
 */
public function addRestriction(\MyBundle\Document\Restriction $restriction)
{
    $this->restrictions[] = $restriction;
}

I could update this function to insert the restriction into the desired position, but I would like to know if there is an easiest way.


Solution

  • The solution is to use custom collections.

    http://docs.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/custom-collections.html

    This is the SortedCollection class I've developed. When adding an element just put it in the correct position and slice the other elements.

    <?php
    
    use Doctrine\Common\Collections\ArrayCollection;
    
    /**
     * Array collection with elements alwais in order
     */
    class SortedCollection extends ArrayCollection{
    
        /**
         * Interrnal flag to avoid recursive call to reindex function while reindexing
         * @var boolean
         */
        protected $reindexing = false;
    
        public function add($value){
            $it = $this->getIterator();
            while($it->valid() && $it->current()->compareTo($value) < 0){
                $it->next();
            }
    
            // slice elements
            $prev = $value;
            while ($it->valid()){
                // Save current element
                $aux = $it->current();
    
                // Add previous element
                $this->offsetSet($it->key(), $prev);
    
                // Set previous element to current for next iteration
                $prev = $aux;
                $it->next();
            }
    
            // Add final element
            parent::add($prev);
    
            return true;
        }
    }
    

    And in the Document just set the SortedCollection class in the constructor.

    <?php
    
    class MyDocument
    {
        /** 
         * @MongoDB\EmbedMany(
         *     collectionClass="SortedCollection",
         *     targetDocument="...."
         * )
         */
        private $sorted_list = array();
    
        public function __construct()
        {
            $this->sorted_list = new SortedCollection();
        }
    }