avris / micrus-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
表单很复杂。有许多事情你必须考虑:将现有对象(如果有)绑定到表单的每个字段,在用户提交表单后验证它们,如果无效,则重新显示它,并绑定数据回对象...
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 示例项目。
小部件
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') }}`,每个小部件都被Bootstrap类包裹,标签有2列,小部件有10列。
内置样式有
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