Data Access and Service Level Security (Doctrine & ZF)

We recently started using Doctrine 2.2 and parts of Zend Framework 2 to improve organization, reduce duplication, among other things. Today I started throwing ideas for implementing a service level, to act as an intermediary between our controllers and Doctrine objects.

Currently, most of our logic is in the controller. In addition, we use the action assistant to check for specific permissions; however, I came up with a new approach after implementing Zend \ Di. I started creating models for specific objects that use Zend \ Di to enter an EntityManager instance and current user rights.

The controller code is as follows:

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

And an example of one of our service levels. The constructor takes two parameters: one accepts the EntityManager, and the other Entity \ UserAccess object - Zend \ Di is entered.

 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; } } 
  • Is this template suitable for what we are trying to accomplish?
  • Is it good to have permission checks within the service level that is published?
  • As I understand it, the view logic is still in the controller, which allows us to use the flexibility of the model in various contexts (JSON, XML, HTML, etc.). Thoughts?

I am pleased with how it works so far, but if someone sees a flaw in the way we do this, please write your thoughts.

+6
source share
1 answer

I love what you are doing here, and I think your separation of concerns is good. We are experimenting with taking this one step further using custom repositories. For example, if a standard service model / method might look like this:

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

... we add things that require DQL to the repository to leave all the DQLs from the models, for example:

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

In the above model, the repository class is as follows:

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

Note that we simply extended Doctrine \ ORM \ EntityRepository, which means that we don’t need to redefine all the standard Doctrine repository methods, but we can redefine them if necessary, and we can add our own ones.

Thus, with regard to access control, this gives us the opportunity to add restrictions based on identification or other conditions of the recording level at a very low level by accessing the business logic in your services from the repository. By doing this this way, services are not aware of the implementation. As long as we strictly talk about not putting DQL in other parts of the application, we can achieve business restrictions at the record level for any class that accesses the database through the repository. (Beware of custom DQL at higher levels of the application).

Example:

  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; } } 
+1
source

Source: https://habr.com/ru/post/914388/


All Articles