Checks whether a method/function call has too many arguments.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * This software package is licensed under `AGPL, Commercial` license[s]. |
||
5 | * |
||
6 | * @package maslosoft/signals |
||
7 | * @license AGPL, Commercial |
||
8 | * |
||
9 | * @copyright Copyright (c) Peter Maselkowski <[email protected]> |
||
10 | * |
||
11 | */ |
||
12 | |||
13 | namespace Maslosoft\Signals\Builder; |
||
14 | |||
15 | use Maslosoft\Addendum\Exceptions\ParseException; |
||
16 | use Maslosoft\Addendum\Interfaces\AnnotatedInterface; |
||
17 | use Maslosoft\Addendum\Utilities\AnnotationUtility; |
||
18 | use Maslosoft\Addendum\Utilities\FileWalker; |
||
19 | use Maslosoft\Signals\Helpers\DataSorter; |
||
20 | use Maslosoft\Signals\Helpers\NameNormalizer; |
||
21 | use Maslosoft\Signals\Interfaces\ExtractorInterface; |
||
22 | use Maslosoft\Signals\Meta\DocumentMethodMeta; |
||
23 | use Maslosoft\Signals\Meta\DocumentPropertyMeta; |
||
24 | use Maslosoft\Signals\Meta\DocumentTypeMeta; |
||
25 | use Maslosoft\Signals\Meta\SignalsMeta; |
||
26 | use Maslosoft\Signals\Signal; |
||
27 | use ReflectionClass; |
||
28 | use ReflectionException; |
||
29 | use UnexpectedValueException; |
||
30 | use Exception; |
||
31 | |||
32 | /** |
||
33 | * Addendum extractor |
||
34 | * |
||
35 | * @author Piotr Maselkowski <pmaselkowski at gmail.com> |
||
36 | */ |
||
37 | class Addendum implements ExtractorInterface |
||
38 | { |
||
39 | |||
40 | // Data keys for annotations extraction |
||
41 | const SlotFor = 'SlotFor'; |
||
42 | const SignalFor = 'SignalFor'; |
||
43 | // Default annotation names |
||
44 | const SlotName = 'SlotFor'; |
||
45 | const SignalName = 'SignalFor'; |
||
46 | |||
47 | /** |
||
48 | * Signal instance |
||
49 | * @var Signal |
||
50 | */ |
||
51 | private $signal = null; |
||
52 | |||
53 | /** |
||
54 | * Signals and slots data |
||
55 | * @var mixed |
||
56 | */ |
||
57 | private $data = [ |
||
58 | Signal::Slots => [ |
||
59 | ], |
||
60 | Signal::Signals => [ |
||
61 | ] |
||
62 | ]; |
||
63 | |||
64 | /** |
||
65 | * Scanned file paths |
||
66 | * @var string[] |
||
67 | */ |
||
68 | private $paths = []; |
||
69 | |||
70 | /** |
||
71 | * Annotations mathing patterns |
||
72 | * @var string[] |
||
73 | */ |
||
74 | private $patterns = []; |
||
75 | |||
76 | 15 | public function __construct() |
|
77 | { |
||
78 | $annotations = [ |
||
79 | 15 | self::SlotFor, |
|
80 | self::SignalFor |
||
81 | 15 | ]; |
|
82 | 15 | foreach ($annotations as $annotation) |
|
83 | { |
||
84 | 15 | $annotation = preg_replace('~^@~', '', $annotation); |
|
85 | 15 | $this->patterns[] = sprintf('~@%s~', $annotation); |
|
86 | 15 | } |
|
87 | 15 | } |
|
88 | |||
89 | /** |
||
90 | * Get signals and slots data |
||
91 | * @return mixed |
||
92 | */ |
||
93 | public function getData() |
||
94 | { |
||
95 | (new FileWalker([], [$this, 'processFile'], $this->signal->paths, $this->signal->ignoreDirs))->walk(); |
||
0 ignored issues
–
show
|
|||
96 | DataSorter::sort($this->data); |
||
97 | return $this->data; |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * Get scanned paths. This is available only after getData call. |
||
102 | * @return string[] |
||
103 | */ |
||
104 | public function getPaths() |
||
105 | { |
||
106 | return $this->paths; |
||
107 | } |
||
108 | |||
109 | /** |
||
110 | * Set signal instance |
||
111 | * @param Signal $signal |
||
112 | */ |
||
113 | public function setSignal(Signal $signal) |
||
114 | { |
||
115 | $this->signal = $signal; |
||
116 | } |
||
117 | |||
118 | /** |
||
119 | * @param string $file |
||
120 | */ |
||
121 | public function processFile($file, $contents) |
||
122 | { |
||
123 | $file = realpath($file); |
||
124 | $this->paths[] = $file; |
||
125 | // Remove initial `\` from namespace |
||
126 | try |
||
127 | { |
||
128 | $annotated = AnnotationUtility::rawAnnotate($file); |
||
129 | } |
||
130 | catch (ParseException $e) |
||
131 | { |
||
132 | $this->log($e, $file); |
||
133 | return; |
||
134 | } |
||
135 | catch (UnexpectedValueException $e) |
||
136 | { |
||
137 | $this->log($e, $file); |
||
138 | return; |
||
139 | } |
||
140 | $namespace = preg_replace('~^\\\\+~', '', $annotated['namespace']); |
||
141 | $className = $annotated['className']; |
||
142 | |||
143 | |||
144 | // Use fully qualified name, class must autoload |
||
145 | $fqn = $namespace . '\\' . $className; |
||
146 | NameNormalizer::normalize($fqn); |
||
147 | |||
148 | try |
||
149 | { |
||
150 | $info = new ReflectionClass($fqn); |
||
151 | } |
||
152 | catch (ReflectionException $e) |
||
153 | { |
||
154 | $this->log($e, $file); |
||
155 | return; |
||
156 | } |
||
157 | $isAnnotated = $info->implementsInterface(AnnotatedInterface::class); |
||
158 | $hasSignals = $this->hasSignals($contents); |
||
159 | $isAbstract = $info->isAbstract() || $info->isInterface(); |
||
160 | |||
161 | // Old classes must now implement interface |
||
162 | // Brake BC! |
||
163 | if ($hasSignals && !$isAnnotated && !$isAbstract) |
||
164 | { |
||
165 | throw new UnexpectedValueException(sprintf('Class %s must implement %s to use signals', $fqn, AnnotatedInterface::class)); |
||
166 | } |
||
167 | |||
168 | // Skip not annotated class |
||
169 | if (!$isAnnotated) |
||
170 | { |
||
171 | return; |
||
172 | } |
||
173 | |||
174 | // Skip abstract classes |
||
175 | if ($isAbstract) |
||
176 | { |
||
177 | return; |
||
178 | } |
||
179 | $meta = @SignalsMeta::create($fqn); |
||
180 | /* @var $typeMeta DocumentTypeMeta */ |
||
181 | $typeMeta = $meta->type(); |
||
182 | |||
183 | // Signals |
||
184 | foreach ($typeMeta->signalFor as $slot) |
||
185 | { |
||
186 | $this->data[Signal::Slots][$slot][$fqn] = true; |
||
187 | } |
||
188 | |||
189 | // Slots |
||
190 | // For constructor injection |
||
191 | foreach ($typeMeta->slotFor as $slot) |
||
192 | { |
||
193 | $key = implode('@', [$fqn, '__construct', '()']); |
||
194 | $this->data[Signal::Signals][$slot][$fqn][$key] = true; |
||
195 | } |
||
196 | |||
197 | // For method injection |
||
198 | foreach ($meta->methods() as $methodName => $method) |
||
199 | { |
||
200 | /* @var $method DocumentMethodMeta */ |
||
201 | foreach ($method->slotFor as $slot) |
||
202 | { |
||
203 | $key = implode('@', [$fqn, $methodName, '()']); |
||
204 | $this->data[Signal::Signals][$slot][$fqn][$key] = sprintf('%s()', $methodName); |
||
205 | } |
||
206 | } |
||
207 | |||
208 | // For property injection |
||
209 | foreach ($meta->fields() as $fieldName => $field) |
||
210 | { |
||
211 | /* @var $field DocumentPropertyMeta */ |
||
212 | foreach ($field->slotFor as $slot) |
||
213 | { |
||
214 | $key = implode('@', [$fqn, $fieldName]); |
||
215 | $this->data[Signal::Signals][$slot][$fqn][$key] = sprintf('%s', $fieldName); |
||
216 | } |
||
217 | } |
||
218 | } |
||
219 | |||
220 | private function hasSignals($contents) |
||
221 | { |
||
222 | foreach ($this->patterns as $pattern) |
||
223 | { |
||
224 | if (preg_match($pattern, $contents)) |
||
225 | { |
||
226 | return true; |
||
227 | } |
||
228 | } |
||
229 | return false; |
||
230 | } |
||
231 | |||
232 | private function log(Exception $e, $file) |
||
233 | { |
||
234 | $msg = sprintf('Exception: "%s" while scanning file `%s`', $e->getMessage(), $file); |
||
235 | $this->signal->getLogger()->warning($msg); |
||
236 | } |
||
237 | |||
238 | } |
||
239 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.