popphp/pop-form

Pop PHP 框架的表单组件

4.1.4 2024-08-22 13:33 UTC

README

Build Status Coverage Status

Join the chat at https://popphp.slack.com Join the chat at https://discord.gg/TZjgT74U7E

概述

pop-form 是一个用于管理、渲染和验证 HTML 表单的强大组件。使用它,您可以完全控制表单的外观和功能,以及细粒度控制字段验证。功能包括

  • 字段元素创建和配置
  • 验证
    • 使用任何可调用的验证对象,例如 pop-validator 或自定义验证器
  • 过滤
  • 基于数据库表的字段动态生成

pop-formPop PHP 框架 的一个组件。

顶部

安装

使用 Composer 安装 pop-form

composer require popphp/pop-form

或者,在您的 composer.json 文件中要求它

"require": {
    "popphp/pop-form" : "^4.0.0"
}

顶部

快速入门

连接表单对象的最基本方法是使用简单的配置。

use Pop\Form\Form;

$fields = [
    'username' => [
        'type'     => 'text',
        'label'    => 'Username:',
        'required' => true
    ],
    'email' => [
        'type'  => 'email',
        'label' => 'Email:'
    ],
    'submit' => [
        'type'  => 'submit',
        'value' => 'SUBMIT'
    ]
];

$form = Form::createFromConfig($fields);

if ($_POST) {
    $form->setFieldValues($_POST);
    if (!$form->isValid()) {
        echo $form; // Has errors
    } else {
        echo 'Valid!';
    }
} else {
    echo $form;
}

渲染的表单将如下所示

<form action="#" method="post" id="pop-form" class="pop-form">
    <fieldset id="pop-form-fieldset-1" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" />
            </dd>
            <dt>
                <label for="email">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
</form>

