【问题标题】:Data access and security in service layer (Doctrine & ZF)服务层中的数据访问和安全性(Doctrine & ZF)
【发布时间】:2012-05-08 16:54:02
【问题描述】:

我们最近开始使用 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 等)。想法?

到目前为止,我对这种工作方式感到满意,但如果有人看到我们这样做的任何缺点,请发表您的想法。

【问题讨论】:

  • 只是我的两便士认证。我不相信有正确的方法和错误的方法,但是,我开始将身份验证放入服务层,然后将其移至我的控制器中。我的理由是服务层是我的内部 API,我应该使用我的控制器层向世界公开它,因此它应该决定谁可以访问什么。此外,如果我想构建任何内部工具/脚本等,我不需要在它们中构建身份验证来使用我的服务层。
  • 注意不要混淆身份验证和访问控制。在将任何域类带入图片之前,身份验证可以(应该?)进入模块。只有在您建立了用户身份之后。示例:如果 (!$authService->hasIdentity()) 在您的域服务模型中。
  • @JamieSutherland:我不同意。服务定义业务逻辑;控制器是请求和相应业务逻辑之间的桥梁。例如,您可能只有一个用于订购产品的服务,但您可能有多个用于 HTTP 请求、API 请求等的控制器。如果您担心 ACL 会请求特定(例如,通过 HTTP,您可能期望用户会话,而您期望 API 请求的密钥),请概括您的 ACL 实现以允许这样做。跨度>

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


【解决方案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;
        }
    }

【讨论】:

  • 我强烈反对将访问控制代码放在存储库中。存储库不应该对应用程序的业务逻辑一无所知。它应该只关心检索数据。业务逻辑用于更高级别的服务和类。
  • 我认为从 Doctrine 实现中抽象存储库也是一个好主意。使用组合(默认的 Doctrine 存储库类)而不是继承。这与对接口的编码相结合,使您可以轻松地交换部分或全部存储库中的数据源(我喜欢称之为“映射器”,以避免与 Doctrine 存储库混淆)。
猜你喜欢
  • 1970-01-01
  • 2020-11-28
  • 1970-01-01
  • 1970-01-01
  • 2011-03-26
  • 2010-09-13
  • 2011-02-17
  • 1970-01-01
  • 2018-08-27
相关资源
最近更新 更多