Completed
Push — master ( 4a529d...c0bd0d )
by Greg
07:29
created

AnnotatedCommandFactory   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 195
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 33
lcom 1
cbo 5
dl 0
loc 195
rs 9.3999
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A setCommandProcessor() 0 4 1
A commandProcessor() 0 4 1
A setIncludeAllPublicMethods() 0 4 1
A getIncludeAllPublicMethods() 0 4 1
A hookManager() 0 4 1
A addListener() 0 4 1
A notify() 0 11 4
A createCommandsFromClass() 0 11 2
A getCommandInfoListFromClass() 0 20 3
A createCommandInfo() 0 4 1
A createCommandsFromClassInfo() 0 14 3
A createSelectedCommandsFromClassInfo() 0 13 3
A isCommandMethod() 0 7 2
A registerCommandHooksFromClassInfo() 0 8 3
A registerCommandHook() 0 14 2
A getNthWord() 0 8 2
A createCommand() 0 13 1
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
use Consolidation\AnnotatedCommand\Hooks\HookManager;
8
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
9
10
/**
11
 * The AnnotatedCommandFactory creates commands for your application.
12
 * Use with a Dependency Injection Container and the CommandFactory.
13
 * Alternately, use the CommandFileDiscovery to find commandfiles, and
14
 * then use AnnotatedCommandFactory::createCommandsFromClass() to create
15
 * commands.  See the README for more information.
16
 *
17
 * @package Consolidation\AnnotatedCommand
18
 */
19
class AnnotatedCommandFactory
20
{
21
    protected $commandProcessor;
22
    protected $listeners = [];
23
    protected $includeAllPublicMethods = true;
24
25
    public function __construct()
26
    {
27
        $this->commandProcessor = new CommandProcessor(new HookManager());
28
    }
29
30
    public function setCommandProcessor($commandProcessor)
31
    {
32
        $this->commandProcessor = $commandProcessor;
33
    }
34
35
    public function commandProcessor()
36
    {
37
        return $this->commandProcessor;
38
    }
39
40
    public function setIncludeAllPublicMethods($includeAllPublicMethods)
41
    {
42
        $this->includeAllPublicMethods = $includeAllPublicMethods;
43
    }
44
45
    public function getIncludeAllPublicMethods()
46
    {
47
        return $this->includeAllPublicMethods;
48
    }
49
50
    public function hookManager()
51
    {
52
        return $this->commandProcessor()->hookManager();
53
    }
54
55
    public function addListener($listener)
56
    {
57
        $this->listeners[] = $listener;
58
    }
59
60
    protected function notify($commandFileInstance)
61
    {
62
        foreach ($this->listeners as $listener) {
63
            if ($listener instanceof CommandCreationListenerInterface) {
64
                $listener->notifyCommandFileAdded($commandFileInstance);
65
            }
66
            if (is_callable($listener)) {
67
                $listener($commandFileInstance);
68
            }
69
        }
70
    }
71
72
    public function createCommandsFromClass($commandFileInstance, $includeAllPublicMethods = null)
73
    {
74
        // Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor.
75
        if (!isset($includeAllPublicMethods)) {
76
            $includeAllPublicMethods = $this->getIncludeAllPublicMethods();
77
        }
78
        $this->notify($commandFileInstance);
79
        $commandInfoList = $this->getCommandInfoListFromClass($commandFileInstance);
80
        $this->registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance);
81
        return $this->createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods);
82
    }
83
84
    public function getCommandInfoListFromClass($classNameOrInstance)
85
    {
86
        $commandInfoList = [];
87
88
        // Ignore special functions, such as __construct and __call, and
89
        // accessor methods such as getFoo and setFoo, while allowing
90
        // set or setup.
91
        $commandMethodNames = array_filter(
92
            get_class_methods($classNameOrInstance) ?: [],
93
            function ($m) {
94
                return !preg_match('#^(_|get[A-Z]|set[A-Z])#', $m);
95
            }
96
        );
97
98
        foreach ($commandMethodNames as $commandMethodName) {
99
            $commandInfoList[] = new CommandInfo($classNameOrInstance, $commandMethodName);
100
        }
101
102
        return $commandInfoList;
103
    }
