Skip to content

Commit 6013a80

Browse files
committed
Fixes #10 - Refactoring of SimpleCascadeStrategy into smaller classes, better separation of concerns, and more testable
Added tests too
1 parent 3f91c23 commit 6013a80

File tree

7 files changed

+434
-92
lines changed

7 files changed

+434
-92
lines changed

src/CascadeStrategy/SimpleCascadeStrategy.php

Lines changed: 14 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
namespace MyCLabs\ACL\CascadeStrategy;
44

5-
use Doctrine\Common\Util\ClassUtils;
65
use Doctrine\ORM\EntityManager;
76
use MyCLabs\ACL\Model\Authorization;
8-
use MyCLabs\ACL\Model\CascadingResource;
97
use MyCLabs\ACL\Model\ResourceInterface;
108
use MyCLabs\ACL\Repository\AuthorizationRepository;
9+
use MyCLabs\ACL\ResourceGraph\CascadingResourceGraphTraverser;
1110
use MyCLabs\ACL\ResourceGraph\ResourceGraphTraverser;
11+
use MyCLabs\ACL\ResourceGraph\ResourceGraphTraverserDispatcher;
1212

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

2525
/**
26-
* @var ResourceGraphTraverser[]
26+
* @var ResourceGraphTraverserDispatcher
2727
*/
28-
private $resourceGraphTraversers = [];
28+
private $resourceGraphTraverser;
2929

3030
public function __construct(EntityManager $entityManager)
3131
{
3232
$this->entityManager = $entityManager;
33+
34+
$this->resourceGraphTraverser = new ResourceGraphTraverserDispatcher();
35+
// Default traverser for CascadingResource
36+
$this->resourceGraphTraverser->setResourceGraphTraverser(
37+
'MyCLabs\ACL\Model\CascadingResource',
38+
new CascadingResourceGraphTraverser($entityManager, $this->resourceGraphTraverser)
39+
);
3340
}
3441

