EasyAdminBundle icon indicating copy to clipboard operation
EasyAdminBundle copied to clipboard

Custom entity name representation for autocomplete

Open hhamon opened this issue 4 years ago • 5 comments

Hi everyone!

Allow one to customize the string representation of an entity for the autocomplete field:

The current implementation doesn't allow one to change the way an entity is represented in an autocomplete field. The EntityPaginator::getResultsAsJson method doesn't leverage any extension point.

class EntityPaginator
{
    public function getResultsAsJson(): string
    {
        foreach ($this->getResults() ?? [] as $entityInstance) {
            $entityDto = $this->entityFactory->createForEntityInstance($entityInstance);

            $jsonResult['results'][] = [
                'entityId' => $entityDto->getPrimaryKeyValueAsString(),
                'entityAsString' => $entityDto->toString(), // <------------ unable to change this hardcoded statement
            ];
        }

        $jsonResult['has_next_page'] = $this->hasNextPage();

        return json_encode($jsonResult);
    }
}

Of course, we can use the __toString() method but in my case I need a different representation that is not the one I use elsewhere with my already implemented __toString method.

I'm using the autocomple feature to allow the user find and select a US city among thousands cities in the database. As you know, you can have cities with the same name in different US states. My current Locality::__toString method already returns the city name because this is what I just need everywhere else I want to output a Locality entity as a string.

However, for the autocomplete field, I need to render the Locality instance a little bit differently to also show the US state name:

Nevada > Las Vegas
New Mexico > Las Vegas

For now, I'm fully overriding the autocomplete action of the base CRUD controller but that's a lot of work for just changing the way an entity is converted to string.

Potential solutions

  1. Add the possibility to configure a callback function in the CRUD object for the autocomplete only
public function configureCrud(Crud $crud): Crud
{
    return $crud
        ->setAutocompleteInstanceNormalizer(static fn (Locality $entity) => $entity->myCustomStringRendering());
}
  1. Inject this custom callback in the EntityPaginator::getResultsAsJson method
public function autocomplete(AdminContext $context): JsonResponse
{
    // ...

    return JsonResponse::fromJsonString($paginator->getResultsAsJson($context->getAutocompleteInstanceNormalizer()));
}
  1. The getResultsAsJson method will receive a callable or null value. If it's given a callable, then call it to compute the entity string name, otherwise fallback on __toString (if it exists) or the default implementation.
class EntityPaginator
{
    public function getResultsAsJson(callable $entityAsStringNormalizer = null): string
    {
        foreach ($this->getResults() ?? [] as $entityInstance) {
            $entityDto = $this->entityFactory->createForEntityInstance($entityInstance);

            // Solution 1: the normalizer is invoked here instead of the DTO::toString method
            $entityAsString = $entityAsStringNormalizer ? \call_user_func_array($callable, [$entityInstance]) : $entityDto->toString();

            // Solution 2: the normalizer is given to the DTO
            $entityAsString = $entityDto->toString($entityAsStringNormalizer);

            $jsonResult['results'][] = [
                'entityId' => $entityDto->getPrimaryKeyValueAsString(),
                'entityAsString' => $entityAsString
            ];
        }

        $jsonResult['has_next_page'] = $this->hasNextPage();

        return json_encode($jsonResult);
    }
}

What do you think of this pragmatic approach?

hhamon avatar Sep 11 '20 08:09 hhamon

@javiereguiluz do you have any feedbacks to provide en this please?

hhamon avatar Sep 14 '20 08:09 hhamon

@hhamon For me this looks like a perfect and really clean solution.. Do you plan to do a PR?

michaelKaefer avatar Feb 23 '22 17:02 michaelKaefer

@hhamon Please create a PR for this as I also need this in my project.

pluk77 avatar Mar 09 '22 04:03 pluk77

I took a step forward and implemented this solution in #5189. It would be great if you guys took a spin on that and test it in your projects to make sure it works as expected and solves your requirements :smile_cat:

Also - code reviews would be great :+1:

Lustmored avatar Apr 22 '22 11:04 Lustmored

@Lustmored - I have tested. Works great.

Thanks!!!!

alexndlm avatar Apr 22 '22 17:04 alexndlm