-
-
Notifications
You must be signed in to change notification settings - Fork 13
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 generic annotations to EventInterface
#32
Conversation
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 also take a look at the CS failures: they are quite noisy :-D
Took care of the checks =) Looks like I was sadly unable to extend the template when calling |
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, now comes the hard part: testing this :D
In practice, you added a lot of type bindings to allow for better ergonomics around Event
and EventInterface
, but we don't know if they will work in the wild.
My suggestion is to have the types be verified by a directory checked by psalm.xml
, with examples such as:
/**
* @param EventInterface<MyObject, array<non-empty-string, 1|2|3>> $e
* @return array{
* MyObject,
* array<non-empty-string, 1|2|3>,
* 1|2|3
* }
*/
function foo(EventInterface $e): MyObject {
return [
$e->getTarget(),
$e->getParams(),
$e->getParam('foo')
];
}
The above, but for various examples (constructor call, target change, params change, etc.)
src/Event.php
Outdated
* @param string|object|null $target | ||
* @psalm-param TTarget $target | ||
* @param array|ArrayAccess|object|null $params | ||
* @psalm-param TParams|null $params |
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.
Perhaps just drop TParams|null
, and only keep TParams
? That would fix this type binding, IMO, and remove the suppression on protected $params
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 better yeah, paired with the reversal of the ctor null
change.
The suppression for protected $params
is because array<empty, empty>
cannot be assigned to TParams
. There seems to be no way to infer an empty array for TParams
when the ctor receives null
as the third argument.
I could fix that with $this->setParams($params ?? [])
, but that would change behavior when someone has extended Event
without calling the parent's ctor. So I figured the suppression was a better solution.
Removing the $params = []
confuses Psalm because the default type for new Event()
will become Event<null, mixed>
which is not accepted by the likes of EventManager::triggerEvent()
.
Removing |null
will cause a static error in the ctor because it thinks the type can never be null
anymore.
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 added a check that verifies the intended behavior: https://github.com/laminas/laminas-eventmanager/pull/32/files#diff-89d6387e68462753db6f5bd8f489601c82db2d8af81c02ff4e27f868e9386dd8R10-R28
psalm/EventInterfaceChecks.php
Outdated
// /** | ||
// * @param EventInterface $e | ||
// * @return array | ||
// */ | ||
// public function checkSetParamDoesNotAlterTemplate(EventInterface $e): array | ||
// { | ||
// | ||
// } | ||
|
||
// TODO: Check ctor inferrence, setParams setTarget out changes, ignore setParam() or getParam() |
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.
Just tracking this TODO
BTW, It will either help or hurt :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.
@villermen please address the comments made by @Ocramius, other than those, looks good 👍
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'd prefer to see the psalm tests in a subdirectory of /test/ - that way, autoloading is already setup, psalm & phpcs runs and files are export ignored - not a blocker though.
Signed-off-by: Villermen <villermen@gmail.com>
Signed-off-by: Villermen <villermen@gmail.com>
Signed-off-by: Villermen <villermen@gmail.com>
…s on limits of current Psalm setup Signed-off-by: Villermen <villermen@gmail.com>
Signed-off-by: Villermen <villermen@gmail.com>
Signed-off-by: Villermen <villermen@gmail.com>
Signed-off-by: Villermen <villermen@gmail.com>
It won't work when params is an object. Signed-off-by: Villermen <villermen@gmail.com>
…ay check in Event::__construct() Signed-off-by: Villermen <villermen@gmail.com>
Signed-off-by: Villermen <villermen@gmail.com>
Signed-off-by: Villermen <villermen@gmail.com>
Signed-off-by: Villermen <villermen@gmail.com>
Signed-off-by: Villermen <villermen@gmail.com>
Signed-off-by: Villermen <villermen@gmail.com>
Signed-off-by: Villermen <villermen@gmail.com>
Signed-off-by: Villermen <villermen@gmail.com>
@gsteel Actually agreed with you there, but then I noticed "benchmarks/". That's also a testing directory separate from "test/" so I stuck to that pattern. Ended up being happier with this situation because these classes are not necessarily tests that have to be run by PHPUnit. Will correct when you still prefer adding below "test/". |
Signed-off-by: Villermen <villermen@gmail.com>
…ality Signed-off-by: Villermen <villermen@gmail.com>
I don't have strong opinions on this @villermen - it's all good as long as it's all export ignored and subject to the same CS rules etc 👍 |
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.
Hot damn the tests on this are good! Thanks @villermen!
I added a couple more feedback points around verifying psalm-this-out
, mostly because I got burnt by laminas/laminas-stdlib#78
If those are handled, we 🚢
} | ||
|
||
/** | ||
* Individual params obtained via `getParam()` can't be inferred because their keys/values can't be selected from |
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.
Good enough though 👍
psalm/EventInterfaceChecks.php
Outdated
* | ||
* @param EventInterface<null, array{foo: int}> $e | ||
* @return array{ | ||
* EventInterface<null, array{foo: int}>, |
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 still kinda weird: fixable somehow? 🤔
See my reverts from laminas/laminas-stdlib#78
…ify them on Event Signed-off-by: Villermen <villermen@gmail.com>
To no avail. Signed-off-by: Villermen <villermen@gmail.com>
I see that you opted for |
Signed-off-by: Villermen <villermen@gmail.com>
@Ocramius I think it does! After I've also looked into getting it to work without this-out, but it become very cumbersome to deal with then. Ctor inference sets a type that's too narrow, setters can't use template types. The best we can do in that case is defining the templates and using them on the getters. |
Signed-off-by: Villermen <villermen@gmail.com>
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.
Let's roll with this and see how it goes: it's a massive improvement, and we all learned a lot from it, so it's time to test it on the field 👍
Thanks @villermen! |
EventInterface
Description
It would be cool if you could somehow hint at the types of an event's target and params that IDEs and static code checking tools can work with. I've added a rough outline of what I mean in the PR using PHPStan-targeted annotations.
This way you can define the target and parameter types of an event in one go instead of doing it on each line the event is accessed:
becomes
This leaves the type annotation code in the main docblock of the method.
Of course you could also create an actual native-class for each event. That's more type-safe, but creates a lot of additional overhead. I can totally understand if that's the desired approach too, so take this is a suggestion only =)