Completed
Push — master ( 1c5efd...75f82a )
by Greg
12s
created

HookManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
namespace Consolidation\AnnotatedCommand\Hooks;
3
4
use Symfony\Component\Console\Command\Command;
5
use Symfony\Component\Console\Input\InputInterface;
6
use Symfony\Component\Console\Output\OutputInterface;
7
8
use Consolidation\AnnotatedCommand\ExitCodeInterface;
9
use Consolidation\AnnotatedCommand\OutputDataInterface;
10
11
/**
12
 * Manage named callback hooks
13
 */
14
class HookManager
15
{
16
    protected $hooks = [];
17
18
    const ARGUMENT_VALIDATOR = 'validate';
19
    const PROCESS_RESULT = 'process';
20
    const ALTER_RESULT = 'alter';
21
    const STATUS_DETERMINER = 'status';
22
    const EXTRACT_OUTPUT = 'extract';
23
24
    public function __construct()
25
    {
26
    }
27
28
    /**
29
     * Add a hook
30
     *
31
     * @param string   $name     The name of the command to hook
32
     *   ('*' for all)
33
     * @param string   $hook     The name of the hook to add
34
     * @param mixed $callback The callback function to call
35
     */
36
    public function add($name, $hook, callable $callback)
37
    {
38
        $this->hooks[$name][$hook][] = $callback;
39
    }
40
41
    /**
42
     * Add a validator hook
43
     *
44
     * @param type ValidatorInterface $validator
45
     * @param type $name The name of the command to hook
46
     *   ('*' for all)
47
     */
48
    public function addValidator(ValidatorInterface $validator, $name = '*')
49
    {
50
        $this->hooks[$name][self::ARGUMENT_VALIDATOR][] = $validator;
51
    }
52
53
    /**
54
     * Add a result processor.
55
     *
56
     * @param type ProcessResultInterface $resultProcessor
57
     * @param type $name The name of the command to hook
58
     *   ('*' for all)
59
     */
60
    public function addResultProcessor(ProcessResultInterface $resultProcessor, $name = '*')
61
    {
62
        $this->hooks[$name][self::PROCESS_RESULT][] = $resultProcessor;
63
    }
64
65
    /**
66
     * Add a result alterer. After a result is processed
67
     * by a result processor, an alter hook may be used
68
     * to convert the result from one form to another.
69
     *
70
     * @param type AlterResultInterface $resultAlterer
71
     * @param type $name The name of the command to hook
72
     *   ('*' for all)
73
     */
74
    public function addAlterResult(AlterResultInterface $resultAlterer, $name = '*')
75
    {
76
        $this->hooks[$name][self::ALTER_RESULT][] = $resultAlterer;
77
    }
78
79
    /**
80
     * Add a status determiner. Usually, a command should return
81
     * an integer on error, or a result object on success (which
82
     * implies a status code of zero). If a result contains the
83
     * status code in some other field, then a status determiner
84
     * can be used to call the appropriate accessor method to
85
     * determine the status code.  This is usually not necessary,
86
     * though; a command that fails may return a CommandError
87
     * object, which contains a status code and a result message
88
     * to display.
89
     * @see CommandError::getExitCode()
90
     *
91
     * @param type StatusDeterminerInterface $statusDeterminer
92
     * @param type $name The name of the command to hook
93
     *   ('*' for all)
94
     */
95
    public function addStatusDeterminer(StatusDeterminerInterface $statusDeterminer, $name = '*')
96
    {
97
        $this->hooks[$name][self::STATUS_DETERMINER][] = $statusDeterminer;
98
    }
99
100
    /**
101
     * Add an output extractor. If a command returns an object
102
     * object, by default it is passed directly to the output
103
     * formatter (if in use) for rendering. If the result object
104
     * contains more information than just the data to render, though,
105
     * then an output extractor can be used to call the appopriate
106
     * accessor method of the result object to get the data to
107
     * rendered.  This is usually not necessary, though; it is preferable
108
     * to have complex result objects implement the OutputDataInterface.
109
     * @see OutputDataInterface::getOutputData()
110
     *
111
     * @param type ExtractOutputInterface $outputExtractor
112
     * @param type $name The name of the command to hook
113
     *   ('*' for all)
114
     */
115
    public function addOutputExtractor(ExtractOutputInterface $outputExtractor, $name = '*')
116
    {
117
        $this->hooks[$name][self::EXTRACT_OUTPUT][] = $outputExtractor;
118
    }
119
120
    /**
121
     * Get a set of hooks with the provided name(s).
122
     *
123
     * @param string|array $names The name of the function being hooked.
124
     * @param string $hook The specific hook name (e.g. alter)
125
     *
126
     * @return callable[]
127
     */
128
    public function get($names, $hook)
129
    {
130
        $hooks = [];
131
        foreach ((array)$names as $name) {
132
            $hooks = array_merge($hooks, $this->getHook($name, $hook));
133
        }
134
        return $hooks;
135
    }
136
137
    public function validateArguments($names, $args)
138
    {
139
        $validators = $this->getValidators($names);
140
        foreach ($validators as $validator) {
141
            $validated = $this->callValidator($validator, $args);
142
            if (is_object($validated)) {
143
                return $validated;
144
            }
145
            if (is_array($validated)) {
146
                $args = $validated;
147
            }
148
        }
149
        return $args;
150
    }
151
152
    /**
153
     * Process result and decide what to do with it.
154
     * Allow client to add transformation / interpretation
155
     * callbacks.
156
     */
157
    public function alterResult($names, $result, $args)
158
    {
159
        $processors = $this->getProcessResultHooks($names);
160
        foreach ($processors as $processor) {
161
            $result = $this->callProcessor($processor, $result, $args);
162
        }
163
        $alterers = $this->getAlterResultHooks($names);
164
        foreach ($alterers as $alterer) {
165
            $result = $this->callProcessor($alterer, $result, $args);
166
        }
167
168
        return $result;
169
    }
170
171
    /**
172
     * Call all status determiners, and see if any of them
173
     * know how to convert to a status code.
174
     */
175
    public function determineStatusCode($names, $result)
176
    {
177
        // If the result (post-processing) is an object that
178
        // implements ExitCodeInterface, then we will ask it
179
        // to give us the status code.
180
        if ($result instanceof ExitCodeInterface) {
181
            return $result->getExitCode();
182
        }
183
184
        // If the result does not implement ExitCodeInterface,
185
        // then we'll see if there is a determiner that can
186
        // extract a status code from the result.
187
        $determiners = $this->getStatusDeterminers($names);
188
        foreach ($determiners as $determiner) {
189
            $status = $this->callDeterminer($determiner, $result);
190
            if (isset($status)) {
191
                return $status;
192
            }
193
        }
194
    }
195
196
    /**
197
     * Convert the result object to printable output in
198
     * structured form.
199
     */
200
    public function extractOutput($names, $result)
201
    {
202
        if ($result instanceof OutputDataInterface) {
203
            return $result->getOutputData();
204
        }
205
206
        $extractors = $this->getOutputExtractors($names);
207
        foreach ($extractors as $extractor) {
208
            $structuredOutput = $this->callExtractor($extractor, $result);
209
            if (isset($structuredOutput)) {
210
                return $structuredOutput;
211
            }
212
        }
213
214
        return $result;
215
    }
216
217
    protected function getValidators($names)
218
    {
219
        return $this->getHooks($names, self::ARGUMENT_VALIDATOR);
220
    }
221
222
    protected function getStatusDeterminers($names)
223
    {
224
        return $this->getHooks($names, self::STATUS_DETERMINER);
225
    }
226
227
    protected function getProcessResultHooks($names)
228
    {
229
        return $this->getHooks($names, self::PROCESS_RESULT);
230
    }
231
232
    protected function getAlterResultHooks($names)
233
    {
234
        return $this->getHooks($names, self::ALTER_RESULT);
235
    }
236
237
    protected function getOutputExtractors($names)
238
    {
239
        return $this->getHooks($names, self::EXTRACT_OUTPUT);
240
    }
241
242
    /**
243
     * Get a set of hooks with the provided name(s). Include the
244
     * pre- and post- hooks, and also include the global hooks ('*')
245
     * in addition to the named hooks provided.
246
     *
247
     * @param string|array $names The name of the function being hooked.
248
     * @param string $hook The specific hook name (e.g. alter)
249
     *
250
     * @return callable[]
251
     */
252
    protected function getHooks($names, $hook)
253
    {
254
        $names = (array)$names;
255
        $names[] = '*';
256
        return array_merge(
257
            $this->get($names, "pre-$hook"),
258
            $this->get($names, $hook),
259
            $this->get($names, "post-$hook")
260
        );
261
    }
262
263
    /**
264
     * Get a single named hook.
265
     *
266
     * @param string $name The name of the hooked method
267
     * @param string $hook The specific hook name (e.g. alter)
268
     *
269
     * @return callable[]
270
     */
271
    protected function getHook($name, $hook)
272
    {
273
        if (isset($this->hooks[$name][$hook])) {
274
            return $this->hooks[$name][$hook];
275
        }
276
        return [];
277
    }
278
279
    protected function callValidator($validator, $args)
280
    {
281
        if ($validator instanceof ValidatorInterface) {
282
            return $validator->validate($args);
283
        }
284
        if (is_callable($validator)) {
285
            return $validator($args);
286
        }
287
    }
288
289
    protected function callProcessor($processor, $result, $args)
290
    {
291
        $processed = null;
292
        if ($processor instanceof ProcessResultInterface) {
293
            $processed = $processor->process($result, $args);
294
        }
295
        if (is_callable($processor)) {
296
            $processed = $processor($result, $args);
297
        }
298
        if (isset($processed)) {
299
            return $processed;
300
        }
301
        return $result;
302
    }
303
304
    protected function callDeterminer($determiner, $result)
305
    {
306
        if ($determiner instanceof StatusDeterminerInterface) {
307
            return $determiner->determineStatusCode($result);
308
        }
309
        if (is_callable($determiner)) {
310
            return $determiner($result);
311
        }
312
    }
313
314
    protected function callExtractor($extractor, $result)
315
    {
316
        if ($extractor instanceof ExtractOutputInterface) {
317
            return $extractor->extractOutput($result);
318
        }
319
        if (is_callable($extractor)) {
320
            return $extractor($result);
321
        }
322
    }
323
}
324