Displaying self referencing entity in twig - symfony

I'm trying to render a tree of links using Doctrine's self referencing association. I have an entity like:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="link")
* #ORM\Entity(repositoryClass="App\Repository\LinkRepository")
*/
class Link
{
/**
* #ORM\Column(name="link_id", type="integer", nullable=false, options={"unsigned"=true})
* #ORM\Id()
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $linkId;
/**
* #ORM\Column(name="name", type="string", length=50)
*/
private $name;
/**
* #ORM\Column(name="url", type="string", length=500)
*/
private $url;
/**
* #ORM\Column(name="parent_id", type="integer")
*/
private $parentId;
/**
* #ORM\OneToMany(targetEntity="Link", mappedBy="parent")
*/
private $children;
/**
* #ORM\ManyToOne(targetEntity="Link", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="link_id")
*/
private $parent;
public function __construct()
{
$this->children = new ArrayCollection();
}
public function getLinkId(): ?int
{
return $this->linkId;
}
// Getters and setters ...
/**
* #return ArrayCollection
*/
public function getChildren()
{
return $this->children;
}
}
and in my controller I have it fetching the link and calling the twig template like:
public function link(int $linkId, LinkRepository $linkRepository)
{
$link = $linkRepository->findOneBy(['linkId' => $linkId]);
return $this->render('link.html.twig',
[
'link' => $link
]);
}
And the twig template is something like this:
{% extends 'base.html.twig' %}
{% block body %}
{{ link.name }}
<h2>Children:</h2>
{% import _self as macros %}
<ul>
<li>{{ link.name }}
{{ macros.link_tree(link) }}
</li>
</ul>
{% endblock %}
{% macro link_tree(link) %}
{% import _self as macros %}
<ul>
{% for linkChild in link.children %}
<li>
{{ link.name }}
{% if linkChild.children %}
<ul>
{{ macros.link_tree(linkChild.children) }}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}
Although when I call this controller it gives me this error:
Neither the property "children" nor one of the methods "children()",
"getchildren()"/"ischildren()"/"haschildren()" or "__call()" exist and
have public access in class "Doctrine\ORM\PersistentCollection".
This seems to be when I make reference to linkChild.children in the twig template.
How can I loop recursively over children() in twig?

First of all your Doctrine mapping is invalid: You should remove the parentId field, there is no need for it since you have already added the appropriate association with parent field.
Secondly, you should use Symfony's ParamConverter to get the link inside the controller like this:
public function link(Link $link)
Yes, it is as easy as it seems, you can get the link just by typehinting the link variable in your controller action, no need to use the LinkRepository there. You can find more about ParamConverter here.
Finally, it seems to be an issue in your data, because you got an instance of Doctrine Collection when you expect an instance of Link class. Try to debug, using {{ dump() }} inside your twig template, at this point there are not enough data to help you further with this one. But you definitely should first fix the mapping issue before trying again.

So it turns out I was sending the wrong thing to the macro, it was expecting linkChild and I was passing linkChild.children. Inside the macro it was trying to reference linkChild.children.children
This was fixed by using:
{{ link.name }}
{% if linkChild.children %}
<ul>
{{ macros.link_tree(linkChild) }}
</ul>
{% endif %}

Related

Symfony - dropdown list with entity.propriety filter

i have 3 entities named Answer, Skill and Jointure.
Answer and Skill are linked to Jointure with a ManyToOne relation.
I display them in twig like that :
class HomeController extends AbstractController
{
/**
* #var JointureRepository
*/
public function __construct(JointureRepository $repository, ObjectManager $em)
{
$this->repository = $repository;
$this->em = $em;
}
/**
* #Route("/", name="home")
*/
public function index()
{
$JointureRepository = $this->getDoctrine()->getRepository(Jointure::class);
$arrJointures = $JointureRepository->findAll();
$this->em->flush();
return $this->render('pages/home.html.twig', [
'controller_name' => 'HomeController',
'jointure' => $arrJointures,
]);
}
}
and in my twig view :
{% for object in jointure %}
{% for skill in object.skills %}
{{skill.label}}
{% endfor %}
{% endfor %}
I've created a dropdown button who list all the skill.label properties who exists like that :
EDIT : Here my twig button :
<div class="form-group ">
<select id="inputState " class="form-control">
<option selected>Compétence</option>
{% for object in jointure %}
{% for skill in object.skills %}
<option>{{skill.label}}</option>
{% endfor %}
{% endfor %}
</select>
</div>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary btn-block">Search</button>
</div>
</div>
I want to show / display all answer.userEmail who have this related skill.label in a view in my template.I have to use EntityType ? Many thanks
You should start using Symfony Forms. Here is documentation https://symfony.com/doc/current/forms.html. It is not that simple at the beginning but it definitely worith it. Then you will be able to use EntityType https://symfony.com/doc/current/reference/forms/types/entity.html

twig inheritance and symfony2 controller variables

