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

valksor / php-bundle / 21323304205

24 Jan 2026 11:21PM UTC coverage: 61.471% (-3.6%) from 65.046%
21323304205

push

github

k0d3r1s
wip

3 of 3 new or added lines in 1 file covered. (100.0%)

113 existing lines in 4 files now uncovered.

493 of 802 relevant lines covered (61.47%)

1.47 hits per line

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

90.52
/Kernel/AbstractKernel.php
1
<?php declare(strict_types = 1);
2

3
/*
4
 * This file is part of the Valksor package.
5
 *
6
 * (c) Davis Zalitis (k0d3r1s)
7
 * (c) SIA Valksor <packages@valksor.com>
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12

13
namespace Valksor\Bundle\Kernel;
14

15
use LogicException;
16
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
17
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
18
use Symfony\Component\Dotenv\Dotenv;
19
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
20
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
21
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
22

23
use function array_merge;
24
use function explode;
25
use function file_exists;
26
use function glob;
27
use function is_dir;
28
use function is_file;
29
use function sort;
30
use function sprintf;
31
use function strlen;
32
use function ucfirst;
33

34
use const GLOB_NOSORT;
35

36
abstract class AbstractKernel extends BaseKernel
37
{
38
    use MicroKernelTrait;
39
    protected ?string $apps = null;
40

41
    protected ?string $infrastructure = null;
42

43
    /** @var array<string, BundleInterface>|null */
44
    private ?array $allBundles = null;
45

46
    public function __construct(
47
        string $environment,
48
        bool $debug,
49
        private readonly string $id,
50
    ) {
51
        $_SERVER['APP_KERNEL_NAME'] = $this->id;
7✔
52
        $_ENV['APP_KERNEL_NAME'] = $this->id;
7✔
53

54
        if (null === $this->apps) {
7✔
UNCOV
55
            throw new LogicException('Apps dir not set');
×
56
        }
57

58
        if (null === $this->infrastructure) {
7✔
UNCOV
59
            throw new LogicException('Infrastructure dir not set');
×
60
        }
61

62
        parent::__construct($environment, $debug);
7✔
63

64
        $this->loadAppEnvironmentFiles();
7✔
65
    }
66

67
    public function getAppConfigDir(): string
68
    {
69
        return $this->getProjectDir() . '/' . $this->apps . '/' . $this->id . '/config';
6✔
70
    }
71

72
    public function getCacheDir(): string
73
    {
74
        return (($_SERVER['APP_CACHE_DIR'] ?? $this->getProjectDir()) . '/var/cache') . '/' . $this->id . '/' . $this->environment;
2✔
75
    }
76

77
    public function getConfigDir(): string
78
    {
79
        return $this->getProjectDir() . '/' . $this->infrastructure . '/config';
2✔
80
    }
81

82
    public function getLogDir(): string
83
    {
84
        return (($_SERVER['APP_LOG_DIR'] ?? $this->getProjectDir()) . '/var/log') . '/' . $this->id;
2✔
85
    }
86

87
    public function registerBundles(): iterable
88
    {
89
        foreach ($this->getAllBundles() as $class => $envs) {
1✔
90
            if ($envs[$this->environment] ?? $envs['all'] ?? false) {
1✔
91
                yield new $class();
1✔
92
            }
93
        }
94
    }
95

96
    protected function configureContainer(
97
        ContainerConfigurator $container,
98
    ): void {
99
        $infrastructureDir = $this->getProjectDir() . '/' . $this->infrastructure . '/config';
1✔
100
        $appDir = $this->getAppConfigDir();
1✔
101

102
        $container->parameters()
1✔
103
            ->set('app.id', $this->id)
1✔
104
            ->set('app.namespace', ucfirst(explode('.', $this->id, 2)[0]));
1✔
105

106
        $this->importInfrastructurePackagesWithOverride($infrastructureDir, $appDir, $container);
1✔
107
        $this->importConfig($infrastructureDir . '/services.%s', $container);
1✔
108
        $this->importConfigWithWildcardSupport($appDir . '/{packages}/*.%s', $container);
1✔
109
        $this->importConfigWithWildcardSupport($appDir . '/{packages}/' . $this->environment . '/*.%s', $container);
1✔
110
        $this->importConfig($appDir . '/services.%s', $container);
1✔
111
    }
112

113
    protected function configureRoutes(
114
        RoutingConfigurator $routes,
115
    ): void {
116
        $infrastructureDir = $this->getProjectDir() . '/' . $this->infrastructure . '/config';
1✔
117
        $appDir = $this->getAppConfigDir();
1✔
118

119
        $this->importInfrastructureRoutesWithOverride($infrastructureDir, $appDir, $routes);
1✔
120
        $this->importRoutesWithWildcardSupport($appDir . '/{routes}/*.%s', $routes);
1✔
121
        $this->importRoutesWithWildcardSupport($appDir . '/{routes}/' . $this->environment . '/*.%s', $routes);
1✔
122
        $this->importRoutes($appDir . '/routes.%s', $routes);
1✔
123
    }
