Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Displaying nested array in twig

Here is my Message entity. It is a class that defines messages between users in my app.

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

    /**
     * @var string
     * @Assert\NotBlank(message="private_message.title.blank")
     * @ORM\Column(name="title", type="string", length=50)
     */
    protected $title;

    /**
     * @Assert\NotBlank(message="private_message.receiver.blank")
     * @ORM\ManyToOne(targetEntity="MedAppBundle\Entity\User")
     * @ORM\JoinColumn(referencedColumnName="id")
     */
    protected $receiver;
    /**
     * @ORM\ManyToOne(targetEntity="MedAppBundle\Entity\User")
     * @ORM\JoinColumn(referencedColumnName="id")
     */
    protected $sender;

    /**
     * @var string
     * @Assert\NotBlank(message="private_message.content.blank")
     * @ORM\Column(name="content", type="string")
     */
    protected $content;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="sentAt", type="datetime")
     */
    protected $sentAt;


    /**
     * @var boolean
     *
     * @ORM\Column(name="isSpam", type="boolean")
     */
    protected $isSpam = false;


    /**
     * @var \DateTime
     *
     * @ORM\Column(name="seenAt", type="datetime",nullable=true)
     */
    protected $seenAt = null;

    /**
     * @ORM\ManyToOne(targetEntity="PrivateMessageBundle\Entity\Message")
     * @ORM\JoinColumn(referencedColumnName="id",nullable=true)
     */
    protected $replyof;

    /**
     * @ORM\OneToMany(targetEntity="PrivateMessageBundle\Entity\Message", mappedBy="replyof")
     **/
    private $replies;

    public function __construct() {
        $this->replies = new ArrayCollection();
    }

What's important to note is the replyof variable, which tells what message is a parent of the message. If it's NULL, then the message is not a reply, it's a parent message(a root).

And the messages variable, which is an array of Messages that are replies to the message. These replies can have replies, themselves. This array can also be NULL, for leaf nodes, because they don't have any reply.

All the other variables contain just some fields that define an actual message between two users.

What I am trying to do is display in Twig all of my messages in an arborescent format, like so:

message1 - root message, reply of none, but has replies
   reply1 - first reply of message 1
      reply1 first reply of reply 1 of message 1, leaf with no further replies
   reply2 - second reply of message 1, leaf with no further replies

message2 - root message, no replies and a reply of none

The problem is that Twig only supports foreach loops and I'm not sure how to display this format when it has a higher depth, bigger than two.

{% for reply in message.replies %}
    <li> sent by: {{ reply.sender }} </li>
    <li> title: {{ reply.title }} </li>
    <li> content: {{ reply.content }} </li>
    <li> date: {{ reply.sentAt|date('d-m-Y H:i:s') }} </li>
    <hr>
{% endfor %}

This will display every reply of a message, but how can I display nested messages, in full depth?

like image 662
George Irimiciuc Avatar asked Sep 08 '15 21:09

George Irimiciuc


2 Answers

I didn't tested it by you should be able to loop over replies:

{% for reply in message.replies %}
    {% if loop.first %}<ul>{% endif %}
    <li> sent by: {{ reply.sender }} </li>
    <li> title: {{ reply.title }} </li>
    <li> content: {{ reply.content }} </li>
    <li> date: {{ reply.sentAt|date('d-m-Y H:i:s') }} </li>
    {% for reply in reply.replies %}
        {% if loop.first %}<li><ul>{% endif %}
        <li> sent by: {{ reply.sender }} </li>
        <li> title: {{ reply.title }} </li>
        <li> content: {{ reply.content }} </li>
        <li> date: {{ reply.sentAt|date('d-m-Y H:i:s') }} </li>
        {% if loop.last %}</ul></li>{% endif %}
    {% endfor %}
    {% if loop.last %}</ul>{% endif %}
{% endfor %}

It will display only 2 levels of replies. You can use a Twig macro to define a reusable function that should recursively display replies:

{# define the macro #}
{% macro displayReply(reply) %}
    <li> sent by: {{ reply.sender }} </li>
    <li> title: {{ reply.title }} </li>
    <li> content: {{ reply.content }} </li>
    <li> date: {{ reply.sentAt|date('d-m-Y H:i:s') }} </li>
    {% for reply in reply.replies %}
        {% if loop.first %}<li><ul>{% endif %}
        {{ displayReply(reply) }}
        {% if loop.last %}</ul></li>{% endif %}
    {% endfor %}
{% endmacro %}

{# use the macro #}
{% for reply in message.replies %}
    {% if loop.first %}<ul>{% endif %}
    {{ displayReply(reply) }}
    {% if loop.last %}</ul>{% endif %}
{% endfor %}

Depending on your query, it may display replies in the wrong order, you may need to sort replies in descending order in your query.

like image 181
A.L Avatar answered Oct 21 '22 05:10

A.L


You can do a recursive approach as follow:

in the main twig, you print the main message, and iterate recursively in a partial as follow:

## main twig
Root message:
   <ul>
    <li> sent by: {{ message.sender }} </li>
    <li> title: {{ message.title }} </li>
    <li> content: {{ message.content }} </li>
    <li> date: {{ message.sentAt|date('d-m-Y H:i:s') }} </li>
    {{ include('AcmeDemoBundle:Message:_elem.html.twig', {'replies': message.replies ) }}
  </ul>

and

## AcmeDemoBundle:Message:_elem.html.twig
<ul>
{% for reply in replies %}
    <li> sent by: {{ reply.sender }} </li>
    <li> title: {{ reply.title }} </li>
    <li> content: {{ reply.content }} </li>
    <li> date: {{ reply.sentAt|date('d-m-Y H:i:s') }} </li>
    {{ include('AcmeDemoBundle:Message:_elem.html.twig', {'replies': reply.replies ) }}
{% endfor %}
</ul>

hope this help

like image 34
Matteo Avatar answered Oct 21 '22 07:10

Matteo