avris / forms
Requires
- avris/container: ^1.0
- avris/http: ^4.0
- avris/localisator: ^4.0
- twig/twig: ^2.4
Requires (Dev)
- avris/micrus: ^4.0-dev
- phpunit/phpunit: ^6.4
- squizlabs/php_codesniffer: ^3.2
- symfony/var-dumper: ^4.0
Suggests
- avris/micrus: Web framework that this library was created for
README
表单很复杂。有许多事情你必须考虑:将现有对象(如果有的话)绑定到表单的每个单独字段,在用户提交表单后验证它们,如果无效,则重新显示它并绑定POST数据以及验证错误,将数据绑定回对象...
Avris Forms 添加了一个抽象层,处理所有这些。你只需要定义你需要字段列表及其配置选项。你将得到一个对象,它将为你处理所有这些。只需在控制器中处理它,并在视图中显示它。
安装
composer require avris/forms
配置
依赖项通过 DI 容器处理。你可以自己配置它或使用内置的 DI 构建器。
$formsDir = $projectDir . '/vendor/avris/forms';
$locale = 'en';
$builder = (new LocalisatorBuilder())
->registerExtension(new LocalisatorExtension($locale, [$formsDir . '/translations']))
->registerExtension(new FormsExtension(
[
UserForm::class => [],
PostForm::class => ['@logger'],
],
[CustomWidget::class => []]
));
$localisator = $builder->build(LocalisatorInterface::class);
$loader = new FilesystemLoader([$projectDir . '/templates', $formsDir . '/templates']);
self::$twig = new Environment($loader, [
'cache' => __DIR__ . '/_output/cache',
'debug' => true,
'strict_variables' => true,
]);
self::$twig->addExtension(new FormRenderer());
self::$twig->addExtension(new LocalisatorTwig($localisator));
$widgetFactory = $builder->build(WidgetFactory::class);
请注意,所有表单和部件都是服务,因此你可以简单地注入依赖项到它们中。
Twig 是渲染表单 HTML 所必需的。它期望有一个 l(string $key, array $replacements)
函数用于翻译。你可以自己提供它,或者使用如上所示的 Avris Localisator。
用法
表单定义
<?php
namespace App\Form;
use App\Entity\User;
use Avris\Forms\Assert as Assert;
use Avris\Forms\Security\CsrfProviderInterface;
use Avris\Forms\Widget as Widget;
use Avris\Forms\Form;
use Avris\Forms\WidgetFactory;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
class RegisterForm extends Form
{
/** @var EntityRepository */
private $repo;
// inject dependencies as with any other service
public function __construct(
WidgetFactory $widgetFactory,
CsrfProviderInterface $csrfProvider,
EntityManagerInterface $em
) {
parent::__construct($widgetFactory, $csrfProvider);
$this->repo = $em->getRepository(User::class);
}
public function configure()
{
$this
->add('username', Widget\Text::class, [
'placeholder' => 'Username can only contain latin letters and numbers',
], [
new Assert\NotBlank(),
new Assert\Regexp('^[A-Za-z0-9]+$', 'Username can only contain latin letters and numbers')),
new Assert\MinLength(5),
new Assert\MaxLength(25),
new Assert\Callback(
function (string $value) {
return !$this->repo->findOneBy(['username' => $value]);
},
'Username already exists in the database'
),
])
->add('email', Widget\Email::class, [], [
new Assert\NotBlank(),
new Assert\Callback(
function (string $value) {
return !$this->repo->findOneBy(['email' => $value]);
},
'Email already exists in the database'
),
])
->add('password', Widget\Password::class, [], [
new Assert\NotBlank(),
new Assert\MinLength(5),
new Assert\Callback(
function () {
return $this->getData()['password'] === $this->getData()['passwordRepeat'];
},
'Passwords don\'t match'
)
])
->add('passwordRepeat', Widget\Password::class, [], new Assert\NotBlank)
->add('source', Widget\Choice::class, [
'choices' => [
'facebook' => 'Facebook',
'twitter' => 'Twitter',
'friend' => 'Friend',
],
'add_empty' => true,
])
->add('agree', Widget\Checkbox::class, [
'label' => '',
'sublabel' => 'I agree to the terms and conditions')
], new Assert\NotBlank)
;
}
// just a custom helper
public function getPassword()
{
return $this->getData()['password'];
}
// if not specified, will fallback to the short class name of the object it's bound to
// it's useful to specify it, if you have multiple X-related forms on the same page (like Login and Register)
public function getName(): string
{
return 'Register';
}
}
使用表单
public function registerAction(WidgetFactory $widgetFactory, EntityManagerInterface $em, RequestInterface $request)
{
$form = $widgetFactory->build(RegisterForm::class);
$form->bindObject(new User());
$form->bindRequest($request);
if ($form->isValid()) {
$user = $form->buildObject();
$em->persist($post);
$em->flush();
return $this->redirectToRoute('myaccount');
}
return $this->render('User/register.html.twig', ['form' => $form]);
}
并在视图中这样显示
<form method="post" class="form">
{{ widget(form, form.name, 'Avris\\Forms\\Style\\Bootstrap') }}
<div class="form-group">
<button type="submit" class="btn btn-block btn-primary">Log in</button>
</div>
</form>
有关更多示例,请参阅 Micrus Demo 项目。
部件
add(string $name, string $type = Widget\Text::class, array $options = [], array $asserts = [])
方法让你可以将字段添加到你的表单中。$name
参数必须是唯一的,因为它将是对象的属性名。$type
是一个字符串,定义了应使用哪个部件。
Text
(默认)数字
整数
电子邮件
网址
隐藏
密码
文本区域
Checkbox
,选项sublabel
-- 在复选框旁边显示的文本
日期
日期时间
时间
Choice
-- 选项choices => [key => value, ...]
(required) -- 将向用户展示的选项列表;它可以是简单的键值列表,也可以是对象数组 -- 在第二种情况下,Avris Forms 将保留这些对象,但你需要提供一个将它们映射到键值列表的方法(对于键:选项keys
,对于值:选项labels
或方法__toString
)add_empty => bool
(default: false)multiple => bool
(default: false) -- 如果用户可以选择一个或多个选项,expanded => bool
(default: false) -- 如果应使用单个select
控件或多个checkbox
/radio
控件,keys => callback
(optional, required ifchoices
is an array of objects) -- 将对象映射到唯一的字符串标识符,例如:function (User $user) { return $user->getId(); }
labels => callback
(optional) -- 将对象映射到其文本表示形式(如果没有提供,则使用__toString
)。
文件
Custom
-- 允许自定义 HTML,不将任何内容绑定到对象;选项template
(required) - 要显示的 Twig 模板的名称
Csrf
-- CSRF 保护,自动添加到每个表单,除非用选项csrf = false
创建。Multiple
-- 以子表单表格形式表示的值数组;选项widget
(必需) -- 单个子表单的类名lineStyle
(默认: Avris\Forms\Style\BootstrapInlineNoLabel)add => bool
(默认: true)remove => bool|callback
(默认: true)element_prototype
element_options
element_asserts
TextAddon
(bootstrap),选项before
(可选)after
(可选)
NumberAddon
(bootstrap),类似于TextAddon
的选项
注意,由于表单作为小部件,您可以在不同的表单内轻松嵌套表单(无论是直接用于一对一关系,还是使用 Multiple
小部件用于一对多关系)。
所有小部件的选项
label
class
attr
-- HTML 属性数组helper
readonly
default
-- 默认数据prototype
-- 绑定的默认对象
自定义小部件
要创建您自己的小部件
- 创建一个扩展
Avris\Forms\Widget\Widget
的类,其中包含所有逻辑(数据转换) - 在依赖注入(DI)中注册它
- 创建相应的 Twig 模板,例如
MyApp\Widget\Foo
类应该有一个Form/MyApp/Widget/Foo.html.twig
模板。
断言
可用的断言有
NotBlank
电子邮件
网址
MaxLength
MinLength
Regexp
数字
整数
Min
Max
Step
MinCount
MaxCount
日期
日期时间
时间
MinDate
MaxDate
ObjectValidator
CorrectPassword
Choice
Csrf
MinCount
MaxCount
File\File
File\Image
File\Extension
File\Type
File\MaxHeight
File\MinHeight
File\MaxWidth
File\MinWidth
File\MaxSize
File\MaxRatio
许多小部件会自动添加相关断言,因此您不必这样做。
样式
当将表单渲染为 HTML 时,您可以指定样式。例如,使用 `{{ widget(form, form.name, 'Avris\Forms\Style\Bootstrap2') }}`,每个小部件都被包裹在具有 2 列标签和 10 列小部件的 Bootstrap 类中。
内置样式有
DefaultStyle
Bootstrap
Bootstrap1
Bootstrap2
Bootstrap3
BootstrapHalf
BootstrapInline
BootstrapInlineNoLabel
BootstrapMini
BootstrapNoLabel
您可以通过实现 Avris\Forms\Style\FormStyleInterface
来创建自己的样式。
框架集成
Micrus
尽管 Avris Forms 可以独立使用,但它们最初是作为 Micrus Framework 的一部分创建的。因此,与该框架的集成非常简单。只需在您的 App\App:registerModules
中注册模块即可。
yield new \Avris\Forms\FormsModule;
您可以在控制器中使用助手函数
/**
* @M\Route("/add", name="postAdd")
* @M\Route("/{uuid:id}/edit", name="postEdit")
*/
public function formAction(EntityManagerInterface $em, RequestInterface $request, Post $post = null)
{
if (!$post) {
$post = new Post($this->getUser());
}
$form = $this->form(PostForm::class, $post, $request); // creates a form and binds the object and the request
if ($this->handleForm($form, $post)) { // validates the form and if valid, binds the data back to $post
$post->handleFileUpload($this->getProjectDir());
$em->persist($post);
$em->flush();
return $this->redirectToRoute('postRead', ['id' => $post->getId()]);
}
return $this->render(['form' => $form], 'Post/form');
}
版权
- 作者: Andre Prusinowski (Avris.it)
- 许可: MIT