2011年11月7日月曜日

[symfony2]ユーザーとかのフォームとかコントローラーとか

少し間が開きましたが、認証関連のフォームを作ってみました。

1.UserとRoleのフォームを作る
それぞれ以下のコマンドを実行します。
php app/console doctrine:generate:crud --entity=AcmeSecurityBundle:User --route-prefix=AcmeSecrityBundle_user --with-write
php app/console doctrine:generate:crud --entity=AcmeSecurityBundle:Role --route-prefix=AcmeSecrityBundle_role --with-write

「doctrine:generate:crud」は指定したエンティティに対して、新規追加、一覧、編集、詳細のフォームを作るコマンドみたいです。
--entity 作成するフォームのエンティティを指定
--route-prefix ルーターの接頭辞
--with-write 一緒にnewとdeleteアクションを作ってくれる?
フォーマットの指定はアノテーションでやりました。

するとsymfonyが自動で入力フォームをサクッと作ってくれました。
できたものを確認すると、多分以下ファイルが出来てました。
src/Acme/SecurityBundle/Controller/UserController.php
src/Acme/SecurityBundle/Form/UserType.php
src/Acme/SecurityBundle/Resources/views/User/edit.html.twig
src/Acme/SecurityBundle/Resources/views/User/index.html.twig
src/Acme/SecurityBundle/Resources/views/User/new.html.twig
src/Acme/SecurityBundle/Resources/views/User/show.html.twig
*Roleも同じものが出来てました。

早速ブラウザで「ect.com/web/app_dev.php/user/」にアクセスすると一覧が出てました。
2.Role.phpの改造
そのまま新規追加してみようと思いまして、新規追加するリンクをクリックしたらエラーが出ました。
Roleエンティティに__toString()メソッドを作ってくれない?みたいな感じです。
多分ユーザーの権限の入力フォームがselectタグで、そこに表示する用の文字列を吐き出せば良いかなと当たりをつけて以下のコードを追加してみました。
# src/Acme/SecurityBundle/Entity/Role.php
public function __tostring()
 {
  return $this->name;
 }

再読込みをしてみると無事表示されました。

3.パスワードをハッシュ化する用のユーティリティクラス
このままだと、パスワードは平文のままデータベースに保存されます。それだと色々とまずいのでハッシュ化する機能を追加する。
パスワードをハッシュ化するようなユーティリティクラスを作る。前に機能拡張した時のコメントと日本symfonyユーザー会のセキュリティ パスワードのエンコードを参考に以下の様に作ってみました。新しくUtilディレクトリを作って、クラス名はまあ適当な感じです。
# src/Acme/SecurityBundle/Util/PasswordUtil.php
namespace Acme\SecurityBundle\Util;

class PasswordUtil
{
 /**
  *
  * パスワードをハッシュ化する一連の処理を行う。
  * @param From $form
  * @param Controller $controller
  * @return Form $form
  */
 public static function encodePasswordUtil($form, $controller)
 {
  //まず下準備
  $factory = $controller->get('security.encoder_factory');
  //$formからエンティティを取り出す
  $data = $form->getData();
  //下準備2
  $encoder = $factory->getEncoder($data);
  //パスワードをハッシュ値にする
  $hashedPassword = $encoder->encodePassword($data->getPassword(), $data->getSalt());
  //エンティティにハッシュ化したパスワードをセットする。
  $data->setPassword($hashedPassword);
  //$formにエンティティをセットする
  $form->setData($data);
  return $form;
 }

 // 文字列がハッシュか確認する。
 // @param $password フォームの内容が入っているリクエスト。
 // @return bool 長さ40で英数字の場合はtrue
 public static function isHashUtil($password)
 {
  $pattern = '/[a-zA-z0-9]{40}/i';
  if(strlen($password)===40 && preg_match($pattern, $password)){
   return true;
  }else{
   return false;
  }
 }
}


4.パスワードをハッシュ化するために変更したユーザーコントローラー
続いてユーザーコントローラー
# src/Acme/SecurityBundle/Controller/UserController.php

