42
41

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.

symfony でのform作成の基本

Last updated at Posted at 2017-05-30

はじめに

案件で何もわからないままsymfonyを使い始めたけど、日本語でのまとまった情報が少ない。。。
meetupとかあるみたいだけど、最近動きが少ない様に見える・・・(php界隈詳しくないので勘違いかもしれません)
特に情報がまとまっていることが少なかったForm関連のクラスを自動生成されたコードに少し手を加えたものを基本にまとめてみました。

今回使用したsymfonyのversionは2.8です。

## 実際に登録・編集 作ってみる

Entity

  • まずはテーブルのEntityクラスを作成します。テーブルに連動したメンバーとgetter,setterを作成。
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * ToDo
 *
 * @ORM\Table(name="to_do")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\ToDoRepository")
 */
class ToDo
{

    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="task", type="integer", length=1)
     */
    private $task;

    /**
     * @var string
     *
     * @ORM\Column(name="memo", type="string", length=255, nullable=true)
     */
    private $memo;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="r_datetime", type="datetime", nullable=true)
     */
    private $rDatetime;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="u_datetime", type="datetime", nullable=true)
     */
    private $uDatetime;

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set task
     *
     * @param string $task
     * @return ToDo
     */
    public function setTask($task)
    {
        $this->task = $task;

        return $this;
    }

    /**
     * Get task
     *
     * @return string 
     */
    public function getTask()
    {
        return $this->task;
    }

    /**
     * Set memo
     *
     * @param string $memo
     * @return ToDo
     */
    public function setMemo($memo)
    {
        $this->memo = $memo;

        return $this;
    }

    /**
     * Get memo
     *
     * @return string 
     */
    public function getMemo()
    {
        return $this->memo;
    }

    /**
     * Set rDatetime
     *
     * @param \DateTime $rDatetime
     * @return ToDo
     */
    public function setRDatetime($rDatetime)
    {
        $this->rDatetime = $rDatetime;

        return $this;
    }

    /**
     * Get rDatetime
     *
     * @return \DateTime
     */
    public function getRDatetime()
    {
        return $this->rDatetime;
    }

    /**
     * Set uDatetime
     *
     * @param \DateTime $uDatetime
     * @return ToDo
     */
    public function setUDatetime($uDatetime)
    {
        $this->uDatetime = $uDatetime;

        return $this;
    }

    /**
     * Get uDatetime
     *
     * @return \DateTime
     */
    public function getUDatetime()
    {
        return $this->uDatetime;
    }

    /**
     * @ORM\PrePersist
     */
    public function refreshRDatetime()
    {
        $this->setRDatetime(new \Datetime());
    }

    /**
     * @ORM\PrePersist
     * @ORM\PreUpdate
     */
    public function refreshUDatetime()
    {
        $this->setUDatetime(new \Datetime());
    }

}

FormType

  • 次にentityに結びついたFormTypeを作成します。
<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type as InputType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints;

class ToDoType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('task',
                InputType\ChoiceType::class,
                [
                    'choices' => [
                        '1' => 'work',
                        '2' => 'hobby'
                    ],

                    'constraints' =>
                        [
                            new Constraints\NotBlank()
                        ]
                ]
            )
            ->add('memo',
                InputType\TextareaType::class,
                [
                    'constraints' => [
                        new Constraints\NotBlank(),
                        new Constraints\Length(['max' => 255])
                    ]
                ]);

        $entity = $builder->getData();
        if(!$entity->getId()){
            $builder->add('Create', InputType\SubmitType::class,
                [
                    'attr' => [
                        'class' => 'btn btn-primary'
                    ]
                ]
            );
        }else{
            $builder->add('Edit', InputType\SubmitType::class,
            [
            'attr' => [
                'class' => 'btn btn-primary'
            ]
                ]
            );
        }
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\ToDo'
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_todo';
    }


}

  • configureOptions
    • data_classの部分でこのFormTypeクラスと結びつくEntityのクラス名を指定しています。
  • buildForm
    • 基本的にどんなパターンの実装でも使う可能性があるのは、buildFormの中で記載されている addメソッドです。この部分で実際にformに作成されるinputの種類とvalidationを記載することができます。
      今回の場合はユーザーが編集できる項目はtaskmemoの2つだけなので二つの項目をaddしています。
      第1引数にメンバーの名前、第2引数にformの種類、第3引数にはoptionを渡せます。
            ->add('task',
                InputType\ChoiceType::class,
                [
                    'choices' => [
                        '1' => 'work',
                        '2' =>'hobby'
                    ]
                ],
                [
                    'constraints' =>
                        [
                            new Constraints\NotBlank()
                        ]
                ]
            )

