EasyAdmin 3.X - How to see related entities `toString` instead of the number of association in the list?
Asked Answered
A

5

7

I have an entity Product with a ManyToMany relation to an entity Category

/**
 * @ORM\ManyToMany(targetEntity="App\Domain\Category", inversedBy="stalls")
 */
private $categories;

//...

/**
 * @return Collection|Category[]
 */
public function getCategories(): Collection
{
    return $this->categories;
}

In the ProductCrudController class I have the following configureFields method:

public function configureFields(string $pageName): iterable
{
    return [
        Field::new('name'),
        Field::new('description'),
        AssociationField::new('categories'),
    ];
}

When creating/editing a Product everything works as expected in the relation, but in the list of products instead of showing the related categories I see the number of categories the product has. How can I change this behaviour?

In the following image the first product has 1 category and the second one in the list has 2 different categories. I would like the name of the categories to be shown here.

enter image description here

As a side note: Category class has a __toString method returning the name of the category.

EDIT:

The behaviour I am looking for is the same as the Tags column in the following image:

enter image description here

Almira answered 3/9, 2020 at 16:50 Comment(0)
D
10

You can format the value using the method formatValue like this :

->formatValue(function ($value, $entity) {
                $str = $entity->getCategories()[0];
                for ($i = 1; $i < $entity->getCategories()->count(); $i++) {
                    $str = $str . ", " . $entity->getCategories()[$i];
                }
                return $str;
              })
Dasteel answered 7/9, 2020 at 11:13 Comment(3)
I will try this formatValue func, thanks. For the purpose of your solution maybe you can use implode (php.net/manual/en/function.implode.php) to return the same result?Almira
Very good solution, but please not that this in its current form does not allow for code reuse if you need this for multiple entities.Rheostat
Indeed, the implode variant is preferable, IMHO, as suggested by @hosseio, like: ``` php AssociationField::new('categories') ->onlyOnDetail() ->formatValue(function ($value, $entity) { return implode(', ', $entity->getCategories()->toArray()); }) ```Pampas
C
14

You can make a template for that like so:

// somewhere here templates/admin/field/category.html.twig
{% for category in field.value %}
  {%- set url = ea_url()
    .setController('Path\\To\\Your\\CategoryCrudController')
    .setAction('detail')
    .setEntityId(category.id)
  -%}
  <a href="{{ url }}">
    {{ category.name }}{% if not loop.last %}, {% endif %}
  </a>
{% else %}  
  <span class="badge badge-secondary">None</span>
{% endfor %}

And just add it to the field

// in ProductCrudController
AssociationField::new('categories')->setTemplatePath('admin/field/category.html.twig'),
Collector answered 30/11, 2020 at 23:14 Comment(1)
This one should be the right answer, imoReformism
D
10

You can format the value using the method formatValue like this :

->formatValue(function ($value, $entity) {
                $str = $entity->getCategories()[0];
                for ($i = 1; $i < $entity->getCategories()->count(); $i++) {
                    $str = $str . ", " . $entity->getCategories()[$i];
                }
                return $str;
              })
Dasteel answered 7/9, 2020 at 11:13 Comment(3)
I will try this formatValue func, thanks. For the purpose of your solution maybe you can use implode (php.net/manual/en/function.implode.php) to return the same result?Almira
Very good solution, but please not that this in its current form does not allow for code reuse if you need this for multiple entities.Rheostat
Indeed, the implode variant is preferable, IMHO, as suggested by @hosseio, like: ``` php AssociationField::new('categories') ->onlyOnDetail() ->formatValue(function ($value, $entity) { return implode(', ', $entity->getCategories()->toArray()); }) ```Pampas
S
5

I had the same issue on my detail page. So instead of a template, I change the field type depending on the pagename

if (Crud::PAGE_DETAIL === $pageName) {
   $field = ArrayField::new('field')->setLabel('label');
} else {
   $field = AssociationField::new('field')->setLabel('label');
}
Skilled answered 31/1, 2021 at 20:37 Comment(0)
H
2

I will do that way :

->formatValue(function ($value, $entity) {
    return implode(",",$entity->getCategories()->toArray());
})
Humour answered 15/2, 2022 at 15:39 Comment(1)
Welcome to Stack Overflow! Your answers will have more long-term value if you include a brief explanation of why or how your code solves the asker’s question. Also, note that this question is over a year old and already has an accepted answer. That doesn’t mean you can’t still add an answer if you think your solution adds something the others don’t, but it would be more helpful to explain what that is.Woodham
U
0

Building on top of the most upvoted answer, you could make the Twig snippet universal like this:

{% for member in field.value %}
    {%- if field.customOption('crudControllerFqcn') is not empty -%}
        {%- set url = ea_url()
            .setController(field.customOption('crudControllerFqcn'))
            .setAction('detail')
            .setEntityId(member.id)
        -%}
        <a href="{{ url }}">
            {{ member }}
        </a>
    {%- else -%}
        {{ member }}
    {%- endif -%}

    {%- if not loop.last %}, {% endif -%}
{% else %}
    <span class="badge badge-secondary">None</span>
{% endfor %}

This way you could set the link destination in your CRUD controller and use the template everywhere. When no CRUD controller is not set, you will still have the individual items enumerated, but not as links.

In my TeamCrudController, I'm doing this:

public function configureFields(string $pageName): \Generator
{
    yield TextField::new('name')
        ->setDisabled()
    ;

    $members = AssociationField::new('members')
        ->hideOnForm()

    ;

    if (Crud::PAGE_DETAIL === $pageName) { // I want to see the number on INDEX, but the list on DETAIL
        $members
            ->setCrudController(EmployeeCrudController::class)
            ->setTemplatePath('admin/field/expanded_association_field.html.twig')
        ;
    }

    yield $members;
}

Alternatively, if you would like to become the default behaviour, override the EasyAdminBundle template by placing the following content in templates/bundles/EasyAdminBundle/crud/field/association.html.twig:

{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
{% if 'toMany' == field.customOptions.get('associationType') %}
    {% if ea.crud.currentPage == 'detail' %}
        {% for member in field.value %}
            {%- if field.customOption('crudControllerFqcn') is not empty -%}
                {%- set url = ea_url()
                    .setController(field.customOption('crudControllerFqcn'))
                    .setAction('detail')
                    .setEntityId(member.id)
                -%}
                <a href="{{ url }}">
                    {{- member -}}
                </a>
            {%- else -%}
                {{ member }}
            {%- endif -%}

            {%- if not loop.last %}, {% endif -%}
        {% else %}
            <span class="badge badge-secondary">None</span>
        {% endfor %}

    {% else %}
        <span class="badge badge-secondary">{{ field.formattedValue }}</span>
    {% endif %}
{% else %}
    {% if field.customOptions.get('relatedUrl') is not null %}
        <a href="{{ field.customOptions.get('relatedUrl') }}">{{ field.formattedValue }}</a>
    {% else %}
        {{ field.formattedValue }}
    {% endif %}
{% endif %}

Ulrick answered 11/1, 2023 at 23:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.