26
votes

I read in the Twig documentation that it is possible to iterate over an associative array in the following manner:

{% for key, value in array %}  
 {{key}}  
 {{value}}  
{% endfor %}  

I was wondering whether this is possible for objects of type stdClass as well.

I would have expected Twig to iterate over the property values of the object taking the property names as keys. Instead, the instruction block contained in the for loop is not executed at all.

7
What means "it does not seem to work"?KingCrunch
@KingCrunch It means that it did not work when I tried it, but I might be doing something wrong. I'm interested whether it is a feature supported by the templating engine. I'll edit my message for clarity.Bogdan
@Bodgan What I tried to ask: What happens instead? ;)KingCrunch
@KingCrunch It does not iterate at all over the object's properties, with the syntax that I provided above.Bogdan
@KingCrunch To be more precise, it simply ignores the construct and continues rendering. It does not cause an exception to be raised.Bogdan

7 Answers

21
votes

You can first cast the object to array. You can build own filter casting your object to array. More about filters is available here: http://twig.sensiolabs.org/doc/advanced.html#filters

It could then look like that:

{% for key, value in my_object|cast_to_array %}
9
votes

To complete Tadeck's answer here is how:

If you have not ever created or setup a Twig extension (filter), you will need to follow this instruction first http://symfony.com/doc/2.7/cookbook/templating/twig_extension.html

1) add to your AppBundle/Twig/AppExtension.php ('cast_to_array')

public function getFilters()
{
    return array(
        new \Twig_SimpleFilter('md2html', array($this, 'markdownToHtml'), array('is_safe' => array('html'))),
        new \Twig_SimpleFilter('price', array($this, 'priceFilter')),
        new \Twig_SimpleFilter('cast_to_array', array($this, 'objectFilter')),
    );
}

2) add to your AppBundle/Twig/AppExtension.php

public function objectFilter($stdClassObject) {
    // Just typecast it to an array
    $response = (array)$stdClassObject;

    return $response;
}

3) In your example.html.twig loop thru with twig and the filter.

{% for key, value in row|cast_to_array %}
       <td id="col" class="hidden-xs">{{ value }}</td>
{% endfor %}

Done, I hope it helps. From Tadeck's pointer.

8
votes

After loading TWIG, add this filter:

$twig->addFilter( new Twig_SimpleFilter('cast_to_array', function ($stdClassObject) {
    $response = array();
    foreach ($stdClassObject as $key => $value) {
        $response[] = array($key, $value);
    }
    return $response;
}));

It's named cast_to_array after Tadeck's suggestion. :) I'm sure it doesn't work for any kind of stdClass Object, but it sure solved my problem with printing PHP associative arrays :) Use it as follows:

{% for key, value in my_object|cast_to_array %}
    <td>{{ value.1 }}</td>
{% endfor %}

Side story

Since I got into this SO page a lot, I think it's pertinent to show where I'm using Twig for iterating over object properties, so it's helpful for other people with the same problem: I was trying to print a table from a .json source, but PHP's json_decode transforms any "key" : "value" into a PHP associative array, which Twig doesn't print by default. So this filter cuts and delivers a regular array to be used by Twig.

source.json

{
    "user": {
        "family": {
            "table": [{
                "First_Name": "John",
                "Last_Name": "Foo",
                "Age": 25,
                "Role": "Brother"
            }, {
                "First_Name": "Mary",
                "Last_Name": "Bar",
                "Age": 14,
                "Role": "Sister"
            }, {
                "First_Name": "Joe",
                "Last_Name": "Baz",
                "Age": 33,
                "Role": "Uncle"
            }]
        }
    }
}

Twig

<table>
  <thead>
    <tr> {# get table headers from the table row #}
      {% for row in user.family.table.0|cast_to_array %}
        <th>{{ row.0 | replace({'_': ' '}) }}</th>
      {% endfor %}
    </tr>
  </thead>
  <tbody>
    {% for row in user.family.table %}
      <tr>
      {% for key, value in row|cast_to_array %}
        <td>{{ value.1 }}</td>
      {% endfor %}
      </tr>
    {% endfor %}
  </tbody>
</table>
4
votes

In case this helps someone else. You can have Twig iterate the properties of your object provided you implement PHP's Iterator interface.

In my case I have a generic object the uses the magic methods __get(), __set(), __isset(), and __unset() while storing key value pairs in a private array. This works fine in Twig until you want to iterate over the object using something like this

<ul>
{% for prop, value in object %}
    <li>{{prop|replace({'_': ' '})|title}}</li>
{% endfor %}
</ul>

To make it work I had to implement the Iterator interface. Then the above code worked perfectly.

Now because of the magic __get() the property names are not case sensitive either so each of these work also.

<ul>
{% for object in arrayOfObjects %}
    <li>{{ object.property }}</li>
    <li>{{ object.Property }}</li>
    <li>{{ object.PROPERTY }}</li>
{% endfor %}
</ul>
4
votes

I know this is old but, wouldn't

$assoc_array = json_decode(json_encode($stdClassObject), TRUE);

work just as well?

0
votes
  {% for key, item in content.field_invoice_no if key|first != '#' %} <tr>
      <td>{{ item }}</td>
       <td> {{ content.field_invoice_date[key]  }}     </td>
       <td> {{ content.field_invoice_price[key]   }}       </td>
       </tr>
    {% endfor %}

Iterating object over key value

0
votes

I finally figured out a vanilla Twig solution. I make use of the reduce filter to transform an object to an array.

Let's say we get an address object from the backend and want to convert it into an associative array in order to render each line separate.

{% set address = { firstName: 'Stack', lastName: 'Overflow', address: 'here', zipCode: '1234' } %}

We reduce the keys and add them to an empty array

{% set addressLines = address|keys|reduce((carry, key) => carry|merge({ (key): attribute(address, key) }), []) %}

This results in:

[ 'firstName' => 'Stack', 'lastName' => 'Overflow', 'address'=> 'here', 'zipCode' => '1234' ]

Now we can render this in a for loop

{% set allowedKeys = ['firstName', 'lastName', 'address'] %}
{% for key, line in addressLines|filter((v, k) => k in allowedKeys) %}
    {{ line }}<br/>
{% endfor %}