0
votes

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?

2

2 Answers

1
votes

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.

0
votes

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 %}