Passed
Push — master ( 76b450...5fc191 )
by Peter
02:19
created

Manager::simpleOptionCallback()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 1
nop 1
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AbterPhp\Framework\Module;
6
7
use AbterPhp\Framework\Constant\Module;
8
use Opulence\Cache\ICacheBridge;
9
use Opulence\Console\Commands\Command;
10
use Opulence\Ioc\Bootstrappers\Bootstrapper;
11
12
class Manager
13
{
14
    const CACHE_KEY_HTTP_BOOTSTRAPPERS = 'AbterPhp:HttpBootstrappers';
15
    const CACHE_KEY_CLI_BOOTSTRAPPERS  = 'AbterPhp:CliBootstrappers';
16
    const CACHE_KEY_COMMANDS           = 'AbterPhp:Commands';
17
    const CACHE_KEY_ROUTE_PATHS        = 'AbterPhp:RoutePaths';
18
    const CACHE_KEY_EVENTS             = 'AbterPhp:Events';
19
    const CACHE_KEY_MIDDLEWARE         = 'AbterPhp:Middleware';
20
    const CACHE_KEY_MIGRATION_PATHS    = 'AbterPhp:MigrationPaths';
21
    const CACHE_KEY_RESOURCE_PATH      = 'AbterPhp:Resource';
22
23
    /** @var Loader */
24
    protected $loader;
25
26
    /** @var ICacheBridge|null */
27
    protected $cacheBridge;
28
29
    /** @var array|null */
30
    protected $modules;
31
32
    /**
33
     * Manager constructor.
34
     *
35
     * @param Loader            $sourceRoots
36
     * @param ICacheBridge|null $cacheBridge
37
     */
38
    public function __construct(Loader $loader, ?ICacheBridge $cacheBridge = null)
39
    {
40
        $this->loader      = $loader;
41
        $this->cacheBridge = $cacheBridge;
42
    }
43
44
    /**
45
     * @return Bootstrapper[]
46
     */
47
    protected function cacheWrapper(string $cacheKey, callable $callback): array
48
    {
49
        // phpcs:disable Generic.CodeAnalysis.EmptyStatement
50
        try {
51
            if ($this->cacheBridge && $this->cacheBridge->has($cacheKey)) {
52
                return (array)$this->cacheBridge->get($cacheKey);
53
            }
54
        } catch (\Exception $e) {
55
            // It's always safe to skip reading the cache
56
        }
57
        // phpcs:enable Generic.CodeAnalysis.EmptyStatement
58
59
        $this->init();
60
61
        $bootstrappers = call_user_func($callback, $this->modules);
62
63
        // phpcs:disable Generic.CodeAnalysis.EmptyStatement
64
        try {
65
            if ($this->cacheBridge) {
66
                $this->cacheBridge->set($cacheKey, $bootstrappers, PHP_INT_MAX);
67
            }
68
        } catch (\Exception $e) {
69
            // It's always safe to skip writing the cache
70
        }
71
        // phpcs:enable Generic.CodeAnalysis.EmptyStatement
72
73
        return $bootstrappers;
74
    }
75
76
    /**
77
     * @return Bootstrapper[]
78
     */
79
    public function getHttpBootstrappers(): array
80
    {
81
        $callback = function (array $modules) {
82
            $bootstrappers = [];
83
            foreach ($modules as $module) {
84
                if (isset($module[Module::BOOTSTRAPPERS])) {
85
                    $bootstrappers = array_merge($bootstrappers, $module[Module::BOOTSTRAPPERS]);
86
                }
87
                if (isset($module[Module::HTTP_BOOTSTRAPPERS])) {
88
                    $bootstrappers = array_merge($bootstrappers, $module[Module::HTTP_BOOTSTRAPPERS]);
89
                }
90
            }
91
92
            return $bootstrappers;
93
        };
94
95
        return $this->cacheWrapper(static::CACHE_KEY_HTTP_BOOTSTRAPPERS, $callback);
96
    }
97
98
    /**
99
     * @return Bootstrapper[]
100
     */
101
    public function getCliBootstrappers(): array
102
    {
103
        $callback = function (array $modules) {
104
            $bootstrappers = [];
105
            foreach ($modules as $module) {
106
                if (isset($module[Module::BOOTSTRAPPERS])) {
107
                    $bootstrappers = array_merge($bootstrappers, $module[Module::BOOTSTRAPPERS]);
108
                }
109
                if (isset($module[Module::CLI_BOOTSTRAPPERS])) {
110
                    $bootstrappers = array_merge($bootstrappers, $module[Module::CLI_BOOTSTRAPPERS]);
111
                }
112
            }
113
114
            return $bootstrappers;
115
        };
116
117
        return $this->cacheWrapper(static::CACHE_KEY_CLI_BOOTSTRAPPERS, $callback);
118
    }
119
120
    /**
121
     * @return Command[]
122
     */
123
    public function getCommands(): array
124
    {
125
        $callback = function (array $modules) {
126
            $commands = [];
127
            foreach ($modules as $module) {
128
                if (isset($module[Module::COMMANDS])) {
129
                    $commands = array_merge($commands, $module[Module::COMMANDS]);
130
                }
131
            }
132
133
            return $commands;
134
        };
135
136
        return $this->cacheWrapper(static::CACHE_KEY_COMMANDS, $callback);
137
    }
138
139
    /**
140
     * @return string[][]
141
     */
142
    public function getEvents(): array
143
    {
144
        return $this->cacheWrapper(static::CACHE_KEY_EVENTS, $this->namedOptionsCallback(Module::EVENTS));
145
    }
146
147
    /**
148
     * @return string[][]
149
     */
150
    public function getMiddleware(): array
151
    {
152
        return $this->cacheWrapper(
153
            static::CACHE_KEY_MIDDLEWARE,
154
            $this->prioritizedOptionsCallback(Module::MIDDLEWARE)
155
        );
156
    }
157
158
    /**
159
     * @return string[][]
160
     */
161
    public function getRoutePaths(): array
162
    {
163
        return $this->cacheWrapper(
164
            static::CACHE_KEY_ROUTE_PATHS,
165
            $this->prioritizedOptionsCallback(Module::ROUTE_PATHS, true)
166
        );
167
    }
168
169
    /**
170
     * @return string[][]
171
     */
172
    public function getMigrationPaths(): array
173
    {
174
        return $this->cacheWrapper(
175
            static::CACHE_KEY_MIGRATION_PATHS,
176
            $this->prioritizedOptionsCallback(Module::MIGRATION_PATHS)
177
        );
178
    }
179
180
    /**
181
     * @return string[]
182
     */
183
    public function getResourcePaths(): array
184
    {
185
        return $this->cacheWrapper(
186
            static::CACHE_KEY_RESOURCE_PATH,
187
            $this->simpleOptionCallback(Module::RESOURCE_PATH)
188
        );
189
    }
190
191
    /**
192
     * Creates a callback that will simply merge a 2-dimensions array
193
     *
194
     * Examples
195
     * Module A: ['A' => ['a', 'b', 'c'], 'B' => ['d', 'b']]
196
     * Module B: ['A' => ['a', 'b'], 'C' => ['a']]
197
     * Result:   ['A' => ['a', 'b', 'c', 'a', 'b'], 'B' => ['d', 'b'], 'C' => ['a']]
198
     *
199
     * @param string $option
200
     *
201
     * @return callable
202
     */
203
    protected function namedOptionsCallback(string $option): callable
204
    {
205
        return function ($modules) use ($option) {
206
            $merged = [];
207
            foreach ($modules as $module) {
208
                if (!isset($module[$option])) {
209
                    continue;
210
                }
211
                foreach ($module[$option] as $eventType => $events) {
212
                    if (!isset($merged[$eventType])) {
213
                        $merged[$eventType] = [];
214
                    }
215
                    $merged[$eventType] = array_merge($merged[$eventType], $events);
216
                }
217
            }
218
219
            return $merged;
220
        };
221
    }
222
223
    /**
224
     * Creates a callback that will try to keep the prioritization of the options in place
225
     *
226
     * Examples
227
     * Module A: [3 => ['a', 'b', 'c'], 10 => ['d', 'b'], 12 => ['a']]
228
     * Module B: [10 => ['a', 'b'], 14 => ['a']]
229
     * Result:   [3 => ['a', 'b', 'c'], 10 => ['d', 'b', 'a', 'b'], 12 => ['a'], 14 => ['a']]
230
     *
231
     * @param string $option
232
     * @param bool   $reversed
233
     *
234
     * @return callable
235
     */
236
    protected function prioritizedOptionsCallback(string $option, bool $reversed = false): callable
237
    {
238
        return function ($modules) use ($option, $reversed) {
239
            $merged = [];
240
            foreach ($modules as $module) {
241
                if (!isset($module[$option])) {
242
                    continue;
243
                }
244
                foreach ($module[$option] as $priority => $priorityPaths) {
245
                    if (!isset($merged[$priority])) {
246
                        $merged[$priority] = [];
247
                    }
248
                    $merged[$priority] = array_merge($merged[$priority], $priorityPaths);
249
                }
250
            }
251
252
            if ($reversed) {
253
                krsort($merged);
254
            } else {
255
                ksort($merged);
256
            }
257
258
            $flattened = [];
259
            foreach ($merged as $priorityPaths) {
260
                $flattened = array_merge($flattened, $priorityPaths);
261
            }
262
263
            return $flattened;
264
        };
265
    }
266
267
    /**
268
     * Creates a callback that will return a simple option
269
     *
270
     * Examples
271
     * Module A: [3 => ['a', 'b', 'c'], 10 => ['d', 'b'], 12 => ['a']]
272
     * Module B: [10 => ['a', 'b'], 14 => ['a']]
273
     * Result:   [3 => ['a', 'b', 'c'], 10 => ['d', 'b', 'a', 'b'], 12 => ['a'], 14 => ['a']]
274
     *
275
     * @param string $option
276
     * @param bool   $reversed
277
     *
278
     * @return callable
279
     */
280
    protected function simpleOptionCallback(string $option): callable
281
    {
282
        return function ($modules) use ($option) {
283
            $merged = [];
284
            foreach ($modules as $module) {
285
                if (!isset($module[$option])) {
286
                    continue;
287
                }
288
                $merged[$module[Module::IDENTIFIER]] = $module[$option];
289
            }
290
291
            return $merged;
292
        };
293
    }
294
295
296
297
    /**
298
     * @return $this
299
     */
300
    protected function init(): Manager
301
    {
302
        if ($this->modules) {
303
            return $this;
304
        }
305
306
        $this->modules = $this->loader->loadModules();
307
308
        return $this;
309
    }
310
}
311