Skip to content

Commit

Permalink
Fixes #10 - Refactoring of SimpleCascadeStrategy into smaller classes…
Browse files Browse the repository at this point in the history
…, better separation of concerns, and more testable

Added tests too
  • Loading branch information
mnapoli committed Jun 18, 2014
1 parent 3f91c23 commit 6013a80
Show file tree
Hide file tree
Showing 7 changed files with 434 additions and 92 deletions.
105 changes: 14 additions & 91 deletions src/CascadeStrategy/SimpleCascadeStrategy.php
Expand Up @@ -2,13 +2,13 @@

namespace MyCLabs\ACL\CascadeStrategy;

use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\EntityManager;
use MyCLabs\ACL\Model\Authorization;
use MyCLabs\ACL\Model\CascadingResource;
use MyCLabs\ACL\Model\ResourceInterface;
use MyCLabs\ACL\Repository\AuthorizationRepository;
use MyCLabs\ACL\ResourceGraph\CascadingResourceGraphTraverser;
use MyCLabs\ACL\ResourceGraph\ResourceGraphTraverser;
use MyCLabs\ACL\ResourceGraph\ResourceGraphTraverserDispatcher;

/**
* Simple cascade: authorizations are cascaded from a resource to its sub-resources.
Expand All @@ -23,31 +23,28 @@ class SimpleCascadeStrategy implements CascadeStrategy
private $entityManager;

/**
* @var ResourceGraphTraverser[]
* @var ResourceGraphTraverserDispatcher
*/
private $resourceGraphTraversers = [];
private $resourceGraphTraverser;

public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;

$this->resourceGraphTraverser = new ResourceGraphTraverserDispatcher();
// Default traverser for CascadingResource
$this->resourceGraphTraverser->setResourceGraphTraverser(
'MyCLabs\ACL\Model\CascadingResource',
new CascadingResourceGraphTraverser($entityManager, $this->resourceGraphTraverser)
);
}

/**
* {@inheritdoc}
*/
public function cascadeAuthorization(Authorization $authorization, ResourceInterface $resource)
{
// Find sub-resources
$subResources = [];
if ($resource instanceof CascadingResource) {
$subResources = $this->getAllSubResources($resource);
} else {
$traverser = $this->getResourceGraphTraverser(ClassUtils::getClass($resource));

if ($traverser) {
$subResources = $traverser->getAllSubResources($resource);
}
}
$subResources = $this->resourceGraphTraverser->getAllSubResources($resource);

// Cascade authorizations
$authorizations = [];
Expand All @@ -66,17 +63,7 @@ public function processNewResource(ResourceInterface $resource)
/** @var AuthorizationRepository $repository */
$repository = $this->entityManager->getRepository('MyCLabs\ACL\Model\Authorization');

// Find parent resources
$parentResources = [];
if ($resource instanceof CascadingResource) {
$parentResources = $this->getAllParentResources($resource);
} else {
$traverser = $this->getResourceGraphTraverser(ClassUtils::getClass($resource));

if ($traverser) {
$parentResources = $traverser->getAllParentResources($resource);
}
}
$parentResources = $this->resourceGraphTraverser->getAllParentResources($resource);

// Find root authorizations on the parent resources
$authorizationsToCascade = [];
Expand All @@ -97,76 +84,12 @@ public function processNewResource(ResourceInterface $resource)
return $authorizations;
}

/**
* Get all parent resources recursively.
* @param CascadingResource $resource
* @return ResourceInterface[]
*/
private function getAllParentResources(CascadingResource $resource)
{
$parents = [];

foreach ($resource->getParentResources($this->entityManager) as $parentResource) {
$parents[] = $parentResource;
if ($parentResource instanceof CascadingResource) {
$parents = array_merge($parents, $this->getAllParentResources($parentResource));
}
}

return $this->unique($parents);
}

/**
* Get all sub-resources recursively.
* @param CascadingResource $resource
* @return ResourceInterface[]
*/
private function getAllSubResources(CascadingResource $resource)
{
$subResources = [];

foreach ($resource->getSubResources($this->entityManager) as $subResource) {
$subResources[] = $subResource;
if ($subResource instanceof CascadingResource) {
$subResources = array_merge($subResources, $this->getAllSubResources($subResource));
}
}

return $this->unique($subResources);
}

private function unique(array $array)
{
$result = [];

foreach ($array as $item) {
if (! in_array($item, $result, true)) {
$result[] = $item;
}
}

return $result;
}

/**
* @param string $entityClass
* @param ResourceGraphTraverser $resourceGraphTraverser
*/
public function setResourceGraphTraverser($entityClass, $resourceGraphTraverser)
{
$this->resourceGraphTraversers[$entityClass] = $resourceGraphTraverser;
}

/**
* @param string $entityClass
* @return ResourceGraphTraverser|null
*/
private function getResourceGraphTraverser($entityClass)
{
if (isset($this->resourceGraphTraversers[$entityClass])) {
return $this->resourceGraphTraversers[$entityClass];
}

return null;
$this->resourceGraphTraverser->setResourceGraphTraverser($entityClass, $resourceGraphTraverser);
}
}
108 changes: 108 additions & 0 deletions src/ResourceGraph/CascadingResourceGraphTraverser.php
@@ -0,0 +1,108 @@
<?php