提交时,如果表单值未通过验证,表单将重新渲染并显示错误(注意用户名字段下的错误 div

<form action="/" method="post" id="pop-form" class="pop-form">
    <fieldset id="pop-form-fieldset-1" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" />
                <div class="error">This field is required.</div>
            </dd>
            <dt>
                <label for="email">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="test@test.com" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
</form>

表单对象默认使用 POST 作为方法,当前 REQUEST_URI 作为操作,但可以通过多种方式更改这些值

$form = new Form($fields, , '/form-action', 'GET');
$form = Form::createFromConfig($fields, '/form-action', 'GET');
$form->setMethod('GET')
    ->setAction('/form-action');

顶部

字段元素

可以通过直接与表单元素对象和表单对象本身进行接口连接来连接表单。

use Pop\Form\Form;
use Pop\Form\Element\Input;
use Pop\Validator;

$form = new Form();
$form->setAttribute('id', 'my-form');

$username = new Input\Text('username');
$username->setLabel('Username:')
    ->setRequired(true)
    ->setAttribute('size', 40)
    ->addValidator(new Validator\AlphaNumeric());

$email = new Input\Email('email');
$email->setLabel('Email:')
    ->setRequired(true)
    ->setAttribute('size', 40);

$submit = new Input\Submit('submit', 'SUBMIT');

// Add a single field
$form->addField($username);

// Add multiple fields
$form->addFields([$email, $submit]);

if ($_POST) {
    $form->setFieldValues($_POST);
    if (!$form->isValid()) {
        echo $form; // Has errors
    } else {
        echo 'Valid!';
    }
} else {
    echo $form;
}

上述示例中有几个不同的概念

  1. 我们创建了表单对象并给它设置了 id 属性。
  2. 我们创建了单个字段元素,设置了它们的名称、标签、属性、验证器等。
  3. 我们将字段元素添加到表单对象中。
  4. 我们检查了 $_POST 提交。如果没有检测到,我们只渲染表单。
  5. 如果检测到 $_POST 提交
    1. 使用 $_POST 数组中的值设置字段值(在没有任何 过滤 的情况下这是一个坏主意)
    2. 检查表单对象是否通过验证。如果不通过,则重新渲染表单并显示错误。如果通过,则可以继续。

第一次遍历时,表单将如下所示

<form action="/" method="post" id="my-form">
    <fieldset d="my-form-fieldset-1" class="my-form-fieldset"></fieldset>
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" size="40" />
            </dd>
            <dt>
                <label for="email" class="required">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="" required="required" size="40" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
</form>

如果未通过验证,则将显示错误。在这种情况下,用户名不是字母数字字符

<form action="/" method="post" id="my-form">
    <fieldset d="my-form-fieldset-1" class="my-form-fieldset"></fieldset>
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" size="40" />
                <div class="error">The value must only contain alphanumeric characters.</div>
            </dd>
            <dt>
                <label for="email" class="required">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="" required="required" size="40" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
</form>

顶部

字段配置

我们可以像上面一样使用字段配置数组,这有助于简化流程

use Pop\Form\Form;
use Pop\Validator;

$fields = [
    'username' => [
        'type'       => 'text',
        'label'      => 'Username:',
        'required'   => true,
        'attributes' => [
            'size' => 40
        ],
        'validators' => [
            new Validator\AlphaNumeric()
        ]
    ],
    'email' => [
        'type'       => 'email',
        'label'      => 'Email:',
        'required'   => true,
        'attributes' => [
            'size' => 40
        ]
    ],
    'submit' => [
        'type'  => 'submit',
        'value' => 'SUBMIT'
    ]
];

$form = Form::createFromConfig($fields);
$form->setAttribute('id', 'my-form');

if ($_POST) {
    $form->setFieldValues($_POST);
    if (!$form->isValid()) {
        echo $form; // Has errors
    } else {
        echo 'Valid!';
    }
} else {
    echo $form;
}

顶部

字段集

可以使用多个字段集配置来生成包含更多有组织元素的大型表单。这需要配置包含多个字段配置数组的数组

use Pop\Form\Form;

$fields = [
    [
        'username' => [
            'type'       => 'text',
            'label'      => 'Username:',
            'required'   => true,
        ],
        'email' => [
            'type'       => 'email',
            'label'      => 'Email:',
            'required'   => true,
        ],
        'submit' => [
            'type'  => 'submit',
            'value' => 'SUBMIT'
        ]
    ],
    [
        'first_name' => [
            'type'  => 'text',
            'label' => 'First Name:',
        ],
        'last_name' => [
            'type'  => 'text',
            'label' => 'Last Name:',
        ],
    ],
    [
        'submit' => [
            'type'  => 'submit',
            'value' => 'SUBMIT'
        ]
    ]
];

$form = Form::createFromFieldsetConfig($fields);

这将生成以下带有适当 fieldset 分组的 HTML

<form action="#" method="post" id="my-form" class="pop-form">
    <fieldset id="my-form-fieldset-1" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" />
            </dd>
            <dt>
                <label for="email" class="required">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="" required="required" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
    <fieldset id="my-form-fieldset-2" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="first_name">First Name:</label>
            </dt>
            <dd>
                <input type="text" name="first_name" id="first_name" value="" />
            </dd>
            <dt>
                <label for="last_name">Last Name:</label>
            </dt>
            <dd>
                <input type="text" name="last_name" id="last_name" value="" />
            </dd>
        </dl>
    </fieldset>
    <fieldset id="my-form-fieldset-3" class="pop-form-fieldset">
        <dl>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
</form>

顶部

图例

如果您想为每个多个字段集添加标签,可以通过使用配置中的 legend 值作为数组键来完成

use Pop\Form\Form;

$fields = [
    'Account Info' => [
        'username' => [
            'type'       => 'text',
            'label'      => 'Username:',
            'required'   => true,
        ],
        'email' => [
            'type'       => 'email',
            'label'      => 'Email:',
            'required'   => true,
        ],
        'submit' => [
            'type'  => 'submit',
            'value' => 'SUBMIT'
        ]
    ],
    'Personal Info' => [
        'first_name' => [
            'type'  => 'text',
            'label' => 'First Name:',
        ],
        'last_name' => [
            'type'  => 'text',
            'label' => 'Last Name:',
        ],
    ],
    [
        'submit' => [
            'type'  => 'submit',
            'value' => 'SUBMIT'
        ]
    ]
];

$form = Form::createFromFieldsetConfig($fields);

这将生成以下带有适当 fieldset 分组的 HTML

<form action="#" method="post" id="my-form" class="pop-form">
    <fieldset id="my-form-fieldset-1" class="pop-form-fieldset">
        <legend>Account Info</legend>
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" />
            </dd>
            <dt>
                <label for="email" class="required">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="" required="required" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
    <fieldset id="my-form-fieldset-2" class="pop-form-fieldset">
        <legend>Personal Info</legend>
        <dl>
            <dt>
                <label for="first_name">First Name:</label>
            </dt>
            <dd>
                <input type="text" name="first_name" id="first_name" value="" />
            </dd>
            <dt>
                <label for="last_name">Last Name:</label>
            </dt>
            <dd>
                <input type="text" name="last_name" id="last_name" value="" />
            </dd>
        </dl>
    </fieldset>
    <fieldset id="my-form-fieldset-3" class="pop-form-fieldset">
        <dl>
            <dd>
                <input type="submit" name="submit" id="submit" value="SUBMIT" />
            </dd>
        </dl>
    </fieldset>
</form>

顶部

字段容器

表单元素的默认字段集 HTML 容器是 dldtdd 标签的组合。如果需要其他容器标签,可以像以下示例那样设置。

使用 table
$form = Form::createFromConfig($fields, 'table');
<form action="#" method="post" id="my-form" class="pop-form">
    <fieldset id="my-form-fieldset-1" class="pop-form-fieldset">
        <table>
            <tr>
                <td>
                    <label for="username" class="required">Username:</label>
                </td>
                <td>
                    <input type="text" name="username" id="username" value="" required="required" size="40" />
                </td>
            </tr>
            <tr>
                <td>
                    <label for="email" class="required">Email:</label>
                </td>
                <td>
                    <input type="email" name="email" id="email" value="" required="required" size="40" />
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="submit" name="submit" id="submit" value="SUBMIT" />
                </td>
            </tr>
        </table>
    </fieldset>
</form>
使用 div(或任何其他单一元素容器)
$form = Form::createFromConfig($fields, 'div');
<form action="#" method="post" id="my-form" class="pop-form">
    <fieldset id="my-form-fieldset-1" class="pop-form-fieldset">
        <div>
            <label for="username" class="required">Username:</label>
            <input type="text" name="username" id="username" value="" required="required" size="40" />
        </div>
        <div>
            <label for="email" class="required">Email:</label>
            <input type="email" name="email" id="email" value="" required="required" size="40" />
        </div>
        <div>
            <input type="submit" name="submit" id="submit" value="SUBMIT" />
        </div>
    </fieldset>
</form>

顶部

过滤

如上所述,在处理用户提交的值时,直接使用或在不进行过滤的情况下将其显示在屏幕上是不好的做法。通常可以采用一套过滤器,如strip_tagshtmlentities。因此,在第一个例子中,我们会给<$_POST>块添加过滤器。

use Pop\Filter\Filter;

/** ... Code to create form **/

if ($_POST) {
    $form->addFilter(new Filter('strip_tags'))
         ->addFilter(new Filter('htmlentities', [ENT_QUOTES, 'UTF-8']));
    $form->setFieldValues($_POST);
    if (!$form->isValid()) {
        echo $form; // Has errors
    } else {
        $form->clearFilters();
        $form->addFilter(new Filter('html_entity_decode', [ENT_QUOTES, 'UTF-8']));
        echo 'Valid!';
    }
} else {
    echo $form;
}

当然,strip_tags过滤器会删除任何可能的恶意标签。htmlentities过滤器在表单需要再次使用这些值进行渲染时很有用。

<input type="text" name="username" id="username" 
    value="Hello&quot;World&quot;" required="required" size="40" />

如果没有使用htmlentities过滤器,值内的引号将破坏输入字段的HTML。当然,如果你想在表单验证后使用这些值,你必须调用clearFilters()并使用html_entity_decode来过滤这些值。

顶部

验证

当然,使用像这样一个表单组件的主要理由之一是利用它的验证功能。你已经看到了从pop-validator组件中使用的简单验证器的用法,这些很容易使用。但是,你可以通过扩展pop-validator组件或简单地编写自己的自定义可调用验证器来创建自己的自定义验证器。唯一需要遵循的真正规则是,自定义验证器在成功时必须返回null,在失败时返回一个字符串消息,该消息随后用于错误显示。以下是一些示例

使用闭包
$username = new Input\Text('username');
$username->addValidator(function ($value) {
    if (strlen($value) < 6) {
        return 'The username value must be greater than or equal to 6.';
    }
});
使用验证器
use Pop\Validator\AlphaNumeric;

$username = new Input\Text('username');
$username->addValidator(new AlphaNumeric());
使用自定义类
class MyValidator
{
    public function validate($value)
    {
        if (strlen($value) < 6) {
            return 'The username value must be greater than or equal to 6';
        }
    }
}

$username = new Input\Text('username');
$username->addValidator([new MyValidator(), 'validate']);
仅验证的表单

存在一个仅用于验证一组字段值的FormValidator类。这个特性的好处是不需要担心渲染整个表单对象,并且只返回适当的验证消息。这对于像API调用这样的用途很有用,其中表单渲染可能由应用程序的另一部分(而不是PHP服务器端)处理。

use Pop\Form\FormValidator;
use Pop\Validator;

$validators = [
    'username' => new Validator\AlphaNumeric(),
    'password' => new Validator\LengthGte(6)
];

$form = new FormValidator($validators);
$form->setValues([
    'username' => 'admin$%^',
    'password' => '12345'
]);

if (!$form->validate()) {
    print_r($form->getErrors());
}

如果字段值有误,$form->getErrors()将返回一个类似这样的错误数组

Array
(
    [username] => Array
        (
            [0] => The value must only contain alphanumeric characters.
        )

    [password] => Array
        (
            [0] => The value length must be greater than or equal to 6.
        )

)

顶部

动态字段

pop-form提供了将表单字段快速连接到数据库列的功能。它要求安装pop-db组件才能工作。假设有一个名为Users的数据库表类,它映射到数据库中的users表。它有六个字段:idusernamepasswordfirst_namelast_nameemail

(有关使用pop-db的更多信息,请点击这里。)

use Pop\Form\Form;
use Pop\Form\Fields;
use MyApp\Table\Users;

// The 4th parameter is an 'omit' to prevent certain fields from displaying
$config = Fields::getConfigFromTable(Users::getTableInfo(), null, null, 'id');
$form   = Form::createFromConfig($config);
echo $form;

这将渲染为

<form action="/" method="post" id="pop-form" class="pop-form">
    <fieldset id="pop-form-fieldset-1" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="username" class="required">Username:</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" required="required" />
            </dd>
            <dt>
                <label for="password" class="required">Password:</label>
            </dt>
            <dd>
                <input type="password" name="password" id="password" value="" required="required" />
            </dd>
            <dt>
                <label for="first_name" class="required">First Name:</label>
            </dt>
            <dd>
                <input type="text" name="first_name" id="first_name" value="" required="required" />
            </dd>
            <dt>
                <label for="last_name" class="required">Last Name:</label>
            </dt>
            <dd>
                <input type="text" name="last_name" id="last_name" value="" required="required" />
            </dd>
            <dt>
                <label for="email" class="required">Email:</label>
            </dt>
            <dd>
                <input type="email" name="email" id="email" value="" required="required" />
            </dd>
        </dl>
    </fieldset>
</form>

你可以设置元素特定的属性和值,还可以设置要省略的字段,如上述示例中的'id'参数。数据库中的任何TEXT列类型都创建为textarea对象,其余的则创建为input文本对象。

顶部

ACL 表单

ACL表单利用pop-acl组件,并扩展了常规表单类,它接受一个包含其角色和资源的ACL对象,并强制执行哪些表单字段可以查看和编辑。以下代码为例

use Pop\Form;
use Pop\Acl;

$acl      = new Acl\Acl();
$admin    = new Acl\AclRole('admin');
$editor   = new Acl\AclRole('editor');
$username = new Acl\AclResource('username');
$password = new Acl\AclResource('password');

$acl->addRoles([$admin, $editor]);
$acl->addResources([$username, $password]);

$acl->deny($editor, 'username', 'edit');
$acl->deny($editor, 'password', 'view');

$fields = [
    'username' => [
        'type'  => 'text',
        'label' => 'Username'
    ],
    'password' => [
        'type'  => 'password',
        'label' => 'Password'
    ],
    'first_name' => [
        'type'  => 'text',
        'label' => 'First Name'
    ],
    'last_name' => [
        'type'  => 'text',
        'label' => 'Last Name'
    ],
    'submit' => [
        'type'  => 'submit',
        'value' => 'Submit'
    ]
];

$form = Form\AclForm::createFromConfig($fields);
$form->setAcl($acl);

$admin没有限制。但是,$editor角色有限制,不能编辑username字段,也不能查看password字段。将$editor设置为表单角色并渲染表单将如下所示

$form->addRole($editor);
echo $form;
<form action="#" method="post" id="pop-form" class="pop-form">
    <fieldset id="pop-form-fieldset-1" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="username">Username</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" readonly="readonly" />
            </dd>
            <dt>
                <label for="first_name">First Name</label>
            </dt>
            <dd>
                <input type="text" name="first_name" id="first_name" value="" />
            </dd>
            <dt>
                <label for="last_name">Last Name</label>
            </dt>
            <dd>
                <input type="text" name="last_name" id="last_name" value="" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="Submit" />
            </dd>
        </dl>
    </fieldset>
</form>

没有password字段,并且username字段已被设置为readonly。将角色更改为$admin,整个表单将无限制地渲染。

$form->addRole($admin);
echo $form;
<form action="#" method="post" id="pop-form" class="pop-form">
    <fieldset id="pop-form-fieldset-1" class="pop-form-fieldset">
        <dl>
            <dt>
                <label for="username">Username</label>
            </dt>
            <dd>
                <input type="text" name="username" id="username" value="" />
            </dd>
            <dt>
                <label for="password">Password</label>
            </dt>
            <dd>
                <input type="password" name="password" id="password" value="" />
            </dd>
            <dt>
                <label for="first_name">First Name</label>
            </dt>
            <dd>
                <input type="text" name="first_name" id="first_name" value="" />
            </dd>
            <dt>
                <label for="last_name">Last Name</label>
            </dt>
            <dd>
                <input type="text" name="last_name" id="last_name" value="" />
            </dd>
            <dd>
                <input type="submit" name="submit" id="submit" value="Submit" />
            </dd>
        </dl>
    </fieldset>
</form>