Symfony 4 - проблема производительности в форме с окном выбора с большим количеством опций - PullRequest
0 голосов
/ 14 сентября 2018

Я создал форму с окном выбора (EntityType) с большим количеством вариантов выбора (около 50 000):

->add(
    'destination', EntityType::class, array(
        'label' => 'à', 
        'multiple' => false,
        'required' => false,
        'class' => Stop::class,
        'attr' => array(
            'class' => 'form-control',
        )
    )
);

Я столкнулся с большой проблемой производительности: за десятки секунд до отображения спискаКогда я нажимаю на нееполе с полем поиска).Проблема в том, что я не могу найти наиболее эффективный способ сделать это через Symfony.

Я видел, что это возможно с помощью средства choice_loader, но нет подробной документации: https://symfony.com/blog/new-in-symfony-3-2-lazy-loading-of-form-choices

Было бы здорово, если кто-то может помочь в этом,

Спасибо за вашу поддержку,

1 Ответ

0 голосов
/ 15 сентября 2018

Когда я сталкиваюсь с такой проблемой, я использую другой подход. Если опция выбора будет иметь более 20 записей, поэтому я изменяю ее на текст ввода с автозаполнением.

Шаги:

  1. Установите хорошую Javascript lib для автозаполнения, например jquery-typeahead

    Мне нравится использовать Wepack Encore в Symfony. С Webpack, Npm и Yarn установка проста, как

    yarn add jquery-typeahead --dev
    

    Вам потребуется запустить yarn run encore dev после установки.

  2. Создайте новый FieldType для вашей формы, чтобы заменить EntityType

    Предположим, что нам нужно создать регистрационную форму с полем city . Поведение по умолчанию будет использовать EntityType и покажет вариант выбора со всеми городами. Чтобы изменить его на автозаполнение, давайте создадим еще один FieldType.

    <?php    
    // src/Form/Type/AutocompleteCityType.php
    namespace App\Form\Type; 
    use Doctrine\ORM\EntityManagerInterface; 
    use Symfony\Component\Form\AbstractType; 
    use Symfony\Component\Form\Extension\Core\Type\SearchType; 
    use Symfony\Component\OptionsResolver\OptionsResolver; 
    class AutocompleteCityType extends AbstractType
    {
    
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults(array(
                'attr' => ['autocomplete' => 'off']
            ));
        }
    
        public function getParent()
        {
            return SearchType::class;
        }
    }
    

ПРИМЕЧАНИЕ. В приведенном выше коде я расширяю SearchType :: class , который является поиском типа ввода (HTML 5).

Наш новый тип поля можно использовать в наших формах, но это просто другое строковое поле. Не будет работать правильно, чтобы заменить EntityType. Нам нужно преобразовать эту строку в сущность. Итак, нам нужен DataTransformer

  1. Создание города для строки DataTransformer

    <?php
    // src/Form/DataTransformer/CityToStringTransformer.php    
    namespace App\Form\DataTransformer;                
    use App\Entity\City; // Pay attention to use your Entities correctly
    use Symfony\Component\Form\DataTransformerInterface;
    use Symfony\Component\Form\Exception\TransformationFailedException;
    
    class CityToStringTransformer implements DataTransformerInterface
    {
        private $entityManager;
    
        public function __construct(EntityManagerInterface $entityManager)
        {
            $this->entityManager = $entityManager;
        }
    
        /**
         * Transforms an object (City) to a string.
         *
         * @param  City|null $city
         * @return string
         */
        public function transform($city)
        {
            if (null === $city) {
                return '';
            }
    
            return $city->getSomethingUnique();
        }
    
        /**
         * Transforms a string to an object (city).
         *
         * @param  string $somethingUnique
         * @return City|null
         * @throws TransformationFailedException if object (city) is not found.
         */
        public function reverseTransform($somethingUnique)
        {
            // empty City? It's optional, so that's ok
            if (!$somethingUnique) {
                return;
            }        
    
            $city = $this->entityManager
                    ->getRepository(City::class)
                    ->findByThatSomethingUnique($somethingUnique);                
    
            if (null === $city) {
                // causes a validation error
                // this message is not shown to the user
                // see the invalid_message option
                throw new TransformationFailedException(sprintf(
                    'The city "%s" cannot be found!',
                    $somethingUnique
                ));
            }
    
            return $city;
        }
    }
    