3542
/**
3643
* {@inheritdoc}
3744
*/
3845
public function cascadeAuthorization(Authorization $authorization, ResourceInterface $resource)
3946
{
40-
// Find sub-resources
41-
$subResources = [];
42-
if ($resource instanceof CascadingResource) {
43-
$subResources = $this->getAllSubResources($resource);
44-
} else {
45-
$traverser = $this->getResourceGraphTraverser(ClassUtils::getClass($resource));
46-
47-
if ($traverser) {
48-
$subResources = $traverser->getAllSubResources($resource);
49-
}
50-
}
47+
$subResources = $this->resourceGraphTraverser->getAllSubResources($resource);
5148

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

69-
// Find parent resources
70-
$parentResources = [];
71-
if ($resource instanceof CascadingResource) {
72-
$parentResources = $this->getAllParentResources($resource);
73-
} else {
74-
$traverser = $this->getResourceGraphTraverser(ClassUtils::getClass($resource));
75-
76-
if ($traverser) {
77-
$parentResources = $traverser->getAllParentResources($resource);
78-
}
79-
}
66+
$parentResources = $this->resourceGraphTraverser->getAllParentResources($resource);
8067

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

100-
/**
101-
* Get all parent resources recursively.
102-
* @param CascadingResource $resource
103-
* @return ResourceInterface[]
104-
*/
105-
private function getAllParentResources(CascadingResource $resource)
106-
{
107-
$parents = [];
108-
109-
foreach ($resource->getParentResources($this->entityManager) as $parentResource) {
110-
$parents[] = $parentResource;
111-
if ($parentResource instanceof CascadingResource) {
112-
$parents = array_merge($parents, $this->getAllParentResources($parentResource));
113-
}
114-
}
115-
116-
return $this->unique($parents);
117-
}
118-
119-
/**
120-
* Get all sub-resources recursively.
121-
* @param CascadingResource $resource
122-
* @return ResourceInterface[]
123-
*/
124-
private function getAllSubResources(CascadingResource $resource)
125-
{
126-
$subResources = [];
127-
128-
foreach ($resource->getSubResources($this->entityManager) as $subResource) {
129-
$subResources[] = $subResource;
130-
if ($subResource instanceof CascadingResource) {
131-
$subResources = array_merge($subResources, $this->getAllSubResources($subResource));
132-
}
133-
}
134-
135-
return $this->unique($subResources);
136-
}
137-
138-
private function unique(array $array)
139-
{
140-
$result = [];
141-
142-
foreach ($array as $item) {
143-
if (! in_array($item, $result, true)) {
144-
$result[] = $item;
145-
}
146-
}
147-
148-
return $result;
149-
}
150-
15187
/**
15288
* @param string $entityClass
15389
* @param ResourceGraphTraverser $resourceGraphTraverser
15490
*/
15591
public function setResourceGraphTraverser($entityClass, $resourceGraphTraverser)
15692
{
157-
$this->resourceGraphTraversers[$entityClass] = $resourceGraphTraverser;
158-
}
159-
160-
/**
161-
* @param string $entityClass
162-
* @return ResourceGraphTraverser|null
163-
*/
164-
private function getResourceGraphTraverser($entityClass)
165-
{
166-
if (isset($this->resourceGraphTraversers[$entityClass])) {
167-
return $this->resourceGraphTraversers[$entityClass];
168-
}
169-
170-
return null;
93+
$this->resourceGraphTraverser->setResourceGraphTraverser($entityClass, $resourceGraphTraverser);
17194
}
17295
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
namespace MyCLabs\ACL\ResourceGraph;
4+
5+
use Doctrine\ORM\EntityManager;
6+
use MyCLabs\ACL\Model\CascadingResource;
7+
use MyCLabs\ACL\Model\ResourceInterface;
8+
9+
/**
10+
* Traverser for resources implementing CascadingResource.
11+
*
12+
* CascadingResource don't return all sub-resources (only the direct ones), so we need to do
13+
* the traversal recursively in this class.
14+
*
15+
* @author Matthieu Napoli <matthieu@mnapoli.fr>
16+
*/
17+
class CascadingResourceGraphTraverser implements ResourceGraphTraverser
18+
{
19+
/**
20+
* @var EntityManager
21+
*/
22+
private $entityManager;
23+
24+
/**
25+
* @var ResourceGraphTraverser
26+
*/
27+
private $parentTraverser;
28+
29+
/**
30+
* @param EntityManager $entityManager
31+
* @param ResourceGraphTraverser $parentTraverser We need the parent traverser to use it
32+
* to recursively traverse resources. This is because CascadingResource returns
33+
* returns only its direct parent and sub-resources.
34+
*/
35+
public function __construct(EntityManager $entityManager, ResourceGraphTraverser $parentTraverser)
36+
{
37+
$this->entityManager = $entityManager;
38+
$this->parentTraverser = $parentTraverser;
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function getAllParentResources(ResourceInterface $resource)
45+
{
46+
if (! $resource instanceof CascadingResource) {
47+
return [];
48+
}
49+
50+
$parentResources = [];
51+
52+
foreach ($resource->getParentResources($this->entityManager) as $parentResource) {
53+
$parentResources[] = $parentResource;
54+
55+
// Recursively get its sub-resources
56+
$parentResources = array_merge(
57+
$parentResources,
58+
$this->parentTraverser->getAllParentResources($parentResource)
59+
);
60+
}
61+
62+
return $this->unique($parentResources);
63+
}
64+
65+
/**
66+
* {@inheritdoc}
67+
*/
68+
public function getAllSubResources(ResourceInterface $resource)
69+
{
70+
if (! $resource instanceof CascadingResource) {
71+
return [];
72+
}
73+
74+
$subResources = [];
75+
76+
foreach ($resource->getSubResources($this->entityManager) as $subResource) {
77+
$subResources[] = $subResource;
78+
79+
// Recursively get its sub-resources
80+
$subResources = array_merge(
81+
$subResources,
82+
$this->parentTraverser->getAllSubResources($subResource)
83+
);
84+
}
85+
86+
return $this->unique($subResources);
87+
}
88+
89+
/**
90+
* Array unique but with objects.
91+
*
92+
* @param array $array
93+
*
94+
* @return array
95+
*/
96+
private function unique(array $array)
97+
{
98+
$result = [];
99+
100+
foreach ($array as $item) {
101+
if (! in_array($item, $result, true)) {
102+
$result[] = $item;
103+
}
104+
}
105+
106+
return $result;
107+
}
108+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace MyCLabs\ACL\ResourceGraph;
4+
5+
use Doctrine\Common\Util\ClassUtils;
6+
use MyCLabs\ACL\Model\ResourceInterface;
7+
8+
/**
9+
* This is a traverser that dispatches to other traversers based on the resource class.
10+
*
11+
* @author Matthieu Napoli <matthieu@mnapoli.fr>
12+
*/
13+
class ResourceGraphTraverserDispatcher implements ResourceGraphTraverser
14+
{
15+
/**
16+
* @var ResourceGraphTraverser[]
17+
*/
18+
private $traversers = [];
19+
20+
/**
21+
* {@inheritdoc}
22+
*/
23+
public function getAllParentResources(ResourceInterface $resource)
24+
{
25+
$traverser = $this->getResourceGraphTraverser($resource);
26+
if (!$traverser) {
27+
return [];
28+
}
29+
30+
return $traverser->getAllParentResources($resource);
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
public function getAllSubResources(ResourceInterface $resource)
37+
{
38+
$traverser = $this->getResourceGraphTraverser($resource);
39+
if (!$traverser) {
40+
return [];
41+
}
42+
43+
return $traverser->getAllSubResources($resource);
44+
}
45+
46+
/**
47+
* @param string $entityClass
48+
* @param ResourceGraphTraverser $resourceGraphTraverser
49+
*/
50+
public function setResourceGraphTraverser($entityClass, ResourceGraphTraverser $resourceGraphTraverser)
51+
{
52+
$this->traversers[$entityClass] = $resourceGraphTraverser;
53+
}
54+
55+
/**
56+
* @param object $resource
57+
* @return ResourceGraphTraverser|null
58+
*/
59+
private function getResourceGraphTraverser($resource)
60+
{
61+
$entityClass = ClassUtils::getClass($resource);
62+
63+
if (isset($this->traversers[$entityClass])) {
64+
return $this->traversers[$entityClass];
65+
}
66+
67+
// We also try using instanceof so that we cover inheritance and interfaces
68+
foreach ($this->traversers as $class => $traverser) {
69+
if ($resource instanceof $class) {
70+
return $traverser;
71+
}
72+
}
73+
74+
return null;
75+
}
76+
}

tests/Integration/Issues/Issue10/Item.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

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

5+
use Doctrine\ORM\EntityManager;
56
use Doctrine\ORM\Mapping as ORM;
7+
use MyCLabs\ACL\Model\CascadingResource;
68
use MyCLabs\ACL\Model\EntityResource;
79

810
/**
911
* @ORM\Entity
1012
*/
11-
class Item implements EntityResource
13+
class Item implements EntityResource, CascadingResource
1214
{
1315
/**
1416
* @ORM\Id @ORM\GeneratedValue
@@ -31,4 +33,14 @@ public function getId()
3133
{
3234
return $this->id;
3335
}
36+
37+
public function getParentResources(EntityManager $entityManager)
38+
{
39+
return [ $this->project ];
40+
}
41+
42+
public function getSubResources(EntityManager $entityManager)
43+
{
44+
return [];
45+
}
3446
}

0 commit comments

Comments
 (0)