PHP::execute()   B
last analyzed

Complexity

Conditions 7
Paths 28

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 7

Importance

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