Примечание: строка должна быть своего рода уникальным ключом для правильной работы и должна хорошо отображаться при автозаполнении и заполнении поля (например, [CityCode] CityName ). DataTransformation не может возвращать более одного результата для метода findByThatSomethingUnique().

Примечание 2: Например, $city->getSomethingUnique() не может быть $city->getId()."-".$city->getName(), а ->findByThatSomethingUnique($somethingUnique) может быть ->findOneById(explode("-", $somethingUnique)[0])

Почти готово. Мы можем использовать оба класса в нашем FormType для замены EntityType.

  1. Использование в FormType

    // src/Form/MyFormType.php
    
    // (...) Other declarations(...)
    use App\Form\DataTransformer\ContactToStringTransformer;
    use App\Form\Type\AutocompleteContactType;
    
    class MyFormType extends AbstractType
    {
        private $cityTransformer;
    
        public function __construct(CityToStringTransformer $cityTransformer)
        {
            $this->cityTransformer = $cityTransformer;
        }
    
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            /* @var $myEntity MyEntity */
            $myEntity = $builder->getData();
            $builder
                ->add('city', AutocompleteCityType::class, [
                    'label' => 'Custom City Label',
                ]) 
            // (...) Other fields (...)
            ;
    
            $builder->get('city')
                ->addModelTransformer($this->cityTransformer);
    
        }
    

С указанным здесь кодом форма будет отображаться правильно, но заголовок должен быть правильно настроен.

Вы можете создать новый тип блока веток для этого нового типа поля. Код ниже должен находиться в вашей пользовательской form_theme

  1. Блок Twig

    {% block autocomplete_city_widget %}
        {% spaceless %}
            <div class="typeahead__container">
                <div class="typeahead__field">
                    <div class="typeahead__query">
                        {{ form_widget(form) }}
                    </div>
                </div>
            </div>
        {% endspaceless %}
    {% endblock %}
    

ПРИМЕЧАНИЕ. Приведенный выше код Twig относится к jquery-typeahead и работает только с полем AutocompleteCityType. Если вы устанавливаете другую библиотеку или изменяете имя класса FieldType, измените его правильно. Также обратите внимание на имена форм, которые изменяют имя блока для визуализации.

Последнее, что нужно написать javascript для typeahead, получить записи.

  1. Javascript Typeahead

    jQuery.typeahead({
        input: "#myForm_city", // Related to FormName and Autocomplete Field Name
        minLength: 1,
        maxItem: 20,
        order: "asc",
        dynamic: true,
        delay: 500,
        backdrop: { "background-color": "#eeeeee" },
        template: "<small style='color:#999;'>{{ '[{{citycode}}] {{cityname}}' }}</small>", // remember that this is a Twig template...
        emptyTemplate: "No results for typed string",
        source: {
            city: {
                display: ["citycode", "cityname"],
                ajax: function (query) {
                    return {
                        type: "POST",
                        url: '{{ path('controller_with_city_list_json_response') }}',
                        path: "city",
                        data: {
                            "q": "{{ '{{query}}' }}",
                            "length" : "40",
                        },
                        callback: {
                            done: function (res) {
                                var d = {};
                                d.city = [];
                                jQuery(res.data).each(function(index, value) {
                                    d.city.push(value.city);
                                });
                                return d;
                            }
                        }
                    }
                }
            }
        },
        callback: {
            onClickAfter: function (node, a, item, event) {
                event.preventDefault();
                jQuery(node).val("[" + item.citycode + "] " + item.cityname);
            }
        },
        debug: false
    });
    

ПРИМЕЧАНИЕ. Приведенному выше коду Typeahead требуется ответ json в формате

{"data":
    [
        {"city":
            {"cityname":"City Name X", "citycode": "NXNX"}
        },
        {"city":
            {"cityname":"City Name Y", "citycode": "NYNY"}
        }
    ]
 }
...