mindplay/kissform

模型驱动表单渲染和输入验证

0.3.4 2018-03-07 11:12 UTC

This package is auto-updated.

Last update: 2024-08-25 19:35:52 UTC


README

模型驱动表单渲染和输入验证。

PHP Version Build Status Code Coverage Scrutinizer Code Quality

模型驱动意味着由模型驱动 - 这意味着,第一步是构建一个模型,用于描述表单上渲染的输入细节以及输入如何进行验证。

在这个库中,模型驱动 并不 意味着 "嵌入到您的领域模型中",而是构建一个专门描述表单输入/输出方面的模型。

概念

该库包含以下类型,具有以下职责

  • 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 返回 intCheckboxField 返回 bool,依此类推。

只能通过这种方式与字段交换适当类型的有效值 - 如果需要访问可能是无效的原始输入值,请使用 InputModelgetInput()setInput() 方法。

这展示了最基本的模式 - 请参阅示例,了解工作示例中的post/redirect/get循环和CSRF保护。

其他特性

此库还有其他预期和实用的功能,包括

  • 默认配置了Bootstrap类名,因为,为什么不呢。

  • class="has-error"添加到包含错误消息的输入中。

  • class="is-required"添加到必填的输入中。

  • 根据非常简单的规则创建nameid属性,例如前缀/后缀,没有名称混淆或复杂的约定要学习。

  • 字段标题会被重复使用,例如在<label>标签和错误消息之间,但如果需要,您也可以自定义错误消息中显示的名称。

  • 默认错误消息可以本地化/自定义。

  • 使用InputRenderer::errorSummary()可以生成基本的错误摘要。

它故意不实现以下任何一项

  • 简单元素:例如<form><fieldset><legend> - 您不需要代码来帮助您创建这些简单的标签,只需输入即可;您的模板将更容易阅读和维护。

  • 表单布局:可能有太多可能的变体,而且它只是HTML,最初做起来真的很简单 - 这不值得。

  • 插件架构:您不需要它 - 只使用日常OO模式来解决问题,就像一个节俭的程序员一样。根据您的业务/项目/模块/场景/模型等需要扩展渲染器和验证器。

此库是一个工具,而不是万能的银弹 - 它尽可能少做,避免发明描述每个细节的复杂概念,而是主要处理重复/错误易发的任务,并在您需要时避开。

要学习的东西很少,而且没有什么需要适合到一个“盒子”中 - 这里几乎没有“架构”,没有“插件”或“扩展”,主要是简单的OOP。

您可以使用应用特定的输入类型扩展表单渲染器,更重要的是,将其扩展到模型/案例特定的渲染器 - 它只是一个类,所以应用您的OOP技能以娱乐和盈利!

为什么是输入验证,而不是(领域)模型验证?

  • 因为领域验证通常特定于某个场景 - 您可以像在控制器或服务中使用简单的if语句一样执行它,然后手动将错误添加到验证器中。

  • 因为输入验证更简单 - 它只是一个类,您可以通过特定案例的验证扩展该类,因为您经常会有只涉及一个场景/模型/案例的验证。构建可重用的领域验证规则作为组件将更加复杂 - 许多这些只会适用于一个场景/案例并且实际上永远不会被重用,所以它们甚至不受益于这种复杂性。

  • 有些情况下甚至不需要领域模型,例如联系或评论表单等。