-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Traversal strategies #5
Traversal strategies #5
Conversation
Closes zf-fr#4 because it no longer needs to implement RecursiveIterator
*/ | ||
public function __construct(TraversalStrategyInterface $strategy = null) | ||
{ | ||
$this->traversalStrategy = ($strategy) ? $strategy : new RecursiveRoleIteratorStrategy; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be $this->traversalStrategy = $strategy ?: new RecursiveRoleIteratorStrategy();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If RecursiveRoleIteratorStrategy and GeneratorStrategy do exactly the same, you should add a check based on the PHP version, and use the most efficient one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bakura10 First, the $this->traversalStrategy
must not be null
. Second, I want to allow developers to use custom strategies, this is why I dont choose the strategy based on the PHP version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructt $strategy ?: new Recursive() is equivalent to : if $straetgy is not null, use strategy, otherwise new Recursive.
It's a shortcut.
Yes I understood abut custom strategy, but you could do:
if (PHP_VER < 5.5) { // Not the right syntax
$this->traversalStrategy = $strategy ?: new RecursiveRoleIteratorStrategy;
} else {
$this->traversalStrategy = $strategy ?: new GeneratorStrategy()
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, nice! I'll make this changes tonight.
Please note that tests are not passing on PHP 5.4 too. I think you'll have to skip some tests if not on PHP 5.5 |
I see, there is a standard way to do it? |
@danizord , I don't remember the exact syntax. Please check the ZF2 tests, do a search on the source code, I don't remember the exact syntax. |
I like the idea in overall. The only thing I'm still not sure is really the Generator thing. This is just my opinion, but I still find the implementation a bit unusual (generators were clearly not thought for recursive it seems). Furthermore, we now have two implementations that do the same thing. The performance question is here a bit tendentious: most of the time you have very few roles (even for a complex system I don't think you will have more than 10 roles), so the advantage is pretty negligeable. While I think it makes sense to have a polyfill for very critical components like the router (in this case you could have a loot of routes so the performance bump makes sense). In this specific case, it seems to me like it is using generators for the sake of using generators. I really find the RecursiveIterator approach much more elegant for solving this issue. Havign said that, I won't be against including it, but I'm not fond of it. |
*/ | ||
public function __construct(TraversalStrategyInterface $strategy = null) | ||
{ | ||
$this->traversalStrategy = $strategy ?: new RecursiveRoleIteratorStrategy; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why aren't you using the GeneratorStrategy by default? It's faster and doesn't have problems with doctrine collections
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because generators are not supported on PHP 5.4
Envoyé de mon iPhone
Le 7 janv. 2014 à 12:05, arekkas notifications@github.com a écrit :
In src/Rbac/Rbac.php:
/
class Rbac
{
/*
\* Determines if access is granted by checking the role and child roles for permission.
\* @var TraversalStrategyInterface
*/
- protected $traversalStrategy;
- /**
\* @param null|TraversalStrategyInterface $strategy
*/
- public function __construct(TraversalStrategyInterface $strategy = null)
- {
Why aren't you using the GeneratorStrategy by default? It's faster and doesn't have problems with doctrine collections$this->traversalStrategy = $strategy ?: new RecursiveRoleIteratorStrategy;
—
Reply to this email directly or view it on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a check for the PHP version here would help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm waiting for the result of the discussion: "GeneratorStrategy is good or bad" to make sure if I should set it by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please: $this->traversalStrategy = $strategy ?: new RecursiveRoleIteratorStrategy();
Note the () at the end. I know it makes no difference but... it's on my namespace :muhaha: .D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bakura10 done.
I like the idea to use the Strategy pattern for the graph traversal. What seems odd to me is that generators are faster than iterators, but then again, this is PHP so everything is possible ;) In conclusion, I'm +1 with this! |
/** @var RoleInterface $child */ | ||
if ($child->hasPermission($permission)) { | ||
foreach ($iterator as $role) { | ||
/* @var RoleInterface $role */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead, just consider $iterator
a RoleInterface[]
That works even for return values in docblocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is correct? $iterator
is actually a Traversable
and Traversable
!== RoleInterface[]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@danizord []
at the end of a type means "a traversable of "
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ocramius No, it means "an array of" AFAIK. So I'd suggest Traversable|RoleInterface[]
. But I dont know if it will work as expected in PHPStorm. I'm using Sublime (:
@bakura10 This is not just for the sake of using generators. See, the
Why? https://github.com/nikic/iter/blob/master/src/iter.php#L490-500 😃 |
It almost solves the Doctrine collections incompatibility
return false; | ||
} | ||
|
||
if (empty($current->getChildren())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please note that this only works on PHP 5.5, please do this as we need to support PHP 5.4:
$children = $current->getChildren();
if (empty($children)) {
return false;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return ! empty($children);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You think I can remove it?
Or pass this responsibility to the role, e.g HierarchicalRoleInterface::hasChildren()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, getChildren will return $this->children, so yeah you can do return !empty($this->children)
It looks pretty good to me, excepting the few namings we should fix. @Ocramius do you think this is good? However I'll want you to also update ZfcRbac to use this code so that I can push both PR at the same time (I suppose this can break ZfcRbac no?) |
The issue with Doctrine collections was solved! Updating PR description. |
Looks good to me. Please could you update ZfcRbac so ZfcRbac does not stay in a zombie state after merging this? |
Please also update the various .dist file, as well as the tests where I have simple entities based on previous Rbac. |
{ | ||
if (null !== $strategy) { | ||
$this->traversalStrategy = $strategy; | ||
} elseif (version_compare(PHP_VERSION, '5.5.0', '>=')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks ugly =/ @bakura10 Don't you think a factory is the best place for this logic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, use PHP_VERSION_ID
instead.
I am with @danizord on this - the constructor argument should not be nullable (may be misunderstood) and injection should happen in a factory.
Ok, create a factory then |
Finally! Sorry for the delay :D
Traversal strategies
The Graph traversal is a very complex problem, there are several ways to do this, but none of them is the best way for all cases. There are many cases that need a specific algorithm. For instance, If you have a very dense role graph, you'll probably want to avoid redundancy when traversing the graph (checking the same role more than once). But if you have a flatter role graph, then you will not care about the redundancy, because the logic to avoid redundancy would be more costly than the redundancy itself.
So, if we make this algorithm pluggable, the RBAC component will become much more flexible, allowing the developers to easily implement their own strategies for specific cases.
I have many ideas for other strategies to implement (e.g a non-redundant generator strategy), but for now I'll suggest two:
Built-in traversal strategies:
RecursiveIteratorIterator
that iterate over instances ofRbac\Traversal\RecursiveRoleIterator
.Benchmarks
I did some performance tests of the built-in strategies to know which one is faster. You can run the benchmarks yourself, just follow the instructions in the danizord/rbac-benchmarks repository. See the results on my PC:
Additional changes
HierarchicalRoleInterface
no longer extendsRecursiveIterator
, this means that the roles are not aware of traversal nor recursion anymore.HierarchicalRole
, now is done in the newRecursiveRoleIterator
.Rbac::isGranted()
now now accepts a collection/array ofRoleInterface
s, but still accepting a singleRoleInterface
instance. If one of the given roles has the required permission, then it returnstrue
.