Completed
Branch master (00c13d)
by Greg
03:16 queued 11s
created

AnnotatedCommandFactory::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
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
        return $this->createSelectedCommandsFromClassInfo(
98
            $commandInfoList,
99
            $commandFileInstance,
100
            function ($commandInfo) use ($includeAllPublicMethods) {
101
                return $includeAllPublicMethods || static::isCommandMethod($commandInfo);
102
            }
103
        );
104
    }
105
106
    public function createSelectedCommandsFromClassInfo($commandInfoList, $commandFileInstance, callable $commandSelector)
107
    {
108
        $commandList = [];
109
110
        foreach ($commandInfoList as $commandInfo) {
111
            if ($commandSelector($commandInfo)) {
112
                $command = $this->createCommand($commandInfo, $commandFileInstance);
113
                $commandList[] = $command;
114
            }
115
        }
116
117
        return $commandList;
118
    }
119
120
    public static function isCommandMethod($commandInfo)
121
    {
122
        if ($commandInfo->hasAnnotation('hook')) {
123
            return false;
124
        }
125
        return $commandInfo->hasAnnotation('command');
126
    }
127
128
    public function registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance)
129
    {
130
        foreach ($commandInfoList as $commandInfo) {
131
            if ($commandInfo->hasAnnotation('hook')) {
132
                $this->registerCommandHook($commandInfo, $commandFileInstance);
133
            }
134
        }
135
    }
136
137
    /**
138
     * Register a command hook given the CommandInfo for a method.
139
     *
140
     * The hook format is:
141
     *
142
     *   @hook type name type
143
     *
144
     * For example, the pre-validate hook for the core-init command is:
145
     *
146
     *   @hook pre-validate core-init
147
     *
148
     * If no command name is provided, then we will presume
149
     * that the name of this method is the same as the name
150
     * of the command being hooked (in a different commandFile).
151
     *
152
     * If no hook is provided, then we will presume that ALTER_RESULT
153
     * is intended.
154
     *
155
     * @param CommandInfo $commandInfo Information about the command hook method.
156
     * @param object $commandFileInstance An instance of the CommandFile class.
157
     */
158
    public function registerCommandHook(CommandInfo $commandInfo, $commandFileInstance)
159
    {
160
        // Ignore if the command info has no @hook
161
        if (!$commandInfo->hasAnnotation('hook')) {
162
            return;
163
        }
164
        $hookData = $commandInfo->getAnnotation('hook');
165
        $hook = $this->getNthWord($hookData, 0, HookManager::ALTER_RESULT);
166
        $commandName = $this->getNthWord($hookData, 1, $commandInfo->getName());
167
168
        // Register the hook
169
        $callback = [$commandFileInstance, $commandInfo->getMethodName()];
170
        $this->commandProcessor()->hookManager()->add($callback, $hook, $commandName);
171
    }
172
173
    protected function getNthWord($string, $n, $default, $delimiter = ' ')
174
    {
175
        $words = explode($delimiter, $string);
176
        if (!empty($words[$n])) {
177
            return $words[$n];
178
        }
179
        return $default;
180
    }
181
182
    public function createCommand(CommandInfo $commandInfo, $commandFileInstance)
183
    {
184
        $command = new AnnotatedCommand($commandInfo->getName());
185
        $commandCallback = [$commandFileInstance, $commandInfo->getMethodName()];
186
        $command->setCommandCallback($commandCallback);
187
        $command->setCommandProcessor($this->commandProcessor);
188
        $command->setCommandInfo($commandInfo);
189
        // Annotation commands are never bootstrap-aware, but for completeness
190
        // we will notify on every created command, as some clients may wish to
191
        // use this notification for some other purpose.
192
        $this->notify($command);
193
        return $command;
194
    }
195
}
196