lexal / stepped-form
步骤表单的实现
Requires
- php: >=8.1
- psr/event-dispatcher: ^1.0
- symfony/deprecation-contracts: ^3.4
Requires (Dev)
- dealerdirect/phpcodesniffer-composer-installer: ^1.0
- infection/infection: ^0.27.9
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.4
- roave/security-advisories: dev-latest
- webimpress/coding-standard: ^1.3
README
使用此包,您可以为步骤表单创建实例并渲染或处理其步骤。
目录
要求
PHP >=8.1
安装
通过 Composer
composer require lexal/stepped-form
使用
-
创建一个步骤。
use Lexal\SteppedForm\Step\StepInterface; final class CustomerStep implements StepInterface { public function handle(mixed $entity, mixed $data): mixed { // do some logic here return $entity; // returns an entity that form will save as step data into the storage } }
-
创建一个表单构建器。
use Lexal\SteppedForm\Form\Builder\FormBuilderInterface; use Lexal\SteppedForm\Step\Builder\StepsBuilder; use Lexal\SteppedForm\Step\Builder\StepsBuilderInterface; use Lexal\SteppedForm\Step\Steps; final class CustomBuilder implements FormBuilderInterface { public function __construct(private readonly StepsBuilderInterface $builder) { } public function build(mixed $entity): Steps { $this->builder->add('customer', new CustomerStep()); // Some additional steps return $this->builder->get(); } } $builder = new CustomBuilder(new StepsBuilder(/* StepControlInterface */, /* DataControlInterface */));
-
创建一个会话存储来保存当前的表单会话密钥,并具有根据初始用户输入(例如客户 ID)将一个表单分割成不同会话的能力。如果没有必要分割表单会话或没有依赖于初始用户输入的依赖关系,请使用默认的
NullSessionStorage
。use Lexal\SteppedForm\Form\Storage\SessionStorageInterface; final class SessionStorage implements SessionStorageInterface { public function get(): ?string { // return current active session key (from redis, database, session or any other storage) return 'main'; } public function put(string $sessionKey): void { // save current form session key } } $sessionControl = new SessionStorage();
-
创建存储和数据控制器。
use Lexal\SteppedForm\Form\DataControl; use Lexal\SteppedForm\Form\StepControl; use Lexal\SteppedForm\Form\Storage\DataStorage; use Lexal\SteppedForm\Form\Storage\FormStorage; $storage = new FormStorage(new InMemoryStorage(), new InMemorySessionStorage()); // can use any other storage (session, database, redis, etc.) $stepControl = new StepControl($storage); $dataControl = new DataControl(new DataStorage($storage));
-
创建一个事件调度器。
use Lexal\SteppedForm\EventDispatcher\EventDispatcherInterface; final class EventDispatcher implements EventDispatcherInterface { public function dispatch(object $event): object { // dispatch events here return $event; } } $dispatcher = new EventDispatcher();
-
创建一个步骤表单。
use Lexal\SteppedForm\EntityCopy\SimpleEntityCopy; use Lexal\SteppedForm\SteppedForm; $form = new SteppedForm( $dataControl, $stepControl, $storage, $builder, $dispatcher, new SimpleEntityCopy(), );
-
在应用程序中使用步骤表单。
/* Starts a new form session */ $form->start( /* entity for initialize a form state */, /* unique session key if you need to split different sessions of one form */, ); /* Returns a TemplateDefinition of rendered step */ $form->render('key'); /* Handles a step logic and saves a new form state */ $form->handle('key', /* any submitted data */); /* Cancels form session */ $form->cancel();
步骤
步骤可以向用户渲染任何信息,使用不同类型的输入,或者根据先前用户输入在后台进行计算。
第一种类型的步骤必须实现 RenderStepInterface
接口。方法 getTemplateDefinition
必须返回包含要渲染的模板名称和数据以传递给模板的 TemplateDefinition
,例如:
use Lexal\SteppedForm\Step\RenderStepInterface; use Lexal\SteppedForm\Step\Steps; use Lexal\SteppedForm\Step\TemplateDefinition; final class CustomerStep implements RenderStepInterface { public function getTemplateDefinition(mixed $entity, Steps $steps): TemplateDefinition { return new TemplateDefinition('customer', ['customer' => $entity]); } public function handle(mixed $entity, mixed $data): mixed { // do some logic here $entity->name = $data['name']; $entity->amount = (float)$data['amount']; return $entity; // return an entity that the form will save as step data into the storage } }
第二种类型的步骤必须实现 StepInterface
。方法 handle
可以通过计算数据来实现业务逻辑,并必须返回更新后的表单实体。该方法将接收作为第二个参数的 null
或先前可渲染步骤提交的数据。
use Lexal\SteppedForm\Step\StepInterface; final class TaxStep implements StepInterface { private const TAX_PERCENT = 20; public function handle(mixed $entity, mixed $data): mixed { // do some logic here $entity->tax = $entity->amount * self::TAX_PERCENT; return $entity; // returns an entity that form will save as step data into the storage } }(返回顶部)
表单构建器
步骤表单使用表单构建器通过表单实体构建步骤集合。
步骤表单可以具有固定数量的步骤或根据先前用户输入数据的不同步骤。
固定步骤列表的步骤表单示例
use Lexal\SteppedForm\Form\Builder\StaticStepsFormBuilder; use Lexal\SteppedForm\Step\Step; use Lexal\SteppedForm\Step\Steps; $steps = new Steps([ new Step('customer', new CustomerStep()), new Step('broker', new BrokerStep()), /* some more steps */ ]); $builder = new StaticStepsFormBuilder($steps);
根据先前用户输入的动态步骤列表的步骤表单示例
use Lexal\SteppedForm\Form\Builder\FormBuilderInterface; use Lexal\SteppedForm\Step\Builder\StepsBuilderInterface; final class CustomBuilder implements FormBuilderInterface { public function __construct(private readonly StepsBuilderInterface $builder) { } public function build(mixed $entity): Steps { $this->builder->add('customer', new CustomerStep()); // add step depending on previous user input if ($entity->createNewBroker) { $this->builder->add('broker', new BrokerStep()); } // Some additional steps return $this->builder->get(); } }
(返回顶部)注意:步骤键必须只包含 "A-z","0-9","-" 和 "_"。
表单数据存储
步骤表单使用存储来存储当前的步骤键和已处理的步骤数据。该包实现了简单的内存存储(ArrayStorage)。要创建自己的存储(例如会话、数据库、Redis),请实现 StorageInterface
接口。
StepControlInterface
和 DataControlInterface
有助于分别处理当前步骤键和步骤数据。
动态步骤表单将在处理步骤时触发清除所有步骤数据。对于静态表单或当前步骤实现 StepBehaviourInterface
且方法 forgetDataAfterCurrent
返回 false
的情况下,步骤数据不会被从存储中清除。
清除当前提交步骤后数据存储的示例(对于动态表单)
use Lexal\SteppedForm\Step\RenderStepInterface; use Lexal\SteppedForm\Step\StepBehaviourInterface; use Lexal\SteppedForm\Step\Steps; use Lexal\SteppedForm\Step\TemplateDefinition; final class CustomerStep implements StepBehaviourInterface { public function getTemplateDefinition(mixed $entity, Steps $steps): TemplateDefinition { // render } public function handle(mixed $entity, mixed $data): mixed { // handle } public function forgetDataAfterCurrent(mixed $entity): bool { return $entity->code === 'NA'; // remove form data after current only when code equals to 'NA' } }(返回顶部)
实体复制
步骤表单使用实体复制将先前提交的实体副本传递给步骤的 handle
方法并将其保存到存储中。
该包已经实现了 SimpleEntityCopy
的 EntityCopyInterface
实现。但是,如果您使用对象作为表单实体,则必须实现 __clone
方法来克隆内部对象。
SimpleEntityCopy
的替代方案是 DeepClone
。
表单事件
表单可以派发以下事件
- BeforeHandleStep - 在处理步骤之前派发。事件包含传递给处理方法的参数、表单实体和步骤实例。事件监听器可以在一些验证之后更新事件数据。
- FormFinished - 当处理完当前步骤后没有后续步骤时,将触发。事件包含表单实体。
表单异常
表单可以抛出以下异常
- AlreadyStartedException - 尝试启动已启动的表单时。
- EntityNotFoundException - 在渲染或处理步骤时找不到前一步骤实体。
- EventDispatcherException - 在分发事件时。
- FormIsNotStartedException - 尝试渲染、处理或取消未启动的表单时。
- NoStepsAddedException - 尝试在没有步骤的情况下启动表单时。
- StepHandleException - 在处理步骤时。
- StepIsNotSubmittedException - 当前一步骤未提交时。
- StepNotFoundException - 尝试渲染或处理不存在的步骤时。
- StepNotRenderableException - 尝试渲染不可渲染的步骤时。
许可证
Stepped Form 采用 MIT 许可证授权。有关完整的许可证文本,请参阅 LICENSE。