Completed
Pull Request — master (#25)
by Greg
02:17
created

AnnotatedCommandFactory   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 180
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 5
Bugs 0 Features 2
Metric Value
wmc 30
c 5
b 0
f 2
lcom 1
cbo 5
dl 0
loc 180
rs 10

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A setCommandProcessor() 0 4 1
A commandProcessor() 0 4 1
A setAllPublicMethodsAreCommands() 0 4 1
A getAllPublicMethodsAreCommands() 0 4 1
A hookManager() 0 4 1
A addListener() 0 4 1
A notify() 0 11 4
A createCommandsFromClass() 0 7 1
A getCommandInfoListFromClass() 0 20 3
A createCommandInfo() 0 4 1
A createCommandsFromClassInfo() 0 13 3
A isCommandMethod() 0 10 3
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 $allPublicMethodsAreCommands = 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 setAllPublicMethodsAreCommands($allPublicMethods)
41
    {
42
        $this->allPublicMethodsAreCommands = $allPublicMethods;
43
    }
44
45
    public function getAllPublicMethodsAreCommands()
46
    {
47
        return $this->allPublicMethodsAreCommands;
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)
73
    {
74
        $this->notify($commandFileInstance);
75
        $commandInfoList = $this->getCommandInfoListFromClass($commandFileInstance);
76
        $this->registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance);
77
        return $this->createCommandsFromClassInfo($commandInfoList, $commandFileInstance);
78
    }
79
80
    public function getCommandInfoListFromClass($classNameOrInstance)
81
    {
82
        $commandInfoList = [];
83
84
        // Ignore special functions, such as __construct and __call, and
85
        // accessor methods such as getFoo and setFoo, while allowing
86
        // set or setup.
87
        $commandMethodNames = array_filter(
88
            get_class_methods($classNameOrInstance) ?: [],
89
            function ($m) {
90
                return !preg_match('#^(_|get[A-Z]|set[A-Z])#', $m);
91
            }
92
        );
93
94
        foreach ($commandMethodNames as $commandMethodName) {
95
            $commandInfoList[] = new CommandInfo($classNameOrInstance, $commandMethodName);
96
        }
97
98
        return $commandInfoList;
99
    }
100
101
    public function createCommandInfo($classNameOrInstance, $commandMethodName)
102
    {
103
        return new CommandInfo($classNameOrInstance, $commandMethodName);
104
    }
105
106
    public function createCommandsFromClassInfo($commandInfoList, $commandFileInstance)
107
    {
108
        $commandList = [];
109
110
        foreach ($commandInfoList as $commandInfo) {
111
            if ($this->isCommandMethod($commandInfo)) {
112
                $command = $this->createCommand($commandInfo, $commandFileInstance);
113
                $commandList[] = $command;
114
            }
115
        }
116
117
        return $commandList;
118
    }
119
120
    protected function isCommandMethod($commandInfo)
121
    {
122
        if ($commandInfo->hasAnnotation('hook')) {
123
            return false;
124
        }
125
        if ($commandInfo->hasAnnotation('command')) {
126
            return true;
127
        }
128
        return $this->getAllPublicMethodsAreCommands();
129
    }
130
131
    public function registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance)
132
    {
133
        foreach ($commandInfoList as $commandInfo) {
134
            if ($commandInfo->hasAnnotation('hook')) {
135
                $this->registerCommandHook($commandInfo, $commandFileInstance);
136
            }
137
        }
138
    }
139
140
    /**
141
     * Register a command hook given the CommandInfo for a method.
142
     *
143
     * The hook format is:
144
     *
145
     *   @hook type name type
146
     *
147
     * For example, the pre-validate hook for the core-init command is:
148
     *
149
     *   @hook pre-validate core-init
150
     *
151
     * If no command name is provided, then we will presume
152
     * that the name of this method is the same as the name
153
     * of the command being hooked (in a different commandFile).
154
     *
155
     * If no hook is provided, then we will presume that ALTER_RESULT
156
     * is intended.
157
     *
158
     * @param CommandInfo $commandInfo Information about the command hook method.
159
     * @param object $commandFileInstance An instance of the CommandFile class.
160
     */
161
    public function registerCommandHook(CommandInfo $commandInfo, $commandFileInstance)
162
    {
163
        // Ignore if the command info has no @hook
164
        if (!$commandInfo->hasAnnotation('hook')) {
165
            return;
166
        }
167
        $hookData = $commandInfo->getAnnotation('hook');
168
        $hook = $this->getNthWord($hookData, 0, HookManager::ALTER_RESULT);
169
        $commandName = $this->getNthWord($hookData, 1, $commandInfo->getName());
170
171
        // Register the hook
172
        $callback = [$commandFileInstance, $commandInfo->getMethodName()];
173
        $this->commandProcessor()->hookManager()->add($commandName, $hook, $callback);
174
    }
175
176
    protected function getNthWord($string, $n, $default, $delimiter = ' ')
177
    {
178
        $words = explode($delimiter, $string);
179
        if (!empty($words[$n])) {
180
            return $words[$n];
181
        }
182
        return $default;
183
    }
184
185
    public function createCommand(CommandInfo $commandInfo, $commandFileInstance)
186
    {
187
        $command = new AnnotatedCommand($commandInfo->getName());
188
        $commandCallback = [$commandFileInstance, $commandInfo->getMethodName()];
189
        $command->setCommandCallback($commandCallback);
190
        $command->setCommandProcessor($this->commandProcessor);
191
        $command->setCommandInfo($commandInfo);
192
        // Annotation commands are never bootstrap-aware, but for completeness
193
        // we will notify on every created command, as some clients may wish to
194
        // use this notification for some other purpose.
195
        $this->notify($command);
196
        return $command;
197
    }
198
}
199