シムノート

PHPフレームワークSymfonyの学習帳

ユーザ用ツール

サイト用ツール


サイドバー

メニュー



このエントリーをはてなブックマークに追加

blog:2015-12-15:超入門_symfony3_form_前編

超入門 Symfony3 : (9) Form【前編】

おみくじサイトに運勢をメンテナンスする為の画面を追加します。その過程でFormの実装方法を学びます。

準備

運勢管理の雛形作成

UnseiControllerに運勢の一覧表示、新規作成、更新、削除のアクションメソッドを追加します。

<?php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; // (a)
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\Unsei;
use Doctrine\ORM\EntityManager;

/**
 * @Route("/unsei")
 */
class UnseiController extends Controller
{
    /**
     * 運勢一覧の表示
     * 
     * @Route("/", name="unsei_index")
     * @Method("GET")  // ①
     * 
     * @param Request $request
     * @return Response
     */
    public function indexAction(Request $request)
    {
        $repository = $this->getDoctrine()->getRepository(Unsei::class);
        $unseis = $repository->findAll();

        return $this->render('unsei/index.html.twig', ['unseis' => $unseis]);
    }
    
    /**
     * 運勢の新規作成
     * 
     * @Route("/new", name="unsei_new")
     * @Method({"GET", "POST"})  // ②
     * 
     * @param Request $request
     * @return Response
     */
    public function newAction(Request $request)
    {
        return new Response('newAction');
    }
    
    /**
     * 運勢の編集
     * 
     * @Route("/{id}/edit", name="unsei_edit")
     * @Method({"GET", "PUT"})  // ③
     * 
     * @param Request $request
     * @return Response
     */
    public function editAction(Request $request, $id)
    {
        return new Response('editAction');
    }
    
    /**
     * 運勢の削除
     * 
     * @Route("/{id}", name="unsei_delete")
     * @Method("DELETE")  // ④
     * 
     * @param Request $request
     * @return Response
     */
    public function deleteAction(Request $request, $id)
    {
        return new Response('deleteAction');
    }
    ...
}

indexAction()だけ実装してあります。その他はまだ、中身は空です。

①②③④ @Methodアノテーションを使って、アクションメソッドにアクセスできるHTTPメソッドを制限してます。 (a)でMethodをインポートするのを忘れないよう注意してください。

ウェブアプリケーションではHTTPのメソッドとリソースの操作を以下のように対応付ける慣習があります。

HTTPメソッドリソースの操作
GET 取得
POST 追加
PUT 更新
DELETE削除

この時点のルートを確認してみましょう。MethodPathに注目してください。

$ php bin/console debug:router --show-controllers
 -------------- ---------- -------- ------ ------------------ ------------------------ 
  Name           Method     Scheme   Host   Path               Controller              
 -------------- ---------- -------- ------ ------------------ ------------------------ 
  ...
  unsei_index    GET        ANY      ANY    /unsei/            AppBundle:Unsei:index   
  unsei_new      GET|POST   ANY      ANY    /unsei/new         AppBundle:Unsei:new     
  unsei_edit     GET|PUT    ANY      ANY    /unsei/{id}/edit   AppBundle:Unsei:edit    
  unsei_delete   DELETE     ANY      ANY    /unsei/{id}        AppBundle:Unsei:delete  
 -------------- ---------- -------- ------ ------------------ ------------------------ 

次に運勢一覧のViewを作成します。

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

{% block title %}運勢一覧{% endblock %}

