【问题标题】:Symfony 4 - performance issue on form with a selectbox with many optionsSymfony 4 - 带有许多选项的选择框的表单性能问题
【发布时间】:2018-09-14 11:52:05
【问题描述】:

我构建了一个带有选择框 (EntityType) 的表单,其中包含大量选择(大约 50 000 个):

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

我面临一个很大的性能问题:当我点击列表时,它会在显示列表前几十秒。

我想解决方案是最初只加载几个元素(例如一百个),然后在用户开始输入时使用 Ajax 请求数据库(我正在使用带有搜索字段的 select2 框)。 问题是我无法通过 Symfony 找出最有效的方法。

我已经看到choice_loader 功能可以做到这一点,但没有可用的详细文档:https://symfony.com/blog/new-in-symfony-3-2-lazy-loading-of-form-choices

如果有人能帮上忙就好了,

感谢您的支持,

【问题讨论】:

  • 自动补全就是答案。 Marcos 在下面列出了一个选项作为答案。我在PUGX autocompleter 上也取得了成功。
  • 您好,感谢您的反馈。我已经尝试过这种方式,它几乎可以工作,但我仍然面临一个问题: - 使用 select2 库它不起作用,我收到以下 js 错误:“if (typeof(text) == 'undefined'){return; }" - 使用 jquery ui,它可以工作,但由于它存储在输入中,如果我希望 Symfony 表单处理器检索它,我只能在字段中显示我的实体的主键,这对于使用来说不是那么直观。 ..知道如何解决这个问题吗?

标签: forms symfony dropdown jquery-selectbox


【解决方案1】:

当我遇到这种麻烦时,我会使用另一种方法。 如果选择选项将有超过 20 个条目,那么我将其更改为具有自动完成功能的输入文本。

步骤如下:

  1. 安装一个好的自动完成 Javascript 库,例如 jquery-typeahead

    我喜欢在 Symfony 中使用Wepack Encore。使用 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-&gt;getSomethingUnique()不能是$city-&gt;getId()."-".$city-&gt;getName()-&gt;findByThatSomethingUnique($somethingUnique)可以是-&gt;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. 树枝块

    {% 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 类的名称,请正确更改它。还要注意更改要呈现的块名称的表单名称。

最后一件事是为 typeahead 编写 javascript 获取条目。

  1. Typeahead Javascript

    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"}
        }
    ]
 }

【讨论】:

    猜你喜欢
    • 2015-04-14
    • 1970-01-01
    • 1970-01-01
    • 2018-09-29
    • 2020-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-22
    相关资源
    最近更新 更多