124

125
    /**
126
     * @return array<string, mixed>
127
     */
128
    protected function getKernelParameters(): array
129
    {
130
        return array_merge(parent::getKernelParameters(), [
1✔
131
            '.kernel.bundles_definition' => $this->getAllBundles(),
1✔
132
            '.kernel.config_dir' => $this->getConfigDir(),
1✔
133
        ]);
1✔
134
    }
135

136
    private function extractBaseDirectoryFromWildcard(
137
        string $wildcardPath,
138
    ): ?string {
139
        $wildcardStart = strpos($wildcardPath, '{');
2✔
140

141
        if (false === $wildcardStart) {
2✔
UNCOV
142
            $wildcardStart = strpos($wildcardPath, '*');
×
143
        }
144

145
        if (false === $wildcardStart) {
2✔
UNCOV
146
            return null;
×
147
        }
148

149
        $dirSeparator = strrpos($wildcardPath, '/', $wildcardStart - strlen($wildcardPath));
2✔
150

151
        if (false === $dirSeparator) {
2✔
152
            return null;
×
153
        }
154

155
        return substr($wildcardPath, 0, $dirSeparator);
2✔
156
    }
157

158
    /**
159
     * @return array<string, BundleInterface>
160
     */
161
    private function getAllBundles(): array
162
    {
163
        if (null !== $this->allBundles) {
3✔
164
            return $this->allBundles;
1✔
165
        }
166

167
        $this->allBundles = [];
3✔
168

169
        $infrastructureBundlesFile = $this->getProjectDir() . '/' . $this->infrastructure . '/config/bundles.php';
3✔
170

171
        if (is_file($infrastructureBundlesFile)) {
3✔
172
            $infrastructureBundles = require $infrastructureBundlesFile;
3✔
173
            $this->allBundles = array_merge($this->allBundles, $infrastructureBundles);
3✔
174
        }
175

176
        $appBundlesFile = $this->getAppConfigDir() . '/bundles.php';
3✔
177

178
        if (is_file($appBundlesFile)) {
3✔
179
            $appBundles = require $appBundlesFile;
1✔
180
            $this->allBundles = array_merge($this->allBundles, $appBundles);
1✔
181
        }
182

183
        return $this->allBundles;
3✔
184
    }
185

186
    private function importAllConfigs(
187
        string $dir,
188
        ContainerConfigurator $container,
189
    ): void {
190
        if (!is_dir($dir)) {
1✔
UNCOV
191
            return;
×
192
        }
193

194
        $files = glob($dir . '/*.php', GLOB_NOSORT) ?: [];
1✔
195
        sort($files);
1✔
196

197
        foreach ($files as $file) {
1✔
198
            $container->import($file);
1✔
199
        }
200
    }
201

202
    private function importAllRoutes(
203
        string $dir,
204
        RoutingConfigurator $routes,
205
    ): void {
206
        if (!is_dir($dir)) {
1✔
UNCOV
207
            return;
×
208
        }
209

210
        $files = glob($dir . '/*.php', GLOB_NOSORT) ?: [];
1✔
211
        sort($files);
1✔
212

213
        foreach ($files as $file) {
1✔
214
            $routes->import($file);
1✔
215
        }
216
    }
217

218
    private function importConfig(
219
        string $filename,
220
        ContainerConfigurator $container,
221
        bool $check = true,
222
    ): void {
223
        $file = sprintf($filename, 'php');
1✔
224

225
        if (!$check || file_exists($file)) {
1✔
226
            $container->import($file);
1✔
227
        }
228
    }
229

230
    private function importConfigWithWildcardSupport(
231
        string $filename,
232
        ContainerConfigurator $container,
233
    ): void {
234
        $file = sprintf($filename, 'php');
1✔
235

236
        // Check if this is a wildcard pattern that needs directory validation
237
        if ($this->isWildcardPattern($file)) {
1✔
238
            $baseDir = $this->extractBaseDirectoryFromWildcard($file);
1✔
239

240
            if ($baseDir && is_dir($baseDir)) {
1✔
241
                $container->import($file);
1✔
242
            }
243
        // If base directory doesn't exist, skip the import silently
UNCOV
244
        } elseif (file_exists($file)) {
×
UNCOV
245
            $container->import($file);
×
246
        }
247
    }
248