104
105
    public function createCommandInfo($classNameOrInstance, $commandMethodName)
106
    {
107
        return new CommandInfo($classNameOrInstance, $commandMethodName);
108
    }
109
110
    public function createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods = null)
111
    {
112
        // Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor.
113
        if (!isset($includeAllPublicMethods)) {
114
            $includeAllPublicMethods = $this->getIncludeAllPublicMethods();
115
        }
116
        return $this->createSelectedCommandsFromClassInfo(
117
            $commandInfoList,
118
            $commandFileInstance,
119
            function ($commandInfo) use ($includeAllPublicMethods) {
120
                return $includeAllPublicMethods || static::isCommandMethod($commandInfo);
121
            }
122
        );
123
    }
124
125
    public function createSelectedCommandsFromClassInfo($commandInfoList, $commandFileInstance, callable $commandSelector)
126
    {
127
        $commandList = [];
128
129
        foreach ($commandInfoList as $commandInfo) {
130
            if ($commandSelector($commandInfo)) {
131
                $command = $this->createCommand($commandInfo, $commandFileInstance);
132
                $commandList[] = $command;
133
            }
134
        }
135
136
        return $commandList;
137
    }
138
139
    public static function isCommandMethod($commandInfo)
140
    {
141
        if ($commandInfo->hasAnnotation('hook')) {
142
            return false;
143
        }
144
        return $commandInfo->hasAnnotation('command');
145
    }
146
147
    public function registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance)
148
    {
149
        foreach ($commandInfoList as $commandInfo) {
150
            if ($commandInfo->hasAnnotation('hook')) {
151
                $this->registerCommandHook($commandInfo, $commandFileInstance);
152
            }
153
        }
154
    }
155
156
    /**
157
     * Register a command hook given the CommandInfo for a method.
158
     *
159
     * The hook format is:
160
     *
161
     *   @hook type name type
162
     *
163
     * For example, the pre-validate hook for the core:init command is:
164
     *
165
     *   @hook pre-validate core:init
166
     *
167
     * If no command name is provided, then this hook will affect every
168
     * command that is defined in the same file.
169
     *
170
     * If no hook is provided, then we will presume that ALTER_RESULT
171
     * is intended.
172
     *
173
     * @param CommandInfo $commandInfo Information about the command hook method.
174
     * @param object $commandFileInstance An instance of the CommandFile class.
175
     */
176
    public function registerCommandHook(CommandInfo $commandInfo, $commandFileInstance)
177
    {
178
        // Ignore if the command info has no @hook
179
        if (!$commandInfo->hasAnnotation('hook')) {
180
            return;
181
        }
182
        $hookData = $commandInfo->getAnnotation('hook');
183
        $hook = $this->getNthWord($hookData, 0, HookManager::ALTER_RESULT);
184
        $commandName = $this->getNthWord($hookData, 1);
185
186
        // Register the hook
187
        $callback = [$commandFileInstance, $commandInfo->getMethodName()];
188
        $this->commandProcessor()->hookManager()->add($callback, $hook, $commandName);
189
    }
190
191
    protected function getNthWord($string, $n, $default = '', $delimiter = ' ')
192
    {
193
        $words = explode($delimiter, $string);
194
        if (!empty($words[$n])) {
195
            return $words[$n];
196
        }
197
        return $default;
198
    }
199
200
    public function createCommand(CommandInfo $commandInfo, $commandFileInstance)
201
    {
202
        $command = new AnnotatedCommand($commandInfo->getName());
203
        $commandCallback = [$commandFileInstance, $commandInfo->getMethodName()];
204
        $command->setCommandCallback($commandCallback);
205
        $command->setCommandProcessor($this->commandProcessor);
206
        $command->setCommandInfo($commandInfo);
207
        // Annotation commands are never bootstrap-aware, but for completeness
208
        // we will notify on every created command, as some clients may wish to
209
        // use this notification for some other purpose.
210
        $this->notify($command);
211
        return $command;
212
    }
213
}
214