服务层中的数据访问和安全性(Doctrine& ZF)

时间:2012-04-27 23:28:16

标签: php model-view-controller doctrine-orm zend-framework2 service-layer

我们最近开始使用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 个答案:

答案 0 :(得分: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;
        }
    }