Loader::scanDirectories()   B
last analyzed

Complexity

Conditions 8
Paths 10

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 11
nc 10
nop 1
dl 0
loc 21
rs 8.4444
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 DirectoryIterator;
9
use LogicException;
10
11
class Loader
12
{
13
    protected const MODULE_FILE_NAME = 'abter.php';
14
15
    protected const ERROR_MSG_UNRESOLVABLE_DEPENDENCIES = 'Not able to determine module order. Likely circular dependency found.'; // phpcs:ignore
16
17
    /** @var string[] */
18
    protected array $sourceRoots;
19
20
    protected string $moduleFileName;
21
22
    /**
23
     * Loader constructor.
24
     *
25
     * @param array  $sourceRoots
26
     * @param string $moduleFileName
27
     */
28
    public function __construct(array $sourceRoots, $moduleFileName = '')
29
    {
30
        $this->sourceRoots = $sourceRoots;
31
32
        $this->moduleFileName = $moduleFileName ?: static::MODULE_FILE_NAME;
33
    }
34
35
    /**
36
     * @return array<string,mixed>[]
37
     */
38
    public function loadModules(): array
39
    {
40
        $rawModules = [];
41
        foreach ($this->findModules() as $path) {
42
            /** @var array<string,mixed> $rawModule */
43
            $rawModule = include $path;
44
45
            if (empty($rawModule[Module::ENABLED])) {
46
                continue;
47
            }
48
49
            $rawModules[] = $rawModule;
50
        }
51
52
        if (count($rawModules) === 0) {
53
            return [];
54
        }
55
56
        return $this->sortModules($rawModules);
57
    }
58
59
    /**
60
     * @param array<string,mixed>[] $rawModules
61
     * @param array<string,string>  $sortedIds
62
     *
63
     * @return array
64
     */
65
    protected function sortModules(array $rawModules, array $sortedIds = []): array
66
    {
67
        $sortedModules = [];
68
        while (!empty($rawModules)) {
69
            $sortedCount = count($sortedIds);
70
71
            foreach ($rawModules as $idx => $rawModule) {
72
                foreach ($rawModule[Module::DEPENDENCIES] as $dep) {
73
                    if (!isset($sortedIds[$dep])) {
74
                        continue 2;
75
                    }
76
                }
77
78
                $moduleId             = $rawModule[Module::IDENTIFIER];
79
                $sortedIds[$moduleId] = $moduleId;
80
                $sortedModules[]      = $rawModule;
81
                unset($rawModules[$idx]);
82
            }
83
84
            if ($sortedCount === count($sortedIds)) {
85
                throw new LogicException(static::ERROR_MSG_UNRESOLVABLE_DEPENDENCIES);
86
            }
87
        }
88
89
        return $sortedModules;
90
    }
91
92
    /**
93
     * @return array
94
     */
95
    protected function findModules(): array
96
    {
97
        $paths = [];
98
99
        foreach ($this->sourceRoots as $root) {
100
            if (empty($root)) {
101
                continue;
102
            }
103
104
            $paths = array_merge($paths, $this->scanDirectories(new DirectoryIterator($root)));
105
        }
106
107
        return $paths;
108
    }
109
110
    /**
111
     * @param DirectoryIterator $directoryIterator
112
     *
113
     * @return array
114
     */
115
    protected function scanDirectories(DirectoryIterator $directoryIterator): array
116
    {
117
        $paths = [];
118
        foreach ($directoryIterator as $fileInfo) {
119
            if ($fileInfo->isDot() || !$fileInfo->isFile()) {
120
                continue;
121
            }
122
            if ($fileInfo->getFilename() === $this->moduleFileName) {
123
                return [$fileInfo->getRealPath()];
124
            }
125
        }
126
127
        foreach ($directoryIterator as $fileInfo) {
128
            if ($fileInfo->isDot() || !$fileInfo->isDir()) {
129
                continue;
130
            }
131
132
            $paths = array_merge($paths, $this->scanDirectories(new DirectoryIterator($fileInfo->getRealPath())));
133
        }
134
135
        return $paths;
136
    }
137
}
138