Support for Messenger HandleTrait return types#404
Conversation
5d47f63 to
e3790e0
Compare
ondrejmirtes
left a comment
There was a problem hiding this comment.
Instead of DynamicMethodReturnTypeExtension you can use https://apiref.phpstan.org/1.11.x/PHPStan.Type.ExpressionTypeResolverExtension.html which is like that but lets you override any expression type.
e7063fd to
7b4c17e
Compare
|
@ondrejmirtes Thanks for the tip about It took me some time, however finally I've got ready to review version of this PR :) Please take a look in you free time :) Thank you very much! |
|
As static analyzing should works now fine with the result of // src/Action/ListItems.php
namespace App\Action;
use App\Message\ListItemsQuery;
use App\MessageHandler\ListItemsQueryResult;
use Symfony\Component\Messenger\HandleTrait;
use Symfony\Component\Messenger\MessageBusInterface;
class ListItems
{
use HandleTrait;
public function __construct(
private MessageBusInterface $messageBus,
) {
}
public function __invoke(): void
{
$result = $this->handle(new ListItemsQuery(/* ... */));
// $result should be automatically recognized as `ListItemsQueryResult` now by phpstan via SF configuration
// Do something with the result
// ...
}
}However, my target goal was to cover QueryBus classes with dynamic return types based on dynamic query, something like: // src/MessageBus/QueryBus.php
namespace App\MessageBus;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\HandleTrait;
use Symfony\Component\Messenger\MessageBusInterface;
class QueryBus
{
use HandleTrait;
public function __construct(
private MessageBusInterface $messageBus,
) {
}
// wherever that method is called, phpstan should recognize return type based on passed query
public function query($query)
{
return $this->handle($query);
}
}Maybe it's trivial and doable with already existing php-doc annotation or more complex to write some next extension for that, but I don't know how to connect the dots and reuse covered typing of |
b94aa51 to
d62ec03
Compare
|
@ondrejmirtes any chances to review that, please? 🙏 |
|
@ondrejmirtes I can see that version 2.0.0 is released now. Is 1.x abandoned from now? Should I rebase that PR to 2.x branch? Any chances to review and/or merge it please? 🙂 |
ondrejmirtes
left a comment
There was a problem hiding this comment.
No need to rebase this, we could release this in 1.x.
| /tests/tmp | ||
| /build-cs | ||
| /vendor | ||
| /.idea |
There was a problem hiding this comment.
Remove this please. This belongs to a global .gitignore.
|
|
||
| /** @var array{handles?: class-string, method?: string} $tagAttributes */ | ||
| $tagAttributes = $tag->getAttributes(); | ||
| $reflectionClass = $this->reflectionProvider->getClass($serviceClass); |
There was a problem hiding this comment.
This is unsafe. Ask for hasClass first.
| } | ||
|
|
||
| foreach ($handles as $messageClassName => $options) { | ||
| $methodReflection = $reflectionClass->getNativeMethod($options['method'] ?? self::DEFAULT_HANDLER_METHOD); |
There was a problem hiding this comment.
This is unsafe. Ask for hasNativeMethod first.
| private function guessHandledMessages(ClassReflection $reflectionClass): iterable | ||
| { | ||
| if ($reflectionClass->implementsInterface(MessageSubscriberInterface::class)) { | ||
| foreach ($reflectionClass->getName()::getHandledMessages() as $index => $value) { |
There was a problem hiding this comment.
Save the class name into a variable first, then call the method.
| return new MessageMap($messageMap); | ||
| } | ||
|
|
||
| /** @return array<class-string, array<string, string>> */ |
There was a problem hiding this comment.
This does not return an array. It's a generator.
There was a problem hiding this comment.
right, changed to iterable :)
| } | ||
|
|
||
| /** @var array{handles?: class-string, method?: string} $tagAttributes */ | ||
| $tagAttributes = $tag->getAttributes(); |
There was a problem hiding this comment.
Can we add a @return stub for this method instead of @var?
There was a problem hiding this comment.
This shape is dynamic and rely on what tag we're using from SF config. In this case I'm ensuring above that we're handling only messenger.message_handler, so we know what shape it should have.
As far I know stubs are static only, so unfortunately we cannot use them here.
Do you have other idea or it could stay as it is?
| * @phpstan-assert-if-true class-string $index | ||
| * @phpstan-assert-if-true array<string, mixed> $value | ||
| * @phpstan-assert-if-false int $index | ||
| * @phpstan-assert-if-false class-string $value |
There was a problem hiding this comment.
All of these asserts need =: https://phpstan.org/writing-php-code/narrowing-types#equality-assertions
| if ($this->isSupported($expr, $scope)) { | ||
| $arg = $expr->getArgs()[0]->value; | ||
| /** @var class-string[] $argClassNames */ | ||
| $argClassNames = $scope->getType($arg)->getObjectClassNames(); |
| } | ||
|
|
||
| /** | ||
| * @phpstan-assert-if-true MethodCall $expr |
|
|
||
| return $declaringClassReflection->getName() === self::TRAIT_NAME; | ||
| } catch (ReflectionException $e) { | ||
| return false; |
There was a problem hiding this comment.
Instead of catching exception, check if the method exists first.
|
@ondrejmirtes Thanks for review. I addressed all your comments, please take a look again when you have time :) Also, could you take a look on #404 (comment) and answer that if you have an idea how to solve it please? That's my initial goal to resolve by this PR :) |
| } else { | ||
| yield $value => ['method' => self::DEFAULT_HANDLER_METHOD]; | ||
| } | ||
| } catch (ShouldNotHappenException $e) { |
There was a problem hiding this comment.
Never catch ShouldNotHappenException.
| public function getType(Expr $expr, Scope $scope): ?Type | ||
| { | ||
| if ($this->isSupported($expr, $scope)) { | ||
| $arg = $expr->getArgs()[0]->value; |
There was a problem hiding this comment.
$expr->getArgs()[0] might not exist. Check the existence first.
|
@ondrejmirtes both comments fixed. Could you re-check? Thanks, in advance :) |
|
Thank you. Please be available if any issues arise. |
|
@ondrejmirtes Thank you for merging and your help with reviewing that :) Could you take a look on #404 (comment) comment please 🙏 I'm not sure whether I should create separate issue for that? Maybe it's able to achive with already existing features/annotations? If not, do you have an idea how to implement such feature? |
|
@bnowak Feel free to send a failing test so I can understand it better. |
|
Hi, since MessageSubscriberInterface has been deprecated/removed in favour of |
|
@ondrejmirtes I'll take a look on that tomorrow. Thanks for info 😉 |
|
Maybe it's the reason why some of the tests are failing on Symfony 7.3, maybe not: https://github.com/phpstan/phpstan-symfony/actions/runs/16193692378/job/45715050598?pr=447 |
|
Yes it is. I started working on tests separation and skipping the case when Regarding adding support for @ondrejmirtes What do you think? Does such order make sense? |
|
I think AsMessageHandler already existed before Symfony 7, didn't it? It'd make it easy to implement it here, and when my symfony-7-3 is rebased, less tests should fail. |
|
Ahh, you're right. I don't know why I assumed it was introduced in 7.0 😅 Tomorrow will try to handle both topics 😉 |
|
@ondrejmirtes please review #448. Regarding |
The goal is to cover #207.
That's my very first PR in phpstan-symfony plugin, so any feedback/tips/advices are very welcome :)