Passed
Push — master ( 10e57a...e3c124 )
by Ross
02:20
created

TacticianRuleSet   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 159
Duplicated Lines 0 %

Test Coverage

Coverage 97.14%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 76
c 5
b 0
f 0
dl 0
loc 159
ccs 68
cts 70
cp 0.9714
rs 10
wmc 20

5 Methods

Rating   Name   Duplication   Size   Complexity  
B inspectCommandType() 0 60 6
A getInspectableCommandTypes() 0 25 5
B processNode() 0 30 7
A __construct() 0 10 1
A getNodeType() 0 3 1
1
<?php
2
declare(strict_types=1);
3
4
namespace League\Tactician\PHPStan;
5
6
use League\Tactician\Handler\Mapping\CommandToHandlerMapping;
7
use PhpParser\Node;
0 ignored issues
show
Bug introduced by
The type PhpParser\Node was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use PhpParser\Node\Expr\MethodCall;
0 ignored issues
show
Bug introduced by
The type PhpParser\Node\Expr\MethodCall was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use PHPStan\Analyser\Scope;
0 ignored issues
show
Bug introduced by
The type PHPStan\Analyser\Scope was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use PHPStan\Broker\Broker;
0 ignored issues
show
Bug introduced by
The type PHPStan\Broker\Broker was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
use PHPStan\Broker\ClassNotFoundException;
0 ignored issues
show
Bug introduced by
The type PHPStan\Broker\ClassNotFoundException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use PHPStan\Reflection\MissingMethodFromReflectionException;
0 ignored issues
show
Bug introduced by
The type PHPStan\Reflection\Missi...FromReflectionException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use PHPStan\Reflection\ParametersAcceptorSelector;
0 ignored issues
show
Bug introduced by
The type PHPStan\Reflection\ParametersAcceptorSelector was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use PHPStan\Rules\Rule;
0 ignored issues
show
Bug introduced by
The type PHPStan\Rules\Rule was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
use PHPStan\Type\ObjectType;
0 ignored issues
show
Bug introduced by
The type PHPStan\Type\ObjectType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use PHPStan\Type\Type;
0 ignored issues
show
Bug introduced by
The type PHPStan\Type\Type was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use PHPStan\Type\TypeWithClassName;
0 ignored issues
show
Bug introduced by
The type PHPStan\Type\TypeWithClassName was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use PHPStan\Type\UnionType;
0 ignored issues
show
Bug introduced by
The type PHPStan\Type\UnionType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
use function array_filter;
20
use function array_merge;
21
22
/** @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr\MethodCall> */
23
final class TacticianRuleSet implements Rule
24
{
25
    /**
26
     * @var CommandToHandlerMapping
27
     */
28
    private $mapping;
29
    /**
30
     * @var Broker
31
     */
32
    private $broker;
33
    /**
34
     * @var string
35
     */
36
    private $commandBusClass;
37
    /**
38
     * @var string
39
     */
40
    private $commandBusMethod;
41
42 14
    public function __construct(
43
        CommandToHandlerMapping $mapping,
44
        Broker $broker,
45
        string $commandBusClass,
46
        string $commandBusMethod
47
    ) {
48 14
        $this->mapping = $mapping;
49 14
        $this->broker = $broker;
50 14
        $this->commandBusClass = $commandBusClass;
51 14
        $this->commandBusMethod = $commandBusMethod;
52 14
    }
53
54 14
    public function getNodeType(): string
55
    {
56 14
        return MethodCall::class;
57
    }
58
59 14
    public function processNode(Node $methodCall, Scope $scope): array
60
    {
61 14
        if (! $methodCall instanceof MethodCall
62 14
            || ! $methodCall->name instanceof Node\Identifier
0 ignored issues
show
Bug introduced by
The type PhpParser\Node\Identifier was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
63 14
            || $methodCall->name->name !== $this->commandBusMethod) {
64 1
            return [];
65
        }
66
67 13
        $type = $scope->getType($methodCall->var);
68
69 13
        if (! (new ObjectType($this->commandBusClass))->isSuperTypeOf($type)->yes()) {
70
            return [];
71
        }
72
73
        // Wrong number of arguments passed to handle? Delegate to other PHPStan rules
74 13
        if (count($methodCall->args) !== 1) {
75 1
            return []; //
76
        }
77
78 12
        $commandType = $scope->getType($methodCall->args[0]->value);
79
80 12
        $errors = [];
81 12
        foreach ($this->getInspectableCommandTypes($commandType) as $commandType) {
82 11
            $errors = array_merge(
83 11
                $errors,
84 11
                $this->inspectCommandType($methodCall, $scope, $commandType)
85
            );
86
        }
87
88 12
        return $errors;
89
    }
90
91
    /**
92
     * @return array<string>
93
     */
94 11
    private function inspectCommandType(
95
        MethodCall $methodCallOnBus,
96
        Scope $scope,
97
        TypeWithClassName $commandType
98
    ): array {
99 11
        $handlerClassName = $this->mapping->getClassName($commandType->getClassName());
100
101
        try {
102 11
            $handlerClass = $this->broker->getClass($handlerClassName);
103 2
        } catch (ClassNotFoundException $e) {
104
            return [
105 2
                "Tactician tried to route the command {$commandType->getClassName()} but could not find the matching " .
106 2
                "handler {$handlerClassName}.",
107
            ];
108
        }
109
110 10
        $handlerMethodName = $this->mapping->getMethodName($commandType->getClassName());
111
112
        try {
113 10
            $handlerMethod = $handlerClass->getMethod($handlerMethodName, $scope);
114 1
        } catch (MissingMethodFromReflectionException $e) {
115
            return [
116 1
                "Tactician tried to route the command {$commandType->getClassName()} to " .
117 1
                "{$handlerClass->getName()}::{$handlerMethodName} but while the class could be loaded, the method " .
118 1
                "'{$handlerMethodName}' could not be found on the class.",
119
            ];
120
        }
121
122
        /** @var \PHPStan\Reflection\ParameterReflection[] $parameters */
123 9
        $parameters = ParametersAcceptorSelector::selectFromArgs(
124 9
            $scope,
125 9
            $methodCallOnBus->args,
126 9
            $handlerMethod->getVariants()
127 9
        )->getParameters();
128
129 9
        if (count($parameters) === 0) {
130
            return [
131 2
                "Tactician tried to route the command {$commandType->getClassName()} to " .
132 2
                "{$handlerClass->getName()}::{$handlerMethodName} but the method '{$handlerMethodName}' does not " .
133 2
                "accept any parameters.",
134
            ];
135
        }
136
137 7
        if (count($parameters) > 1) {
138
            return [
139 1
                "Tactician tried to route the command {$commandType->getClassName()} to " .
140 1
                "{$handlerClass->getName()}::{$handlerMethodName} but the method '{$handlerMethodName}' accepts " .
141 1
                "too many parameters.",
142
            ];
143
        }
144
145 6
        if ($parameters[0]->getType()->accepts($commandType, true)->no()) {
146
            return [
147 2
                "Tactician tried to route the command {$commandType->getClassName()} to " .
148 2
                "{$handlerClass->getName()}::{$handlerMethodName} but the method '{$handlerMethodName}' has a " .
149 2
                "typehint that does not allow this command.",
150
            ];
151
        }
152
153 4
        return [];
154
    }
155
156
    /** @return TypeWithClassName[] */
157 12
    private function getInspectableCommandTypes(Type $type): array
158
    {
159 12
        $types = [];
160 12
        if ($type instanceof TypeWithClassName) {
161 11
            $types = [$type];
162
        }
163
164 12
        if ($type instanceof UnionType) {
165 1
            $types = array_filter(
166 1
                $type->getTypes(),
167
                function (Type $type) {
168 1
                    return $type instanceof TypeWithClassName;
169 1
                }
170
            );
171
        }
172
173 12
        return array_filter(
174 12
            $types,
175
            function (TypeWithClassName $type) {
176 12
                if (! $this->broker->hasClass($type->getClassName())) {
177
                    return false;
178
                }
179
180 12
                $class = $this->broker->getClass($type->getClassName());
181 12
                return ! $class->isInterface() && ! $class->isAbstract();
182 12
            }
183
        );
184
    }
185
}
186