benarth
benarth

Reputation: 75

Symfony Form render with Self Referenced Entity

I have an Entity containing Self-Referenced mapping.

class Category
{

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=100)
     */
    private $name;

    /**
     * @ORM\OneToMany(targetEntity="Category", mappedBy="parent")
     */
    private $children;

    /**
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="children")
     * @ORM\JoinColumn(name="parent_id", referencedColumnName="id")
     */
    private $parent;

}

In my CategoryType I have this :

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $plan = $this->plan;

    $builder->add('name');

    $builder->add('parent', 'entity', array(
        'class' => 'xxxBundle:Category',
        'property' => 'name',
        'empty_value' => 'Choose a parent category',
        'required' => false,
        'query_builder' => function(EntityRepository $er) use ($plan) {
            return $er->createQueryBuilder('u')
                ->where('u.plan = :plan')
                ->setParameter('plan', $plan)
                ->orderBy('u.id', 'ASC');
        },
    ));
}

Actually, when I render the form field Category this is something like

I would like to know if it's possible and how to display something more like, a kind of a simple tree representation :

Regards.

Upvotes: 3

Views: 1207

Answers (2)

Rybus
Rybus

Reputation: 661

I came up with something which seems correct from what you and jperovic wrote. You will need two new attributes for your Category class :

  • $level will contain ID's of its parents like "idA-idB", etc. this attribute will be use to sort your results when querying your database so you can be certain SubCatOf3 won't come before Cat3 !
  • $treeName will contain what jperovic already wrote and will be printed in the form.

I also used Doctrine Events [doc] so when you update/persist them, you don't have to worry about the value of these attributes.

This is your brand new Category.php file :

/**
* @ORM\HasLifeCycleCallbacks()
*/
class Category 
{

    private $level;

    private $treeName;

    /** 
    * Renders something like : "---- Subcategory A" 
    * @ORM\PreUpdate() 
    * @ORM\PrePersist()
    **/
    public function updateTreeName()
    {
       $itemDepth = 0;
       $parent = $this->parent;
       while ($parent != null) {
           $itemDepth++;
           $parent = $parent->getParent();
       }

       $this->treeName = str_repeat('--', $itemDepth) . ' ' . $this->name
    }

    /** renders something like : "idParent-idChild1-idChild2" 
    * @ORM\PreUpdate() 
    * @ORM\PrePersist()
    **/
    public function updateLevelName()
    {
       $this->level = '';
       $parent = $this->parent;
       while ($parent != null) {
           $parent = $parent->getParent();
           $this->level .= '-' . $p->getId();
       }
     }

    public function getTreeName()
    {
       return $this->treeName;
    }

    public function getLevel()
    {
        return $this->level; 
    }

    // ...
}

Then, put your query_builder in your CategoryRepository.php like this :

namespace Foo\BarBundle\Entity;
use Doctrine\ORM\EntityRepository;

class CategoryRepository extends EntityRepository 
{
    public function getHierarchicalCategoryList($plan)
    {
        $qb = $this->createQueryBuilder('u')
           ->where('u.plan = :plan')
           ->setParameter('plan', $plan)
           ->orderBy('u.level', 'ASC');

        $return $qb;
    }
}

And in your CategoryType.php :

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $plan = $this->plan;

    $builder->add('name');

    $builder->add('parent', 'entity', array(
        'class' => 'xxxBundle:Category',
        'property' => 'treeName',
        'empty_value' => 'Choose a parent category',
        'required' => false,
        'query_builder' => function(EntityRepository $er) use ($plan) {
            return $er->getHierarchicalCategoryList($plan)
        },
    ));
}

Note : this is quick&dirty work so you might need to correct typos, annotations, etc. Yet, you have the idea ! Hope it helps.

Upvotes: 1

Jovan Perovic
Jovan Perovic

Reputation: 20191

This is a really long shot at best, but I think it could be achieved pretty easily.

Within your query_builder you specified the 'property' => 'name'. You would need to change it to 'treeName'. Doctrine will try to find and invoke property's getter method - that's where all the printing logic comes in:

class Category
{
    .... 
    Everything else
    ....

    public function getTreeName(){
        $itemDepth = 0;
        $p = $this->parent;
        while ( $p != null ){
            $itemDepth++;
            $p = $p->getParent();
        }

        return str_repeat('--', $itemDepth) . ' ' . $this->name
    }
}

This could pose a serious performance hit due to need to iterate for each item the depth times.

What do you think? What is the average depth of items?

Just to be clear, name property and its getter and setter are to remain intact.

Upvotes: 0

Related Questions