Manager   B
last analyzed

Complexity

Total Complexity 48

Size/Duplication

Total Lines 357
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 122
c 3
b 0
f 0
dl 0
loc 357
rs 8.5599
wmc 48

17 Methods

Rating   Name   Duplication   Size   Complexity  
A cacheWrapper() 0 28 6
A getMiddleware() 0 5 1
B namedPrioritizedOptionsCallback() 0 33 10
A getHttpBootstrappers() 0 6 1
A getCliBootstrappers() 0 6 1
A getMigrationPaths() 0 5 1
A getResourcePaths() 0 5 1
A simpleOptionCallback() 0 12 3
B prioritizedOptionsCallback() 0 28 7
A getAssetsPaths() 0 5 1
A getRoutePaths() 0 5 1
A simpleNamedOptions() 0 14 4
A getBootstrappers() 0 16 4
A getEvents() 0 3 1
A getCommands() 0 14 3
A init() 0 9 2
A __construct() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Manager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Manager, and based on these observations, apply Extract Interface, too.

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
    protected Loader $loader;
29
30
    protected ?ICacheBridge $cacheBridge;
31
32
    protected ?array $modules = null;
33
34
    /**
35
     * Manager constructor.
36
     *
37
     * @param Loader            $loader
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
     * @param string   $cacheKey
48
     * @param callable $callback
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
        return $this->getBootstrappers(
88
            static::CACHE_KEY_HTTP_BOOTSTRAPPERS,
89
            Module::BOOTSTRAPPERS,
90
            Module::HTTP_BOOTSTRAPPERS
91
        );
92
    }
93
94
    /**
95
     * @return Bootstrapper[]
96
     */
97
    public function getCliBootstrappers(): array
98
    {
99
        return $this->getBootstrappers(
100
            static::CACHE_KEY_CLI_BOOTSTRAPPERS,
101
            Module::BOOTSTRAPPERS,
102
            Module::CLI_BOOTSTRAPPERS
103
        );
104
    }
105
106
    /**
107
     * @return Bootstrapper[]
108
     */
109
    protected function getBootstrappers(string $cacheKey, string ...$keys): array
110
    {
111
        $callback = function (array $modules) use ($keys) {
112
            $bootstrappers = [];
113
            foreach ($modules as $module) {
114
                foreach ($keys as $key) {
115
                    if (array_key_exists($key, $module)) {
116
                        $bootstrappers = array_merge($bootstrappers, $module[$key]);
117
                    }
118
                }
119
            }
120
121
            return $bootstrappers;
122
        };
123
124
        return $this->cacheWrapper($cacheKey, $callback);
125
    }
126
127
    /**
128
     * @return Command[]
129
     */
130
    public function getCommands(): array
131
    {
132
        $callback = function (array $modules) {
133
            $commands = [];
134
            foreach ($modules as $module) {
135
                if (isset($module[Module::COMMANDS])) {
136
                    $commands = array_merge($commands, $module[Module::COMMANDS]);
137
                }
138
            }
139
140
            return $commands;
141
        };
142
143
        return $this->cacheWrapper(static::CACHE_KEY_COMMANDS, $callback);
144
    }
145
146
    /**
147
     * @return string[][]
148
     */
149
    public function getEvents(): array
150
    {
151
        return $this->cacheWrapper(static::CACHE_KEY_EVENTS, $this->namedPrioritizedOptionsCallback(Module::EVENTS));
152
    }
153
154
    /**
155
     * @return string[][]
156
     */
157
    public function getMiddleware(): array
158
    {
159
        return $this->cacheWrapper(
160
            static::CACHE_KEY_MIDDLEWARE,
161
            $this->prioritizedOptionsCallback(Module::MIDDLEWARE)
162
        );
163
    }
164
165
    /**
166
     * @return string[][]
167
     */
168
    public function getRoutePaths(): array
169
    {
170
        return $this->cacheWrapper(
171
            static::CACHE_KEY_ROUTE_PATHS,
172
            $this->prioritizedOptionsCallback(Module::ROUTE_PATHS, true)
173
        );
174
    }
175
176
    /**
177
     * @return string[][]
178
     */
179
    public function getMigrationPaths(): array
180
    {
181
        return $this->cacheWrapper(
182
            static::CACHE_KEY_MIGRATION_PATHS,
183
            $this->prioritizedOptionsCallback(Module::MIGRATION_PATHS)
184
        );
185
    }
186
187
    /**
188
     * @return string[]
189
     */
190
    public function getResourcePaths(): array
191
    {
192
        return $this->cacheWrapper(
193
            static::CACHE_KEY_RESOURCE_PATH,
194
            $this->simpleOptionCallback(Module::RESOURCE_PATH)
195
        );
196
    }
197
198
    /**
199
     * @return string[][]
200
     */
201
    public function getAssetsPaths(): array
202
    {
203
        return $this->cacheWrapper(
204
            static::CACHE_KEY_ASSETS_PATHS,
205
            $this->simpleNamedOptions(Module::ASSETS_PATHS)
206
        );
207
    }
