We recently started using Doctrine 2.2, and parts of Zend Framework 2 in an effort to improve organization, reduce duplication, among other things. Today, I started throwing around ideas for implementing a service layer to act as a intermediary between our controllers and Doctrine entities.
Right now, the majority of our logic resides in the controller. In addition, we use an action helper to test for certain permissions; however, I came up with a new approach after implementing Zend\Di. I started creating entity-specific service models, which use Zend\Di to inject an EntityManager instance, and the current user's permissions.
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 layers. The constructor takes two parameters--one takes the EntityManager, and the other an Entity\UserAccess object--injected by 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;
}
}
- Is this an appropriate pattern to use for what we are trying to accomplish?
- Is it good practice to have the permissions validation within the service layer as posted?
- As I understand it, view logic still resides in the controller, giving the model flexibility to be used in various contexts (JSON, XML, HTML, etc.). Thoughts?
I'm happy with the way this works so far, but if anyone sees any downside to how we are doing this, please post your thoughts.