Completed
Push — master ( 0d30a7...716818 )
by Sebastian
25s queued 11s
created

Hook::formatHookHeadline()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 8
ccs 0
cts 0
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * This file is part of CaptainHook
5
 *
6
 * (c) Sebastian Feldmann <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace CaptainHook\App\Runner;
13
14
use CaptainHook\App\Config;
15
use CaptainHook\App\Console\IO;
16
use CaptainHook\App\Console\IOUtil;
17
use CaptainHook\App\Exception\ActionFailed;
18
use CaptainHook\App\Hooks;
19
use Exception;
20
use RuntimeException;
21
22
/**
23
 * Hook
24
 *
25
 * @package CaptainHook
26
 * @author  Sebastian Feldmann <[email protected]>
27
 * @link    https://github.com/captainhookphp/captainhook
28
 * @since   Class available since Release 0.9.0
29
 */
30
abstract class Hook extends RepositoryAware
31
{
32
    /**
33
     * Hook that should be handled.
34
     *
35
     * @var string
36
     */
37
    protected $hook;
38
39
    /**
40
     * Execute stuff before executing any actions
41 9
     *
42
     * @return void
43
     */
44 9
    public function beforeHook(): void
45
    {
46
        // empty template method
47
    }
48
49
    /**
50
     * Execute stuff before every actions
51 1
     *
52
     * @return void
53
     */
54 1
    public function beforeAction(): void
55
    {
56
        // empty template method
57
    }
58
59
    /**
60
     * Execute stuff after every actions
61 1
     *
62
     * @return void
63
     */
64 1
    public function afterAction(): void
65
    {
66
        //empty template method
67
    }
68
69
    /**
70
     * Execute stuff after all actions
71 9
     *
72
     * @return void
73
     */
74 9
    public function afterHook(): void
75
    {
76
        // empty template method
77
    }
78
79
    /**
80
     * Execute the hook and all its actions
81
     *
82 23
     * @return void
83
     * @throws \Exception
84
     */
85 23
    public function run(): void
86 23
    {
87
        $hookConfig = $this->config->getHookConfig($this->hook);
88
89 23
        // if hook is not enabled in captainhook configuration skip the execution
90 8
        if (!$hookConfig->isEnabled()) {
91 8
            $this->io->write($this->formatHookHeadline('Skip'), true, IO::VERBOSE);
92
            return;
93
        }
94 15
95 4
        $this->io->write($this->formatHookHeadline('Execute'), true, IO::VERBOSE);
96 4
97
        $actions = $this->getActionsToExecute($hookConfig);
98
99 11
        // if no actions are configured do nothing
100 11
        if (count($actions) === 0) {
101 9
            $this->io->write(['', '<info>No actions to execute</info>'], true, IO::VERBOSE);
102 9
            return;
103
        }
104 9
        $this->beforeHook();
105 9
        $this->executeActions($actions);
106
        $this->afterHook();
107
    }
108
109
    /**
110
     * Return all the actions to execute
111
     *
112
     * Returns all actions from the triggered hook but also any actions of virtual hooks that might be triggered.
113
     * E.g. 'post-rewrite' or 'post-checkout' trigger the virtual/artificial 'post-change' hook.
114 9
     * Virtual hooks are special hooks to simplify configuration.
115
     *
116 9
     * @param  \CaptainHook\App\Config\Hook $hookConfig
117
     * @return \CaptainHook\App\Config\Action[]
118 9
     */
119 1
    private function getActionsToExecute(Config\Hook $hookConfig)
120 1
    {
121 1
        $actions = $hookConfig->getActions();
122 1
        if (!Hooks::triggersVirtualHook($hookConfig->getName())) {
123
            return $actions;
124 1
        }
125
126
        $virtualHookConfig = $this->config->getHookConfig(Hooks::getVirtualHook($hookConfig->getName()));
127 8
        if (!$virtualHookConfig->isEnabled()) {
128 8
            return $actions;
129 8
        }
130
        return array_merge($actions, $virtualHookConfig->getActions());
131
    }
132
133
    /**
134
     * Executes all the Actions configured for the hook
135
     *
136
     * @param  \CaptainHook\App\Config\Action[] $actions
137
     * @throws \Exception
138 1
     */
139
    private function executeActions(array $actions): void
140 1
    {
141 1
        if ($this->config->failOnFirstError()) {
142 1
            $this->executeFailOnFirstError($actions);
143 1
        } else {
144 1
            $this->executeFailAfterAllActions($actions);
145
        }
146
    }
147
148
    /**
149
     * Executes all actions and fails at the first error
150
     *
151
     * @param  \CaptainHook\App\Config\Action[] $actions
152
     * @return void
153 7
     * @throws \Exception
154
     */
155
    private function executeFailOnFirstError(array $actions): void
156
    {
157
        foreach ($actions as $action) {
158 7
            $this->handleAction($action);
159 7
        }
160 7
    }
161
162
    /**
163
     * Executes all actions but does not fail immediately
164
     *
165
     * @param \CaptainHook\App\Config\Action[] $actions
166
     * @return void
167
     * @throws \CaptainHook\App\Exception\ActionFailed
168 10
     */
169
    private function executeFailAfterAllActions(array $actions): void
170 10
    {
171
        $failedActions = 0;
172 10
173 1
        foreach ($actions as $action) {
174
            try {
175 9
                $this->handleAction($action);
176
            } catch (Exception $exception) {
177
                $this->io->write($exception->getMessage());
178
                $failedActions++;
179
            }
180
        }
181
182
        if ($failedActions > 0) {
183
            throw new ActionFailed($failedActions . ' action(s) failed; please see above error messages');
184 9
        }
185
    }
186 9
187 9
    /**
188 2
     * Executes a configured hook action
189 2
     *
190
     * @param  \CaptainHook\App\Config\Action $action
191
     * @return void
192 8
     * @throws \Exception
193
     */
194
    private function handleAction(Config\Action $action): void
195
    {
196
        $this->io->write(['', 'Action: <comment>' . $action->getAction() . '</comment>'], true, IO::VERBOSE);
197
198
        if (!$this->doConditionsApply($action->getConditions())) {
199
            $this->io->write(
200
                ['', 'Skipped due to failing conditions'],
201 19
                true,
202
                IO::VERBOSE
203 19
            );
204
            return;
205 19
        }
206 19
207 19
        $execMethod = self::getExecMethod(Util::getExecType($action->getAction()));
208 19
        $this->{$execMethod}($action);
209
    }
210
211
    /**
212
     * Execute a php hook action
213
     *
214
     * @param  \CaptainHook\App\Config\Action $action
215
     * @return void
216
     * @throws \CaptainHook\App\Exception\ActionFailed
217
     */
218
    private function executePhpAction(Config\Action $action): void
219
    {
220
        $this->beforeAction();
221
        $runner = new Action\PHP($this->hook);
222
        $runner->execute($this->config, $this->io, $this->repository, $action);
223
        $this->afterAction();
224
    }
225
226
    /**
227
     * Execute a cli hook action
228
     *
229
     * @param  \CaptainHook\App\Config\Action $action
230
     * @return void
231
     * @throws \CaptainHook\App\Exception\ActionFailed
232
     */
233
    private function executeCliAction(Config\Action $action): void
234
    {
235
        // since the cli has no straight way to communicate back to php
236
        // cli hooks have to handle sync stuff by them self
237
        // so no 'beforeAction' or 'afterAction' is called here
238
        $runner = new Action\Cli();
239
        $runner->execute($this->io, $this->repository, $action);
240
    }
241
242
    /**
243
     * Return the right method name to execute an action
244
     *
245
     * @param  string $type
246
     * @return string
247
     */
248
    public static function getExecMethod(string $type): string
249
    {
250
        $valid = ['php' => 'executePhpAction', 'cli' => 'executeCliAction'];
251
252
        if (!isset($valid[$type])) {
253
            throw new RuntimeException('invalid action type: ' . $type);
254
        }
255
        return $valid[$type];
256
    }
257
258
    /**
259
     * Check if conditions apply
260
     *
261
     * @param  \CaptainHook\App\Config\Condition[] $conditions
262
     * @return bool
263
     */
264
    private function doConditionsApply(array $conditions): bool
265
    {
266
        $conditionRunner = new Condition($this->io, $this->repository, $this->hook);
267
        foreach ($conditions as $config) {
268
            if (!$conditionRunner->doesConditionApply($config)) {
269
                return false;
270
            }
271
        }
272
        return true;
273
    }
274
275
    /**
276
     * Some fancy output formatting
277
     *
278
     * @param  string $mode
279
     * @return string[]
280
     */
281
    private function formatHookHeadline(string $mode): array
282
    {
283
        $headline = ' ' . $mode . ' hook: <comment>' . $this->hook . '</comment> ';
284
        return [
285
            '',
286
            IOUtil::getLineSeparator(8) .
287
            $headline .
288
            IOUtil::getLineSeparator(80 - 8 - strlen(strip_tags($headline)))
289
        ];
290
    }
291
}
292