Passed
Push — master ( 7353cf...70f9f1 )
by Peter
02:51
created

Manager::simpleNamedOptions()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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