PHP::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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