symfonycasts/dynamic-forms 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

symfonycasts/dynamic-forms

Composer 安装命令:

composer require symfonycasts/dynamic-forms

包简介

Add dynamic/dependent fields to Symfony forms

关键字:

README 文档

README

CI

NOTE: This package is currently experimental. It seems to work great - but forms are complex! If you find a bug, please open an issue!

Ever have a form field that depends on another?

You can find a Demo with LiveComponent on Symfony UX.

  • Show a field only if another field is set to a specific value;
  • Change the options of a field based on the value of another field;
  • Have multiple-level dependencies (e.g. field A depends on field B which depends on field C).
public function buildForm(FormBuilderInterface $builder, array $options): void
{
    $builder = new DynamicFormBuilder($builder);

    $builder->add('meal', ChoiceType::class, [
        'choices' => [
            'Breakfast' => 'breakfast',
            'Lunch' => 'lunch',
            'Dinner' => 'dinner',
        ],
    ]);

    $builder->addDependent('mainFood', ['meal'], function(DependentField $field, string $meal) {
        // dynamically add choices based on the meal!
        $choices = ['...'];

        $field->add(ChoiceType::class, [
            'placeholder' => null === $meal ? 'Select a meal first' : sprintf('What is for %s?', $meal->getReadable()),
            'choices' => $choices,
            'disabled' => null === $meal,
        ]);
    });

Installation

Install the package with:

composer require symfonycasts/dynamic-forms

Done - you're ready to build dynamic forms!

Usage

Setting up a dependent field is two parts:

  1. Usage in PHP - set up your Symfony form to handle the dynamic fields;
  2. Updating the Frontend - adding code to your frontend so that when one field changes, part of the form is re-rendered.

Usage in PHP

Start by wrapping your FormBuilderInterface with a DynamicFormBuilder:

use Symfonycasts\DynamicForms\DynamicFormBuilder;
// ...

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    $builder = new DynamicFormBuilder($builder);

    // ...
}

DynamicFormBuilder has all the same methods as FormBuilderInterface plus one extra: addDependent(). If a field depends on another, use this method instead of add()

// src/Form/FeedbackForm.php

// ...
use Symfonycasts\DynamicForms\DependentField;
use Symfonycasts\DynamicForms\DynamicFormBuilder;

class FeedbackForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder =  new DynamicFormBuilder($builder);

        $builder->add('rating', ChoiceType::class, [
            'choices' => [
                'Select a rating' => null,
                'Great' => 5,
                'Good' => 4,
                'Okay' => 3,
                'Bad' => 2,
                'Terrible' => 1
            ],
        ]);

        $builder->addDependent('badRatingNotes', 'rating', function(DependentField $field, ?int $rating) {
            if (null === $rating || $rating >= 3) {
                return; // field not needed
            }

            $field->add(TextareaType::class, [
                'label' => 'What went wrong?',
                'attr' => ['rows' => 3],
                'help' => sprintf('Because you gave a %d rating, we\'d love to know what went wrong.', $rating),
            ]);
        });
    }
}

The addDependent() method takes 3 arguments:

  1. The name of the field to add;
  2. The name (or names) of the field that this field depends on;
  3. A callback that will be called when the form is submitted. This callback receives a DependentField object as the first argument then the value of each dependent field as the next arguments.

Behind the scenes, this works by registering several form event listeners. The callback be executed when the form is first created (using the initial data) and then again when the form is submitted. This means that the callback may be called multiple times.

Rendering the field is the same - just be sure to make sure the field exists if it's conditionally added:

{{ form_start(form) }}
    {{ form_row(form.rating) }}

    {% if form.badRatingNotes is defined %}
        {{ form_row(form.badRatingNotes) }}
    {% endif %}

    <button>Send Feedback</button>
{{ form_end(form) }}

Updating the Frontend

In the previous example, when the rating field changes, the form (or part of the form) needs to be re-rendered so the badRatingNotes field can be added.

This library doesn't handle this for you, but here are the 2 main options:

A) Use Live Components

This is the easiest method: by rendering your form inside a live component, it will automatically re-render when the form changes.

B) Use Symfony UX Turbo

