avris/forms

简单的表单抽象

v4.0.1 2018-01-27 23:34 UTC

This package is auto-updated.

Last update: 2024-08-29 04:48:27 UTC


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 if choices 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');
}

版权