David Rojo David Rojo - 3 months ago 15
PHP Question

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.

Answer

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();
    }
}
Comments