Passed
Pull Request — master (#1)
by Peter
07:07
created

Manager::prioritizedOptionsCallback()   B

Complexity

Conditions 7
Paths 1

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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