monolyth/formulaic

Monolyth 无框架面向对象表单工具


README

PHP8.1+ 的面向对象表单工具

HTML 表单很糟糕。好吧,不,它们超级实用,但编写和服务器端验证它们可能会有点麻烦。Formulaic 提供了一系列工具来减轻这种痛苦。

基本用法

定义一个包含一些字段和其他要求的表单

<?php

use Monolyth\Formulaic\Get;
use Monolyth\Formulaic\Search;
use Monolyth\Formulaic\Button\Submit;

class MyForm extends Get
{
    public function __construct()
    {
        $this[] = (new Search('q'))->isRequired();
        $this[] = new Submit('Go!', 'submit');
    }
}

在您的模板中,可以使用 API 手动调整输出,或者简单地使用 __toString 表单来使用默认值

<?php

$form = new MyForm;
echo $form;

您可以 __toString 单个字段

<?php

$form = new MyForm;

?>
<form name="search" method="get">
    <!-- These two yield identical output using MyForm above: -->
    <?=$form[0]?>
    <?=$form['q']?>
</form>

要验证表单

<?php

$form = new MyForm;
if ($form->valid()) {
    // ...Perform the search...
}

要获取错误列表

<?php

$form = new MyForm;
if ($errors = $form->errors()) {
    // ...Do error handling, or give feedback...
}

表单可以包含字段集

<?php

use Monolyth\Formulaic\Get;
use Monolyth\Formulaic\Fieldset;
use Monolyth\Formulaic\Search;
use Monolyth\Formulaic\Button\Submit;

class MyForm extends Get
{
    public function __construct()
    {
        $this[] = new Fieldset('Global search', function($fieldset) {
            $fieldset[] = new Search('q');
        });
        $this[] = new Fieldset('Search by ID', function($fieldset) {
            $fieldset[] = new Search('id');
        });
        $this[] = new Submit('Go!');
    }
}

在您的输出中

<form method="get">
    <?=$form['Global search']?>
    <?=$form['Search by ID']?>
    <?=$form['submit']?>
</form>

表单中的自定义元素

只需将字符串添加到表单中;它们将原样输出

<?php

// ...
class Form extends Get
{
    public function __construct()
    {
        $this[] = new Radio('foo');
        $this[] = '<h1>custom HTML element!</h1>';
    }
}

内部机制

正如您所猜测的,PostGet 表单分别查看提交和获取数据。这意味着在超全局变量中(对于 Post,包括 $_FILES)的任何匹配数据都将自动设置在表单上。对于组中的元素(不包括字段集),Formulaic 假设它们将位于一个子数组中

<?php

use Monolyth\Formulaic\{ Get, Element\Group, Text };

class Form extends Get
{
    public function __construct()
    {
        $this[] = new Group('foo', function ($group) {
            $group[] = new Text('bar');
        });
    }
}

这将与 $_GET['foo']['bar'] 的值匹配。

对于复选框组(一组相关复选框,例如设置),值假设在它们自己的数组中。例如,对于名为 'foo' 的复选框组,值将作为 $_POST['foo'] = [1, 2, 3] 传递。

添加测试

表单元素可以包含测试,这些测试由 valid()error() 方法使用以生成输出。提供了一些预定义的测试(如 isRequired()),但您可以通过元素上的 addTest 方法轻松添加自己的测试

<?php

$input = new Text('foo');
$input->addTest(fn ($value) => $value == 'bar');

除非用户将 "bar" 输入到文本输入框中,否则上述测试将失败。

绑定模型

Formulaic 在传播表单数据到您的模型方面也表现出色。所有包含大量 isset 调用的样板代码?消失了!

您的模型是一个对象。字面上任何对象。您想要的是任何属性的先前填充值自动设置在您的表单上,并且用户输入的任何值都更新在对象上(然后您可以将其持久化到数据库或任何其他地方,这取决于您)。 猜猜看?这很简单!

<?php

class MyFrom extends Post
{
    //... define the form
    public function __construct()
    {
        $this[] = new Text('foo');
    }
}

$model = new stdClass;
$model->foo = 'bar';
$form = new MyForm;
$form->bind($model);

在上面的示例中,相关的表单(当 __toString 时)将为 foo 输入设置默认值 "bar"。如果 $_POST['foo'] 竟然包含 "buzz",则它将包含 那个。更好的是,在调用 bind 之后,也将如此,使得 $model->foo === 'buzz' 等于 true。太棒了!那是您不再需要考虑的数百万行代码!

绑定可以在任何级别上进行,只需记住它需要在对象上,并且其(子)属性必须与元素的名称匹配。

您会注意到这将模型结构绑定到表单构建上;然而,这并不重要。表单元素按原样显示,只需它们的名称需要与模型匹配。

转换数据

在现实世界中,模型对象通常比基本处理字符串的 HTML 表单要复杂得多。这就是转换器的用武之地:Formulaic 将数据转换为您的模型的方式。

所有元素都支持 withTransformer 方法,该方法基本上接受一个可调用函数。这里的想法是可调用函数的参数是类型提示的(以便确定要使用哪个转换器),并基于该类型返回一个合适的值。某个特定情况的可接受转换器可能是

<?php

class MyModel
{
    public Foo $foo;
}

class MyForm extends Post
{
    public function __construct()
    {
        $this[] = (new Text('foo'))
            ->withTransformer(fn(string $value) => new Foo($value));
    }
}

$model = new MyModel;
$form = new MyForm;
$form->bind($model);
echo get_class($model->foo); // Foo

您可以使用 withTransformers 方法(注意复数)一次定义多个转换器。每个参数都是一个可调用函数。

通常,你需要两个变压器:一个从模型到表单,另一个从表单返回到模型。在某些情况下,输入可能会根据你项目的复杂度而变化;定义你需要的变压器数量。

输入类型提示可能是一个 联合体,在这种情况下,变压器适用于多种类型。交集类型提示不支持,因为在转换上下文中它们并不真正有意义。

GET 表单陷阱!

在 PHP 中,无法区分“常规”页面加载和由提交的 GET 表单触发的页面加载(与 POST 请求不同)。为了确定用户是否提供了值,Formulaic 简单地检查 $_GET 是否为真。请注意,这可能或可能不足以满足你的需求;理论上,一个完全为空的表单也可以提交,这将仍然触发此操作,或者 URL 可能已经包含(无关的)GET 参数。

在这些边缘情况下,扩展 Get 表单并实现你自己的 wasSubmitted 方法进行替代检查,例如检查隐藏表单字段或命名提交按钮。

对于 POST 表单,假设用户在 $_SERVER['REQUEST_METHOD'] == 'POST' 时提供了值。同样,可能存在这种检查对你来说不够好的情况 - 扩展并重写,再次。

GET 或 POST 之外的方法

在这种情况下,PUT 或 DELETE 可能会出现。这些不是开箱即用的,因为 Formulaic 是一个 HTML 表单库,而 HTML 只支持 GET 和 POST。然而,你可能会希望在处理 AJAX 调用的控制器中使用验证和绑定逻辑。在这种情况下,你可以自由地扩展你自己的表单类。

任何扩展都将需要实现 getSource 方法,例如对于 PUT,你需要手动解析 file_get_contents('php://input')。由于我们无法知道它包含的内容(可能是 JSON,但假设是所有错误的源头...)你需要编写适合你需求的自己的实现。

类似地,DELETE 可以 在 URL 中包含 GET 数据。我们并不确定何时需要这种情况,但这里有了它 - 自定义 Delete 表单可能扩展 GET。