If you are already using Symfony UX Turbo on your website, you can have a dynamic form running quickly without any JavaScript.

Or you may want to install Symfony UX Turbo, check out the documentation.

Note

You only need to have Turbo Frame, you can disable Turbo Drive if you do not use it, or do not want to use it. ie: Turbo.session.drive = false;

Simply add a <turbo-frame> around your form:

<turbo-frame id="rating-form">
    {{ form(form) }}
</turbo-frame>

From here you need two small changes:

First, in your form type:

  • You need to add an attribute on the choice field, so it auto-submits the form when changed (may need to be adapted to your own form if more complex)
  • Add a submit button, so in the controller you can differenciate from an auto-submit versus a user action
// src/Form/FeedbackForm.php

// ...

class FeedbackForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder =  new DynamicFormBuilder($builder);

        $builder->add('rating', ChoiceType::class, [
            'choices' => [
                'Select a rating' => null,
                'Great' => 5,
                'Good' => 4,
                'Okay' => 3,
                'Bad' => 2,
                'Terrible' => 1
            ],
+           // This will allow the form to auto-submit on value change
+           'attr' => ['onchange' => 'this.form.requestSubmit()'],
        ]);
+       // This will allow to differenciate between a user submition and an auto-submit
+       $builder->add('submit', SubmitType::class, [
+           'attr' => ['value' => 'submit'], // Needed for Turbo
+       ]);

        $builder->addDependent('badRatingNotes', 'rating', function(DependentField $field, ?int $rating) {
            if (null === $rating || $rating >= 3) {
                return; // field not needed
            }

            $field->add(TextareaType::class, [
                'label' => 'What went wrong?',
                'attr' => ['rows' => 3],
                'help' => sprintf('Because you gave a %d rating, we\'d love to know what went wrong.', $rating),
            ]);
        });
    }
}

Second, in your controller:

// src/Controller/FeedbackController.php

    #[Route('/feedback', name: 'feedback')]
    public function feedback(Request $request): Response
    {
        //...

-       $feedbackForm = $this->createForm(FeedbackForm::class);
+       $feedbackForm = $this->createForm(FeedbackForm::class, options: [
+           // This is needed by Turbo Frame, it is not specific to Dependent Symfony Form Fields
+           'action' => $this->generateUrl('feedback'),
+       ]);
        $feedbackForm->handleRequest($request);
        if ($feedbackForm->isSubmitted() && $feedbackForm->isValid()) {

+           /** @var SubmitButton $submitButton */
+           $submitButton = $feedbackForm->get('submit');
+           if (!$submitButton->isClicked()) {
+               return $this->render('feedback.html.twig', ['feedbackForm' => $feedbackForm]);
+           }

            // Your code here
            // ...

            return $this->redirectToRoute('home');
        }

        return $this->render('feedback.html.twig', ['feedbackForm' => $feedbackForm]);
    }

C) Write custom JavaScript

If you're not using Live Components, nor Turbo Frames, you'll need to write some custom JavaScript to listen to the change event on the rating field and then make an AJAX call to re-render the form. The AJAX call should submit the form to its usual endpoint (or any endpoint that will submit the form), take the HTML response, extract the parts that need to be re-rendered and then replace the HTML on the page.

This is a non-trivial task and there may be room for improvement in this library to make this easier. If you have ideas, please open an issue!

Security Policy

If you discover a security vulnerability, please do not open a public issue or pull request. Instead, please review this repository's Security Policy for instructions on how to report it responsibly.

symfonycasts/dynamic-forms 适用场景与选型建议

symfonycasts/dynamic-forms 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 2.68M 次下载、GitHub Stars 达 145, 最近一次更新时间为 2023 年 08 月 31 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「symfony」 「Forms」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 symfonycasts/dynamic-forms 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 symfonycasts/dynamic-forms 我们能提供哪些服务?
定制开发 / 二次开发

基于 symfonycasts/dynamic-forms 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 2.68M
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 145
  • 点击次数: 18
  • 依赖项目数: 8
  • 推荐数: 0

GitHub 信息

  • Stars: 145
  • Watchers: 8
  • Forks: 15
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2023-08-31