php - utilizando - estilo arquitectonico en capas
Acceso a los datos y seguridad en la capa de servicio(Doctrine & ZF) (1)
Me gusta lo que estás haciendo aquí, y creo que tu separación de preocupaciones es buena. Estamos experimentando con ir un paso más allá, utilizando repositorios personalizados. Entonces, por ejemplo, donde un método estándar de modelo / servicio puede ser así:
public function findAll($sort = null)
{
if (!$sort) $sort = array(''name'' => ''asc'');
return $this->getEm()->getRepository(''Application/Entity/PartType'')
->findAll($sort);
}
... estamos agregando cosas que requieren DQL al repositorio, para mantener todos los DQL fuera de los modelos, por ejemplo:
public function findAllProducts($sort = null)
{
if (!$sort) $sort = array(''name'' => ''asc'');
return $this->getEm()->getRepository(''Application/Entity/PartType'')
->findAllProducts($sort);
}
Para el modelo anterior, la clase de repositorio se ve así:
<?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();
}
}
Tenga en cuenta que simplemente hemos ampliado Doctrine / ORM / EntityRepository, lo que significa que no tenemos que volver a definir todos los métodos estándar del repositorio de Doctrine, pero podemos reemplazarlos si es necesario y podemos agregar nuestros propios personalizados.
Por lo tanto, con respecto al control de acceso, nos brinda la posibilidad de agregar restricciones basadas en la identidad u otras condiciones de nivel de registro a un nivel muy bajo, accediendo a la lógica comercial en sus servicios desde el Repositorio. Al hacerlo de esta manera, los servicios desconocen la implementación. Mientras seamos estrictos acerca de no poner DQL en otras partes de la aplicación, podemos lograr restricciones comerciales de nivel récord para cualquier clase que acceda a la base de datos a través del repositorio. (Tenga cuidado con el DQL personalizado en niveles superiores de la aplicación).
Ejemplo:
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;
}
}
Recientemente comenzamos a usar Doctrine 2.2 y partes de Zend Framework 2 en un esfuerzo por mejorar la organización, reducir la duplicación, entre otras cosas. Hoy, comencé a lanzar ideas para implementar una capa de servicio para actuar como intermediario entre nuestros controladores y las entidades de Doctrine.
En este momento, la mayoría de nuestra lógica reside en el controlador. Además, usamos un asistente de acción para probar ciertos permisos; sin embargo, se me ocurrió un nuevo enfoque después de implementar Zend / Di. Empecé a crear modelos de servicio específicos de la entidad, que usan Zend / Di para inyectar una instancia de EntityManager y los permisos del usuario actual.
El código del controlador es el siguiente:
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();
}
}
Y un ejemplo de una de nuestras capas de servicio. El constructor toma dos parámetros: uno toma el EntityManager y el otro un objeto Entity / UserAccess, inyectado por 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;
}
}
- ¿Es este un patrón apropiado para usar para lo que estamos tratando de lograr?
- ¿Es una buena práctica tener la validación de permisos dentro de la capa de servicio como publicada?
- Tal como lo entiendo, la lógica de visualización aún reside en el controlador, lo que le da flexibilidad al modelo para ser utilizado en varios contextos (JSON, XML, HTML, etc.). ¿Pensamientos?
Estoy contento con la forma en que esto funciona hasta ahora, pero si alguien ve alguna desventaja de cómo lo estamos haciendo, por favor publique sus pensamientos.