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

AnnotatedCommandFactory::notify()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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