Reputation: 75
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
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
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