Passed
Push — master ( a21940...8fc5ed )
by Peter
02:25
created

Manager::getAssetsPaths()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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