craue / formflow-bundle
为您的Symfony项目提供多步表单功能。
Requires
- php: ^7.3 || ^8
- symfony/config: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/dependency-injection: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/event-dispatcher: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/form: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/http-foundation: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/http-kernel: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/options-resolver: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/security-core: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/translation: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/validator: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/yaml: ^4.4 || ^5.4 || ^6.3 || ^7.0
Requires (Dev)
- craue/translations-tests: ^1.1
- doctrine/collections: ^1.8 || ^2.1
- doctrine/common: ^2.9 || ^3.0
- doctrine/dbal: ^2.10 || ^3.0
- doctrine/doctrine-bundle: ^1.10 || ^2.0
- phpstan/extension-installer: ^1.1
- phpstan/phpstan: ^1.10
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-strict-rules: ^1.1
- phpstan/phpstan-symfony: ^1.1
- phpunit/phpunit: ^9.5
- symfony/browser-kit: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/css-selector: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/mime: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/phpunit-bridge: ^7.0
- symfony/security-bundle: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/twig-bundle: ^4.4 || ^5.4 || ^6.3 || ^7.0
Conflicts
- doctrine/dbal: <2.10
- dev-master / 3.7.x-dev
- 3.7.0
- 3.6.0
- 3.5.1
- 3.5.0
- 3.4.1
- 3.4.0
- 3.3.2
- 3.3.1
- 3.3.0
- 3.2.1
- 3.2.0
- 3.1.1
- 3.1.0
- 3.0.4
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- 2.1.10
- 2.1.9
- 2.1.8
- 2.1.7
- 2.1.6
- 2.1.5
- 2.1.4
- 2.1.3
- 2.1.2
- 2.1.1
- 2.1.0
- 2.0.1
- 2.0.0
- 1.1.3
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.0
- dev-prepare-subtree-split-for-library-keep-namespaces
- dev-prepare-subtree-split-for-library-without-proxies
- dev-prepare-subtree-split-for-library
- dev-test-issue-89
This package is auto-updated.
Last update: 2024-09-11 02:26:20 UTC
README
CraueFormFlowBundle为您的Symfony项目提供了构建和处理多步表单的功能。
特性
- 导航(下一步、上一步、重新开始)
- 步骤标签
- 跳过步骤
- 每个步骤不同的验证组
- 处理文件上传
- 动态步骤导航(可选)
- 提交后重定向(即“Post/Redirect/Get”,可选)
以下链接提供了这些特性的实时演示:http://craue.de/symfony-playground/en/CraueFormFlow/。
安装
获取包
在shell中运行以下命令,让Composer下载并安装包:
composer require craue/formflow-bundle
启用包
如果您不使用Symfony Flex,请手动注册包
// in config/bundles.php return [ // ... Craue\FormFlowBundle\CraueFormFlowBundle::class => ['all' => true], ];
或者,对于Symfony 3.4
// in app/AppKernel.php public function registerBundles() { $bundles = [ // ... new Craue\FormFlowBundle\CraueFormFlowBundle(), ]; // ... }
使用方法
本节展示了如何创建一个创建车辆的3步表单流程。您需要在如何设置流程方面选择两种方法之一。
方法A:整个流程使用一个表单类型
这种方法使得将现有(通用)表单转换为表单流程变得容易。
创建流程类
// src/MyCompany/MyBundle/Form/CreateVehicleFlow.php use Craue\FormFlowBundle\Form\FormFlow; use Craue\FormFlowBundle\Form\FormFlowInterface; use MyCompany\MyBundle\Form\CreateVehicleForm; class CreateVehicleFlow extends FormFlow { protected function loadStepsConfig() { return [ [ 'label' => 'wheels', 'form_type' => CreateVehicleForm::class, ], [ 'label' => 'engine', 'form_type' => CreateVehicleForm::class, 'skip' => function($estimatedCurrentStepNumber, FormFlowInterface $flow) { return $estimatedCurrentStepNumber > 1 && !$flow->getFormData()->canHaveEngine(); }, ], [ 'label' => 'confirmation', ], ]; } }
创建表单类型类
您只需要为流程创建一个表单类型类。有一个名为flow_step
的选项,您可以使用它来决定根据要渲染的步骤将哪些字段添加到表单中。
// src/MyCompany/MyBundle/Form/CreateVehicleForm.php use MyCompany\MyBundle\Form\Type\VehicleEngineType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; class CreateVehicleForm extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { switch ($options['flow_step']) { case 1: $validValues = [2, 4]; $builder->add('numberOfWheels', ChoiceType::class, [ 'choices' => array_combine($validValues, $validValues), 'placeholder' => '', ]); break; case 2: // This form type is not defined in the example. $builder->add('engine', VehicleEngineType::class, [ 'placeholder' => '', ]); break; } } public function getBlockPrefix() { return 'createVehicle'; } }
方法B:每个步骤一个表单类型
这种方法使得重用表单类型来组合其他表单变得容易。
创建流程类
// src/MyCompany/MyBundle/Form/CreateVehicleFlow.php use Craue\FormFlowBundle\Form\FormFlow; use Craue\FormFlowBundle\Form\FormFlowInterface; use MyCompany\MyBundle\Form\CreateVehicleStep1Form; use MyCompany\MyBundle\Form\CreateVehicleStep2Form; class CreateVehicleFlow extends FormFlow { protected function loadStepsConfig() { return [ [ 'label' => 'wheels', 'form_type' => CreateVehicleStep1Form::class, ], [ 'label' => 'engine', 'form_type' => CreateVehicleStep2Form::class, 'skip' => function($estimatedCurrentStepNumber, FormFlowInterface $flow) { return $estimatedCurrentStepNumber > 1 && !$flow->getFormData()->canHaveEngine(); }, ], [ 'label' => 'confirmation', ], ]; } }
创建表单类型类
// src/MyCompany/MyBundle/Form/CreateVehicleStep1Form.php use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; class CreateVehicleStep1Form extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $validValues = [2, 4]; $builder->add('numberOfWheels', ChoiceType::class, [ 'choices' => array_combine($validValues, $validValues), 'placeholder' => '', ]); } public function getBlockPrefix() { return 'createVehicleStep1'; } }
// src/MyCompany/MyBundle/Form/CreateVehicleStep2Form.php use MyCompany\MyBundle\Form\Type\VehicleEngineType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; class CreateVehicleStep2Form extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('engine', VehicleEngineType::class, [ 'placeholder' => '', ]); } public function getBlockPrefix() { return 'createVehicleStep2'; } }
将您的流程注册为服务
XML
<services> <service id="myCompany.form.flow.createVehicle" class="MyCompany\MyBundle\Form\CreateVehicleFlow" autoconfigure="true"> </service> </services>
YAML
services: myCompany.form.flow.createVehicle: class: MyCompany\MyBundle\Form\CreateVehicleFlow autoconfigure: true
当不使用自动配置时,您可以让您的流程从父服务继承所需的依赖项。
XML
<services> <service id="myCompany.form.flow.createVehicle" class="MyCompany\MyBundle\Form\CreateVehicleFlow" parent="craue.form.flow"> </service> </services>
YAML
services: myCompany.form.flow.createVehicle: class: MyCompany\MyBundle\Form\CreateVehicleFlow parent: craue.form.flow
创建表单模板
您只需要为流程创建一个模板。您的流程类的实例通过名为flow
的变量传递到模板中,这样您就可以根据当前步骤使用它来渲染表单。
{# in src/MyCompany/MyBundle/Resources/views/Vehicle/createVehicle.html.twig #} <div> Steps: {% include '@CraueFormFlow/FormFlow/stepList.html.twig' %} </div> {{ form_start(form) }} {{ form_errors(form) }} {% if flow.getCurrentStepNumber() == 1 %} <div> When selecting four wheels you have to choose the engine in the next step.<br /> {{ form_row(form.numberOfWheels) }} </div> {% endif %} {{ form_rest(form) }} {% include '@CraueFormFlow/FormFlow/buttons.html.twig' %} {{ form_end(form) }}
CSS
需要一些CSS来正确渲染按钮。在您的基模板中加载提供的文件
<link type="text/css" rel="stylesheet" href="{{ asset('bundles/craueformflow/css/buttons.css') }}" />
...并将资源安装到您的项目中
# in a shell
php bin/console assets:install --symlink web
按钮
您可以通过使用这些变量来添加一个或多个CSS类来自定义默认按钮的外观
craue_formflow_button_class_last
将应用于下一步或完成按钮craue_formflow_button_class_finish
将专门应用于完成按钮craue_formflow_button_class_next
将专门应用于下一步按钮craue_formflow_button_class_back
将应用于上一步按钮craue_formflow_button_class_reset
将应用于重置按钮
使用Bootstrap按钮类的示例
{% include '@CraueFormFlow/FormFlow/buttons.html.twig' with { craue_formflow_button_class_last: 'btn btn-primary', craue_formflow_button_class_back: 'btn', craue_formflow_button_class_reset: 'btn btn-warning', } %}
同样,您可以自定义按钮标签
craue_formflow_button_label_last
用于下一步或完成按钮craue_formflow_button_label_finish
用于完成按钮craue_formflow_button_label_next
用于下一步按钮craue_formflow_button_label_back
用于上一步按钮craue_formflow_button_label_reset
用于重置按钮
示例
{% include '@CraueFormFlow/FormFlow/buttons.html.twig' with { craue_formflow_button_label_finish: 'submit', craue_formflow_button_label_reset: 'reset the flow', } %}
您还可以通过将craue_formflow_button_render_reset
设置为false
来移除重置按钮。
创建操作
// in src/MyCompany/MyBundle/Controller/VehicleController.php public function createVehicleAction() { $formData = new Vehicle(); // Your form data class. Has to be an object, won't work properly with an array. $flow = $this->get('myCompany.form.flow.createVehicle'); // must match the flow's service id $flow->bind($formData); // form of the current step $form = $flow->createForm(); if ($flow->isValid($form)) { $flow->saveCurrentStepData($form); if ($flow->nextStep()) { // form for the next step $form = $flow->createForm(); } else { // flow finished $em = $this->getDoctrine()->getManager(); $em->persist($formData); $em->flush(); $flow->reset(); // remove step data from the session return $this->redirectToRoute('home'); // redirect when done } } return $this->render('@MyCompanyMy/Vehicle/createVehicle.html.twig', [ 'form' => $form->createView(), 'flow' => $flow, ]); }
说明
流程如何工作
- 触发
PreBindEvent
。 - 发送
GetStepsEvent
。 - 使用所有步骤之前保存的数据更新表单数据类。对于每个步骤,发送
PostBindSavedDataEvent
。 - 评估哪些步骤被跳过。确定当前步骤。
- 发送
PostBindFlowEvent
。 - 为当前步骤创建表单。
- 将请求绑定到该表单。
- 发送
PostBindRequestEvent
。 - 验证表单数据。
- 发送
PostValidateEvent
。 - 保存表单数据。
- 进入下一个步骤。
方法 loadStepsConfig
该方法返回的数组用于创建流程的所有步骤。第一个项目将是第一个步骤。然而,你可以显式索引数组以提高可读性。
每个步骤的有效选项有
label
(string
|StepLabel
|null
)- 如果你想要渲染所有步骤的概述,你必须为每个步骤设置
label
选项。 - 如果在一个
StepLabel
实例上使用可调用函数,它必须返回一个字符串值或null
。 - 默认情况下,当在 Twig 中渲染时,标签将使用
messages
域进行翻译。
- 如果你想要渲染所有步骤的概述,你必须为每个步骤设置
form_type
(FormTypeInterface
|string
|null
)- 用于构建该步骤表单的表单类型。
- 此值传递给 Symfony 的表单工厂,因此适用于创建普通表单的规则同样适用。如果使用字符串,它必须是表单类型的 FQCN。
form_options
(array
)- 传递给该步骤表单类型的选项。
skip
(callable
|bool
)- 决定该步骤是否会跳过。
- 如果使用可调用...
- 它将接收估计的当前步骤号和流程作为参数;
- 它必须返回一个布尔值;
- 它可能被调用多次,直到确定实际的当前步骤号。
示例
protected function loadStepsConfig() { return [ [ 'form_type' => CreateVehicleStep1Form::class, ], [ 'form_type' => CreateVehicleStep2Form::class, 'skip' => true, ], [ ], ]; }
protected function loadStepsConfig() { return [ 1 =>[ 'label' => 'wheels', 'form_type' => CreateVehicleStep1Form::class, ], 2 => [ 'label' => StepLabel::createCallableLabel(function() { return 'engine'; }) 'form_type' => CreateVehicleStep2Form::class, 'form_options' => [ 'validation_groups' => ['Default'], ], 'skip' => function($estimatedCurrentStepNumber, FormFlowInterface $flow) { return $estimatedCurrentStepNumber > 1 && !$flow->getFormData()->canHaveEngine(); }, ], 3 => [ 'label' => 'confirmation', ], ]; }
高级内容
验证组
为了验证绑定到流程的表单数据类,将基于步骤的验证组传递给表单类型。默认情况下,如果流程的 getName
方法返回 createVehicle
,则此类组名为 flow_createVehicle_step1
对于第一个步骤。你可以通过显式设置流程的 validationGroupPrefix
属性来自定义此名称。步骤号(1、2、3 等)将由流程追加。
与独立表单相比,在表单类型的 configureOptions
方法中设置 validation_groups
选项在流程的上下文中不会产生任何影响。该值仅被忽略,即会被流程覆盖。但还有其他定义自定义验证组的方法
- 重写流程的
getFormOptions
方法; - 使用
form_options
步骤选项;或 - 使用流程的
setGenericFormOptions
方法。
生成的基于步骤的验证组将由流程添加,除非将 validation_groups
选项设置为 false
、闭包或 GroupSequence。在这种情况下,它将 不会 由流程添加,因此请确保步骤表单按预期进行验证。
禁用先前步骤的重新验证
查看 #98 以了解默认情况下通过重新验证先前步骤的原因。但是,如果你想要(或需要)避免重新验证先前步骤,请将以下内容添加到你的流程类中
// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php class CreateVehicleFlow extends FormFlow { protected $revalidatePreviousSteps = false; // ... }
将通用选项传递给表单类型
要设置适用于所有步骤表单类型(s)的通用选项,可以使用方法 setGenericFormOptions
// in src/MyCompany/MyBundle/Controller/VehicleController.php public function createVehicleAction() { // ... $flow->setGenericFormOptions(['action' => 'targetUrl']); $flow->bind($formData); $form = $flow->createForm(); // ... }
将基于步骤的选项传递给表单类型
要向每个步骤的表单类型传递单独的选项,可以使用步骤配置选项 form_options
// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php protected function loadStepsConfig() { return [ [ 'label' => 'wheels', 'form_type' => CreateVehicleStep1Form:class, 'form_options' => [ 'validation_groups' => ['Default'], ], ], ]; }
另外,要根据之前的步骤设置选项(例如根据提交的数据渲染字段),你可以覆盖你的流程类中的 getFormOptions
方法。
// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php public function getFormOptions($step, array $options = []) { $options = parent::getFormOptions($step, $options); $formData = $this->getFormData(); if ($step === 2) { $options['numberOfWheels'] = $formData->getNumberOfWheels(); } return $options; }
启用动态步骤导航
动态步骤导航意味着渲染的步骤列表将包含直接跳转到特定步骤(已完成的步骤)的链接。要启用它,请将以下内容添加到你的流程类中。
// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php class CreateVehicleFlow extends FormFlow { protected $allowDynamicStepNavigation = true; // ... }
如果你想在提交表单时删除(通过使用此类直接链接添加的)参数,你应该修改模板中打开表单标签的动作,如下所示。
{{ form_start(form, {'action': path(app.request.attributes.get('_route'), app.request.query.all | craue_removeDynamicStepNavigationParameters(flow))}) }}
处理文件上传
文件上传通过将内容进行Base64编码并存储在会话中透明地处理,因此可能会影响性能。此功能默认启用以提高便利性,但可以在流程类中按如下方式禁用。
// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php class CreateVehicleFlow extends FormFlow { protected $handleFileUploads = false; // ... }
默认情况下,系统将使用临时文件目录用于从会话中恢复的文件,在加载步骤数据时使用。你可以设置一个自定义的目录。
// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php class CreateVehicleFlow extends FormFlow { protected $handleFileUploadsTempDir = '/path/for/flow/uploads'; // ... }
启用提交后重定向
此功能将允许在提交步骤后执行重定向,使用GET请求加载包含下一个步骤的页面。要启用它,请将以下内容添加到你的流程类中。
// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php class CreateVehicleFlow extends FormFlow { protected $allowRedirectAfterSubmit = true; // ... }
但你需要自己执行重定向,因此请更新你的动作如下所示。
// in src/MyCompany/MyBundle/Controller/VehicleController.php public function createVehicleAction() { // ... $flow->bind($formData); $form = $submittedForm = $flow->createForm(); if ($flow->isValid($submittedForm)) { $flow->saveCurrentStepData($submittedForm); // ... } if ($flow->redirectAfterSubmit($submittedForm)) { $request = $this->getRequest(); $params = $this->get('craue_formflow_util')->addRouteParameters(array_merge($request->query->all(), $request->attributes->get('_route_params')), $flow); return $this->redirectToRoute($request->attributes->get('_route'), $params); } // ... // return ... }
使用事件
有一些事件你可以订阅。在流程类中直接使用所有这些事件可能如下所示。
// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php use Craue\FormFlowBundle\Event\GetStepsEvent; use Craue\FormFlowBundle\Event\PostBindFlowEvent; use Craue\FormFlowBundle\Event\PostBindRequestEvent; use Craue\FormFlowBundle\Event\PostBindSavedDataEvent; use Craue\FormFlowBundle\Event\PostValidateEvent; use Craue\FormFlowBundle\Event\PreBindEvent; use Craue\FormFlowBundle\Form\FormFlowEvents; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class CreateVehicleFlow extends FormFlow implements EventSubscriberInterface { /** * This method is only needed when _not_ using autoconfiguration. If it's there even with autoconfiguration enabled, * the `removeSubscriber` call ensures that subscribed events won't occur twice. * (You can remove the `removeSubscriber` call if you'll definitely never use autoconfiguration for that flow.) */ public function setEventDispatcher(EventDispatcherInterface $dispatcher) { parent::setEventDispatcher($dispatcher); $dispatcher->removeSubscriber($this); $dispatcher->addSubscriber($this); } public static function getSubscribedEvents() { return [ FormFlowEvents::PRE_BIND => 'onPreBind', FormFlowEvents::GET_STEPS => 'onGetSteps', FormFlowEvents::POST_BIND_SAVED_DATA => 'onPostBindSavedData', FormFlowEvents::POST_BIND_FLOW => 'onPostBindFlow', FormFlowEvents::POST_BIND_REQUEST => 'onPostBindRequest', FormFlowEvents::POST_VALIDATE => 'onPostValidate', ]; } public function onPreBind(PreBindEvent $event) { // ... } public function onGetSteps(GetStepsEvent $event) { // ... } public function onPostBindSavedData(PostBindSavedDataEvent $event) { // ... } public function onPostBindFlow(PostBindFlowEvent $event) { // ... } public function onPostBindRequest(PostBindRequestEvent $event) { // ... } public function onPostValidate(PostValidateEvent $event) { // ... } // ... }