1 | <?php |
||||
2 | |||||
3 | namespace PortlandLabs\Slackbot\Command; |
||||
4 | |||||
5 | use Illuminate\Support\Str; |
||||
6 | use InvalidArgumentException; |
||||
7 | use PortlandLabs\Slackbot\Command\Argument\Manager; |
||||
0 ignored issues
–
show
|
|||||
8 | |||||
9 | /** |
||||
10 | * Largely copied / inspired by Laravel's console component: https://github.com/illuminate/console/blob/master/Parser.php |
||||
11 | */ |
||||
12 | class SignatureParser |
||||
13 | { |
||||
14 | |||||
15 | /** |
||||
16 | * Parse the given console command definition into an array. |
||||
17 | * |
||||
18 | * @param string $expression |
||||
19 | * @param Manager $arguments |
||||
20 | * @return Manager |
||||
21 | * |
||||
22 | * @throws \Exception |
||||
23 | */ |
||||
24 | public static function parse($expression, Manager $arguments) |
||||
25 | { |
||||
26 | $arguments->setCommand(static::name($expression)); |
||||
27 | |||||
28 | if (preg_match_all('/\{\s*(.*?)\s*\}/', $expression, $matches)) { |
||||
29 | if (count($matches[1])) { |
||||
30 | $arguments->add(static::parameters($matches[1], $arguments)); |
||||
0 ignored issues
–
show
The call to
PortlandLabs\Slackbot\Co...ureParser::parameters() has too many arguments starting with $arguments .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
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. Please note the @ignore annotation hint above. ![]() |
|||||
31 | } |
||||
32 | } |
||||
33 | |||||
34 | return $arguments; |
||||
35 | } |
||||
36 | |||||
37 | /** |
||||
38 | * Extract the name of the command from the expression. |
||||
39 | * |
||||
40 | * @param string $expression |
||||
41 | * @return string |
||||
42 | * |
||||
43 | * @throws \InvalidArgumentException |
||||
44 | */ |
||||
45 | protected static function name($expression) |
||||
46 | { |
||||
47 | if (trim($expression) === '') { |
||||
48 | throw new InvalidArgumentException('Console command definition is empty.'); |
||||
49 | } |
||||
50 | |||||
51 | if (! preg_match('/[^\s]+/', $expression, $matches)) { |
||||
52 | throw new InvalidArgumentException('Unable to determine command name from signature.'); |
||||
53 | } |
||||
54 | |||||
55 | return $matches[0]; |
||||
56 | } |
||||
57 | |||||
58 | /** |
||||
59 | * Extract all of the parameters from the tokens. |
||||
60 | * |
||||
61 | * @param array $tokens |
||||
62 | * @return array |
||||
63 | */ |
||||
64 | protected static function parameters(array $tokens) |
||||
65 | { |
||||
66 | $data = []; |
||||
67 | foreach ($tokens as $token) { |
||||
68 | if (preg_match('/-{2,}(.*)/', $token, $matches)) { |
||||
69 | $argument = static::parseOption($matches[1]); |
||||
70 | $data[$argument['longPrefix']] = $argument; |
||||
71 | } else { |
||||
72 | $argument = static::parseArgument($token); |
||||
73 | $data[$argument['token']] = $argument; |
||||
74 | } |
||||
75 | } |
||||
76 | |||||
77 | return $data; |
||||
78 | } |
||||
79 | |||||
80 | /** |
||||
81 | * Parse an argument expression. |
||||
82 | * |
||||
83 | * |
||||
84 | * @param string $token |
||||
85 | * @return array Options are prefix, longprefix, descriptions, defaultValue, required, noValue |
||||
86 | */ |
||||
87 | protected static function parseArgument($token) |
||||
88 | { |
||||
89 | list($token, $description) = static::extractDescription($token); |
||||
90 | |||||
91 | $argument = [ |
||||
92 | 'token' => $token, |
||||
93 | 'description' => $description |
||||
94 | ]; |
||||
95 | |||||
96 | |||||
97 | switch (true) { |
||||
0 ignored issues
–
show
|
|||||
98 | case Str::endsWith($token, '?*'): |
||||
99 | case Str::endsWith($token, '?'): |
||||
100 | $argument['token'] = trim($token, '?*'); |
||||
101 | $argument['required'] = false; |
||||
102 | break; |
||||
103 | case Str::endsWith($token, '*'): |
||||
104 | $argument['token'] = trim($token, '*'); |
||||
105 | $argument['required'] = true; |
||||
106 | break; |
||||
107 | case preg_match('/(.+)\=\*(.+)/', $token, $matches): |
||||
108 | case preg_match('/(.+)\=(.+)/', $token, $matches): |
||||
109 | $argument['token'] = $matches[1]; |
||||
110 | $argument['defaultValue'] = $matches[2]; |
||||
111 | $argument['required'] = false; |
||||
112 | break; |
||||
113 | default: |
||||
114 | $argument['token'] = $token; |
||||
115 | $argument['required'] = true; |
||||
116 | break; |
||||
117 | } |
||||
118 | |||||
119 | return $argument; |
||||
120 | } |
||||
121 | |||||
122 | /** |
||||
123 | * Parse an option expression. |
||||
124 | * |
||||
125 | * @param string $token |
||||
126 | * @return array Options are prefix, longprefix, descriptions, defaultValue, required, noValue |
||||
127 | */ |
||||
128 | protected static function parseOption($token) |
||||
129 | { |
||||
130 | list($token, $description) = static::extractDescription($token); |
||||
131 | |||||
132 | $matches = preg_split('/\s*\|\s*/', $token, 2); |
||||
133 | |||||
134 | if (isset($matches[1])) { |
||||
135 | $shortcut = $matches[0]; |
||||
136 | $token = $matches[1]; |
||||
137 | } else { |
||||
138 | $shortcut = null; |
||||
139 | } |
||||
140 | |||||
141 | $argument = [ |
||||
142 | 'prefix' => $shortcut, |
||||
143 | 'longPrefix' => $token, |
||||
144 | 'description' => $description, |
||||
145 | 'noValue' => true, |
||||
146 | 'required' => false, |
||||
147 | ]; |
||||
148 | |||||
149 | switch (true) { |
||||
0 ignored issues
–
show
|
|||||
150 | case Str::endsWith($token, '='): |
||||
151 | case Str::endsWith($token, '=*'): |
||||
152 | $argument['noValue'] = false; |
||||
153 | $argument['longPrefix'] = trim($token, '=*'); |
||||
154 | break; |
||||
155 | case preg_match('/(.+)\=(.+)/', $token, $matches): |
||||
156 | $argument['noValue'] = false; |
||||
157 | $argument['longPrefix'] = $matches[1]; |
||||
158 | $argument['defaultValue'] = $matches[2]; |
||||
159 | break; |
||||
160 | } |
||||
161 | |||||
162 | return $argument; |
||||
163 | } |
||||
164 | |||||
165 | /** |
||||
166 | * Parse the token into its token and description segments. |
||||
167 | * |
||||
168 | * @param string $token |
||||
169 | * @return array |
||||
170 | */ |
||||
171 | protected static function extractDescription($token) |
||||
172 | { |
||||
173 | $parts = preg_split('/\s+:\s+/', trim($token), 2); |
||||
174 | |||||
175 | return count($parts) === 2 ? $parts : [$token, '']; |
||||
176 | } |
||||
177 | } |
||||
178 |
Let?s assume that you have a directory layout like this:
and let?s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as
OtherDir/Foo.php
does not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php
, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: