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();
    }
}

以及我们的服务层之一的示例。构造函数接受两个参数——一个接受 EntityManager,另一个接受一个 Entity\UserAccess 对象——由 Zend\Di 注入。

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 存储库方法,但如果需要,我们可以覆盖它们,并且可以添加我们自己的自定义方法。

因此,关于访问控制,它使我们能够通过从存储库访问服务中的业务逻辑,在非常低的级别添加基于身份的约束或其他记录级别的条件。通过这种方式,服务不知道实现。只要我们严格不将 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 回答