Symfony2でビジネスロジックを大量にコントローラに書かない構成
気がつくとコントローラに大量のビジネスロジックが含まれるようなプロジェクトがよくあるけれども、複数の場所に同じロジックが存在していたりコントローラの見通しが悪くなったりするので望ましい状況ではない。そんな状況に陥らないようにするための構成。
EntityRepositoryを使う
以下のようなEntityクラスを仮定する。
<?php // Entityクラス namespace Foo\BarBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Foo\BarBundle\Repository\UserRepository; /** * @ORM\Entity * @ORM\Table(name="user_table") * @ORM\Entity(repositoryClass="Foo\BarBundle\Repository\UserRepository") */ class User{ /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(type="boolean") */ private $locked = false; /** * @ORM\Column(type="boolean") */ private $enabled = true; // ゲッタ、セッタ面倒なので書くの省きます。以下にあると思ってください。 // ... }
有効なユーザ(lockedがfalse, enabledがtrue)を探すには、以下のようなコードになる。
$dql = <<<DQL SELECT u FROM FooBarBundle:User u WHERE u.locked = false AND u.enabled = true ORDER BY u.id DQL; $users = $em->createQuery($dql) ->getResult(); // もしくは $users = $em->getRepository('FooBarBundle:User u') ->findBy(array( 'locked' => false, 'enabled' => true, ));
これぐらいだとコントローラに書いてしまってもいいかな、という程度の条件だけど、もっと条件が複雑になってきたときにいろんなところに毎回同じ処理を書きたくはないし、コントローラに複雑な処理を書くのはいかがなものかと思う。そこで、共通の処理をくくりだせるようになっているのがRepositoryクラス。
上記のEntityクラスに含まれるアノテーション@ORM\Entity(repositoryClass="Foo\BarBundle\Repository\UserRepository")がRepositoryクラスの指定。
下記のような感じでRepositoryクラスを定義。
<?php // Repositoryクラス namespace Foo\BarBundle\Repository; use Doctrine\ORM\EntityRepository; use Foo\BarBundle\Entity\User; class UserRepository extends EntityRepository { public function createSearchEnabledUsersQueryBuilder() { $dql = <<<DQL SELECT u FROM FooBarBundle:User u WHERE u.locked = false AND u.enabled = true ORDER BY u.id DQL; return $this->getEntityManager() ->createQuery($dql); } public function searchEnabledUsers() { return $this->createSearchEnabledUsersQueryBuilder() ->getResult(); } }
これで、以下のようにsearchEnabledUsersを呼び出すことができるようになる。
$users = $em->getRepository('FooBarBundle:User u') ->searchEnabledUsers();
ビジネスロジックを書くためのサービスを定義する
たとえば複数のテーブルからデータを取得して、それを使ってinsertしたり外部サービスに送信したり、などというような複雑な処理があるとして、それを書くための場所は標準構成では特に定められていないけれど、Modelを定義するための場所を独自に作って、そこに書いてしまうのがいいのかな、と思ってる。たとえば、Foo\BarBundle\Manager以下にUserManagerを定義してみる。
<?php namespace Foo\BarBundle\Manager; use Foo\BarBundle\Entity\User; class UserManager { private $encoderFactory; private $entityManager; public function setEncoder($encoderFactory) { $this->encoderFactory = $encoderFactory; } public function setEntityManager($entityManager) { $this->entityManager = $entityManager; } public static function get($encoderFactory, $entityManager) { $obj = new UserManager(); $obj->setEncoderFactory($encoderFactory); $obj->setEntityManager($entityManager); return $obj; } public function registerUser($info) { $user = new User(); // なんかいろいろする $user->setSalt(md5(rand(10000,99999).$info->username)); $encoder = $this->encoderFactory->getUser($user); $user->setPassword($encoder->encodePassword($info->password, $user->getSalt())); // ... $this->em->persist($user); $this->em->flush(); return $user; } }
これを使えるようにするためには、Resources/config/services.ymlにサービス登録の設定を追加する必要がある。
parameters: foo_bar.user_manager.class: Foo\BarBundle\Manager\UserManager services: foo_bar.user_manager: class: %foo_bar.user_manager.class% factory_class: %foo_bar.user_manager.class% factory_method: get arguments: encoder: @security.encoder_factory entityManager: @doctrine.orm.entity_manager
コントローラから使うときは以下のような感じで。
// ユーザ登録 $userManager = $this->get('foo_bar.user_manager'); $user = $userManager->registerUser($data);