Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Twig : separator in a "for" tag

Tags:

twig

symfony

Is there a syntax to separate some elements in a "for" tag please ?

For example I have a list of users, and I want to display their username with a "-" separator, so the expected result would be : Mickael - Dave - Chris - ...

I have found this solution :

{% for user in entity.users %}
    {{ user.name }}{% if not loop.last %} - {% endif %}
{% endfor %}

But this is not very elegant. The join filter does not seem appropriate in a loop.

like image 680
ncrocfer Avatar asked Dec 26 '22 17:12

ncrocfer


1 Answers

AFAIK, no, you can't use the join filter for your use case without workarounds. This is because you need to pull getName out of the object property. join just link each element of the Traversable instance with the given glue.

There are however some workarounds if you need it.

Add a __toString method

In your User entity, you can add a __toString method to get the name of the user by default.
You should however take care if no other object are using the current default toString as it may cause conflicts

namespace Acme\FooBundle\Entity;

class User
{
    public function __toString()
    {
        return $this->getName();
    }
}

Then you can use in your twig the join filter

{{ entity.users|join(' - ') }}

Map the usernames in the controller

In your controller, right before sending parameters to the view, you can map all the usernames for them to fit in an array.
Note: I assume getUsers is a PersistentCollection, if not, use array_map instead

// Acme/FooBundle/Controller/MyController#fooAction

$users = $entity->getUsers();
$usernames = $users->map(function(User $user) {
    return $user->getName();
});

return $this->render('AcmeFooBundle:My:foo.html.twig', array(
    'entity' => $entity,
    'usernames' => $usernames
);

Then in your twig

{{ usernames|join(' - ') }}

Create a TWIG filter

In your application, you can create a Twig Extension. In this example, I'll create a filter called joinBy which will act like join by specifying a method to join elements by.

 Service declaration

Straight and easy, nothing too harsh but a standard declaration like in the docs

services.yml

acme_foo.tools_extension:
    class: Acme\FooBundle\Twig\ToolsExtension
    tags:
        - { name: twig.extension }

Extension creation

@Acme\FooBundle\Twig\ToolsExtension

namespace Acme\FooBundle\Twig;

use Twig_Extension, Twig_Filter_Method;
use InvalidArgumentException, UnexpectedValueException;
use Traversable;

/**
 * Tools extension provides commons function
 */
class ToolsExtension extends Twig_Extension
{
    /**
     * {@inheritDoc}
     */
    public function getName()
    {
        return 'acme_foo_tools_extension';
    }

    /**
     * {@inheritDoc}
     */
    public function getFilters()
    {
        return array(
            'joinBy' => new Twig_Filter_Method($this, 'joinBy')
        );
    }

    /**
     * Implode-like by specifying a value of a traversable object
     *
     * @param mixed  $data  Traversable data
     * @param string $value Value or method to call
     * @param string $join  Join string
     *
     * @return string Joined data
     */
    public function joinBy($data, $value, $join = null)
    {
        if (!is_array($data) && !($data instanceof Traversable)) {
            throw new InvalidArgumentException(sprintf(
                "Expected array or instance of Traversable for ToolsExtension::joinBy, got %s",
                gettype($data)
            ));
        }

        $formatted = array();

        foreach ($data as $row) {
            $formatted[] = $this->getInput($row, $value);
        }

        return implode($formatted, $join);
    }

    /**
     * Fetches the input of a given property
     *
     * @param  mixed  $row  An array or an object
     * @param  string $find Property to find
     * @return mixed  Property's value
     *
     * @throws UnexpectedValueException When no matching with $find where found
     */
    protected function getInput($row, $find)
    {
        if (is_array($row) && array_key_exists($find, $row)) {
            return $row[$find];
        }

        if (is_object($row)) {
            if (isset($row->$find)) {
                return $row->$find;
            }

            if (method_exists($row, $find)) {
                return $row->$find();
            }

            foreach (array('get%s', 'is%s') as $indic) {
                $method = sprintf($indic, $find);

                if (method_exists($row, $method)) {
                    return $row->$method();
                }
            }

            if (method_exists($row, $method)) {
                return $row->$method();
            }
        }

        throw new UnexpectedValueException(sprintf(
            "Could not find any method to resolve \"%s\" for %s",
            $find,
            is_array($row)
                ? 'Array'
                : sprintf('Object(%s)', get_class($row))
        ));
    }
}

Usage

You can now use it in your twig by calling

{{ entity.users|joinBy('name', ' - ') }}
# or
{{ entity.users|joinBy('getName', ' - ') }}
like image 55
Touki Avatar answered Jan 30 '23 05:01

Touki