1
|
|
|
<?php |
2
|
|
|
namespace Consolidation\AnnotatedCommand; |
3
|
|
|
|
4
|
|
|
use Symfony\Component\Console\Command\Command; |
5
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
6
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
7
|
|
|
|
8
|
|
|
class AnnotatedCommandFactory |
9
|
|
|
{ |
10
|
|
|
protected $commandProcessor; |
11
|
|
|
protected $listeners = []; |
12
|
|
|
|
13
|
|
|
public function __construct() |
14
|
|
|
{ |
15
|
|
|
$this->commandProcessor = new CommandProcessor(new HookManager()); |
16
|
|
|
} |
17
|
|
|
|
18
|
|
|
public function setCommandProcessor($commandProcessor) |
19
|
|
|
{ |
20
|
|
|
$this->commandProcessor = $commandProcessor; |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
public function commandProcessor() |
24
|
|
|
{ |
25
|
|
|
return $this->commandProcessor; |
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
public function addListener($listener) |
29
|
|
|
{ |
30
|
|
|
$this->listeners[] = $listener; |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
protected function notify($commandFileInstance) |
34
|
|
|
{ |
35
|
|
|
foreach ($this->listeners as $listener) { |
36
|
|
|
if ($listener instanceof CommandCreationListenerInterface) { |
37
|
|
|
$listener->notifyCommandFileAdded($commandFileInstance); |
38
|
|
|
} |
39
|
|
|
if (is_callable($listener)) { |
40
|
|
|
$listener($commandFileInstance); |
41
|
|
|
} |
42
|
|
|
} |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
public function createCommandsFromClass($commandFileInstance) |
46
|
|
|
{ |
47
|
|
|
$this->notify($commandFileInstance); |
48
|
|
|
$commandInfoList = $this->getCommandInfoListFromClass($commandFileInstance); |
49
|
|
|
return $this->createCommandsFromClassInfo($commandInfoList, $commandFileInstance); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
public function getCommandInfoListFromClass($classNameOrInstance) |
53
|
|
|
{ |
54
|
|
|
$commandInfoList = []; |
55
|
|
|
|
56
|
|
|
// Ignore special functions, such as __construct and __call, and |
57
|
|
|
// accessor methods such as getFoo and setFoo, while allowing |
58
|
|
|
// set or setup. |
59
|
|
|
$commandMethodNames = array_filter( |
60
|
|
|
get_class_methods($classNameOrInstance) ?: [], |
61
|
|
|
function ($m) { |
62
|
|
|
return !preg_match('#^(_|get[A-Z]|set[A-Z])#', $m); |
63
|
|
|
} |
64
|
|
|
); |
65
|
|
|
|
66
|
|
|
foreach ($commandMethodNames as $commandMethodName) { |
67
|
|
|
$commandInfoList[] = new CommandInfo($classNameOrInstance, $commandMethodName); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
return $commandInfoList; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
public function createCommandInfo($classNameOrInstance, $commandMethodName) |
74
|
|
|
{ |
75
|
|
|
return new CommandInfo($classNameOrInstance, $commandMethodName); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
public function createCommandsFromClassInfo($commandInfoList, $commandFileInstance) |
79
|
|
|
{ |
80
|
|
|
$commandList = []; |
81
|
|
|
|
82
|
|
|
foreach ($commandInfoList as $commandInfo) { |
83
|
|
|
if (!$commandInfo->hasAnnotation('hook')) { |
84
|
|
|
$command = $this->createCommand($commandInfo, $commandFileInstance); |
85
|
|
|
$commandList[] = $command; |
86
|
|
|
} |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
return $commandList; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
public function registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance) |
93
|
|
|
{ |
94
|
|
|
foreach ($commandInfoList as $commandInfo) { |
95
|
|
|
if ($commandInfo->hasAnnotation('hook')) { |
96
|
|
|
$this->registerCommandHook($commandInfo, $commandFileInstance); |
97
|
|
|
} |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Register a command hook given the CommandInfo for a method. |
103
|
|
|
* |
104
|
|
|
* The hook format is: |
105
|
|
|
* |
106
|
|
|
* @hook type name type |
107
|
|
|
* |
108
|
|
|
* For example, the pre-validate hook for the core-init command is: |
109
|
|
|
* |
110
|
|
|
* @hook pre-validate core-init |
111
|
|
|
* |
112
|
|
|
* If no command name is provided, then we will presume |
113
|
|
|
* that the name of this method is the same as the name |
114
|
|
|
* of the command being hooked (in a different commandFile). |
115
|
|
|
* |
116
|
|
|
* If no hook is provided, then we will presume that ALTER_RESULT |
117
|
|
|
* is intended. |
118
|
|
|
* |
119
|
|
|
* @param CommandInfo $commandInfo Information about the command hook method. |
120
|
|
|
* @param object $commandFileInstance An instance of the CommandFile class. |
121
|
|
|
*/ |
122
|
|
|
public function registerCommandHook(CommandInfo $commandInfo, $commandFileInstance) |
123
|
|
|
{ |
124
|
|
|
// Ignore if the command info has no @hook |
125
|
|
|
if (!$commandInfo->hasAnnotation('hook')) { |
126
|
|
|
return; |
127
|
|
|
} |
128
|
|
|
$hookData = $commandInfo->getAnnotation('hook'); |
129
|
|
|
$hook = $this->getNthWord($hookData, 0, HookManager::ALTER_RESULT); |
130
|
|
|
$commandName = $this->getNthWord($hookData, 1, $commandInfo->getName()); |
131
|
|
|
|
132
|
|
|
// Register the hook |
133
|
|
|
$callback = [$commandFileInstance, $commandInfo->getMethodName()]; |
134
|
|
|
$this->commandProcessor()->hookManager()->add($commandName, $hook, $callback); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
protected function getNthWord($string, $n, $default, $delimiter = ' ') |
138
|
|
|
{ |
139
|
|
|
$words = explode($delimiter, $string); |
140
|
|
|
if (!empty($words[$n])) { |
141
|
|
|
return $words[$n]; |
142
|
|
|
} |
143
|
|
|
return $default; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
public function createCommand(CommandInfo $commandInfo, $commandFileInstance) |
147
|
|
|
{ |
148
|
|
|
$command = new AnnotatedCommand($commandInfo->getName()); |
149
|
|
|
$commandCallback = [$commandFileInstance, $commandInfo->getMethodName()]; |
150
|
|
|
$command->setCommandCallback($commandCallback); |
151
|
|
|
$command->setCommandProcessor($this->commandProcessor, $commandInfo->getAnnotations()); |
|
|
|
|
152
|
|
|
$command->setCommandInfo($commandInfo); |
153
|
|
|
// Annotation commands are never bootstrap-aware, but for completeness |
154
|
|
|
// we will notify on every created command, as some clients may wish to |
155
|
|
|
// use this notification for some other purpose. |
156
|
|
|
$this->notify($command); |
157
|
|
|
return $command; |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
|
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.