From 09b9c7505387c3c5d7f2d13ed4e6429e379e19f4 Mon Sep 17 00:00:00 2001 From: Ross Tuck Date: Sun, 28 Jul 2019 14:30:42 +0200 Subject: [PATCH] Split PHPStan support out into own repo --- .travis.yml | 2 +- README.md | 36 +--- composer.json | 3 - handler-mapping-loader.php | 10 - phpstan.neon | 18 -- src/PHPStan/HandlerReturnTypeExtension.php | 83 -------- src/PHPStan/MappingLoader.php | 19 -- src/PHPStan/TacticianRuleSet.php | 158 --------------- .../HandlerReturnTypeExtensionTest.php | 34 ---- tests/PHPStan/TacticianRuleSetTest.php | 185 ------------------ .../AcceptsExactParameterTypehintMatch.php | 21 -- .../data/AcceptsGenericObjectTypehint.php | 21 -- .../data/AcceptsMissingParameterTypehint.php | 21 -- .../AcceptsSuperclassAsParameterTypeHint.php | 26 --- .../CommandBusCalledWithTooManyParameters.php | 9 - ...sNotAcceptSomeUnrelatedScalarParameter.php | 21 -- ...esNotAcceptSubclassAsParameterTypeHint.php | 26 --- .../DoesNotInterfereWithOtherMethodCalls.php | 7 - .../data/HandlerDoesNotTakeParameters.php | 21 -- .../data/HandlerTakesTooManyParameters.php | 21 -- .../data/IncorrectHandlerReturnType-5.json | 7 - .../data/IncorrectHandlerReturnType.php | 26 --- .../PHPStan/data/InspectsEachTypeInUnion.php | 27 --- tests/PHPStan/data/MissingHandlerClass-0.json | 7 - tests/PHPStan/data/MissingHandlerClass.php | 14 -- tests/PHPStan/data/MissingHandlerMethod.php | 21 -- .../PHPStan/data/MissingHandlerReturnType.php | 23 --- tests/PHPStan/data/VoidReturnType-2.json | 12 -- tests/PHPStan/data/VoidReturnType.php | 23 --- 29 files changed, 2 insertions(+), 900 deletions(-) delete mode 100644 handler-mapping-loader.php delete mode 100644 phpstan.neon delete mode 100644 src/PHPStan/HandlerReturnTypeExtension.php delete mode 100644 src/PHPStan/MappingLoader.php delete mode 100644 src/PHPStan/TacticianRuleSet.php delete mode 100644 tests/PHPStan/HandlerReturnTypeExtensionTest.php delete mode 100644 tests/PHPStan/TacticianRuleSetTest.php delete mode 100644 tests/PHPStan/data/AcceptsExactParameterTypehintMatch.php delete mode 100644 tests/PHPStan/data/AcceptsGenericObjectTypehint.php delete mode 100644 tests/PHPStan/data/AcceptsMissingParameterTypehint.php delete mode 100644 tests/PHPStan/data/AcceptsSuperclassAsParameterTypeHint.php delete mode 100644 tests/PHPStan/data/CommandBusCalledWithTooManyParameters.php delete mode 100644 tests/PHPStan/data/DoesNotAcceptSomeUnrelatedScalarParameter.php delete mode 100644 tests/PHPStan/data/DoesNotAcceptSubclassAsParameterTypeHint.php delete mode 100644 tests/PHPStan/data/DoesNotInterfereWithOtherMethodCalls.php delete mode 100644 tests/PHPStan/data/HandlerDoesNotTakeParameters.php delete mode 100644 tests/PHPStan/data/HandlerTakesTooManyParameters.php delete mode 100644 tests/PHPStan/data/IncorrectHandlerReturnType-5.json delete mode 100644 tests/PHPStan/data/IncorrectHandlerReturnType.php delete mode 100644 tests/PHPStan/data/InspectsEachTypeInUnion.php delete mode 100644 tests/PHPStan/data/MissingHandlerClass-0.json delete mode 100644 tests/PHPStan/data/MissingHandlerClass.php delete mode 100644 tests/PHPStan/data/MissingHandlerMethod.php delete mode 100644 tests/PHPStan/data/MissingHandlerReturnType.php delete mode 100644 tests/PHPStan/data/VoidReturnType-2.json delete mode 100644 tests/PHPStan/data/VoidReturnType.php diff --git a/.travis.yml b/.travis.yml index 4991d2f..7819bcb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ before_script: - mkdir -p build/logs script: - - vendor/bin/phpstan --level=max analyse src + - vendor/bin/phpstan --level=max analyse src tests - vendor/bin/phpunit --coverage-clover=build/logs/clover.xml - vendor/bin/phpcs --standard=PSR2 src diff --git a/README.md b/README.md index f0b877e..33fefa2 100644 --- a/README.md +++ b/README.md @@ -20,45 +20,11 @@ Using Composer: ## Plugins The core Tactician package is small but there are several plugin packages that extend the usefulness of Tactician: +- [PHPStan](https://github.com/thephpleague/tactician-phpstan): Add static analysis support to Tactician. Highly recommended. - [Logger](https://github.com/thephpleague/tactician-logger): Adds PSR-3 logging support for receiving, completing or failing commands. - [Doctrine](https://github.com/thephpleague/tactician-doctrine): Wraps commands in separate Doctrine ORM transactions. - [and many more](https://packagist.org/search/?q=tactician) -## PHPStan Integration - -Traditionally, command buses can obscure static analysis. The Tactician PHPStan plugin helps bring stronger type checking by finding missing handler classes, validating handler return types and more. - -You'll need to make your `CommandToHandlerMapping` available to PHPStan. The easiest way to do this is to create a small bootstrap file that returns the same Handler configuration you use in your app. - -A simple version of this might look like: - -~~~ -# handler-mapper-loader.php -mapping = $mapping; - } - - public function setBroker(Broker $broker): void - { - $this->broker = $broker; - } - - public function getClass(): string - { - return CommandBus::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === 'handle'; - } - - public function getTypeFromMethodCall( - MethodReflection $methodReflection, - MethodCall $methodCall, - Scope $scope - ): Type { - $commandType = $scope->getType($methodCall->args[0]->value); - - if (! $commandType instanceof ObjectType) { - return new MixedType(); - } - - try { - $handlerClass = $this->broker->getClass( - $this->mapping->getClassName($commandType->getClassName()) - ); - } catch (ClassNotFoundException $e) { - return new MixedType(); - } - - $methodName = $this->mapping->getMethodName($commandType->getClassName()); - - try { - $method = $handlerClass->getMethod($methodName, $scope)->getVariants(); - } catch (MissingMethodFromReflectionException $e) { - return new MixedType(); - } - - return ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->args, $method)->getReturnType(); - } -} diff --git a/src/PHPStan/MappingLoader.php b/src/PHPStan/MappingLoader.php deleted file mode 100644 index d1b9a71..0000000 --- a/src/PHPStan/MappingLoader.php +++ /dev/null @@ -1,19 +0,0 @@ -mapping = $mapping; - $this->broker = $broker; - } - - public function getNodeType(): string - { - return MethodCall::class; - } - - public function processNode(Node $methodCall, Scope $scope): array - { - if (! $methodCall instanceof MethodCall) { - return []; - } - - $type = $scope->getType($methodCall->var); - - if (! (new ObjectType(CommandBus::class))->isSuperTypeOf($type)->yes()) { - return []; - } - - // Wrong number of arguments passed to handle? Delegate to other PHPStan rules - if (count($methodCall->args) !== 1) { - return []; // - } - - $commandType = $scope->getType($methodCall->args[0]->value); - - $errors = []; - foreach ($this->getInspectableCommandTypes($commandType) as $commandType) { - $errors = array_merge( - $errors, - $this->inspectCommandType($methodCall, $scope, $commandType) - ); - } - - return $errors; - } - - /** - * @return array - */ - private function inspectCommandType( - MethodCall $methodCallOnBus, - Scope $scope, - TypeWithClassName $commandType - ): array { - $handlerClassName = $this->mapping->getClassName($commandType->getClassName()); - - try { - $handlerClass = $this->broker->getClass($handlerClassName); - } catch (ClassNotFoundException $e) { - return [ - "Tactician tried to route the command {$commandType->getClassName()} but could not find the matching " . - "handler {$handlerClassName}.", - ]; - } - - $handlerMethodName = $this->mapping->getMethodName($commandType->getClassName()); - - try { - $handlerMethod = $handlerClass->getMethod($handlerMethodName, $scope); - } catch (MissingMethodFromReflectionException $e) { - return [ - "Tactician tried to route the command {$commandType->getClassName()} to " . - "{$handlerClass->getName()}::{$handlerMethodName} but while the class could be loaded, the method " . - "'{$handlerMethodName}' could not be found on the class.", - ]; - } - - /** @var \PHPStan\Reflection\ParameterReflection[] $parameters */ - $parameters = ParametersAcceptorSelector::selectFromArgs( - $scope, - $methodCallOnBus->args, - $handlerMethod->getVariants() - )->getParameters(); - - if (count($parameters) === 0) { - return [ - "Tactician tried to route the command {$commandType->getClassName()} to " . - "{$handlerClass->getName()}::{$handlerMethodName} but the method '{$handlerMethodName}' does not " . - "accept any parameters.", - ]; - } - - if (count($parameters) > 1) { - return [ - "Tactician tried to route the command {$commandType->getClassName()} to " . - "{$handlerClass->getName()}::{$handlerMethodName} but the method '{$handlerMethodName}' accepts " . - "too many parameters.", - ]; - } - - if ($parameters[0]->getType()->accepts($commandType, true)->no()) { - return [ - "Tactician tried to route the command {$commandType->getClassName()} to " . - "{$handlerClass->getName()}::{$handlerMethodName} but the method '{$handlerMethodName}' has a " . - "typehint that does not allow this command.", - ]; - } - - return []; - } - - /** @return TypeWithClassName[] */ - private function getInspectableCommandTypes(Type $type): array - { - if ($type instanceof TypeWithClassName) { - return [$type]; - } - - if ($type instanceof UnionType) { - return array_filter( - $type->getTypes(), - function (Type $type) { - return $type instanceof TypeWithClassName; - } - ); - } - - return []; - } -} diff --git a/tests/PHPStan/HandlerReturnTypeExtensionTest.php b/tests/PHPStan/HandlerReturnTypeExtensionTest.php deleted file mode 100644 index 0bf0360..0000000 --- a/tests/PHPStan/HandlerReturnTypeExtensionTest.php +++ /dev/null @@ -1,34 +0,0 @@ -createBroker() - ); - } - - public function testCanNotFindMatchingHandlerClass(): void - { - $this->analyse( - [__DIR__ . '/data/MissingHandlerClass.php'], - [ - [ - 'Tactician tried to route the command MissingHandlerClass\SomeCommand ' . - 'but could not find the matching handler MissingHandlerClass\SomeCommandHandler.', - 14 - ] - ] - ); - } - - public function testCanNotFindCorrectMethodOnHandler(): void - { - $this->analyse( - [__DIR__ . '/data/MissingHandlerMethod.php'], - [ - [ - 'Tactician tried to route the command MissingHandlerMethod\SomeCommand to ' . - 'MissingHandlerMethod\SomeCommandHandler::handle but while the class could be loaded, ' . - 'the method \'handle\' could not be found on the class.', - 21 - ] - ] - ); - } - - public function testHandlerDoesNotTakeParameters(): void - { - $this->analyse( - [__DIR__ . '/data/HandlerDoesNotTakeParameters.php'], - [ - [ - 'Tactician tried to route the command HandlerDoesNotTakeParameters\SomeCommand to ' . - 'HandlerDoesNotTakeParameters\SomeCommandHandler::handle but the method \'handle\' ' . - 'does not accept any parameters.', - 21 - ] - ] - ); - } - - public function testHandlerTakesTooManyParameters(): void - { - $this->analyse( - [__DIR__ . '/data/HandlerTakesTooManyParameters.php'], - [ - [ - 'Tactician tried to route the command HandlerTakesTooManyParameters\SomeCommand to ' . - 'HandlerTakesTooManyParameters\SomeCommandHandler::handle but the method \'handle\' ' . - 'accepts too many parameters.', - 21 - ] - ] - ); - } - - public function testAcceptsExactParameterTypehintMatch(): void - { - $this->analyse( - [__DIR__ . '/data/AcceptsExactParameterTypehintMatch.php'], - [ - ] - ); - } - - public function testAcceptsSuperclassAsParameterTypeHint(): void - { - $this->analyse( - [__DIR__ . '/data/AcceptsSuperclassAsParameterTypeHint.php'], - [ - ] - ); - } - - public function testAcceptsGenericObjectTypehint(): void - { - $this->analyse( - [__DIR__ . '/data/AcceptsGenericObjectTypehint.php'], - [ - ] - ); - } - - public function testDoesNotAcceptSubclassAsParameterTypeHint(): void - { - $this->analyse( - [__DIR__ . '/data/DoesNotAcceptSubclassAsParameterTypeHint.php'], - [ - [ - 'Tactician tried to route the command DoesNotAcceptSubclassAsParameterTypeHint\SomeCommand to ' . - 'DoesNotAcceptSubclassAsParameterTypeHint\SomeCommandHandler::handle but the method \'handle\' ' . - 'has a typehint that does not allow this command.', - 26 - ] - ] - ); - } - - public function testDoesNotAcceptSomeUnrelatedScalarParameter(): void - { - $this->analyse( - [__DIR__ . '/data/DoesNotAcceptSomeUnrelatedScalarParameter.php'], - [ - [ - 'Tactician tried to route the command DoesNotAcceptSomeUnrelatedScalarParameter\SomeCommand to ' . - 'DoesNotAcceptSomeUnrelatedScalarParameter\SomeCommandHandler::handle but the method \'handle\' ' . - 'has a typehint that does not allow this command.', - 21 - ] - ] - ); - } - - public function testCommandBusCalledWithTooManyParameters(): void - { - $this->analyse( - [__DIR__ . '/data/CommandBusCalledWithTooManyParameters.php'], - [ - ] - ); - } - - public function testDoesNotInterfereWithOtherMethodCalls(): void - { - $this->analyse( - [__DIR__ . '/data/DoesNotInterfereWithOtherMethodCalls.php'], - [ - ] - ); - } - - public function testAcceptsMissingParameterTypehint(): void - { - $this->analyse( - [__DIR__ . '/data/AcceptsMissingParameterTypehint.php'], - [ - ] - ); - } - - public function testInspectsEachTypeInUnion(): void - { - $this->analyse( - [__DIR__ . '/data/InspectsEachTypeInUnion.php'], - [ - [ - 'Tactician tried to route the command InspectsEachTypeInUnion\OtherCommand but could not find the matching handler InspectsEachTypeInUnion\OtherCommandHandler.', - 26 - ], - [ - 'Tactician tried to route the command InspectsEachTypeInUnion\SomeCommand to InspectsEachTypeInUnion\SomeCommandHandler::handle but the method \'handle\' does not accept any parameters.', - 26 - ] - ] - ); - } -} diff --git a/tests/PHPStan/data/AcceptsExactParameterTypehintMatch.php b/tests/PHPStan/data/AcceptsExactParameterTypehintMatch.php deleted file mode 100644 index 409e1c3..0000000 --- a/tests/PHPStan/data/AcceptsExactParameterTypehintMatch.php +++ /dev/null @@ -1,21 +0,0 @@ -handle(new SomeCommand()); diff --git a/tests/PHPStan/data/AcceptsGenericObjectTypehint.php b/tests/PHPStan/data/AcceptsGenericObjectTypehint.php deleted file mode 100644 index 8d57abe..0000000 --- a/tests/PHPStan/data/AcceptsGenericObjectTypehint.php +++ /dev/null @@ -1,21 +0,0 @@ -handle(new SomeCommand()); diff --git a/tests/PHPStan/data/AcceptsMissingParameterTypehint.php b/tests/PHPStan/data/AcceptsMissingParameterTypehint.php deleted file mode 100644 index 72a6192..0000000 --- a/tests/PHPStan/data/AcceptsMissingParameterTypehint.php +++ /dev/null @@ -1,21 +0,0 @@ -handle(new SomeCommand()); diff --git a/tests/PHPStan/data/AcceptsSuperclassAsParameterTypeHint.php b/tests/PHPStan/data/AcceptsSuperclassAsParameterTypeHint.php deleted file mode 100644 index f4c92e9..0000000 --- a/tests/PHPStan/data/AcceptsSuperclassAsParameterTypeHint.php +++ /dev/null @@ -1,26 +0,0 @@ -handle(new SomeCommand()); diff --git a/tests/PHPStan/data/CommandBusCalledWithTooManyParameters.php b/tests/PHPStan/data/CommandBusCalledWithTooManyParameters.php deleted file mode 100644 index 6682ffd..0000000 --- a/tests/PHPStan/data/CommandBusCalledWithTooManyParameters.php +++ /dev/null @@ -1,9 +0,0 @@ -handle(new \stdClass(), true, false, true, 'again'); diff --git a/tests/PHPStan/data/DoesNotAcceptSomeUnrelatedScalarParameter.php b/tests/PHPStan/data/DoesNotAcceptSomeUnrelatedScalarParameter.php deleted file mode 100644 index 337ca03..0000000 --- a/tests/PHPStan/data/DoesNotAcceptSomeUnrelatedScalarParameter.php +++ /dev/null @@ -1,21 +0,0 @@ -handle(new SomeCommand()); diff --git a/tests/PHPStan/data/DoesNotAcceptSubclassAsParameterTypeHint.php b/tests/PHPStan/data/DoesNotAcceptSubclassAsParameterTypeHint.php deleted file mode 100644 index 9a2b9b9..0000000 --- a/tests/PHPStan/data/DoesNotAcceptSubclassAsParameterTypeHint.php +++ /dev/null @@ -1,26 +0,0 @@ -handle(new SomeCommand()); diff --git a/tests/PHPStan/data/DoesNotInterfereWithOtherMethodCalls.php b/tests/PHPStan/data/DoesNotInterfereWithOtherMethodCalls.php deleted file mode 100644 index 8bb98ef..0000000 --- a/tests/PHPStan/data/DoesNotInterfereWithOtherMethodCalls.php +++ /dev/null @@ -1,7 +0,0 @@ -diff(new \DateTime('yesterday')); diff --git a/tests/PHPStan/data/HandlerDoesNotTakeParameters.php b/tests/PHPStan/data/HandlerDoesNotTakeParameters.php deleted file mode 100644 index e8b4408..0000000 --- a/tests/PHPStan/data/HandlerDoesNotTakeParameters.php +++ /dev/null @@ -1,21 +0,0 @@ -handle(new SomeCommand()); diff --git a/tests/PHPStan/data/HandlerTakesTooManyParameters.php b/tests/PHPStan/data/HandlerTakesTooManyParameters.php deleted file mode 100644 index 0b20fca..0000000 --- a/tests/PHPStan/data/HandlerTakesTooManyParameters.php +++ /dev/null @@ -1,21 +0,0 @@ -handle(new SomeCommand()); diff --git a/tests/PHPStan/data/IncorrectHandlerReturnType-5.json b/tests/PHPStan/data/IncorrectHandlerReturnType-5.json deleted file mode 100644 index 31d7c44..0000000 --- a/tests/PHPStan/data/IncorrectHandlerReturnType-5.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "message": "Parameter #1 $input of function array_values expects array, string given.", - "line": 23, - "ignorable": true - } -] \ No newline at end of file diff --git a/tests/PHPStan/data/IncorrectHandlerReturnType.php b/tests/PHPStan/data/IncorrectHandlerReturnType.php deleted file mode 100644 index 9bc2dfc..0000000 --- a/tests/PHPStan/data/IncorrectHandlerReturnType.php +++ /dev/null @@ -1,26 +0,0 @@ -handle(new SomeCommand()) - ); -} diff --git a/tests/PHPStan/data/InspectsEachTypeInUnion.php b/tests/PHPStan/data/InspectsEachTypeInUnion.php deleted file mode 100644 index c161888..0000000 --- a/tests/PHPStan/data/InspectsEachTypeInUnion.php +++ /dev/null @@ -1,27 +0,0 @@ -handle($something); -} diff --git a/tests/PHPStan/data/MissingHandlerClass-0.json b/tests/PHPStan/data/MissingHandlerClass-0.json deleted file mode 100644 index bcbd146..0000000 --- a/tests/PHPStan/data/MissingHandlerClass-0.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "message": "Tactician tried to route the command MissingHandlerClass\\SomeCommand but could not find the matching handler MissingHandlerClass\\SomeCommandHandler.", - "line": 14, - "ignorable": true - } -] \ No newline at end of file diff --git a/tests/PHPStan/data/MissingHandlerClass.php b/tests/PHPStan/data/MissingHandlerClass.php deleted file mode 100644 index e8e9743..0000000 --- a/tests/PHPStan/data/MissingHandlerClass.php +++ /dev/null @@ -1,14 +0,0 @@ -handle(new SomeCommand()); diff --git a/tests/PHPStan/data/MissingHandlerMethod.php b/tests/PHPStan/data/MissingHandlerMethod.php deleted file mode 100644 index c010e90..0000000 --- a/tests/PHPStan/data/MissingHandlerMethod.php +++ /dev/null @@ -1,21 +0,0 @@ -handle(new SomeCommand()); diff --git a/tests/PHPStan/data/MissingHandlerReturnType.php b/tests/PHPStan/data/MissingHandlerReturnType.php deleted file mode 100644 index 5127182..0000000 --- a/tests/PHPStan/data/MissingHandlerReturnType.php +++ /dev/null @@ -1,23 +0,0 @@ -handle(new SomeCommand()); -} diff --git a/tests/PHPStan/data/VoidReturnType-2.json b/tests/PHPStan/data/VoidReturnType-2.json deleted file mode 100644 index 000f867..0000000 --- a/tests/PHPStan/data/VoidReturnType-2.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "message": "Binary operation \"+\" between 1 and void results in an error.", - "line": 22, - "ignorable": true - }, - { - "message": "Result of method League\\Tactician\\CommandBus::handle() (void) is used.", - "line": 22, - "ignorable": true - } -] \ No newline at end of file diff --git a/tests/PHPStan/data/VoidReturnType.php b/tests/PHPStan/data/VoidReturnType.php deleted file mode 100644 index b9beb62..0000000 --- a/tests/PHPStan/data/VoidReturnType.php +++ /dev/null @@ -1,23 +0,0 @@ -handle(new SomeCommand()); -}