• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

willy68 / pg-module-installer / 16390576168

19 Jul 2025 04:25PM UTC coverage: 93.258% (-0.5%) from 93.785%
16390576168

push

github

willy68
First commit

7 of 8 new or added lines in 1 file covered. (87.5%)

166 of 178 relevant lines covered (93.26%)

3.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.26
/src/ModuleInstaller.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace PgFramework\ComposerInstaller;
6

7
use CallbackFilterIterator;
8
use Composer\Composer;
9
use Composer\EventDispatcher\EventSubscriberInterface;
10
use Composer\IO\IOInterface;
11
use Composer\Package\BasePackage;
12
use Composer\Plugin\PluginInterface;
13
use Composer\Script\Event;
14
use Composer\Script\ScriptEvents;
15
use FilesystemIterator;
16
use RecursiveDirectoryIterator;
17
use RecursiveIteratorIterator;
18
use SplFileInfo;
19

20
class ModuleInstaller implements
21
    EventSubscriberInterface,
22
    PluginInterface
23
{
24
    private Composer $composer;
25
    private IOInterface $io;
26
    private array $modules = [];
27
    private int $writeLockEx = LOCK_EX;
28

29
    /**
30
     * Called whenever composer (re)generates the autoloader.
31
     *
32
     * Recreates PgFramework Module path map, based on composer information
33
     * and available app plugins.
34
     *
35
     * @param Event $event Composer's event object.
36
     * @return void
37
     */
38
    public static function run(Event $event): void
×
39
    {
40
        $composer = $event->getComposer();
×
41
        $io = $event->getIO();
×
42

43
        $instance = new static();
×
44
        $instance->composer = $composer;
×
45
        $instance->io = $io;
×
46
        $instance->postAutoloadDump($event);
×
47
    }
48

49
    /**
50
     * Called whenever composer (re)generates the autoloader.
51
     *
52
     * Recreates PgFramework Module path map, based on composer information
53
     * and available app plugins.
54
     *
55
     * @param Event $event Composer's event object.
56
     * @return void
57
     */
58
    public function postAutoloadDump(Event $event): void
3✔
59
    {
60
        $config = $this->composer->getConfig();
3✔
61
        $vendorDir = $config->get('vendor-dir');
3✔
62
        $projectDir = dirname($vendorDir);
3✔
63

64
        $packages = $this->composer->getRepositoryManager()->getLocalRepository()->getPackages();
3✔
65
        $this->io->write('<info>Search pg-modules packages</info>');
3✔
66
        $packages = $this->findModulesPackages($packages);
3✔
67
        if (empty($packages)) {
3✔
68
            $this->io->write('<info>pg-modules packages not found, abort</info>');
1✔
69
            return;
1✔
70
        }
71
        $modules = $this->findModulesClass($packages);
2✔
72
        if (empty($modules)) {
2✔
73
            $this->io->write('<info>pg-modules not found in packages, abort</info>');
1✔
74
            return;
1✔
75
        }
76

77
        $configFile = $this->getConfigFile($projectDir);
1✔
78
        $this->writeConfigFile($configFile, $modules);
1✔
79
    }
80

81
    public function findModulesPackages(array $packages): array
5✔
82
    {
83
        $modulesPackages = [];
5✔
84
        foreach ($packages as $package) {
5✔
85
            if ($package->getType() === 'pg-module') {
5✔
86
                $this->io->write(
3✔
87
                    sprintf(
3✔
88
                        '<info>  Found pg-module type package: %s</info>',
3✔
89
                        $package->getPrettyName()
3✔
90
                    )
3✔
91
                );
3✔
92
                $modulesPackages[] = $package;
3✔
93
            }
94
        }
95
        return $modulesPackages;
5✔
96
    }
97

98
    /**
99
     * @param BasePackage[] $packages
100
     * @return array
101
     */
102
    public function findModulesClass(array $packages): array
3✔
103
    {
104
        $modules = [];
3✔
105
        foreach ($packages as $package) {
3✔
106
            $path = $this->composer->getInstallationManager()->getInstallPath($package);
3✔
107
            $moduleClasses = $this->findModuleClass($package, $path);
3✔
108
            $modules = array_merge($modules, $moduleClasses);
3✔
109
        }
110
        return $modules;
3✔
111
    }
112

113
    public function findModuleClass(BasePackage $package, string $packagePath): array
5✔
114
    {
115
        $modules = [];
5✔
116
        $autoload = $package->getAutoload();
5✔
117
        foreach ($autoload as $type => $pathMap) {
5✔
118
            if ($type !== 'psr-4') {
5✔
119
                continue;
1✔
120
            }
121

122
            $paths = $this->mapNamespacePaths($pathMap, $packagePath);
4✔
123
            foreach ($paths as $path) {
4✔
124
                $files = $this->getPhpFiles($path);
4✔
125
                if (!empty($files)) {
4✔
126
                    $newModules = $this->getModulesClass($files);
3✔
127
                    $modules = array_merge($modules, $newModules);
3✔
128
                }
129
            }
130
        }
131
        return $modules;
5✔
132
    }
133

134
    public function mapNamespacePaths(array $pathMap, string $packagePath): array
4✔
135
    {
136
        $result = [];
4✔
137
        foreach ($pathMap as $namespace => $paths) {
4✔
138
            $paths = array_values((array)$paths);
4✔
139
            foreach ($paths as $path) {
4✔
140
                assert(is_string($namespace));
141
                $src = sprintf(
4✔
142
                    '%s/%s',
4✔
143
                    $packagePath,
4✔
144
                    $path
4✔
145
                );
4✔
146
                $result[] = [rtrim($namespace, '\\') => $src];
4✔
147
            }
148
        }
149
        return $result;
4✔
150
    }
151

152
    public function getPhpFiles(array $paths): array
4✔
153
    {
154
        $files = [];
4✔
155
        foreach ($paths as $dir) {
4✔
156
            if (is_dir($dir)) {
4✔
157
                $files = $this->getFiles($dir, 'php', '.dist.');
3✔
158
                $files = array_map(
3✔
159
                    fn (string $file) => str_replace('\\', '/', $file),
3✔
160
                    array_keys($files)
3✔
161
                );
3✔
162
            }
163
        }
164
        return $files;
4✔
165
    }
166

167
    public function getFiles(string $path, string $ext = 'php', ?string $exclude = null): array
4✔
168
    {
169
        // from https://stackoverflow.com/a/41636321
170
        return iterator_to_array(
4✔
171
            new CallbackFilterIterator(
4✔
172
                new RecursiveIteratorIterator(
4✔
173
                    new RecursiveDirectoryIterator(
4✔
174
                        $path,
4✔
175
                        FilesystemIterator::FOLLOW_SYMLINKS | FilesystemIterator::SKIP_DOTS
4✔
176
                    ),
4✔
177
                    RecursiveIteratorIterator::CHILD_FIRST
4✔
178
                ),
4✔
179
                fn (SplFileInfo $file) => $file->isFile() &&
4✔
180
                    str_ends_with($file->getFilename(), '.' . $ext) &&
4✔
181
                    !str_starts_with($file->getBasename(), '.') &&
4✔
182
                    (null === $exclude || false === stripos($file->getBasename(), $exclude))
4✔
183
            )
4✔
184
        );
4✔
185
    }
186

187
    public function getModulesClass(array $files): array
4✔
188
    {
189
        // Improved regex pattern with better performance
190
        $pattern = '/namespace\s+([a-zA-Z0-9_\\\\]+)\s*;.*?class\s+([a-zA-Z0-9_]+)\s+extends\s+Module/s';
4✔
191

192
        foreach ($files as $file) {
4✔
193
            // Skip if the file doesn't exist or is not readable
194
            if (!is_readable($file)) {
4✔
NEW
195
                continue;
×
196
            }
197

198
            $content = file_get_contents($file);
4✔
199
            if (preg_match($pattern, $content, $m)) {
4✔
200
                $namespace = $m[1];
4✔
201
                $moduleName = $m[2];
4✔
202
                $this->modules[$namespace . '\\' . $moduleName] = $moduleName;
4✔
203
                $this->io->write(
4✔
204
                    sprintf(
4✔
205
                        '<info>      Found pg-module: %s</info>',
4✔
206
                        $moduleName
4✔
207
                    )
4✔
208
                );
4✔
209
            }
210
        }
211
        return $this->modules;
4✔
212
    }
213

214
    public function getConfigFile(string $projectDir): string
2✔
215
    {
216
        return $projectDir .
2✔
217
            DIRECTORY_SEPARATOR .
2✔
218
            'src' .
2✔
219
            DIRECTORY_SEPARATOR .
2✔
220
            'Bootstrap' .
2✔
221
            DIRECTORY_SEPARATOR .
2✔
222
            'PgFramework.php';
2✔
223
    }
224

225
    public function writeConfigFile(string $configFile, array $modules): bool
5✔
226
    {
227
        if (!is_file($configFile)) {
5✔
228
            $this->io->write(
1✔
229
                sprintf(
1✔
230
                    "<info>Config file\n %s \n don't exist in this project, writing dummy file</info>",
1✔
231
                    $configFile
1✔
232
                )
1✔
233
            );
1✔
234
            $this->writeFile($configFile, '', '');
1✔
235
        }
236

237
        $content = file_get_contents($configFile);
5✔
238
        $regex = '/declare\S+\s*;\s+([use\S\s]*)\s+return\s+\[\s+\'modules\'\s+=>\s+\[\s+([\S\s]+)\s+]\s+/';
5✔
239
        if (preg_match($regex, $content, $m)) {
5✔
240
            $writeFile = false;
5✔
241
            $useStr = $m[1];
5✔
242
            if (!$useStr) {
5✔
243
                $useStr = "";
3✔
244
            }
245
            $useStr = trim($useStr) . "\n";
5✔
246
            $modulesStr = $m[2];
5✔
247
            $modulesStr = trim($modulesStr) . "\n";
5✔
248
            foreach ($modules as $useStatement => $classModule) {
5✔
249
                if (str_contains($modulesStr, $classModule . '::class')) {
5✔
250
                    $this->io->write(
2✔
251
                        sprintf(
2✔
252
                            '<info>Module %s already exist in config file</info>',
2✔
253
                            $classModule
2✔
254
                        )
2✔
255
                    );
2✔
256
                    continue;
2✔
257
                }
258
                $writeFile = true;
4✔
259
                $modulesStr .= "\t\t$classModule::class,\n";
4✔
260
                $useStr .= "use $useStatement;\n";
4✔
261

262
                $this->io->write(
4✔
263
                    sprintf(
4✔
264
                        '<info>Write module %s in config file</info>',
4✔
265
                        $classModule
4✔
266
                    )
4✔
267
                );
4✔
268
            }
269
            if ($writeFile) {
5✔
270
                $useStr = trim($useStr);
4✔
271
                $modulesStr = trim($modulesStr);
4✔
272
                return (bool)$this->writeFile($configFile, $useStr, "\t\t" . $modulesStr);
4✔
273
            }
274
        }
275
        $this->io->write('<info>Nothing to update in config file.</info>');
1✔
276
        return false;
1✔
277
    }
278

279
    public function writeFile(string $configFile, string $useStr, string $modulesStr): bool|int
4✔
280
    {
281
        $content = <<<php
4✔
282
<?php
283

284
/** This file is auto generated, do not edit */
285

286
declare(strict_types=1);
287

288
%s
289

290
return [
291
    'modules' => [
292
%s
293
    ]
294
];
295

296
php;
4✔
297
        return file_put_contents($configFile, sprintf($content, $useStr, $modulesStr), $this->writeLockEx);
4✔
298
    }
299

300
    /**
301
     * @inheritDoc
302
     */
303
    public static function getSubscribedEvents(): array
1✔
304
    {
305
        return [
1✔
306
            ScriptEvents::POST_AUTOLOAD_DUMP => 'postAutoloadDump',
1✔
307
        ];
1✔
308
    }
309

310
    /**
311
     * @param int $writeLockEx
312
     */
313
    public function setWriteLockEx(int $writeLockEx = 0): void
16✔
314
    {
315
        $this->writeLockEx = $writeLockEx;
16✔
316
    }
317

318
    /**
319
     * @inheritDoc
320
     */
321
    public function activate(Composer $composer, IOInterface $io)
16✔
322
    {
323
        $this->composer = $composer;
16✔
324
        $this->io = $io;
16✔
325
    }
326

327
    /**
328
     * @inheritDoc
329
     */
330
    public function deactivate(Composer $composer, IOInterface $io)
×
331
    {
332
    }
×
333

334
    /**
335
     * @inheritDoc
336
     */
337
    public function uninstall(Composer $composer, IOInterface $io)
×
338
    {
339
    }
×
340
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc