EasyCorp / EasyAdminBundle

EasyAdmin is a fast, beautiful and modern admin generator for Symfony applications.
MIT License
4.04k stars 1.02k forks source link

AssociationField relatedUrl for toMany and scoped index page #6150

Open klkvsk opened 6 months ago

klkvsk commented 6 months ago

Currently only toOne associations rendered at index/detail pages as link to detail page of target entity. And toMany associations are rendered just as a count. It is very often you need to see all of the children entities of particular parent entity, given both have crud controllers.

Example:

This will require:

A partial implementation can be done by utilizing existing Filters mechanism instead of scoping, but scoping will be very nice to have for a better developer and user experience, as it is a very common use case.

klkvsk commented 6 months ago

I have partially implemented this behaviour as a custom FieldConfigurator:

#[AutoconfigureTag('ea.field_configurator', [ 'priority' => -100 ])]
final class AssociationToManyConfigurator implements FieldConfiguratorInterface
{
    private AdminUrlGeneratorInterface $adminUrlGenerator;

    public function __construct(AdminUrlGenerator $adminUrlGenerator)
    {
        $this->adminUrlGenerator = $adminUrlGenerator;
    }

    public function supports(FieldDto $field, EntityDto $entityDto): bool
    {
        return AssociationField::class === $field->getFieldFqcn();
    }

    public function configure(FieldDto $field, EntityDto $entityDto, AdminContext $context): void
    {
        $targetEntityFqcn = $field->getDoctrineMetadata()->get('targetEntity');
        // the target CRUD controller can be NULL; in that case, field value doesn't link to the related entity
        $targetCrudControllerFqcn = $field->getCustomOption(AssociationField::OPTION_EMBEDDED_CRUD_FORM_CONTROLLER)
            ?? $context->getCrudControllers()->findCrudFqcnByEntityFqcn($targetEntityFqcn);

        $targetEntityField = $field->getDoctrineMetadata()->get('mappedBy');
        if (!$targetEntityField) {
            return;
        }

        $field->setCustomOption(
            AssociationField::OPTION_RELATED_URL,
            $this->generateLinkToAssociatedEntity($targetCrudControllerFqcn, $targetEntityField, $entityDto->getPrimaryKeyValue())
        );

        // to utilize relatedUrl rendering in crud/field/association.twig.html
        $field->setCustomOption('associationType', 'toManyClickable');
    }

    private function generateLinkToAssociatedEntity(?string $crudController, string $targetEntityField, mixed $parentId): ?string
    {
        if (null === $crudController) {
            return null;
        }

        // TODO: check if user has permission to see the related entity
        return $this->adminUrlGenerator
            ->setController($crudController)
            ->setAction(Action::INDEX)
            ->set(EA::FILTERS, [
                $targetEntityField => [
                    'comparison' => '=',
                    'value' => $parentId,
                ]
            ])
            ->unset(EA::PAGE)
            ->unset(EA::QUERY)
            ->unset(EA::SORT)
            ->generateUrl();
    }
} 

But you'll need to set up Filters for every inversed association manually.