208
209
    /**
210
     * Creates a callback that will return a simple option for each module it's defined for
211
     *
212
     * Examples
213
     * Module A: 'a'
214
     * Module B: 'b'
215
     * Result:   ['Module A' => 'a', 'Module B' => 'b']
216
     *
217
     * @param string $option
218
     *
219
     * @return callable
220
     */
221
    protected function simpleOptionCallback(string $option): callable
222
    {
223
        return function ($modules) use ($option) {
224
            $merged = [];
225
            foreach ($modules as $module) {
226
                if (!isset($module[$option])) {
227
                    continue;
228
                }
229
                $merged[$module[Module::IDENTIFIER]] = $module[$option];
230
            }
231
232
            return $merged;
233
        };
234
    }
235
236
    /**
237
     * Creates a callback that allows overriding previously definid options
238
     *
239
     * Examples
240
     * Module A: ['a' => 'a', 'b' => 'b']
241
     * Module B: ['b' => 'c', 'c' => 'd']
242
     * Result:   ['a' => ['a'], 'b' => ['b', 'c'], 'c' => ['d']]
243
     *
244
     * @param string $option
245
     *
246
     * @return callable
247
     */
248
    protected function simpleNamedOptions(string $option): callable
249
    {
250
        return function ($modules) use ($option) {
251
            $merged = [];
252
            foreach ($modules as $module) {
253
                if (!isset($module[$option])) {
254
                    continue;
255
                }
256
                foreach ($module[$option] as $key => $value) {
257
                    $merged[$key][] = $value;
258
                }
259
            }
260
261
            return $merged;
262
        };
263
    }
264
265
    /**
266
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
267
     *
268
     * Creates a callback that will simply merge a 2-dimensions array
269
     *
270
     * Examples
271
     * Module A: ['A' => [5 => ['z'], 10 => ['a', 'b', 'c']], 'B' => [10 => ['d', 'b']]]
272
     * Module B: ['A' => [10 => ['a', 'b']], 'C' => [10 => ['a']]]
273
     * Result:   ['A' => ['z', 'a', 'b', 'c', 'a', 'b'], 'B' => ['d', 'b'], 'C' => ['a']]
274
     *
275
     * @param string $option
276
     *
277
     * @return callable
278
     */
279
    protected function namedPrioritizedOptionsCallback(string $option): callable
280
    {
281
        return function ($modules) use ($option) {
282
            $prioritized = [];
283
            foreach ($modules as $module) {
284
                if (!isset($module[$option])) {
285
                    continue;
286
                }
287
                foreach ($module[$option] as $eventType => $prioritizedEvents) {
288
                    if (!$prioritizedEvents) {
289
                        continue;
290
                    }
291
                    foreach ($prioritizedEvents as $priority => $events) {
292
                        if (!isset($prioritized[$eventType][$priority])) {
293
                            $prioritized[$eventType][$priority] = [];
294
                        }
295
                        $prioritized[$eventType][$priority] = array_merge($prioritized[$eventType][$priority], $events);
296
                    }
297
                }
298
            }
299
300
            $merged = [];
301
            foreach ($prioritized as $eventType => $events) {
302
                krsort($events);
303
                foreach ($events as $priority => $priorityEvents) {
304
                    if (empty($merged[$eventType])) {
305
                        $merged[$eventType] = [];
306
                    }
307
                    $merged[$eventType] = array_merge($merged[$eventType], $priorityEvents);
308
                }
309
            }
310
311
            return $merged;
312
        };
313
    }
314
315
    /**
316
     * Creates a callback that will try to keep the prioritization of the options in place
317
     *
318
     * Examples
319
     * Module A: [3 => ['a', 'b', 'c'], 10 => ['d', 'b'], 12 => ['a']]
320
     * Module B: [10 => ['a', 'b'], 14 => ['a']]
321
     * Result:   [3 => ['a', 'b', 'c'], 10 => ['d', 'b', 'a', 'b'], 12 => ['a'], 14 => ['a']]
322
     *
323
     * @param string $option
324
     * @param bool   $reversed
325
     *
326
     * @return callable
327
     */
328
    protected function prioritizedOptionsCallback(string $option, bool $reversed = false): callable
329
    {
330
        return function ($modules) use ($option, $reversed) {
331
            $merged = [];
332
            foreach ($modules as $module) {
333
                if (!isset($module[$option])) {
334
                    continue;
335
                }
336
                foreach ($module[$option] as $priority => $priorityPaths) {
337
                    if (!isset($merged[$priority])) {
338
                        $merged[$priority] = [];
339
                    }
340
                    $merged[$priority] = array_merge($merged[$priority], $priorityPaths);
341
                }
342
            }
343
344
            if ($reversed) {
345
                krsort($merged);
346
            } else {
347
                ksort($merged);
348
            }
349
350
            $flattened = [];
351
            foreach ($merged as $priorityPaths) {
352
                $flattened = array_merge($flattened, $priorityPaths);
353
            }
354
355
            return $flattened;
356
        };
357
    }
358
359
360
    /**
361
     * @return $this
362
     */
363
    protected function init(): Manager
364
    {
365
        if (null !== $this->modules) {
366
            return $this;
367
        }
368
369
        $this->modules = $this->loader->loadModules();
370
371
        return $this;
372
    }
373
}
374