Passed
Push — master ( 369dd9...0c3679 )
by Fabien
01:59
created

HookLoader::attachHooks()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 5
nc 4
nop 2
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Churn\Event;
6
7
use Churn\Event\Hook\AfterAnalysisHook;
8
use Churn\Event\Hook\AfterFileAnalysisHook;
9
use Churn\Event\Hook\BeforeAnalysisHook;
10
use Churn\Event\Subscriber\AfterAnalysisHookDecorator;
11
use Churn\Event\Subscriber\AfterFileAnalysisHookDecorator;
12
use Churn\Event\Subscriber\BeforeAnalysisHookDecorator;
13
use Churn\File\FileHelper;
14
use InvalidArgumentException;
15
16
/**
17
 * @internal
18
 */
19
final class HookLoader
20
{
21
22
    /**
23
     * @var array<string, string>
24
     */
25
    private $decorators;
26
27
    /**
28
     * @var string
29
     */
30
    private $basePath;
31
32
    /**
33
     * @param string $basePath The base path for the hooks files.
34
     */
35
    public function __construct(string $basePath)
36
    {
37
        $this->decorators = [
38
            AfterAnalysisHookDecorator::class => AfterAnalysisHook::class,
39
            AfterFileAnalysisHookDecorator::class => AfterFileAnalysisHook::class,
40
            BeforeAnalysisHookDecorator::class => BeforeAnalysisHook::class,
41
        ];
42
        $this->basePath = $basePath;
43
    }
44
45
    /**
46
     * @param array<string> $hooks The list of hooks to attach.
47
     * @param Broker $broker The event broker.
48
     * @throws InvalidArgumentException If a hook is invalid.
49
     */
50
    public function attachHooks(array $hooks, Broker $broker): void
51
    {
52
        if ([] === $hooks) {
53
            return;
54
        }
55
56
        foreach ($hooks as $hook) {
57
            if (!$this->attach($hook, $broker)) {
58
                throw new InvalidArgumentException('Invalid hook: ' . $hook);
59
            }
60
        }
61
    }
62
63
    /**
64
     * @param string $hookPath The class name or the file path of the hook.
65
     * @param Broker $broker The event broker.
66
     */
67
    public function attach(string $hookPath, Broker $broker): bool
68
    {
69
        $foundSubscriber = false;
70
        $subscribers = [];
71
72
        foreach ($this->loadHooks($hookPath) as $hookName) {
73
            $subscribers = \array_merge($subscribers, $this->hookToSubscribers($hookName));
74
        }
75
76
        foreach ($subscribers as $subscriber) {
77
            $broker->subscribe($subscriber);
78
            $foundSubscriber = true;
79
        }
80
81
        return $foundSubscriber;
82
    }
83
84
    /**
85
     * @param string $hookPath The class name or the file path of the hook.
86
     * @return array<mixed>
87
     */
88
    private function loadHooks(string $hookPath): array
89
    {
90
        if (\class_exists($hookPath)) {
91
            return [$hookPath];
92
        }
93
94
        $hookPath = FileHelper::toAbsolutePath($hookPath, $this->basePath);
95
96
        if (!\is_file($hookPath)) {
97
            return [];
98
        }
99
100
        $numberOfClasses = \count(\get_declared_classes());
101
        \Churn\Event\includeOnce($hookPath);
102
103
        return \array_slice(\get_declared_classes(), $numberOfClasses);
104
    }
105
106
    /**
107
     * @param string $hookName The class name of the hook.
108
     * @return array<mixed>
109
     */
110
    private function hookToSubscribers(string $hookName): array
111
    {
112
        $subscribers = [];
113
114
        foreach ($this->decorators as $decorator => $hook) {
115
            if (!\is_subclass_of($hookName, $hook)) {
116
                continue;
117
            }
118
119
            $subscribers[] = new $decorator($hookName);
120
        }
121
122
        return $subscribers;
123
    }
124
}
125
126
/**
127
 * Scope isolated include.
128
 *
129
 * Prevents access to $this/self from included files.
130
 *
131
 * @param string $file The PHP file to include.
132
 */
133
function includeOnce(string $file): void
134
{
135
    include_once $file;
136
}
137