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
Enh: Code de-duplication by introducing PermissionManager::handlePermissionStateChange()
#6750
Conversation
PermissionManager::handlePermissionStateChange()
2fdef41
to
f086f43
Compare
@luke- I'm quite puzzled at the failing tests again: https://github.com/humhub/humhub/actions/runs/7232996921/job/19707789671#step:18:1557 seem to be complete bullocks, as the string The other error in |
3898529
to
70a69cd
Compare
Locally, the tests pass. |
70a69cd
to
04838f2
Compare
95ad6c7
to
0803a59
Compare
@martin-rueegg Why do we need the methods `checkBool` and `checkFloat` if they are not used?
We don't need them.
I added them for reasons of consistency.
If you want, I can certainly replace some code in other parts of the Core,
but I wanted to keep the PR focused.
|
What do you think about introducing a new helper class I assume the method names are aligned with the existing Is it useful that e.g. |
Sounds good to me.
Correct. If we put the But maybe we should do a seperate PR for that move?
Well, I don't really see an additional value in returning a boolean to tell me, if a variable is a string or not:
Shall we
|
@martin-rueegg Thank you sounds good to me. We can mark |
Ok cool. Just let me know what of the above checklist do you want in this PR? All of it? Or just check the ones you prefer. |
a4b3156
to
fcd2ccc
Compare
I've now done all in this one PR. Also, I've added tests for the new methods. |
@martin-rueegg Thank you for the implementation. From my side, we can add the Although, you know my philosophy, I always prefer implementations according to the KISS principle. Ideally something like this, which can be understood quite easy: public static function filterFloat(mixed $value, bool $strict = false, bool $throwErrorOnStrict = false): ?float
{
if (!is_float($value) && $strict) {
if ($throwErrorOnStrict) {
throw new InvalidArgumentException('No float given!');
}
return null;
}
return floatval($value);
} @yurabakhtin Can you please also take a look into this? |
@luke- Yes, your suggested method code looks simpler, maybe we could use the simple fucntion |
I'll have a look again. Not aware 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.
@martin-rueegg Here are a few more remarks. I would also like to limit the flexibility a bit (make checkType()
private). Besides that, we can merge the PR from my side.
* @return string|null | ||
* @since 1.16 | ||
*/ | ||
public static function checkType($input, $types, ?bool $requireAll = false, ?string $throw = '$input', ?array &$typesChecked = null): ?string |
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 would prefer the method to be private.
public static function checkType($input, $types, ?bool $requireAll = false, ?string $throw = '$input', ?array &$typesChecked = null): ?string | |
private static function checkType($input, $types, ?bool $requireAll = false, ?string $throw = '$input', ?array &$typesChecked = null): ?string |
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 that would be a real shame. This function is complementing the filterClassType()
for types that are not necessarily class names or instances.
Particularly, since many functions return one data type on success (eg. an array of records) or false
on error (rather than null
), this method makes it very easy to check such an input parameter:
/**
* @param array|false $result from previous function
*/
public function foo($result) { // <- no strict type possible here!
DataTypeHelper::checkType($result, ['array', 'bool']);
// proceed with code
}
So I personally would see filterInt($var)
as a shortcut to checkType($var, 'int')
Can we keep this public?
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 understand that the method is very powerful, but at the same time it is also very complex.
For example, when I see the following call somewhere in the code, I am completely confused:
$someString = DataTypeHelper::checkType($value, ['array', 'string'], false, '$value', &$typesChecked);
I would be completely fine with a method like, which throws an exception on failure
DataTypeHelper::ensureType(mixed $value, array $types) : void;
A few other thoughts/ambiguities for checkType()
which is why I want to keep it private
:
- Why
?string
as return type? I would expectbool
for such type of helper - Do we really need
$requireAll
option? I would drop it for simplicity. - I understand the
$throw
is useful in some situations, but I don't like the style to pass a variable name as string. - I roughly remember that you explained the purpose of
&$typesChecked
- Before we introduce a more complex type validation/helper API (500 lines+ ), we should perhaps also check whether there are ready-made libraries for this. We can very easily replace helper methods
filterInt()
with other implementations later on. However, this will not be possible with more complex methods such ascheckType()
.
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 would be completely fine with a method like, which throws an exception on failure
DataTypeHelper::ensureType(mixed $value, array $types) : void;
How about this:
- One "ensure" method (throwing an exception)
DataTypeHelper::ensureType($input, array $types, string $parameterOrMessage = '$input'): void
- One "check" method (returning the type)
DataTypeHelper::checkType($input, array $types, bool $returnIndex = false): ?string
.
Why
?string
as return type? I would expectbool
for such type of helper
Because like that you can use it e.g. in a switch
statement like so:
switch (DataTypeHelper::checkType($value, ['string', 'int', Stringable::class])) {
case Stringable::class:
$value = (string)$value;
case 'string':
// Do something with a string value
break;
case 'int':
// do something else with an int
break;
default:
// do nothing
}
.
Do we really need
$requireAll
option? I would drop it for simplicity.
Nope. Could be handy if you want to test two interfaces at once. But in such a case just use the method twice ...
.
I understand the
$throw
is useful in some situations, but I don't like the style to pass a variable name as string.
Better with the above suggestion?
.
I roughly remember that you explained the purpose of
&$typesChecked
I've removed it.
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 would be completely fine with a method like, which throws an exception on failure
DataTypeHelper::ensureType(mixed $value, array $types) : void;
How about this:
- One "ensure" method (throwing an exception)
DataTypeHelper::ensureType($input, array $types, string $parameterOrMessage = '$input'): void
- One "check" method (returning the type)
DataTypeHelper::checkType($input, array $types, bool $returnIndex = false): ?string
Why
?string
as return type? I would expectbool
for such type of helperBecause like that you can use it e.g. in a
switch
statement like so:switch (DataTypeHelper::checkType($value, ['string', 'int', Stringable::class])) { case Stringable::class: $value = (string)$value; case 'string': // Do something with a string value break; case 'int': // do something else with an int break; default: // do nothing }
Ok, I see. How about then calling the method DataTypeHelper::getType(mixed $mixed, array $allowedTypes): ?string
?
So:
DataTypeHelper::ensureType(mixed $input, array $allowedTypes): void
DataTypeHelper::getType(mixed $input, array $allowedTypes): ?string
DataTypeHelper::hasType(mixed $input, array $allowedTypes): bool
I'm still not sure, about:
- Passing the variable name as string to the
ensureType()
method. For me the code usage looks cluttered.if (DataTypeHelper::ensureType($param1, ['string'], '$param1') && DataTypeHelper::ensureType($param2, ['int'], '$param2')) { /* ... */ }
- I'm thinking about raising the HH minVersion to
8.1
and use Enums instead of strings
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.
Ok, I see. How about then calling the method
DataTypeHelper::getType(mixed $mixed, array $allowedTypes): ?string
?So:
DataTypeHelper::ensureType(mixed $input, array $allowedTypes): void
DataTypeHelper::getType(mixed $input, array $allowedTypes): ?string
DataTypeHelper::hasType(mixed $input, array $allowedTypes): bool
Sure, that could be done.
However, hasType
is kinda just an alias to getType
: except for the strict type, the later would always yield the same result as the former, since only the empty string ""
would yield false
, but that could never happen. In such a case null
would be returned which is ==
to false
:
if (DataTypeHelper::getType("some string", ['string', 'int'])) {
// result "string" is "true"
}
The only time you would actually need to use DataTypeHelper::hasType()
(with a strict boolean return type) is when you'd pass the result straight to another fixed-type method:
function foo(bool $isString) {
// $isString cannot be auto-converted here.
}
// so this would fail:
foo(DataTypeHelper::getType("some string", ['string', 'int'])));
// instead, this would be required:
DataTypeHelper::getType("some string", ['string', 'int']) !== null);
As such, that would also be the exact implementation:
public function hasType($input, array $allowedTypes): bool {
return DataTypeHelper::getType($input, $allowedTypes) !== null);
}
I'm still not sure, about:
- Passing the variable name as string to the
ensureType()
method. For me the code usage looks cluttered.if (DataTypeHelper::ensureType($param1, ['string'], '$param1') && DataTypeHelper::ensureType($param2, ['int'], '$param2')) { /* ... */ }
I understand. Well, in such a case, where you only have one possible type, you could use a strictly-typed parameter, which is always the better option anyway. (But I don't think that was the point of your argument.)
Furthermore you would no longer need the if
. And rewriting it without the &&
would certainly increase readability:
function bar($param1, $param2) {
DataTypeHelper::ensureType($param1, ['string', 'int'], '$param1');
DataTypeHelper::ensureType($param2, [someClass::class, 'is_scalar'], 'Please make sure $param2 has the right format');
/* continue with type-safe values here */
}
Besides, the parameter is optional. But it helps to write a meaningful exception message. Which also is as close to the PHP's own type exception (naming the parameter name and number).
If you do not want to throw an exception, but just do something if a given type is povided, then you do no longer have that parameter:
if (DataTypeHelper::hasType($param1, ['string', 'int']) && DataTypeHelper::hasType($param2, [someClass::class, 'is_scalar']) {
/* do something conditionally with type-safe values here */
}
- I'm thinking about raising the HH minVersion to
8.1
and use Enums instead of strings
You mean to raise the PHP min version?
I'm not quite sure what would enum's help in this context? For the $allowedTypes
parameter?
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.
Ok, I see. How about then calling the method
DataTypeHelper::getType(mixed $mixed, array $allowedTypes): ?string
?
So:
DataTypeHelper::ensureType(mixed $input, array $allowedTypes): void
DataTypeHelper::getType(mixed $input, array $allowedTypes): ?string
DataTypeHelper::hasType(mixed $input, array $allowedTypes): bool
Sure, that could be done.
However,
hasType
is kinda just an alias togetType
: except for the strict type, the later would always yield the same result as the former, since only the empty string""
would yieldfalse
, but that could never happen. In such a casenull
would be returned which is==
tofalse
:if (DataTypeHelper::getType("some string", ['string', 'int'])) { // result "string" is "true" }
The only time you would actually need to use
DataTypeHelper::hasType()
(with a strict boolean return type) is when you'd pass the result straight to another fixed-type method:function foo(bool $isString) { // $isString cannot be auto-converted here. } // so this would fail: foo(DataTypeHelper::getType("some string", ['string', 'int']))); // instead, this would be required: DataTypeHelper::getType("some string", ['string', 'int']) !== null);
As such, that would also be the exact implementation:
public function hasType($input, array $allowedTypes): bool { return DataTypeHelper::getType($input, $allowedTypes) !== null); }
I'm still not sure, about:
- Passing the variable name as string to the
ensureType()
method. For me the code usage looks cluttered.if (DataTypeHelper::ensureType($param1, ['string'], '$param1') && DataTypeHelper::ensureType($param2, ['int'], '$param2')) { /* ... */ }
I understand. Well, in such a case, where you only have one possible type, you could use a strictly-typed parameter, which is always the better option anyway. (But I don't think that was the point of your argument.)
Furthermore you would no longer need the
if
. And rewriting it without the&&
would certainly increase readability:function bar($param1, $param2) { DataTypeHelper::ensureType($param1, ['string', 'int'], '$param1'); DataTypeHelper::ensureType($param2, [someClass::class, 'is_scalar'], 'Please make sure $param2 has the right format'); /* continue with type-safe values here */ }
Besides, the parameter is optional. But it helps to write a meaningful exception message. Which also is as close to the PHP's own type exception (naming the parameter name and number).
If you do not want to throw an exception, but just do something if a given type is povided, then you do no longer have that parameter:
if (DataTypeHelper::hasType($param1, ['string', 'int']) && DataTypeHelper::hasType($param2, [someClass::class, 'is_scalar']) { /* do something conditionally with type-safe values here */ }
- I'm thinking about raising the HH minVersion to
8.1
and use Enums instead of stringsYou mean to raise the PHP min version?
I'm not quite sure what would enum's help in this context? For the
$allowedTypes
parameter?
For me, I would prefer instead of if (static::getType($var, ['string', 'int']) === 'string') { ... }
something like: if (static::getType($var, [DataType::String, DataType::Integer]) === DataType::String) { ... }
.
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 thinking about raising the HH minVersion to
8.1
and use Enums instead of stringsYou mean to raise the PHP min version?
I'm not quite sure what would enum's help in this context? For the$allowedTypes
parameter?For me, I would prefer instead of
if (static::getType($var, ['string', 'int']) === 'string') { ... }
something like:if (static::getType($var, [DataType::String, DataType::Integer]) === DataType::String) { ... }
.
I see.
Well, if you raise PHP to v8.1, then I'm happy to create such an Enum.
Otherwise, I'm happy to create regular constants in the current DataTypeHelper
class.
Please note, that validation of the input types can not be done exclusively by enum, as class, interface and trait names are 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.
Thanks @luke- for your review and comments.
* @return string|null | ||
* @since 1.16 | ||
*/ | ||
public static function checkType($input, $types, ?bool $requireAll = false, ?string $throw = '$input', ?array &$typesChecked = null): ?string |
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 that would be a real shame. This function is complementing the filterClassType()
for types that are not necessarily class names or instances.
Particularly, since many functions return one data type on success (eg. an array of records) or false
on error (rather than null
), this method makes it very easy to check such an input parameter:
/**
* @param array|false $result from previous function
*/
public function foo($result) { // <- no strict type possible here!
DataTypeHelper::checkType($result, ['array', 'bool']);
// proceed with code
}
So I personally would see filterInt($var)
as a shortcut to checkType($var, 'int')
Can we keep this public?
cf00fa8
to
378ff2c
Compare
To have a consistent API, I have now streamlined the methods of
Additionally, there are some short-cuts:
|
0b33b69
to
0e017a8
Compare
@martin-rueegg Thanks for the overview. The following questions:
|
It is certainly possible. I don't consider it useful:
Since that was your previous suggestion, I had considered that. However, I found it confusing, as the method does not necessarily return the type of the We could of course add such a variant, which will behave more like
That would be the same as $type = $allowedTypes === null ? get_debug_type($value) : DataTypeHelper::matchType($value, $allowedTypes);
I find it fascinating, that you struggle with an well-documented optional parameter. AFAIK you are using PhpStorm, too. There, all you need to do is to hoover the mouse over the method and it will pop-up the documentation to the function where the parameter is explained. Also, the parameter names are shown in front of the parameter. Does that not help clarifying? Also, I guess if the result is used in a real-life example, it would also be more explanatory: $index = DataTypeHelper::matchType($row, ['array', ActiveRecord::class], false, true);
switch ($index ?? -1) {
case 0: // record is in array-format
// do something with array
break;
case 1: // record is an ActiveRecord instance
// do something with record
break;
default:
// abort
return;
} Otherwise, would it be better to use a // definition
const THROW_EXCEPTION = 1;
const RETURN_INDEX = 2;
function DataTypeHelper::matchType($value, $allowedTypes, int $flags);
// usage
$index = DataTypeHelper::matchType($value, ['array', ActiveRecord::class], self::THROW_EXCEPTION + self::RETURN_INDEX); That would make the calling code more expressive, once written. However, it would be more difficult to produce the code and would additionally require evaluation and validation of the flags.
Fair enough. It can be removed. |
Ok, that was just an idea. Then let's leave it as it is.
Ok, understand. Then lets stay with
I don't want to have to rely on any helpers or comments to understand the code quickly. The code should be always simple as possible, even when a bit slower. switch(DataTypeHelper::matchType($row, ['array', ActiveRecord::class]))
{
case 'array':
// do something
break;
case ActiveRecord::class:
// do something
break;
default:
// abort
break;
}
Thanks for the suggestion. But I am less concerned with the parameter, which is reasonably clear, and more with how it is used.
|
Sure, that's not the issue. I made a bad example. The issue is only when you use callables. Because you can't compare them as easily. But I'm sure another work-around can be found in such a case. I'll drop the parameter and will make a new PR in case I have a strong use case. |
Thank you. Sorry if I'm a bit too specific. But especially with Helpers, better to be as compact as possible... |
To have a consistent API, I have now streamlined the methods of
Additionally, there are some short-cuts:
Furthermore, I've
|
@martin-rueegg Thanks. I overlooked the fact that the
|
It was only intended as a shortcut for However, I've now changed the return type to |
Currently failing tests seem unrelated to any change in this PR |
4f9855a
to
0d4298b
Compare
@martin-rueegg Hmm strange, tests in |
0d4298b
to
6f92221
Compare
dcc9452
to
0d87b18
Compare
0d87b18
to
5a96c8e
Compare
The error message is really not helpful, stating that some DB fields are not found during Github API test. The issue, however, seem to have been a syntax error in one of the files. |
@luke- Tests successful now. Ready to merge? |
@martin-rueegg Yes, thanks for your effort here! |
Code de-duplication by introducing
PermissionManager::handlePermissionStateChange()
PR Admin
What kind of change does this PR introduce?
Does this PR introduce a breaking change?
The PR fulfills these requirements
develop
branch, not themaster
branch if no hotfixFix #xxx[,#xxx]
, where "xxx" is the Github issue number)If adding a new feature, the PR's description includes:
Other information: