Completed
Push — master ( 2c0f5a...91d1b0 )
by Greg
02:28
created

AnnotatedCommandFactory   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 169
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 6
Bugs 0 Features 2
Metric Value
wmc 28
c 6
b 0
f 2
lcom 1
cbo 5
dl 0
loc 169
rs 10

15 Methods

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