1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SymfonyAdvent Calendar 2019

Day 16

Symfony4.4のフォームで自然数のバリデーションを行う

Last updated at Posted at 2019-12-22

はじめに

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

src/src/Entity/Student.php
<?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
src/src/Form/StudentType.php
<?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!

src/src/Controller/StudentController.php
<?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',
        ]);
    }
}
src/templates/student/index.html.twig
{% 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 を開くと以下のようになります。

image.png

Formを組み込む

SymfonyドキュメントのFormの章のRendering Formsを参考にしてControllerにFormを組み込みます。

src/src/Controller/StudentController.php
<?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(),
        ]);
    }
}
src/templates/student/index.html.twig
~~ 省略 ~~~
<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>

リロードすると、以下のようになります。
image.png

Submitボタンがないので、Creating Form Classesを参考にしてStudentTypeに追記します。

src/src/Form/StudentType.php
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('studentNo')
            ->add('Name')
            ->add('save', SubmitType::class)
        ;
    }

image.png

SymfonyのベストプラクティスのAdd Form Buttons in Templatesを見ると、Submitボタンは、フォームタイプに組み込むよりは、テンプレートに組み込む方ことで、フォームタイプの汎用性が上がると記載があります。

テンプレートに移していきましょう。

まずは、さきほどStudentTypeに追記したSubmitボタンをコメントアウトします。

src/src/Form/StudentType.php
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('studentNo')
            ->add('Name')
//            ->add('save', SubmitType::class)
        ;
    }

テンプレートにsubmitボタンをつけます。
How to Customize Form Renderingを参考にしてフォームのレンダリングを調整します。

識別するようにボタン名をsave2にします。

src/templates/student/index.html.twig
<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>

このようになりました。

image.png

Student Noにバリデーションを追加する

student noは、EntityでIntegerを指定していますので、フォームのinput typenumberが指定されています。

ただ、このままですと、-1-2とか指定できてしまいますので、生徒番号として自然数のみにしたいので、フォームのバリデーションを指定していく必要があります。

src/src/Entity/Student.php
    /**
     * @ORM\Column(type="integer")
     */
    private $studentNo;

image.png

バリデーションなので、ValidationのNumber Constraintsを見つけます。
そこに、PositiveOrZeroというのがあります。
※自然数ならPositiveなのですが、画像取得していたときに間違えていました。。。汗

image.png

こんな感じでEntityの該当の変数にアノテーションでバリデーションを追記できます。
ただ、今回は、フォームで制限したいなぁ~と考えていますので、調査していきます。

src/src/Entity/Student.php
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を以下のように修正します。

src/src/Form/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)
        ;
    }

こんな感じで見た目は変更がありませんが、バリデーションを設定できました。
image.png

バリデーションを試すには、Controllerにも手を入れる必要がありました。
Processing Formsを参考にして、フォームの入力値のチェックを追加します。

src/src/Controller/StudentController.php
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(),
        ]);
    }

}

こちらでバリデーションを確認してみましょう。
きちんと、バリデーションができましたね。

2019-12-23_00h13_15.gif

Bootstrap4を適用する

バリデーションがでたのですが、装飾がないので味気ないですね。
Bootstrap4を適用してみましょう。

Bootstrap 4 Form Themeを参考にします。

ドキュメントとは違いますが、base.html.twigを以下のように修正しました。

src/templates/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を修正してテーマを指定します。

src/config/packages/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になりました。

image.png

この状態でバリデーションを確認してみます。
いい感じで、バリデーションのエラーが表示されました。

image.png

最後に

今回作成したサンプルプログラムを以下で公開しています。
参考にしていただければ幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?