naucon / form
该包为PHP提供表单组件,用于处理和验证HTML表单。还包括一个表单辅助工具,用于将表单渲染为HTML标记(PHP或Smarty模板)。
Requires
- php: ^8.1
- doctrine/annotations: ^1.0
- doctrine/cache: ^1.0
- naucon/htmlbuilder: ~1.0
- naucon/utility: ~1.0
- psr/log: ~1.0
- symfony/cache: ^6.0
- symfony/event-dispatcher: ^6.0
- symfony/finder: ^6.0
- symfony/options-resolver: ^6.0
- symfony/property-access: ^6.0
- symfony/security-csrf: ^6.0
- symfony/translation: ^6.0
- symfony/validator: ^6.0
- symfony/yaml: ^6.0
Requires (Dev)
- naucon/iban: ~1.0
- phpunit/phpunit: ^9.0
- symfony/config: ^6.0
- symfony/intl: ^6.0
- twig/twig: ^2.4
Suggests
- naucon/iban: Is required to validate iban.
- symfony/config: Is required to use xml mapping.
- twig/twig: Is required for using the twig form extension
README
关于
该包为PHP提供表单组件,用于处理和验证HTML表单。还包括一个表单辅助工具,用于将表单渲染为HTML标记(PHP或Smarty模板)。
在网站开发中最常见的任务之一是表单。此包可以帮助您以简单的方式处理和验证表单,以便维护和安全。
该包可以集成到任何PHP网站应用程序中(至少PHP5.5+)。
功能
- 通过getter和setter将表单数据绑定到实体
- 通过实体中定义的验证规则验证表单数据
- 通过同步器令牌(也称为安全令牌或XSRF/CSRF令牌)来保护表单,以防止XSRF/CSRF漏洞
- 支持表单集合
- 翻译表单标签和表单违规
- 表单辅助工具以渲染HTML标记
- 使用输出过滤渲染HTML表单以防止XSS漏洞
- 表单辅助工具的Smarty插件
兼容性
- PHP 7.1 - 7.4
安装
通过Composer安装最新版本
composer require naucon/form
示例
启动内置的web服务器以查看示例
cd examples
php -S 127.0.0.1:3000
在浏览器中打开url
http://127.0.0.1:3000/index.html
开始使用
1. 实体
首先,我们需要一个实体。实体可以是一个具有getter和setter的普通PHP类。类的属性必须通过getter和setter或公开访问。提交的表单数据将映射到该实体的一个实例。
<?php
class User
{
protected $username;
protected $email;
protected $newsletter;
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getEmail()
{
return $this->email;
}
public function setEmail($email)
{
$this->email = $email;
}
public function getNewsletter()
{
return $this->newsletter;
}
public function setNewsletter($newsletter)
{
$this->newsletter = $newsletter;
}
}
}
创建实体的一个实例并将其分配给您的表单。
require_once 'User.php';
$user = new User();
2. 表单绑定
接下来,我们获取表单管理器类的实例并创建一个表单实例,该实例执行实际的表单处理。
将实体作为第一个参数添加到表单类中。
use Naucon\Form\FormManager;
$formManager = new FormManager();
$form = $formManager->createForm($user, 'yourform');
作为第二个参数提供表单的名称或命名空间。在上面的示例中,我们使用yourform
。这个唯一的名称类似于命名空间或键,以确保您的表单不会与其他表单冲突。
CSRF-Token需要一个会话。所以请确保您已经启动了会话。
<?php
session_start();
use Naucon\Form\FormManager;
$formManager = new FormManager();
$form = $formManager->createForm($user, 'yourform');
...
现在我们可以绑定提交的表单数据。因此,我们调用bind()
方法并添加提交的表单数据,例如$_POST
或任何其他数组作为参数。为了确保表单正常工作,您应该始终调用bind()
方法,即使没有提交表单数据。使用isBound()
您可以验证提交的表单数据是否已正确绑定到实体。
如果实体已绑定,您可以使用实体执行任何其他操作,例如保存到数据库。
$form->bind(isset($_POST) ? $_POST : null);
if ($form->isBound()) {
// some action, like saving the data to database
}
确保关闭会话以将会话数据写入会话存储。
// here rendered the form html!
session_write_close();
?>
3. XSRF/CSRF保护
当bind()
提交时,只有在提交了同步器令牌(也称为安全令牌或xsrf/csrf令牌)并且该令牌有效时,才会处理数据。此令牌通过在表单呈现时设置随机令牌到会话中,防止XSRF/CSRF攻击。在表单中,该令牌添加到隐藏字段中。当表单数据提交时,该令牌是有效负载的一部分,并将与会话中先前存储的令牌进行比较。只有当这两个令牌相同,绑定才会被处理。
<form method="post">
<input type=text name="_csrf_token" value="<?php echo $form->getSynchronizerToken(); ?>" />
标记如下所示
<form method="post" id="yourform">
<input type="hidden" name="_csrf_token" value="67187c7e5abeb31b93339ab5cbb263b6" />
可以通过调用带有参数false
的setSynchronizerTokenValidation()
方法来禁用XSRF/CSRF保护。
$formManager->setSynchronizerTokenValidation(false);
或者
$form->setSynchronizerTokenValidation(false);
4. 验证
为了验证提交的表单数据,我们使用Symfony验证组件。约束(验证规则)在静态方法loadValidatorMetadata()
中定义,可以是yml文件、xml文件或实体中的注解。
如何定义约束和可用的验证器,请参阅Symfony验证文档:https://symfony.ac.cn/doc/2.8/validation.html
在我们的例子中,我们在User
实体中添加了一个静态方法loadValidatorMetadata()
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('username', new Assert\NotBlank());
$metadata->addPropertyConstraint('username', new Assert\Length(
array('min' => 5, 'max' => 50)
));
$metadata->addPropertyConstraint('email', new Assert\Email());
$metadata->addPropertyConstraint('age', new Assert\Type('numeric'));
}
您还可以使用yml、xml文件或注解。有关如何定义约束的更多信息,请参阅Symfony验证文档:https://symfony.ac.cn/doc/current/components/validator.html
$options['config_paths'] = array(__DIR__ . '/Resources/config/');
$formManager = new FormManager($options);
如果您需要为特定用例排除某些验证约束,或者您希望仅通过配置应用条件子集,则可以使用验证组来完成
$options['validation_groups'] = array('Default', 'registration');
如果在表单选项中提供了验证组名称数组,则只有在验证表单时标记了这些组名称的约束才会被应用。有关验证组配置的更多信息,请参阅https://symfony.ac.cn/doc/current/validation/groups.html文档。
在调用bind()
和isBound()
之后,我们调用isValid()
。换句话说,我们在绑定实体后进行验证。
$form->bind(isset($_POST) ? $_POST : null);
if ($form->isBound()
&& $form->isValid()) {
// some action, like saving the data to database
}
// here rendered the form html!
session_write_close();
?>
验证错误存储在FormError
实例中。它们可以通过以下方法访问
$form->getErrors()
$entityContainer->hasError('username')
$entityContainer->getError('username')
$entityContainer->getError('username')->getMessage();
$entityContainer
是EntityContainer
的实例。它不是实体User
。EntityContainer
包含实体。可以通过调用EntityContainer::getEntity()
来访问。
如果您的表单中只有一个实体,您可以通过调用来访问EntityContainer
$form->getFirstEntityContainer()
如果您的表单包含多个实体,您可以遍历EntityContainer
foreach ($form->getEntityContainerIterator() as $entityContainer) {
...
}
实体容器也可以通过表单实例进行迭代。
foreach ($form as $entityContainer) {
...
}
5. 翻译
为了翻译表单标签和验证违规,我们使用symfony翻译组件。
要更改当前语言,请调用setLocales
$form->getTranslator()->setLocale('de_DE');
您可以在配置中指定当前语言和备用语言
$options = [
'locale' => 'en_EN',
'fallback_locales' => array('en_EN'),
];
$formManager = new FormManager($options);
默认情况下,将加载表单和symfony组件的约束的默认翻译。要添加表单标签或自己的约束的翻译,您必须指定一个或多个路径,以查找这些翻译
$paths = array(realpath(__DIR__) . '/Resources/translations');
$options['translator_paths' => $paths];
$formManager = new FormManager($options);
6. 表单渲染
使用表单助手
使用表单助手,您可以轻松渲染HTML表单字段、标签、按钮,无需担心字段名称和值。
以下表单助手可用,并且可以通过FormHelper
实例或smarty插件访问。
- 选择
- "checkbox"
- "radio"
- "select"
- 字段
- "error"
- "hidden"
- "label"
- "password"
- "text"
- "textarea"
- 标签
- "errors"
- "reset"
- "submit"
限制:目前表单助手不考虑不同的HTML标准,如HTML4.01、xhtml、HTML5等。还有一些HTML标记符号缺失。
使用PHP模板的FormHelper
首先,您必须创建FormHelper
类的实例。表单助手需要一个Form
实例的实例。
<?php
use Naucon\Form\FormHelper;
$formHelper = new FormHelper($form);
表单助手包含以下方法来渲染表单元素
- formStart($method='post', $action=null, $enctype=null, $options=array())
- formEnd()
- formField($helperName, $propertyName, array $options=array())
- formChoice($helperName, $propertyName, $choices, array $options=array())
- formTag($helperName, $content=null, array $options=array())
使用FormHelper渲染表单的示例
echo $formHelper->formStart();
echo $formHelper->formTag('errors');
foreach ($formHelper as $entityContainer) {
?>
<fieldset>
<?php echo $formHelper->formField('label', 'username'); ?>
<?php echo $formHelper->formField('text', 'username', array('maxlength' => 32)); ?>
<?php echo $formHelper->formField('error', 'username'); ?>
<br/>
<?php echo $formHelper->formField('label', 'email'); ?>
<?php echo $formHelper->formField('text', 'email'); ?>
<?php echo $formHelper->formField('error', 'email'); ?>
<br/>
<?php echo $formHelper->formField('label', 'newsletter'); ?>
<?php echo $formHelper->formField('checkbox', 'newsletter', 1); ?>
<?php echo $formHelper->formField('error', 'newsletter'); ?>
<br/>
</fieldset>
<fieldset>
<?php echo $formHelper->formTag('submit', 'Submit'); ?>
<?php echo $formHelper->formTag('reset', 'Reset'); ?>
</fieldset>
<?php
}
echo $formHelper->formEnd();
使用Smarty3模板的FormHelper
首先,我们必须将表单助手smarty插件添加到smarty中。
$smarty->setPluginsDir('vendor/Naucon/Form/Helper/view/smarty');
然后,我们必须将表单实例分配给smarty。
$smarty->assign('form', $form);
表单包包含以下块和功能插件
如何在smarty模板中渲染表单的示例
{ncform from=$form}
{ncform_tag type='errors'}
<fieldset>
{ncform_field type='label' field='username'}
{ncform_field type='text' field='username' maxlength=32}
{ncform_field type='error' field='username'}
<br/>
{ncform_field type='label' field='email'}
{ncform_field type='text' field='email'}
{ncform_field type='error' field='email'}
<br/>
{ncform_field type='label' field='newsletter'}
{ncform_choice type='radio' field='newsletter' value=1}
{ncform_field type='error' field='newsletter'}
<br/>
</fieldset>
<fieldset>
{ncform_tag type='submit' value='Submit'}
{ncform_tag type='reset' value='Reset'}
</fieldset>
{/ncform}
上述示例渲染以下HTML标记
<form method="post" id="yourform">
<input type="hidden" name="_ncToken" value="67187c7e5abeb31b93339ab5cbb263b6" />
<fieldset>
<label for="yourform_username">username</label>
<input type="text" name="yourform[username]" value="" id="yourform_username" maxlength="32" />
<br/>
<label for="yourform_email">email</label>
<input type="text" name="yourform[email]" value="" id="yourform_email" />
<br/>
<label for="yourform_newsletter">newsletter</label>
<input type="checkbox" name="yourform[newsletter]" value="1" id="yourform_newsletter" />
<br/>
</fieldset>
<fieldset>
<input type="submit" value="Submit" />
<input type="reset" value="Reset" />
</fieldset>
</form>
也可以使用{ncform_field}
元素来输出输入字段的仅部分,例如id、name、value
<input type="text" name="{ncform_field type='name' field='email'}" value="{ncform_field type='value' field='email'}" id="{ncform_field type='id' field='email'}" maxlength="32" />
FormHelper与Twig模板
首先,您需要注册twig扩展
use Naucon\Form\Helper\Twig\NcFormExtension; $twig = new \Twig\Environment($loader); $twig->addExtension(new NcFormExtension());
示例
{{ ncform_start(form, method='post', action='some-action', enctype='some type', {furtherOptions:'option'}) }} {{ ncform_field(form, 'text', 'activation_code', { style: 'some style', id: 'some id', value: 'some value', maxlength: 'some lenght', class: 'css class', required: 'required', 'data-attribute': 'some attribute'}) }} {{ ncform_end(form) }}
没有表单辅助器
没有表单辅助器,您必须自己将表单字段添加到HTML标记中。请务必设置正确的name属性 - 否则,表单数据无法绑定。
name属性以表单名称yourform
开头,后面跟着括号内的属性名称[username]
。
<input type="text" name="yourform[username]" value="" />
<input type="text" name="yourform[email]" value="" />
<input type="checkbox" name="yourform[newsletter]" value="1" />
在具有多个实体的表单中,name属性必须还包含括号内的实体名称[0]
,位于表单和属性名称之间
<input type="text" name="yourform[0][username]" value="" />
<input type="text" name="yourform[1][username]" value="" />
或者,您也可以要求EntityContainer实例提供name属性和值 - 如果您喜欢。
<label>Username</label>
<input type="text" name="<?php echo $entityContainer->getFormName('username'); ?>" value="<?php echo $entityContainer->getFormValue('username'); ?>" />
<?php
if ($formEntity->hasError('username')) {
echo 'WRONG: ' . $formEntity->getError('username')->getMessage();
}
?>
<br/>
<label>First name</label>
<input type="text" name="<?php echo $entityContainer->getFormName('firstname'); ?>" value="<?php echo $entityContainer->getFormValue('firstname'); ?>" />
<?php
if ($formEntity->hasError('firstname')) {
echo 'WRONG: ' . $formEntity->getError('firstname')->getMessage();
}
?>
<br/>
表单集合
要处理(绑定和验证)多个表单,您可以使用表单集合。可以通过从FormManager
调用createFormCollection()
来创建表单集合。第一个参数添加实体数组。第二个参数是表单名称。实体不必具有相同的名称 - 但在以下示例中我们这样做。
$products = array(new Product(), new Product(), new Product());
use Naucon\Form\FormManager;
$formManager = new FormManager();
$form = $formManager->createFormCollection($products, 'yourforms');
$form->bind($_POST);
要渲染表单,我们像这样遍历表单实体
foreach ($form->getEntityContainerIterator() as $entityContainer) {
可以使用collection_type
选项来指定表单集合的行为。值可以是以下选项之一:FormCollectionInterface::COLLECTION_TYPE_ALL、FormCollectionInterface::COLLECTION_TYPE_MANY、FormCollectionInterface::COLLECTION_TYPE_ONE。
FormCollectionInterface::COLLECTION_TYPE_ALL
:有效载荷必须绑定所有表单实体FormCollectionInterface::COLLECTION_TYPE_ANY
:有效载荷可以绑定任何匹配的表单实体FormCollectionInterface::COLLECTION_TYPE_ONE
:有效载荷必须绑定一个定义的表单实体。哪个定义通过表单选项。FormCollectionInterface::COLLECTION_TYPE_MANY
:有效载荷必须绑定一个或多个表单实体。哪些定义通过表单选项。
集合类型之一
例如,您有两个具有不同字段集的表单,用户必须从中选择。我们会使用COLLECTION_TYPE_ONE或COLLECTION_TYPE_MANY。添加一个带有集合类型的第三个参数。
$creditCardEntity = new CreditCard();
$directDebitEntity = new DirectDebit();
$paymentMethods = array('cc' => $creditCardEntity, 'dd' => $directDebitEntity);
use Naucon\Form\FormManager;
use Naucon\Form\FormCollectionInterface;
$formManager = new FormManager();
$forms = $formManager->createFormCollection($paymentMethods, 'payment_forms', FormCollectionInterface::COLLECTION_TYPE_ONE);
$forms->bind($_POST);
对于每个表单实体,我们添加一个单选按钮或复选框,如下所示
foreach ($forms->getEntityContainerIterator() as $entityContainer) {
if ($entityContainer->getName()=='cc') {
?>
<fieldset>
<input type="radio" name="<?php echo $forms->getFormOptionName() ?>" value="<?php echo $entityContainer->getName() ?>" <?php if ($forms->isFormOptionSelected($entityContainer->getName())) echo ' checked="checked"' ?> />
<label>Credit Card</label>
<br/>
</fieldet>
使用辅助器,它将看起来像这样
$formHelper = new FormHelper($form);
echo $formHelper->formStart();
echo $formHelper->formTag('errors');
foreach ($formHelper as $formEntity) {
if ($formEntity->getName()=='cc') {
?>
<fieldset>
<?php echo $formHelper->formOption('radio', 'cc' ); ?>
在smarty中,它将看起来像这样
{ncform from=$form}
{ncform_tag type='errors'}
{foreach from=$formHelper item="formEntity"}
{if $formEntity->getName()=='cc'}
<fieldset>
{ncform_option type='radio' choice='cc'}
要定义默认表单选项,请调用FormCollection::setFormOptionDefaultValues(array $defaultValues)
或FormCollection::addFormOptionDefaultValues($formEntityName)
。
$forms->setFormOptionDefaultValues(array('cc'))
或者
$forms->addFormOptionDefaultValues('dd');
配置
可以通过在创建表单管理器或表单实例时添加参数来配置表单。
$formManager = new FormManager($options);
或者
$form = $formManager->createForm($entity, 'testform', $options);
默认选项如下所示
$options = [
'csrf_parameter' => '_csrf_token',
'csrf_protection' => true,
'collection_type' => FormCollectionInterface::COLLECTION_TYPE_ALL,
'locale' => 'en_EN',
'fallback_locales' => array('en_EN'),
'translator_paths' => array(),
'config_paths' => array()
];
csrf_parameter
= 指定csrf隐藏输入字段的name属性csrf_protection
= 启用/禁用csrf保护collection_type
= 查看表单集合文档locale
= 默认语言fallback_locales
= 如果在当前语言中找不到翻译,则回退语言translator_paths
= 指定翻译文件的位置config_paths
= 指定验证文件的位置
表单钩子
通过表单实体的钩子,您可以影响表单处理。在以下示例中,我们使用预验证钩子从已绑定的属性中删除空格。我们还向后验证钩子添加了额外的验证。
class Address
{
protected $streetName;
protected $streetNumber;
protected $postalCode;
protected $town;
protected $country;
...
public function prevalidatorHook(FormHook $formHook)
{
$this->streetName = trim($this->streetName);
$this->streetNumber = trim($this->streetNumber);
$this->postalCode = trim($this->postalCode);
$this->town = trim($this->town);
$this->country = trim($this->country);
}
public function postvalidatorHook(FormHook $formHook)
{
if ($this->postalCode != '54321') {
$formHook->setError('postal_code', 'has unexpected value');
}
}
...
钩子基本上是表单实体中的一个具有定义的命名转换的方法。如果存在于表单实体中,则在处理时调用它。
- postbindHook(在实体绑定之后调用)
- prevalidatorHook(在验证实体之前调用)
- postvalidatorHook(在实体验证后调用)
钩子接收一个FormHook
实例,该实例允许您以受限的方式访问实体容器以更改错误
- getErrors()
- hasErrors()
- getError($key)
- hasError($key)
- setError($key, $message)
路线图
- 解耦翻译、验证器与配置(更小,更多选项)
- 测试使用alpha实体名称/集合的标签
- 测试编码值
- 将表单错误重命名为违规
- 表单集合选项的违规(包括翻译)
- csrf验证的违规(包括翻译)
- 绑定和验证的状态机
- 添加smarty块插件以限制表单输出为第一个或最后一个实体(用于表格和按钮)。
- 添加smarty块插件以限制到定义的实体(表单集合)。