Im trying my first project using symfony2 + twig. I created basic twig template with defined blocks. It basically looks like this
{% block content %}
some content...
{% endblock %}
{% block footer %}
{{ footer.content}}
{% endblock %}
I want footer to be same for all pages. Footer is loaded from DB and its set in controller. I wanted inherit from template described above to other pages but I have to always set footer in controller otherwise variable is not defined.
My questions is if exists any 'nice' way how to set footer variable for multiple templates inherited from parent template?
The solution : Embedding Controllers
In some cases, you need to do more than include a simple template.
Suppose you have a sidebar in your layout that contains the three most
recent articles. Retrieving the three articles may include querying
the database or performing other heavy logic that can't be done from
within a template.
The solution is to simply embed the result of an entire controller
from your template. First, create a controller that renders a certain
number of recent articles:
Controller
// src/AppBundle/Controller/ArticleController.php
namespace AppBundle\Controller;
// ...
class ArticleController extends Controller
{
public function recentArticlesAction($max = 3)
{
// make a database call or other logic
// to get the "$max" most recent articles
$articles = ...;
return $this->render(
'article/recent_list.html.twig',
array('articles' => $articles)
);
}
}
View
{# app/Resources/views/article/recent_list.html.twig #}
{% for article in articles %}
<a href="/article/{{ article.slug }}">
{{ article.title }}
</a>
{% endfor %}
Layout
{# app/Resources/views/base.html.twig #}
{# ... #}
<div id="sidebar">
{{ render(controller(
'AppBundle:Article:recentArticles',
{ 'max': 3 }
)) }}
</div>
you can do with one of the following, 'write a custom Twig Extension'
<?php
namespace AppBundle\Extension;
class MyTwigExtension extends \Twig_Extension
{
private $em;
private $conn;
public function __construct(\Doctrine\ORM\EntityManager $em) {
$this->em = $em;
$this->conn = $em->getConnection();
}
public function getFunctions()
{
return array(
'users' => new \Twig_Function_Method($this, 'getUsers'),
);
}
public function getUsers()
{
$sql = "SELECT * FROM users ORDER BY accountname";
return $this->conn->fetchAll($sql);
}
public function getName()
{
return 'smproc4_twig_extension';
}
}
Register an Extension as a Service
services:
my.twig.extension:
class: AppBundle\Extension\MyTwigExtension
tags:
- { name: twig.extension }
arguments:
em: "#doctrine.orm.entity_manager"
Using the custom Extension
Hello {{ name }}!
<ul>
{% for user in users() %}
<li>{{ user.accountname }}</li>
{% endfor %}
</ul>
have a look at the
How to Write a custom Twig Extension
Creating an Extension

how to add a ID in my symfony links?

i have a question about the linking system in symfony.
I use normal links like this:
click me
and then in the controller the corrosponding action looks like:
/**
* #Route("/my_path", name="my_path")
* #Template()
*/
public function myAction()
{ ...
now, i have this links in a list and i need to add a item.id to each.
so the controller looks like:
/**
* #Route("/my_path/id", name="my_path")
* #Template()
*/
public function myAction($id)
{ ...
and all of this is in a loop:
{% for item in items %}
click me <br>
{% endfor %}
how to add the item.id into the path?
found the solution:
{% for item in items %}
click me <br>
{% endfor %}

Symfony2, How to cast data type in a twig file?

I have some scenario like below :
/**
* #ORM\Entity
* #ORM\Table(name="role")
*/
class Role
{
/**
* #ORM\OneToMany(targetEntity="RolesFeatures", mappedBy="role", cascade={"all"})
**/
private $rolesFeatures;
}
In my index file I would like to get them:
{{ role.rolesFeatures.getId() }}
I get this :
An exception has been thrown during the rendering of a template ("Catchable Fatal
Error: Object of class Doctrine\ORM\PersistentCollection could not be converted to string
in C:\wamp\www\PMI_sf2\app\cache\dev\twig\63\81\679fca1c2da64d0ebbcd5661bc6d.php line 99")
in PMIHomePagesBundle:HomePages:mainHome.html.twig at line 49.
How I can Cast Doctrine\ORM\PersistentCollection to it real object class ?
rolesFeatures is an array so you need to iterate over it. Something like:
{% for roleFeature in role.rolesFeatures %}
{{ roleFeature.id }}
{% endfor %}

Symfony2 join not used, unnesessary queries fired

I have following problem:
my Query with joins contains all the nesessary data but symfony / twig creates more queries
each {{ entity.group.name }} produces a new query
controller:
/**
* #Route("/gameplan", name="game_plan")
* #Template()
* //#Secure(roles="ROLE_USER")
*/
public function indexAction()
{
$em = $this->getDoctrine()->getEntityManager();
$entities = $em->getRepository('OnemediaFeedFighterBundle:Game')->createQueryBuilder('q')
->leftJoin('q.group', 'g')
->leftJoin('q.teamGame', 'tg')
->leftJoin('tg.Team', 't')
->getQuery()->getResult();
return array('entities' => $entities);
}
template:
{% extends "::layout.html.twig" %}
{% block content %}
<h1>Game plan</h1>
{% for entity in entities %}
{{ entity.place }}<br />{{ entity.group.name }}<br />
{% endfor %}
{% endblock content %}
You can achieve this by adding the ->select("q, g")

Resources