{% block body %}
    <table>
        <tr>
            <th>運勢</th>
            <th>操作</th>
        </tr>
        {% for unsei in unseis %}
        <tr>
            <td>{{ unsei.name }}</td>
            <td>
                {# ① #}
                <a href="{{ path('unsei_edit', {'id': unsei.id}) }}">編集</a>
                削除
            </td>
        </tr>
        {% endfor %}
    </table>

    <hr>

    {# ② #}
    <a href="{{ path('unsei_new') }}">新規追加</a>
{% endblock %}

① 運勢の編集処理へのリンクを記述しています。 path()を使って、リンク先のパスを生成します。 第1引数にはルート名を、第2引数にはルートパラメータのハッシュを渡します。 ここではunsei.idを渡しています。

render前: <a href="{{ path('unsei_edit', {'id': unsei.id}) }}">編集</a>
render後: <a href="/unsei/1/edit">編集</a>

② 運勢の新規作成処理へのリンクを記述しています。

render前: <a href="{{ path('unsei_new') }}">新規追加</a>
render後: <a href="/unsei/new">新規追加</a>

ここで動作確認を行います。http://localhost:8000/unsei にアクセスすると、運勢一覧が表示されます。

メニューの作成

次にbase.html.twigにメニューを追加します。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>{% block title %}Welcome!{% endblock %}</title>
        <link rel="stylesheet" href="/css/app.css" /> {# ① #}
        {% block stylesheets %}{% endblock %}
        <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
    </head>
    <body>
    	{% include '_menu.html.twig' %} {# ② #}

        {% block body %}{% endblock %}
        {% block javascripts %}{% endblock %}
    </body>
</html>

app.cssの適用を追加します。
bodyタグincludeを使って、_menu.html.twigを挿入します。

<ul class="menu">
	<li><a href="{{ path('omikuji') }}">おみくじ</a></li>
	<li><a href="{{ path('unsei_index') }}">管理</a></li>
</ul>

メニューの表示部分を独立したファイルにしました。 ファイル名はincludeをされる部分的なviewであることが分かりやすいように“_”アンダースコアを付けました。

最後にCSSファイルを追加します。

.menu {
    padding-left: 0;
}
.menu > li {
    display: inline-block;
}

webディレクトリの下にcssディレクトリを作成してapp.cssを追加します。
メニュー用のスタイルを定義しています。

ここで動作確認を行います。http://localhost:8000/unsei にアクセスすると、運勢一覧が表示されます。メニューも表示されるはずです。

Formの作成

運勢の新規作成アクションであるnewAction()を実装します。その中で、Formを作成します。

...
use Symfony\Component\Form\Extension\Core\Type\TextType; // (a)
...
class UnseiController extends Controller
{
    ...
    /**
     * 運勢の新規作成
     * 
     * @Route("/new", name="unsei_new")
     * @Method({"GET", "POST"}) 
     * 
     * @param Request $request
     * @return Response
     */ 
    public function newAction(Request $request)
    {
        $unsei = new Unsei(); // ①
        
        $form = $this->createFormBuilder($unsei) // ②
            ->add('name', TextType::class) // ③
            ->getForm(); // ④

        return $this->render('unsei/new.html.twig', [ // ⑤
            'form' => $form->createView()
        ]);
    }
    ...
}

① 内容が空のUnseiエンティティを作成します。

② FormBuilderを作成します。その時、Formに保持させるオブジェクトとしてUnseiオブジェクトを渡しています。

③ FormBuilderにInput Textフィールドを追加しています。

<input type="text" name="name" ...>

(a)のTextTypeのインポートを忘れないようにしてください。
使用できる項目型はこちらを参照してください。

④ FormBuilderからFormを生成します。

new.html.twig$form->createView()で作成したFormViewを渡しています。

Formの表示

次にnew.html.tiwgを追加します。

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

{% block title %}新規運勢{% endblock %}

{% block body %}
    <h1>新規運勢</h1>

    {# ① フォームの表示 #}
    {{ form_start(form) }}
        {{ form_widget(form) }}
        <input type="submit" value="追加" />
    {{ form_end(form) }}

    <hr>
    <a href="{{ path('unsei_index') }}">運勢一覧へ戻る</a>
{% endblock %}

① フォームを表示しています。
form_start()でformの開始タグを表示します。
form_widget()で入力項目とラベルを表示します。CSRF対策のトークンもhidden項目として自動で出力されます。
form_end()でフォームの終了タグを表示します。

ブラウザでHTMLをソースを確認すると以下のようになっているはずです。

<form name="form" method="post">
  <div id="form">
    <div>
      <label for="form_name" class="required">Name</label>
      <input type="text" id="form_name" name="form[name]" required="required" />
    </div>
    <input type="hidden" id="form__token" name="form[_token]" value="3fIEtiq9n8dnMyYPmRY1rZttekDf_EEE1_4KJts2H4o" />
  </div>
  <input type="submit" value="追加" />
</form>

formタグにaction属性を指定していないことに注意してください。追加ボタンを押すと現在のURLである“/unsei/new”にHTTPのPOSTメソッドでアクセスされ、再度UnseiControllernewActionが実行されます。

formタグでactionやmethod属性を指定したい場合は、下記のようにForm作成時にsetAction()setMethod()を使います。

$form = $this->createFormBuilder($unsei)
    ->setAction($this->generateUrl('unsei_new'))
    ->setMethod('POST')
    ->add('name', TextType::class)
    ->getForm();

// form_start() での出力は以下のようになります
// <form name="form" method="post" action="/unsei/new">

http://localhost:8000/unsei/new にアクセスして動作確認を行います。フォームが表示されるはずです。
フォームが表示されるだけで、まだ追加はできません。

Form送信のハンドリング

Formから送信されたデータのハンドリングを実装します。newAction()を修正します。

...
class UnseiController extends Controller
{
    ... 
    public function newAction(Request $request)
    {
        $unsei = new Unsei();
        
        $form = $this->createFormBuilder($unsei)
            ->add('name', TextType::class)
            ->getForm();

        // Form送信のハンドリング
        $form->handleRequest($request);                   // ①
        if ($form->isSubmitted() && $form->isValid()) {   // ②
            /** @var EntityManager $em */
            $em = $this->getDoctrine()->getManager();     // ③
            $em->persist($unsei);
            $em->flush();
            
            return $this->redirectToRoute('unsei_index'); // ④
        }

        return $this->render('unsei/new.html.twig', [
            'form' => $form->createView()
        ]);
    }
    ...
}

① FormオブジェクトにRequestを処理するように渡しています。Fromオブジェクトは送信データの有無をチェックして、送信データ(ここでは'name')が有れば、それを保持しているオブジェクト(ここではUnsei)にセットします。その時、オブジェクトのValidationチェックも行います。

Fromは内部でValidatorを使って保持しているオブジェクトのValidationチェックを実行しています。Validationチェックはオブジェクトに制約(constraints)を定義することで行われます。

前回やったValidationを覚えているでしょうか? Unseiクラスには既にNotBlankUniqueEntityの制約(constraints)が定義されてます。 運勢の新規作成で既に登録されている名前を入れるとエラーになります。

② Formオブジェクトに送信データがあったか、Validationチェックでエラーが無かったかを確認しています。

③ DBにUnseiエンティティを保存しています。

④ ルート名を指定して運勢一覧にリダイレクトしています。

http://localhost:8000/unsei/new にアクセスして動作確認を行います。フォームから新しい運勢を追加してみてください。 スペースを入力したり、既に存在する名前を入れて、エラーになるか確認してみてください。


Comments



41 +3 = ?
blog/2015-12-15/超入門_symfony3_form_前編.txt · 最終更新: 2015/12/23 08:17 by tsubo