上記の部分ではToDoのEntityのtaskの入力項目を選択式(choiceType)でフォームを生成し、実際のセレクトボックスの選択肢を第三引数に配列でchoicesで渡しています。

Controller

  • Controllerクラスです。自動生成で作成したのでCRUDが一通り実装されていますが、今回は新規と編集のみ説明します。
namespace AppBundle\Controller;

use AppBundle\Entity\ToDo;
use AppBundle\Form\ToDoType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;

/**
 * Todo controller.
 *
 * @Route("todo")
 */
class ToDoController extends Controller
{
    /**
     * Lists all toDo entities.
     *
     * @Route("/", name="todo_index")
     * @Method("GET")
     */
    public function indexAction()
    {
        $em = $this->getDoctrine()->getManager();

        $toDos = $em->getRepository('AppBundle:ToDo')->findAll();

        return $this->render('todo/index.html.twig', array(
            'toDos' => $toDos,
        ));
    }

    /**
     * Creates a new toDo entity.
     *
     * @Route("/new", name="todo_new")
     * @Method({"GET", "POST"})
     */
    public function newAction(Request $request)
    {
        $toDo = new Todo();
        $form = $this->createForm(new ToDoType(), $toDo);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->persist($toDo);
            $em->flush($toDo);

            return $this->redirectToRoute('todo_show', array('id' => $toDo->getId()));
        }

        return $this->render('todo/new.html.twig', array(
            'toDo' => $toDo,
            'form' => $form->createView(),
        ));
    }

    /**
     * Finds and displays a toDo entity.
     *
     * @Route("/{id}", name="todo_show")
     * @Method("GET")
     */
    public function showAction(ToDo $toDo)
    {
        $deleteForm = $this->createDeleteForm($toDo);

        return $this->render('todo/show.html.twig', array(
            'toDo' => $toDo,
            'delete_form' => $deleteForm->createView(),
        ));
    }

    /**
     * Displays a form to edit an existing toDo entity.
     *
     * @Route("/{id}/edit", name="todo_edit")
     * @Method({"GET", "POST"})
     */
    public function editAction(Request $request, ToDo $toDo)
    {
        $deleteForm = $this->createDeleteForm($toDo);
        $editForm = $this->createForm(new ToDoType(), $toDo);
        $editForm->handleRequest($request);

        if ($editForm->isSubmitted() && $editForm->isValid()) {
            $this->getDoctrine()->getManager()->flush();

            return $this->redirectToRoute('todo_edit', array('id' => $toDo->getId()));
        }

        return $this->render('todo/edit.html.twig', array(
            'toDo' => $toDo,
            'edit_form' => $editForm->createView(),
            'delete_form' => $deleteForm->createView(),
        ));
    }

    /**
     * Deletes a toDo entity.
     *
     * @Route("/{id}", name="todo_delete")
     * @Method("DELETE")
     */
    public function deleteAction(Request $request, ToDo $toDo)
    {
        $form = $this->createDeleteForm($toDo);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->remove($toDo);
            $em->flush();
        }

        return $this->redirectToRoute('todo_index');
    }

    /**
     * Creates a form to delete a toDo entity.
     *
     * @param ToDo $toDo The toDo entity
     *
     * @return \Symfony\Component\Form\Form The form
     */
    private function createDeleteForm(ToDo $toDo)
    {
        return $this->createFormBuilder()
            ->setAction($this->generateUrl('todo_delete', array('id' => $toDo->getId())))
            ->setMethod('DELETE')
            ->getForm()
        ;
    }
}
  • 新規作成
    新規作成は下記のactionで行っています。
    $this->createForm(new ToDoType(), $toDo);の部分で空のentityとformTypeを渡し、formのインスタンスを作成し、view側に渡しています。
    $form->handleRequest($request)の部分でviewから渡されたRequestの中からEntityに紐づく値とformの状態を取得します。
    値を取得した後
    if ($form->isSubmitted() && $form->isValid())
    の所で、$formがsubmitされているかつ、formTypeのvalidationが通るかを確認し、通った場合はdoctrineを使ってDBの登録を行います。
    /**
     * Creates a new toDo entity.
     *
     * @Route("/new", name="todo_new")
     * @Method({"GET", "POST"})
     */
    public function newAction(Request $request)
    {
        $toDo = new Todo();
        $form = $this->createForm(new ToDoType(), $toDo);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->persist($toDo);
            $em->flush($toDo);

            return $this->redirectToRoute('todo_show', array('id' => $toDo->getId()));
        }

        return $this->render('todo/new.html.twig', array(
            'toDo' => $toDo,
            'form' => $form->createView(),
        ));
    }
  • 編集
    次に編集部分の解説です。
    編集と似た構成ですが、編集の場合は既存のentityを事前に取得するところが異なります。
    @Route("/{id}/edit", name="todo_edit")で指定した{id}の値を利用し、
    既存のentityはParamConverterを介し、自動で取得しています。
    取得したentityは第二引数に指定しています。
    取得したtodoのentityをcreateFormの第二引数に渡すことでformに既存のentityの値を与えています。
    残りの部分は新規作成と同様で$form->handleRequest($request)の部分でviewから渡されたRequestの中からEntityに紐づく値とformの状態を取得します。
    値を取得した後
    if ($form->isSubmitted() && $form->isValid())
    の所で、$formがsubmitされているかつ、formTypeのvalidationが通るかを確認し、通った場合はdoctrineを使ってDBの登録を行います。