249
    private function importInfrastructurePackagesWithOverride(
250
        string $infrastructureConfigDir,
251
        string $appConfigDir,
252
        ContainerConfigurator $container,
253
    ): void {
254
        $this->importAllConfigs($infrastructureConfigDir . '/packages', $container);
1✔
255
        $this->importAllConfigs($infrastructureConfigDir . '/packages/' . $this->environment, $container);
1✔
256

257
        $this->importAllConfigs($appConfigDir . '/packages', $container);
1✔
258
        $this->importAllConfigs($appConfigDir . '/packages/' . $this->environment, $container);
1✔
259
    }
260

261
    private function importInfrastructureRoutesWithOverride(
262
        string $infrastructureConfigDir,
263
        string $appConfigDir,
264
        RoutingConfigurator $routes,
265
    ): void {
266
        $this->importAllRoutes($infrastructureConfigDir . '/routes', $routes);
1✔
267
        $this->importAllRoutes($infrastructureConfigDir . '/routes/' . $this->environment, $routes);
1✔
268

269
        $this->importAllRoutes($appConfigDir . '/routes', $routes);
1✔
270
        $this->importAllRoutes($appConfigDir . '/routes/' . $this->environment, $routes);
1✔
271

272
        $infrastructureFile = $infrastructureConfigDir . '/routes.php';
1✔
273

274
        if (is_file($infrastructureFile)) {
1✔
275
            $routes->import($infrastructureFile);
1✔
276
        }
277
    }
278

279
    private function importRoutes(
280
        string $filename,
281
        RoutingConfigurator $routes,
282
        bool $check = true,
283
    ): void {
284
        $file = sprintf($filename, 'php');
1✔
285

286
        if (!$check || file_exists($file)) {
1✔
287
            $routes->import($file);
1✔
288
        }
289
    }
290

291
    private function importRoutesWithWildcardSupport(
292
        string $filename,
293
        RoutingConfigurator $routes,
294
    ): void {
295
        $file = sprintf($filename, 'php');
1✔
296

297
        // Check if this is a wildcard pattern that needs directory validation
298
        if ($this->isWildcardPattern($file)) {
1✔
299
            $baseDir = $this->extractBaseDirectoryFromWildcard($file);
1✔
300

301
            if ($baseDir && is_dir($baseDir)) {
1✔
302
                $routes->import($file);
1✔
303
            }
304
        // If base directory doesn't exist, skip the import silently
UNCOV
305
        } elseif (file_exists($file)) {
×
UNCOV
306
            $routes->import($file);
×
307
        }
308
    }
309

310
    private function isWildcardPattern(
311
        string $filename,
312
    ): bool {
313
        return str_contains($filename, '{') || str_contains($filename, '*');
2✔
314
    }
315

316
    /**
317
     * Load app-specific environment files following Symfony's standard hierarchy.
318
     *
319
     * Files are loaded in order (later files override earlier ones):
320
     * 1. /infrastructure/.env                            - Infrastructure environment file
321
     * 2. /infrastructure/.env.local                      - Local infrastructure overrides (gitignored)
322
     * 3. /infrastructure/.env.{environment}              - Environment-specific infrastructure (e.g., .env.dev, .env.prod)
323
     * 4. /infrastructure/.env.{environment}.local        - Environment-specific local infrastructure overrides (gitignored)
324
     * 5. /apps/{app_id}/.env                     - Base environment file
325
     * 6. /apps/{app_id}/.env.local               - Local overrides (gitignored)
326
     * 7. /apps/{app_id}/.env.{environment}       - Environment-specific (e.g., .env.dev, .env.prod)
327
     * 8. /apps/{app_id}/.env.{environment}.local - Environment-specific local overrides (gitignored)
328
     *
329
     * This mirrors the standard Symfony environment loading but for app-specific configuration.
330
     */
331
    private function loadAppEnvironmentFiles(): void
332
    {
333
        $appDir = $this->getProjectDir() . '/' . $this->apps . '/' . $this->id;
7✔
334
        $infrastructureDir = $this->getProjectDir() . '/' . $this->infrastructure;
7✔
335
        $dotenv = new Dotenv();
7✔
336

337
        $envFiles = [
7✔
338
            $infrastructureDir . '/.env',
7✔
339
            $infrastructureDir . '/.env.local',
7✔
340
            $infrastructureDir . '/.env.' . $this->environment,
7✔
341
            $infrastructureDir . '/.env.' . $this->environment . '.local',
7✔
342
            $appDir . '/.env',
7✔
343
            $appDir . '/.env.local',
7✔
344
            $appDir . '/.env.' . $this->environment,
7✔
345
            $appDir . '/.env.' . $this->environment . '.local',
7✔
346
        ];
7✔
347

348
        foreach ($envFiles as $envFile) {
7✔
349
            if (is_file($envFile)) {
7✔
350
                $dotenv->load($envFile);
1✔
351
            }
352
        }
353
    }
354
}
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