namespace MyCLabs\ACL\ResourceGraph;

use Doctrine\ORM\EntityManager;
use MyCLabs\ACL\Model\CascadingResource;
use MyCLabs\ACL\Model\ResourceInterface;

/**
* Traverser for resources implementing CascadingResource.
*
* CascadingResource don't return all sub-resources (only the direct ones), so we need to do
* the traversal recursively in this class.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class CascadingResourceGraphTraverser implements ResourceGraphTraverser
{
/**
* @var EntityManager
*/
private $entityManager;

/**
* @var ResourceGraphTraverser
*/
private $parentTraverser;

/**
* @param EntityManager $entityManager
* @param ResourceGraphTraverser $parentTraverser We need the parent traverser to use it
* to recursively traverse resources. This is because CascadingResource returns
* returns only its direct parent and sub-resources.
*/
public function __construct(EntityManager $entityManager, ResourceGraphTraverser $parentTraverser)
{
$this->entityManager = $entityManager;
$this->parentTraverser = $parentTraverser;
}

/**
* {@inheritdoc}
*/
public function getAllParentResources(ResourceInterface $resource)
{
if (! $resource instanceof CascadingResource) {
return [];
}

$parentResources = [];

foreach ($resource->getParentResources($this->entityManager) as $parentResource) {
$parentResources[] = $parentResource;

// Recursively get its sub-resources
$parentResources = array_merge(
$parentResources,
$this->parentTraverser->getAllParentResources($parentResource)
);
}

return $this->unique($parentResources);
}

/**
* {@inheritdoc}
*/
public function getAllSubResources(ResourceInterface $resource)
{
if (! $resource instanceof CascadingResource) {
return [];
}

$subResources = [];

foreach ($resource->getSubResources($this->entityManager) as $subResource) {
$subResources[] = $subResource;

// Recursively get its sub-resources
$subResources = array_merge(
$subResources,
$this->parentTraverser->getAllSubResources($subResource)
);
}

return $this->unique($subResources);
}

/**
* Array unique but with objects.
*
* @param array $array
*
* @return array
*/
private function unique(array $array)
{
$result = [];

foreach ($array as $item) {
if (! in_array($item, $result, true)) {
$result[] = $item;
}
}

return $result;
}
}
76 changes: 76 additions & 0 deletions src/ResourceGraph/ResourceGraphTraverserDispatcher.php
@@ -0,0 +1,76 @@
<?php

namespace MyCLabs\ACL\ResourceGraph;

use Doctrine\Common\Util\ClassUtils;
use MyCLabs\ACL\Model\ResourceInterface;

/**
* This is a traverser that dispatches to other traversers based on the resource class.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ResourceGraphTraverserDispatcher implements ResourceGraphTraverser
{
/**
* @var ResourceGraphTraverser[]
*/
private $traversers = [];

/**
* {@inheritdoc}
*/
public function getAllParentResources(ResourceInterface $resource)
{
$traverser = $this->getResourceGraphTraverser($resource);
if (!$traverser) {
return [];
}

return $traverser->getAllParentResources($resource);
}

/**
* {@inheritdoc}
*/
public function getAllSubResources(ResourceInterface $resource)
{
$traverser = $this->getResourceGraphTraverser($resource);
if (!$traverser) {
return [];
}

return $traverser->getAllSubResources($resource);
}

/**
* @param string $entityClass
* @param ResourceGraphTraverser $resourceGraphTraverser
*/
public function setResourceGraphTraverser($entityClass, ResourceGraphTraverser $resourceGraphTraverser)
{
$this->traversers[$entityClass] = $resourceGraphTraverser;
}

/**
* @param object $resource
* @return ResourceGraphTraverser|null
*/
private function getResourceGraphTraverser($resource)
{
$entityClass = ClassUtils::getClass($resource);

if (isset($this->traversers[$entityClass])) {
return $this->traversers[$entityClass];
}

// We also try using instanceof so that we cover inheritance and interfaces
foreach ($this->traversers as $class => $traverser) {
if ($resource instanceof $class) {
return $traverser;
}
}

return null;
}
}
14 changes: 13 additions & 1 deletion tests/Integration/Issues/Issue10/Item.php
Expand Up @@ -2,13 +2,15 @@

namespace Tests\MyCLabs\ACL\Integration\Issues\Issue10;

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping as ORM;
use MyCLabs\ACL\Model\CascadingResource;
use MyCLabs\ACL\Model\EntityResource;

/**
* @ORM\Entity
*/
class Item implements EntityResource
class Item implements EntityResource, CascadingResource
{
/**
* @ORM\Id @ORM\GeneratedValue
Expand All @@ -31,4 +33,14 @@ public function getId()
{
return $this->id;
}

public function getParentResources(EntityManager $entityManager)
{
return [ $this->project ];
}

public function getSubResources(EntityManager $entityManager)
{
return [];
}
}

0 comments on commit 6013a80

Please sign in to comment.