今回はdeleteの部分の説明は割愛します。

    /**
     * Displays a form to edit an existing toDo entity.
     *
     * @Route("/{id}/edit", name="todo_edit")
     * @Method({"GET", "POST"})
     */
    public function editAction(Request $request, ToDo $toDo)
    {
        $deleteForm = $this->createDeleteForm($toDo);
        $editForm = $this->createForm(new ToDoType(), $toDo);
        $editForm->handleRequest($request);

        if ($editForm->isSubmitted() && $editForm->isValid()) {
            $this->getDoctrine()->getManager()->flush();

            return $this->redirectToRoute('todo_edit', array('id' => $toDo->getId()));
        }

        return $this->render('todo/edit.html.twig', array(
            'toDo' => $toDo,
            'edit_form' => $editForm->createView(),
            'delete_form' => $deleteForm->createView(),
        ));
    }

view

  • 新規作成画面
    新規作成画面はこれだけです。formを実際に作っている部分は下記のコードでform_startとform_endでformの開始、終了位置を指定、form_widget(form)の部分で渡されたTodoTypeにaddされた入力箇所のhtmlを出力しています。

{% extends 'base.html.twig' %}

{% block body %}
    <h1>Todo creation</h1>

    {{ form_start(form) }}
        {{ form_widget(form) }}
    {{ form_end(form) }}

    <ul class="unstyled">
        <li>
            <a id="index" class="btn btn-info" href="{{ path('todo_index') }}">Back to the list</a>
        </li>
    </ul>
{% endblock %}
  • 実際の画面
    ToDoNew.png

    {{ form_start(form) }}
        {{ form_widget(form) }}
    {{ form_end(form) }}
  • 編集
    編集もほぼ同様です。
{% extends 'base.html.twig' %}

{% block body %}
    <h1>Todo edit</h1>

    {{ form_start(edit_form) }}
        {{ form_widget(edit_form) }}
    {{ form_end(edit_form) }}

    <ul class="unstyled">
        <li>
            <a class="btn btn-info" href="{{ path('todo_index') }}">Back to the list</a>
        </li>
        <li>
            {{ form_start(delete_form) }}
                <input id="delete" class="btn btn-danger" type="submit" value="Delete">
            {{ form_end(delete_form) }}
        </li>
    </ul>
{% endblock %}
  • 実際の画面
    ToDoEdit.png

最後に

symfonyではEntityにannnotionで直接validationを付与して、validationを行うことができます。
しかし、実際の実装において必ずしもentityの項目と
画面の項目の実装が一致するとは限りません。
実際のvalidation処理の管理はformTypeクラスに切り分けて、entityクラスは出来るだけ、実態に関連する項目や処理だけを入れた方がコードとして読みやすい気がします。
ちゃんと扱えれば、なんでも出来るframeworkだと思いますが、本当まとまった情報が少ない・・・

github

参考
http://api.symfony.com/2.8/Symfony/Component/Form/FormBuilderInterface.html#method_add
http://symfony.com/doc/current/reference/forms/types.html

42
41
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
42
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?