-
Notifications
You must be signed in to change notification settings - Fork 821
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
add inGroup(s) methods to Group #7046
add inGroup(s) methods to Group #7046
Conversation
src/Security/Group.php
Outdated
* @param string|int|Group $group | ||
* @return bool | ||
*/ | ||
public function inGroup($group) { |
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.
Would be good if the phpdoc told me what this method did. :)
Is this checking that the provided group is IN the invoked group, or the other way around? Does this work recursively?
Tests would also be nice.
src/Security/Group.php
Outdated
} elseif ($group instanceof Group) { | ||
$groupCheckObj = $group; | ||
} else { | ||
user_error('Member::inGroup(): Wrong format for $group parameter', E_USER_ERROR); |
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.
InvalidArgumentException is preferred response in this case.
src/Security/Group.php
Outdated
user_error('Member::inGroup(): Wrong format for $group parameter', E_USER_ERROR); | ||
} | ||
|
||
if(!$groupCheckObj) 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.
psr-2 will fail here. All conditionals need { }
src/Security/Group.php
Outdated
$groupCheckObj = Group::get()->byID($group); | ||
} elseif (is_string($group)) { | ||
$SQL_group = Convert::raw2sql($group); | ||
$groupCheckObj = Group::get_one("\"Code\" = '{$SQL_group}'"); |
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 use ORM filter API instead of raw SQL please.
Tests added, and other feedback addressed @tractorcow - thanks for taking the time to look at it |
216ef34
to
f4a8a74
Compare
src/Security/Group.php
Outdated
$groupCheckObj = Group::get()->byID($group); | ||
} elseif (is_string($group)) { | ||
$SQL_group = Convert::raw2sql($group); | ||
$groupCheckObj = Group::get()->filter(['Code' => $SQL_group])->first(); |
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.
Don't raw2sql when using filter api; It'll double encode.
Group::get()->filter('Code', $group)->first();
is all you need
* @param string|int|Group $group Group instance, Group Code or ID | ||
* @return bool Returns TRUE if the Group is a child of the given group, otherwise FALSE | ||
*/ | ||
public function inGroup($group) |
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.
Edge case, is $group->inGroup($group)
?
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 think it should return true. Added a test.
src/Security/Group.php
Outdated
* Check if the group is a child of the given groups or any parent groups | ||
* | ||
* @param array $groups | ||
* @param bool $strict set to TRUE if must be in ALL groups, or FALSE if must be in ANY |
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.
Not a well named arg; $strict
means something other than what you're using it for. Did you mean to flag as conjunctive (and) or disjunctive (or)? Perhaps $conjunctive = false
is better?
src/Security/Group.php
Outdated
* @param bool $strict set to TRUE if must be in ALL groups, or FALSE if must be in ANY | ||
* @return bool Returns TRUE if the Group is a child of any of the given groups, otherwise FALSE | ||
*/ | ||
public function inGroups($groups, $strict = 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.
Edge case: is $group->inGroups([]);
true? Does it depend on whether the search is conjunctive or disjunctive?
src/Security/Group.php
Outdated
public function inGroups($groups, $strict = false) | ||
{ | ||
foreach ($groups as $group) { | ||
if ($this->inGroup($group)) { |
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 will be calling $this->collateAncestorIDs() once for each loop. Why not inline inGroup() into this method and optimise it a little better? You could create a set of all groupIDs, and another set of collateAncestorIDs(), then do subset comparison (array_intersect). You'd just need to count the intersection to determine if inGroups is true (intersect > 0 for disjunctive search, intersect === total for conjunctive search).
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.
Have reworked this, I think I understood what you meant? Let me know if I've gone down the wrong rabbithole 🐰
src/Security/Group.php
Outdated
protected function identifierToGroup($group) | ||
{ | ||
if (is_numeric($group)) { | ||
$groupCheckObj = Group::get()->byID($group); |
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 are converting the id to a group, and then to an id again. That's a very inefficient SQL overhead.
src/Security/Group.php
Outdated
$candidateIDs[] = $candidate->ID; | ||
} | ||
if ($requireAll) { | ||
return array_intersect($candidateIDs, $ancestorIDs) === $ancestorIDs; |
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.
Sorting issues [1, 2] !== [2, 1]
. You should only need to measure size of each group.
You should also be comparing to $candidateIDs not to $ancestorIDs, since $ancestorIDs may contain groups that aren't being compared to.
src/Security/Group.php
Outdated
if (is_numeric($group)) { | ||
$groupCheckObj = Group::get()->byID($group); | ||
} elseif (is_string($group)) { | ||
$groupCheckObj = Group::get()->filter('Code', $group)->first(); |
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 you require a group somecode
that doesn't exist, your code will silently ignore it and pass.
8d85358
to
4395a35
Compare
tests/php/Security/GroupTest.php
Outdated
public function testGroupInGroupExceptionForFakeGroupID() | ||
{ | ||
$group = new Group(); | ||
$group->inGroup(99999999); |
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 not sure an invalid argument exception should be thrown when a correct type is passed
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 think it should be an exception, but the wrong exception message is thrown. It says "invalid type" when the type is valid, but the ID isn't a real record.
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've updated the exception to cover all the bases - i.e. must be $correctType and exist
src/Security/Group.php
Outdated
*/ | ||
protected function identifierToGroupID($group) | ||
{ | ||
if (is_numeric($group) && Group::get()->byID($group)) { |
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.
Is the extra query needed here?
maybe $group->inGroup(999999); should just be false in this case, rather than an error, to save a whole query.
My earlier comment about checking that the group exists was intended only to check the result of a query if you had already performed it, since there's no new overhead anyway.
src/Security/Group.php
Outdated
if (is_numeric($group) && Group::get()->byID($group)) { | ||
return $group; | ||
} elseif (is_string($group) && Group::get()->filter('Code', $group)->first()) { | ||
return Group::get()->filter('Code', $group)->first()->ID; |
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 are running this entire SQL query twice? Can you not put this into a temp variable.
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 think trying to have a custom error message for every type of invalid argument that's sent in is overkill. It'll be less logic and code just to use a generic message for the exceptions.
Lets keep code complexity down
src/Security/Group.php
Outdated
if ($group) { | ||
return $group->ID; | ||
} | ||
if ($groupID instanceof DataObject) { |
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 fails to catch objects that don't extend from DataObject
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've been diligent keeping up with this PR so I'm going to be diligent with review.
src/Security/Group.php
Outdated
foreach ($groups as $group) { | ||
try { | ||
$candidateIDs[] = $this->identifierToGroupID($group); | ||
} catch (\InvalidArgumentException $e) { |
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 you're catching and returning false, then it's not really an error. Exceptions add alot of overhead (processing time) and shouldn't be used unless it's a real error.
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.
Going back to @dhensby 's feedback
I think trying to have a custom error message for every type of invalid argument that's sent in is overkill. It'll be less logic and code just to use a generic message for the exceptions.
Lets keep code complexity down
It's too hard to get this right just drop exceptions and handle the logic here. If it's $requireAll and null, return false, otherwise if it's null and not $requireAll, don't add it to $candidateIDs. you'll need to move your empty() check to below this loop though, if you could end up with empty $candidateIDs.
src/Security/Group.php
Outdated
$matches = array_intersect($candidateIDs, $ancestorIDs); | ||
return count($candidateIDs) === count($matches); | ||
} | ||
return count(array_intersect($candidateIDs, $ancestorIDs)) > 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.
same expression repeated above. Why not count($matches) > 0
?
src/Security/Group.php
Outdated
if ($requireAll) { | ||
return count($candidateIDs) === count($matches); | ||
} | ||
return count($matches) > 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.
!empty()
69252b1
to
ab60a16
Compare
Thanks @dhensby and @andrewandante |
No description provided.