6

最近、Doctrine 2.2 と Zend Framework 2 の一部を使用して、組織を改善し、重複を減らすなどの取り組みを開始しました。今日、私は、コントローラーと Doctrine エンティティーの間の仲介役として機能するサービス層を実装するためのアイデアを投げかけ始めました。

現在、ロジックの大部分はコントローラーにあります。さらに、アクション ヘルパーを使用して特定の権限をテストします。しかし、Zend\Di を実装した後、新しいアプローチを思い付きました。Zend\Di を使用して EntityManager インスタンスと現在のユーザーのアクセス許可を注入する、エンティティ固有のサービス モデルの作成を開始しました。

コントローラーのコードは次のとおりです。

class Project_DeleteController extends Webjawns_Controller_Action
{
    public function init()
    {
        $this->_initJsonContext();
    }

    public function indexAction()
    {
        $response = $this->_getAjaxResponse();

        $auditId = (int) $this->_getParam('audit_id');
        if (!$auditId) {
            throw new DomainException('Audit ID required');
        }

        /* @var $auditService Service\Audit */
        $auditService = $this->getDependencyInjector()->get('Service\Audit');

        try {
            $auditService->delete($auditId);
            $response->setStatusSuccess();
        } catch (Webjawns\Exception\SecurityException $e) {
            $this->_noAuth();
        } catch (Webjawns\Exception\Exception $e) {
            $response->setStatusFailure($e->getMessage());
        }

        $response->sendResponse();
    }
}

そして、サービス層の 1 つの例です。コンストラクターは 2 つのパラメーターを受け取ります。1 つは EntityManager を受け取り、もう 1 つは Zend\Di によって注入された Entity\UserAccess オブジェクトを受け取ります。

namespace Service;

use Webjawns\Service\Doctrine,
    Webjawns\Exception;

class Audit extends AbstractService
{
    public function delete($auditId)
    {
        // Only account admins can delete audits
        if (\Webjawns_Acl::ROLE_ACCT_ADMIN != $this->getUserAccess()->getAccessRole()) {
            throw new Exception\SecurityException('Only account administrators can delete audits');
        }

        $audit = $this->get($auditId);

        if ($audit->getAuditStatus() !== \Entity\Audit::STATUS_IN_PROGRESS) {
            throw new Exception\DomainException('Audits cannot be deleted once submitted for review');
        }

        $em = $this->getEntityManager();
        $em->remove($audit);
        $em->flush();
    }

    /**
     * @param integer $auditId
     * @return \Entity\Audit
     */
    public function get($auditId)
    {
        /* @var $audit \Entity\Audit */
        $audit = $this->getEntityManager()->find('Entity\Audit', $auditId);
        if (null === $audit) {
            throw new Exception\DomainException('Audit not found');
        }

        if ($audit->getAccount()->getAccountId() != $this->getUserAccess()->getAccount()->getAccountId()) {
            throw new Exception\SecurityException('User and audit accounts do not match');
        }

        return $audit;
    }
}
  1. これは、達成しようとしていることに使用する適切なパターンですか?
  2. 掲載されているように、サービス レイヤー内でアクセス許可の検証を行うことをお勧めしますか?
  3. 私が理解しているように、ビュー ロジックは引き続きコントローラーに存在し、さまざまなコンテキスト (JSON、XML、HTML など) でモデルを柔軟に使用できるようにします。考え?

これまでのやり方には満足していますが、私たちのやり方にマイナス面を見つけた人は、あなたの考えを投稿してください.

4

1 に答える 1

1

私はあなたがここでやっていることを気に入っています。あなたの関心の分離は良いと思います。カスタムリポジトリを使用して、さらに一歩進んで実験しています。たとえば、標準モデル/サービス メソッドは次のようになります。

public function findAll($sort = null)
{
    if (!$sort) $sort = array('name' => 'asc');
    return $this->getEm()->getRepository('Application\Entity\PartType')
                ->findAll($sort);

}

... DQL を必要とするものをリポジトリに追加して、すべての DQL をモデルから除外します。例:

public function findAllProducts($sort = null)
{
    if (!$sort) $sort = array('name' => 'asc');
    return $this->getEm()->getRepository('Application\Entity\PartType')
                ->findAllProducts($sort);

}

上記のモデルの場合、リポジトリ クラスは次のようになります。

<?php
namespace Application\Repository;

use Application\Entity\PartType;
use Doctrine\ORM\EntityRepository;

class PartTypeRepository extends EntityRepository
{

    public function findAllProducts($order=NULL)
    {
        return $this->_em->createQuery(
                    "SELECT p FROM Application\Entity\PartType p 
                        WHERE p.productGroup IS NOT NULL 
                        ORDER BY p.name"
               )->getResult();
    }

}

Doctrine\ORM\EntityRepository を単純に拡張したことに注意してください。つまり、すべての標準 Doctrine リポジトリ メソッドを再定義する必要はありませんが、必要に応じてそれらをオーバーライドしたり、独自のカスタム メソッドを追加したりできます。

したがって、アクセス制御に関しては、リポジトリからサービスのビジネス ロジックにアクセスすることで、ID ベースの制約やその他のレコード レベルの条件を非常に低いレベルで追加することができます。このようにすることで、サービスは実装を認識しません。アプリの他の部分に DQL を配置しないように厳密に定めている限り、リポジトリを介してデータベースにアクセスするすべてのクラスに対して、レコード レベルのビジネス制約を達成できます。(アプリの上位レベルのカスタム DQL に注意してください)。

例:

    public function findAll($order=NULL)
    {
        // assumes PHP 5.4 for trait to reduce boilerplate locator code
        use authService;

        if($this->hasIdentity()) {
            return $this->_em->createQuery(
                        "SELECT p FROM Application\Entity\PartType p 
                            JOIN p.assignments a 
                            WHERE a.id = " . $this->getIdentity()->getId()
                   )->getResult();
        } else {
            return NULL;
        }
    }
于 2012-11-10T20:07:44.473 に答える