はじめに
Symfony4.4のフォームを作成していて、0以上の整数値の入力をするフォームのバリデーションで四苦八苦をしたのでメモです。
VueやReactを使ったViewが主流なので、昔ながらフォームは需要が少ないこともあり、
また、Symfony4での作成例も少ないようですので、フォームの作成部分を詳しく記述してみました。
これ以外でいい方法があればご教示ください。
TL;DR
EntityにAnotationでバリデーションをつける
この場合は、Entityで指定されてしまうので、かなりキツめのバリデーションになります。
DBの自由度は残して、フォームだけバリデーションをしたい場合は、次を御覧ください。
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Student
{
/**
* @Assert\PositiveOrZero
*/
protected $student_no;
}
※参考文献:PositiveOrZero
フォームでバリデーションをつける
フォームごとに細かくバリデーションを調整したい場合は、こちらがよさそう
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\PositiveOrZero;
use Symfony\Component\Validator\Constraints\NotBlank;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('student_no', NumberType::class, [
'constraints' => [
new PositiveOrZero(),
],
])
;
}
※参考文献:How to Use a Form without a Data Class の Adding Validation
試行錯誤過程
自然数を入力する番号があればいいので、Taskよりは、生徒番号がよさそうなので、Studentクラスで試行錯誤を行ってみます。
Student Entityを作成
生徒番号(StudentNo)と名前(Name)のStudentクラスを作成します。
# php bin/console make:entity
Class name of the entity to create or update (e.g. VictoriousPizza):
> Student
created: src/Entity/Student.php
created: src/Repository/StudentRepository.php
Entity generated! Now let's add some fields!
You can always add more fields later manually or by re-running this command.
New property name (press <return> to stop adding fields):
> studentNo
Field type (enter ? to see all types) [string]:
> integer
Can this field be null in the database (nullable) (yes/no) [no]:
>
updated: src/Entity/Student.php
Add another property? Enter the property name (or press <return> to stop adding fields):
> Name
Field type (enter ? to see all types) [string]:
>
Field length [255]:
>
Can this field be null in the database (nullable) (yes/no) [no]:
>
updated: src/Entity/Student.php
Add another property? Enter the property name (or press <return> to stop adding fields):
>
Success!
Next: When you're ready, create a migration with make:migration
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="App\Repository\StudentRepository")
*/
class Student
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="integer")
*/
private $studentNo;
/**
* @ORM\Column(type="string", length=255)
*/
private $Name;
public function getId(): ?int
{
return $this->id;
}
public function getStudentNo(): ?int
{
return $this->studentNo;
}
public function setStudentNo(int $studentNo): self
{
$this->studentNo = $studentNo;
return $this;
}
public function getName(): ?string
{
return $this->Name;
}
public function setName(string $Name): self
{
$this->Name = $Name;
return $this;
}
}
FormTypeを作成
Formに関連するFormTypeを作成します。
bash-5.0# php bin/console make:form
The name of the form class (e.g. AgreeablePizzaType):
> StudentType
The name of Entity or fully qualified model class name that the new form will be bound to (empty for none):
> Student
created: src/Form/StudentType.php
Success!
Next: Add fields to your form and start using it.
Find the documentation at https://symfony.com/doc/current/forms.html
<?php
namespace App\Form;
use App\Entity\Student;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class StudentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('studentNo')
->add('Name')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Student::class,
]);
}
}
コントローラを作成する
Formを利用するコントローラを作成する
bash-5.0# php bin/console make:controller
Choose a name for your controller class (e.g. FierceKangarooController):
> StudentController
created: src/Controller/StudentController.php
created: templates/student/index.html.twig
Success!
Next: Open your new controller class and add some pages!
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class StudentController extends AbstractController
{
/**
* @Route("/student", name="student")
*/
public function index()
{
return $this->render('student/index.html.twig', [
'controller_name' => 'StudentController',
]);
}
}
{% extends 'base.html.twig' %}
{% block title %}Hello StudentController!{% endblock %}
{% block body %}
<style>
.example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
.example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>
<div class="example-wrapper">
<h1>Hello {{ controller_name }}! ✅</h1>
This friendly message is coming from:
<ul>
<li>Your controller at <code><a href="{{ '/var/www/html/src/Controller/StudentController.php'|file_link(0) }}">src/Controller/StudentController.php</a></code></li>
<li>Your template at <code><a href="{{ '/var/www/html/templates/student/index.html.twig'|file_link(0) }}">templates/student/index.html.twig</a></code></li>
</ul>
</div>
{% endblock %}
http://127.0.0.1:8000/student を開くと以下のようになります。
Formを組み込む
SymfonyドキュメントのFormの章のRendering Formsを参考にしてControllerにFormを組み込みます。
<?php
namespace App\Controller;
use App\Entity\Student;
use App\Form\StudentType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class StudentController extends AbstractController
{
/**
* @Route("/student", name="student")
*/
public function index()
{
$student = new Student();
$form = $this->createForm(StudentType::class, $student);
return $this->render('student/index.html.twig', [
'controller_name' => 'StudentController',
'form' => $form->createView(),
]);
}
}
~~ 省略 ~~~
<div class="example-wrapper">
<h1>Hello {{ controller_name }}! ✅</h1>
This friendly message is coming from:
<ul>
<li>Your controller at <code><a href="{{ '/var/www/html/src/Controller/StudentController.php'|file_link(0) }}">src/Controller/StudentController.php</a></code></li>
<li>Your template at <code><a href="{{ '/var/www/html/templates/student/index.html.twig'|file_link(0) }}">templates/student/index.html.twig</a></code></li>
</ul>
{{ form(form) }}
</div>
Submitボタンがないので、Creating Form Classesを参考にしてStudentTypeに追記します。
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('studentNo')
->add('Name')
->add('save', SubmitType::class)
;
}
SymfonyのベストプラクティスのAdd Form Buttons in Templatesを見ると、Submitボタンは、フォームタイプに組み込むよりは、テンプレートに組み込む方ことで、フォームタイプの汎用性が上がると記載があります。
テンプレートに移していきましょう。
まずは、さきほどStudentTypeに追記したSubmitボタンをコメントアウトします。
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('studentNo')
->add('Name')
// ->add('save', SubmitType::class)
;
}
テンプレートにsubmitボタンをつけます。
How to Customize Form Renderingを参考にしてフォームのレンダリングを調整します。
識別するようにボタン名をsave2にします。
<div class="example-wrapper">
<h1>Hello {{ controller_name }}! ✅</h1>
This friendly message is coming from:
<ul>
<li>Your controller at <code><a href="{{ '/var/www/html/src/Controller/StudentController.php'|file_link(0) }}">src/Controller/StudentController.php</a></code></li>
<li>Your template at <code><a href="{{ '/var/www/html/templates/student/index.html.twig'|file_link(0) }}">templates/student/index.html.twig</a></code></li>
</ul>
{{ form_start(form) }}
{{ form_widget(form) }}
<button class="btn btn-lg btn-primary" name="save" type="submit">save2</button>
{{ form_end(form) }}
</div>
このようになりました。
Student Noにバリデーションを追加する
student no
は、EntityでIntegerを指定していますので、フォームのinput type
にnumberが指定されています。
ただ、このままですと、-1、-2とか指定できてしまいますので、生徒番号として自然数のみにしたいので、フォームのバリデーションを指定していく必要があります。
/**
* @ORM\Column(type="integer")
*/
private $studentNo;
バリデーションなので、ValidationのNumber Constraintsを見つけます。
そこに、PositiveOrZeroというのがあります。
※自然数ならPositiveなのですが、画像取得していたときに間違えていました。。。汗
こんな感じでEntityの該当の変数にアノテーションでバリデーションを追記できます。
ただ、今回は、フォームで制限したいなぁ~と考えていますので、調査していきます。
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Student
{
/**
* @ORM\Column(type="integer")
* @Assert\PositiveOrZero
*/
private $studentNo;
}
Entityではなくバリデーションを追加する方法も、How to Use a Form without a Data Classに記載がありました。
このドキュメントを参考にして、StudentType.php
を以下のように修正します。
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('studentNo', NumberType::class, [
'html5' => true,
'constraints' => [
new NotBlank(),
new PositiveOrZero(),
],
])
->add('Name')
// ->add('save', SubmitType::class)
;
}
こんな感じで見た目は変更がありませんが、バリデーションを設定できました。
バリデーションを試すには、Controllerにも手を入れる必要がありました。
Processing Formsを参考にして、フォームの入力値のチェックを追加します。
class StudentController extends AbstractController
{
/**
* @Route("/student", name="student")
*/
public function index(Request $request)
{
$student = new Student();
$form = $this->createForm(StudentType::class, $student);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
}
return $this->render('student/index.html.twig', [
'controller_name' => 'StudentController',
'form' => $form->createView(),
]);
}
}
こちらでバリデーションを確認してみましょう。
きちんと、バリデーションができましたね。
Bootstrap4を適用する
バリデーションがでたのですが、装飾がないので味気ないですね。
Bootstrap4を適用してみましょう。
Bootstrap 4 Form Themeを参考にします。
ドキュメントとは違いますが、base.html.twig
を以下のように修正しました。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
{# beware that the blocks in your template may be named different #}
{% block head_css %}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
{% endblock %}
{% block head_js %}
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
</html>
そしてtwig.yaml
を修正してテーマを指定します。
twig:
default_path: '%kernel.project_dir%/templates'
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
exception_controller: null
form_themes: ['bootstrap_4_layout.html.twig']
一気にBootstrapになりました。
この状態でバリデーションを確認してみます。
いい感じで、バリデーションのエラーが表示されました。
最後に
今回作成したサンプルプログラムを以下で公開しています。
参考にしていただければ幸いです。