Passed
Push — feature/config-shorthands ( e682c8 )
by Sebastian
05:07
created

PHP::getActionClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 10
c 0
b 0
f 0
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\Action;
13
14
use CaptainHook\App\Config;
15
use CaptainHook\App\Console\IO;
16
use CaptainHook\App\Event\Dispatcher;
17
use CaptainHook\App\Exception\ActionFailed;
18
use CaptainHook\App\Hook\Action;
19
use CaptainHook\App\Hook\Constrained;
20
use CaptainHook\App\Hook\EventSubscriber;
21
use CaptainHook\App\Runner\Action as ActionRunner;
22
use CaptainHook\App\Runner\Shorthand;
23
use Error;
24
use Exception;
25
use RuntimeException;
26
use SebastianFeldmann\Git\Repository;
27
28
/**
29
 * Class PHP
30
 *
31
 * @package CaptainHook
32
 * @author  Sebastian Feldmann <[email protected]>
33
 * @link    https://github.com/captainhook-git/captainhook
34
 * @since   Class available since Release 0.9.0
35
 * @internal
36
 */
37
class PHP implements ActionRunner
38
{
39
    /**
40
     * Name of the currently executed hook
41
     *
42
     * @var string
43
     */
44
    private string $hook;
45
46
    /**
47
     *
48
     * @var \CaptainHook\App\Event\Dispatcher
49
     */
50
    private Dispatcher $dispatcher;
51
52
    /**
53
     * PHP constructor.
54
     *
55
     * @param string $hook Name of the currently executed hook
56
     */
57 12
    public function __construct(string $hook, Dispatcher $dispatcher)
58
    {
59 12
        $this->hook       = $hook;
60 12
        $this->dispatcher = $dispatcher;
61
    }
62
63
    /**
64
     * Execute the configured action
65
     *
66
     * @param  \CaptainHook\App\Config           $config
67
     * @param  \CaptainHook\App\Console\IO       $io
68
     * @param  \SebastianFeldmann\Git\Repository $repository
69
     * @param  \CaptainHook\App\Config\Action    $action
70
     * @return void
71
     * @throws \CaptainHook\App\Exception\ActionFailed
72
     */
73 12
    public function execute(Config $config, IO $io, Repository $repository, Config\Action $action): void
74
    {
75 12
        $class = $this->getActionClass($action->getAction());
76
77
        try {
78
            // if the configured action is a static php method display the captured output and exit
79 12
            if ($this->isStaticMethodCall($class)) {
80 3
                $io->write($this->executeStatic($class));
81 1
                return;
82
            }
83
84
            // if not static, it has to be an 'Action' so let's instantiate
85 9
            $exe = $this->createAction($class);
86
            // check for any given restrictions
87 8
            if (!$this->isApplicable($exe)) {
88 1
                $io->write('Action skipped due to hook constraint', true, IO::VERBOSE);
89 1
                return;
90
            }
91
92
            // make sure to collect all event handlers before executing the action
93 7
            if ($exe instanceof EventSubscriber) {
94 1
                $this->dispatcher->subscribeHandlers($class::getEventHandlers($action));
95
            }
96
97
            // no restrictions run it!
98 7
            $exe->execute($config, $io, $repository, $action);
99 6
        } catch (ActionFailed $e) {
100 2
            throw $e;
101 4
        } catch (Exception $e) {
102 3
            throw new ActionFailed(
103 3
                'Execution failed: ' . PHP_EOL .
104 3
                $e->getMessage() . ' in ' . $e->getFile() . ' line ' . $e->getLine()
105 3
            );
106 1
        } catch (Error $e) {
107 1
            throw new ActionFailed('PHP Error:' . $e->getMessage() . ' in ' . $e->getFile() . ' line ' . $e->getLine());
108
        }
109
    }
110
111
    /**
112
     * Execute static method call and return its output
113
     *
114
     * @param  string $class
115
     * @return string
116
     */
117 3
    private function executeStatic(string $class): string
118
    {
119 3
        [$class, $method] = explode('::', $class);
120 3
        if (!class_exists($class)) {
121 1
            throw new RuntimeException('could not find class: ' . $class);
122
        }
123 2
        if (!method_exists($class, $method)) {
124 1
            throw new RuntimeException('could not find method in class: ' . $method);
125
        }
126 1
        ob_start();
127 1
        $class::$method();
128 1
        return (string)ob_get_clean();
129
    }
130
131
    /**
132
     * Create an action instance
133
     *
134
     * @param string $class
135
     * @return \CaptainHook\App\Hook\Action
136
     * @throws \CaptainHook\App\Exception\ActionFailed
137
     */
138 9
    private function createAction(string $class): Action
139
    {
140 9
        $action = new $class();
141 9
        if (!$action instanceof Action) {
142 1
            throw new ActionFailed(
143 1
                'PHP class ' . $class . ' has to implement the \'Action\' interface'
144 1
            );
145
        }
146 8
        return $action;
147
    }
148
149
    /**
150
     * Is this a static method call
151
     *
152
     * @param  string $class
153
     * @return bool
154
     */
155 12
    private function isStaticMethodCall(string $class): bool
156
    {
157 12
        return (bool)preg_match('#^\\\\.+::.+$#i', $class);
158
    }
159
160
    /**
161
     * Make sure the action can be used during this hook
162
     *
163
     * @param  \CaptainHook\App\Hook\Action $action
164
     * @return bool
165
     */
166 8
    private function isApplicable(Action $action)
167
    {
168 8
        if ($action instanceof Constrained) {
169
            /** @var \CaptainHook\App\Hook\Constrained $action */
170 2
            return $action->getRestriction()->isApplicableFor($this->hook);
171
        }
172 6
        return true;
173
    }
174
175
    /**
176
     * Make sure action shorthands are translated before instantiating
177
     *
178
     * @param  string $action
179
     * @return string
180
     */
181 12
    private function getActionClass(string $action): string
182
    {
183 12
        return Shorthand::isShorthand($action) ? Shorthand::getActionClass($action) : $action;
184
    }
185
}
186