symfony / ux

Symfony UX initiative: a JavaScript ecosystem for Symfony
https://ux.symfony.com/
MIT License
812 stars 295 forks source link

[Autocomplete] incompatible with FrankenPHP's worker mode #1649

Closed pyatnitsev closed 1 week ago

pyatnitsev commented 5 months ago

image

I use https://ux.symfony.com/autocomplete with standard doctrine. first request when cursor pointed to field was ok, but no one more. All next requests ends with 500 error with log, that I have attached above.

This issue related to worker mode. I disable it, now autocomplete works file.

So, I can create a minimal app, that reproduce issue.

related issue for FrankenPHP: https://github.com/dunglas/frankenphp/issues/683

smnandre commented 5 months ago

Is frankenPhp in worker mode fully supported by Symfony/Doctrine/Twig currently ? (genuine question)

pyatnitsev commented 5 months ago

Yes it is. I run it in production. Worker mode support symfony and this components (doctrine and twig), but I have issue with symfony UX's autocomplete component.

smnandre commented 5 months ago

So, I can create a minimal app, that reproduce issue.

Yep, that would make it easier to understand what's happening there, thank you. But as you can see, my knowledge of worker modes is quite... limited 😅

bpacholek commented 1 month ago

Hey @pyatnitsev not sure if you still experience the issue or you have managed to overcome it, but at least until symfony/ux is officially supported with worker modes (of any runner, not only frankenphp) you can use a decorator:

  1. Create a file in your project. For instance: src/Form/Autocomplete/WorkerAwareWrappedEntityTypeAutocompleter.php.
  2. Use this code:
<?php

namespace App\Form\Autocompleter;

use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\UX\Autocomplete\Doctrine\EntityMetadataFactory;
use Symfony\UX\Autocomplete\Doctrine\EntitySearchUtil;
use Symfony\UX\Autocomplete\Form\WrappedEntityTypeAutocompleter;
use Symfony\UX\Autocomplete\OptionsAwareEntityAutocompleterInterface;

final class WorkerAwareWrappedEntityTypeAutocompleter implements OptionsAwareEntityAutocompleterInterface
{
    private WrappedEntityTypeAutocompleter $innerAutocompleter;

    private bool $optionsInitialized;

    public function __construct(
        private string $formType,
        private FormFactoryInterface $formFactory,
        private EntityMetadataFactory $metadataFactory,
        private PropertyAccessorInterface $propertyAccessor,
        private EntitySearchUtil $entitySearchUtil,
    ) {
        $this->innerAutocompleter = new WrappedEntityTypeAutocompleter($formType, $formFactory, $metadataFactory, $propertyAccessor, $entitySearchUtil);
        $this->optionsInitialized = false;
    }

    public function getEntityClass(): string
    {
        return $this->innerAutocompleter->getEntityClass();
    }

    public function createFilteredQueryBuilder(EntityRepository $repository, string $query): QueryBuilder
    {
        return $this->innerAutocompleter->createFilteredQueryBuilder($repository, $query);
    }

    public function getLabel(object $entity): string
    {
        return $this->innerAutocompleter->getLabel($entity);
    }

    public function getValue(object $entity): string
    {
        return $this->innerAutocompleter->getValue($entity);
    }

    public function isGranted(Security $security): bool
    {
        return $this->innerAutocompleter->isGranted($security);
    }

    public function getGroupBy(): mixed
    {
        return $this->innerAutocompleter->getGroupBy();
    }

    public function setOptions(array $options): void
    {
        if ($this->optionsInitialized) {
            return; //silently exit
        }

        $this->optionsInitialized = true;
        $this->innerAutocompleter->setOptions($options);
    }
}

in your services.yaml instruct Symfony's DI to actually use your class:

  ux.autocomplete.wrapped_entity_type_autocompleter:
    class: App\Form\Autocompleter\WorkerAwareWrappedEntityTypeAutocompleter
    abstract: true
    arguments:
      - !abstract 'form type string'
      - '@form.factory'
      - '@ux.autocomplete.entity_metadata_factory'
      - '@property_accessor'
      - '@ux.autocomplete.entity_search_util'

If you now bin/console debug:container | grep autocomplete you should see that all autocomplete services base on your service: image

As you can it is not a perfect solution: it silently prevents second attempt to set options.

The cause of the problem is the fact that autocomplete instances are not re-set and they remain in memory when using worker mode. This causes the setOptions to be executed on a preconfigured form. This will require a major change in how symfony/ux works. Encountered the issue myself today, using the workaround for now, will try to work on a proper solution, but in the meantime check if useful for you.

kbond commented 1 week ago

Should be fixed with #2094.