...
use Acme\SecurityBundle\Util\PasswordUtil;

/**
* User controller.
*
* @Route("/admin/user")
*/
class UserController extends Controller
{
....
 /**
  * Creates a new User entity.
  *
  * @Route("/create", name="AcmeSecurityBundle_user_create")
  * @Method("post")
  * @Template("AcmeSecurityBundle:User:new.html.twig")
  */
 public function createAction()
 {
  $entity = new User();
  $request = $this->getRequest();
  $form = $this->createForm(new UserType(), $entity);
  $form->bindRequest($request);

  if ($form->isValid()) {
   //パスワードをハッシュ化した$formを取得
   $form = PasswordUtil::encodePasswordUtil($form, $this);
   $em = $this->getDoctrine()->getEntityManager();
   $em->persist($entity);
   $em->flush();
   return $this->redirect($this->generateUrl('AcmeSecurityBundle_user_show', array('id' => $entity->getId())));
  }
  return array(
   'entity' => $entity,
   'form' => $form->createView()
  );
 }
...
 /**
  * Edits an existing User entity.
  *
  * @Route("/{id}/update", name="AcmeSecurityBundle_user_update")
  * @Method("post")
  * @Template("AcmeSecurityBundle:User:edit.html.twig")
  */
 public function updateAction($id)
 {
  $em = $this->getDoctrine()->getEntityManager();
  $entity = $em->getRepository('AcmeSecurityBundle:User')->find($id);
  if (!$entity) {
   throw $this->createNotFoundException('Unable to find User entity.');
  }
  $editForm = $this->createForm(new UserType(), $entity);
  $deleteForm = $this->createDeleteForm($id);
  $request = $this->getRequest();
  $editForm->bindRequest($request);
  if ($editForm->isValid()) {
   //パスワードがハッシュ値ならそのままupdate、
   //ハッシュ値で無ければ恐らく変更があったということなので、ハッシュ化してupdate
   //そんな訳で取り敢えずフォームのパスワードを取得
   $data = $editForm->getData();
   $password = $data->getPassword();
   //パスワードがハッシュ化されているか調べる
   if(!PasswordUtil::isHashUtil($password)){
    //パスワードをハッシュ化したFormを受け取る
    $editForm = PasswordUtil::encodePasswordUtil($editForm, $this);
   }
   $em->persist($entity);
   $em->flush();
   return $this->redirect($this->generateUrl('AcmeSecurityBundle_user_edit', array('id' => $id)));
  }

  return array(
   'entity'  => $entity,
   'edit_form' => $editForm->createView(),
   'delete_form' => $deleteForm->createView(),
  );
 }
}


5.ちょっとした解説
PasswordUtil::encodePasswordUtil()関数の引数$formはUserコントローラー内で出てくる以下の2つみたいなヤツです。
$form = $this->createForm(new UserType(), $entity);
$editForm = $this->createForm(new UserType(), $entity);

もう一つの引数$controllerはUserコントローラー自身($this)です。
PasswordUtil::encodePasswordUtil()関数の流れを少し説明すると、「$data = $form->getData();」でエンティティが取得できます。つまりUser.phpです。なので「$data->getPassword()」とか「$data->getSalt()」でフォームに入力された値が取得できます。
これを順調にハッシュ化していって、「$form->setData($data);」でフォームに設定し直して返します。

もう一つのPasswordUtil::isHashUtil()関数はupdateアクションの時に利用しています。ハッシュ値がどうかを検証してます。

UserControllerは自動で出来たコードから変更した箇所は
  1. use Acme\SecurityBundle\Util\PasswordUtil;を追加
  2. createAction()にパスワードがハッシュ化するように色々追加
  3. updateAction()にフォームのパスワードがハッシュ値ではない場合にハッシュ化する処理を色々と追加
updateAction()の方法はあまり良くないですが、一応これでおいておく。
これでひと通りはOKかなと思います。

次はフォームの表示を変更していく予定です。

0 件のコメント:

コメントを投稿