mindplay / kissform
模型驱动表单渲染和输入验证
Requires
- php: >=5.5
- mindplay/lang: ^1.1
Requires (Dev)
- codeception/codeception: ^2
- phpunit/php-code-coverage: >=2, <4
README
模型驱动表单渲染和输入验证。
模型驱动意味着由模型驱动 - 这意味着,第一步是构建一个模型,用于描述表单上渲染的输入细节以及输入如何进行验证。
在这个库中,模型驱动 并不 意味着 "嵌入到您的领域模型中",而是构建一个专门描述表单输入/输出方面的模型。
概念
该库包含以下类型,具有以下职责
-
Field
类描述表单上的可能输入元素 - 它们是什么,看起来像什么,以及它们如何行为;不是 它们的状态。 -
InputModel
包含表单的状态 - 输入元素中的值和任何错误信息。这是一个围绕原始$_GET
或$_POST
数据的薄包装,结合错误状态 - 它可以被序列化,这意味着您可以安全地将它存储在会话变量中。 -
InputRenderer
渲染 HTML 元素(基本输入、标签等)或将更复杂的渲染委托给Field
实例 - 它在渲染时为字段提供InputModel
实例(表单值和错误)。 -
InputValidation
通过对字段运行验证器来管理验证过程。 -
ValidatorInterface
定义验证器类型的接口,这些类型实现了例如电子邮件地址、数字、日期/时间等的验证逻辑。
大多数 Field
类型都能够产生一些内置的验证器 - 这些可以通过调用 InputValidation::check()
来创建和检查。例如,设置 TextField
的 $min_length
属性将导致它创建一个 CheckMinLength
验证器。
这种设计基于这样一个想法,即表单渲染和输入验证之间没有重叠的关注点 - 一个是关于输出,另一个是关于输入。
假设您使用 PRG,当表单最初渲染时,没有用户输入,因此没有东西可以验证;如果表单验证失败,验证将在 POST 请求期间进行,而实际的表单渲染将在单独的 GET 请求期间进行。换句话说,表单渲染和验证实际上从未在同一个请求中发生。
使用方法
一个基本的表单模型可能看起来像这样
class UserForm { /** @var TextField */ public $first_name; /** @var TextField */ public $last_name; public function __construct() { $this->first_name = new TextField('first_name'); $this->first_name->setLabel('First Name'); $this->first_name->setRequired(); $this->last_name = new TextField('last_name'); $this->last_name->setLabel('Last Name'); $this->last_name->setRequired(); } }
使用模型来渲染表单输入
$form = new InputRenderer(@$_POST['user'], 'user'); $t = new UserForm(); ?> <form method="post"> <?= $form->labelFor($t->first_name) . $form->render($t->first_name) . '<br/>' ?> <?= $form->labelFor($t->last_name) . $form->render($t->last_name) . '<br/>' ?> <input class="btn btn-lg btn-primary" type="submit" value="Save" /> </form>
重复使用表单模型来验证用户输入
$model = InputModel::create($_POST['user']); $validator = new InputValidation($model); $validator->check([$t->first_name, $t->last_name]); if ($model->isValid()) { // no errors! } else { var_dump($model->errors); // returns e.g. array("first_name" => "First Name is required") }
请注意,每个字段只记录一个错误 - 首次遇到的错误。
一旦输入通过验证,就可以从各个字段中提取值
$first_name = $form->first_name->getValue($model); $last_name = $form->last_name->getValue($model);
要使用表单实现现有数据的编辑,还可以向表单模型中注入状态
$form->first_name->setValue($model, "Rasmus"); $form->last_name->setValue($model, "Schultz");
请注意,每个 Field
类型的 getValue()
和 setValue()
方法都是类型感知的 - 例如,IntField
返回 int
,CheckboxField
返回 bool
,依此类推。
只能通过这种方式与字段交换适当类型的有效值 - 如果需要访问可能是无效的原始输入值,请使用 InputModel
的 getInput()
和 setInput()
方法。
这展示了最基本的模式 - 请参阅示例,了解工作示例中的post/redirect/get循环和CSRF保护。
其他特性
此库还有其他预期和实用的功能,包括
-
默认配置了Bootstrap类名,因为,为什么不呢。
-
将
class="has-error"
添加到包含错误消息的输入中。 -
将
class="is-required"
添加到必填的输入中。 -
根据非常简单的规则创建
name
和id
属性,例如前缀/后缀,没有名称混淆或复杂的约定要学习。 -
字段标题会被重复使用,例如在
<label>
标签和错误消息之间,但如果需要,您也可以自定义错误消息中显示的名称。 -
默认错误消息可以本地化/自定义。
-
使用
InputRenderer::errorSummary()
可以生成基本的错误摘要。
它故意不实现以下任何一项
-
简单元素:例如
<form>
、<fieldset>
和<legend>
- 您不需要代码来帮助您创建这些简单的标签,只需输入即可;您的模板将更容易阅读和维护。 -
表单布局:可能有太多可能的变体,而且它只是HTML,最初做起来真的很简单 - 这不值得。
-
插件架构:您不需要它 - 只使用日常OO模式来解决问题,就像一个节俭的程序员一样。根据您的业务/项目/模块/场景/模型等需要扩展渲染器和验证器。
此库是一个工具,而不是万能的银弹 - 它尽可能少做,避免发明描述每个细节的复杂概念,而是主要处理重复/错误易发的任务,并在您需要时避开。
要学习的东西很少,而且没有什么需要适合到一个“盒子”中 - 这里几乎没有“架构”,没有“插件”或“扩展”,主要是简单的OOP。
您可以使用应用特定的输入类型扩展表单渲染器,更重要的是,将其扩展到模型/案例特定的渲染器 - 它只是一个类,所以应用您的OOP技能以娱乐和盈利!
为什么是输入验证,而不是(领域)模型验证?
-
因为领域验证通常特定于某个场景 - 您可以像在控制器或服务中使用简单的if语句一样执行它,然后手动将错误添加到验证器中。
-
因为输入验证更简单 - 它只是一个类,您可以通过特定案例的验证扩展该类,因为您经常会有只涉及一个场景/模型/案例的验证。构建可重用的领域验证规则作为组件将更加复杂 - 许多这些只会适用于一个场景/案例并且实际上永远不会被重用,所以它们甚至不受益于这种复杂性。
-
有些情况下甚至不需要领域模型,例如联系或评论表单等。