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

nette / bootstrap / 25403382146

05 May 2026 09:29PM UTC coverage: 90.476% (+0.8%) from 89.655%
25403382146

push

github

dg
default parameters (wwwDir, appDir, ...) are now overridable in user configs

Previously, parameters from getDefaultParameters() were injected into the
compiler AFTER all user configs, which silently overrode any redefinition
in common.neon. As a result, e.g. `wwwDir: %rootDir%/www` in a config had
no effect when the entry script lived outside %rootDir%/www (CLI scripts,
MCP servers, scheduled tasks).

Fix: snapshot defaults into $defaultParameters at construction. In
generateContainer(), inject defaults BEFORE user configs (overridable),
then inject only the diff between $staticParameters and defaults AFTER
configs (authoritative — preserves back-compat for setTempDirectory(),
addStaticParameters() and similar setters).

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

12 existing lines in 3 files now uncovered.

133 of 147 relevant lines covered (90.48%)

0.9 hits per line

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

90.63
/src/Bootstrap/Configurator.php
1
<?php declare(strict_types=1);
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Nette\Bootstrap;
9

10
use Composer\Autoload\ClassLoader;
11
use Composer\InstalledVersions;
12
use Latte;
13
use Nette;
14
use Nette\DI;
15
use Nette\DI\Definitions\Statement;
16
use Tracy;
17
use function in_array, is_array, is_string;
18
use const PHP_RELEASE_VERSION, PHP_SAPI, PHP_VERSION_ID;
19

20

21
/**
22
 * Initial system DI container generator.
23
 */
24
class Configurator
25
{
26
        public const CookieSecret = 'nette-debug';
27

28
        /** @deprecated  use Configurator::CookieSecret */
29
        public const COOKIE_SECRET = self::CookieSecret;
30

31

32
        /** @var array<callable(self, DI\Compiler): void>  Occurs after the compiler is created */
33
        public array $onCompile = [];
34

35
        /** @var array<string, class-string<DI\CompilerExtension>|array{class-string<DI\CompilerExtension>, list<mixed>}> */
36
        public array $defaultExtensions = [
37
                'application' => [Nette\Bridges\ApplicationDI\ApplicationExtension::class, ['%debugMode%', ['%appDir%'], '%tempDir%/cache/nette.application']],
38
                'assets' => [Nette\Bridges\AssetsDI\DIExtension::class, ['%baseUrl%', '%wwwDir%', '%debugMode%']],
39
                'cache' => [Nette\Bridges\CacheDI\CacheExtension::class, ['%tempDir%/cache']],
40
                'constants' => Extensions\ConstantsExtension::class,
41
                'database' => [Nette\Bridges\DatabaseDI\DatabaseExtension::class, ['%debugMode%']],
42
                'decorator' => Nette\DI\Extensions\DecoratorExtension::class,
43
                'di' => [Nette\DI\Extensions\DIExtension::class, ['%debugMode%']],
44
                'extensions' => Nette\DI\Extensions\ExtensionsExtension::class,
45
                'forms' => Nette\Bridges\FormsDI\FormsExtension::class,
46
                'http' => [Nette\Bridges\HttpDI\HttpExtension::class, ['%consoleMode%']],
47
                'inject' => Nette\DI\Extensions\InjectExtension::class,
48
                'latte' => [Nette\Bridges\ApplicationDI\LatteExtension::class, ['%tempDir%/cache/latte', '%debugMode%']],
49
                'mail' => Nette\Bridges\MailDI\MailExtension::class,
50
                'php' => Extensions\PhpExtension::class,
51
                'routing' => [Nette\Bridges\ApplicationDI\RoutingExtension::class, ['%debugMode%']],
52
                'search' => [Nette\DI\Extensions\SearchExtension::class, ['%tempDir%/cache/nette.search']],
53
                'security' => [Nette\Bridges\SecurityDI\SecurityExtension::class, ['%debugMode%']],
54
                'session' => [Nette\Bridges\HttpDI\SessionExtension::class, ['%debugMode%', '%consoleMode%']],
55
                'tracy' => [Tracy\Bridges\Nette\TracyExtension::class, ['%debugMode%', '%consoleMode%']],
56
        ];
57

58
        /** @var list<class-string>  classes which shouldn't be autowired */
59
        public array $autowireExcludedClasses = [
60
                \ArrayAccess::class,
61
                \Countable::class,
62
                \IteratorAggregate::class,
63
                \stdClass::class,
64
                \Traversable::class,
65
        ];
66

67
        /** @var array<string, mixed> */
68
        protected array $staticParameters;
69

70
        /** @var array<string, mixed> */
71
        protected array $dynamicParameters = [];
72

73
        /** @var array<string, object> */
74
        protected array $services = [];
75

76
        /** @var list<string|array<string, mixed>> */
77
        protected array $configs = [];
78

79
        /** @var array<string, mixed> */
80
        private array $defaultParameters;
81

82

83
        public function __construct()
84
        {
85
                $this->defaultParameters = $this->staticParameters = $this->getDefaultParameters();
1✔
86

87
                if (class_exists(InstalledVersions::class) // back compatibility
1✔
88
                        && InstalledVersions::isInstalled('nette/caching')
1✔
89
                        && version_compare(InstalledVersions::getVersion('nette/caching'), '3.3.0', '<')
1✔
90
                ) {
UNCOV
91
                        $this->defaultExtensions['cache'][1][0] = '%tempDir%';
×
92
                }
93
        }
1✔
94

95

96
        /**
97
         * Sets the %debugMode% parameter.
98
         * @param  bool|string|list<string>  $value  IP addresses or computer names whitelist, or true/false
99
         */
100
        public function setDebugMode(bool|string|array $value): static
1✔
101
        {
102
                if (is_string($value) || is_array($value)) {
1✔
103
                        $value = static::detectDebugMode($value);
1✔
104
                }
105

106
                $this->staticParameters['debugMode'] = $value;
1✔
107
                $this->staticParameters['productionMode'] = !$this->staticParameters['debugMode']; // compatibility
1✔
108
                return $this;
1✔
109
        }
110

111

112
        public function isDebugMode(): bool
113
        {
114
                return $this->staticParameters['debugMode'];
1✔
115
        }
116

117

118
        /**
119
         * Sets path to temporary directory.
120
         */
121
        public function setTempDirectory(string $path): static
1✔
122
        {
123
                $this->staticParameters['tempDir'] = $path;
1✔
124
                return $this;
1✔
125
        }
126

127

128
        /**
129
         * Sets the default timezone.
130
         */
131
        public function setTimeZone(string $timezone): static
1✔
132
        {
133
                date_default_timezone_set($timezone);
1✔
134
                @ini_set('date.timezone', $timezone); // @ - function may be disabled
1✔
135
                return $this;
1✔
136
        }
137

138

139
        /**
140
         * @deprecated use addStaticParameters()
141
         * @param  array<string, mixed>  $params
142
         */
143
        public function addParameters(array $params): static
1✔
144
        {
145
                return $this->addStaticParameters($params);
1✔
146
        }
147

148

149
        /**
150
         * Adds static parameters.
151
         * @param  array<string, mixed>  $params
152
         */
153
        public function addStaticParameters(array $params): static
1✔
154
        {
155
                $this->staticParameters = DI\Config\Helpers::merge($params, $this->staticParameters);
1✔
156
                return $this;
1✔
157
        }
158

159

160
        /**
161
         * Adds dynamic parameters.
162
         * @param  array<string, mixed>  $params
163
         */
164
        public function addDynamicParameters(array $params): static
1✔
165
        {
166
                $this->dynamicParameters = $params + $this->dynamicParameters;
1✔
167
                return $this;
1✔
168
        }
169

170

171
        /**
172
         * Adds service instances.
173
         * @param  array<string, object>  $services
174
         */
175
        public function addServices(array $services): static
1✔
176
        {
177
                $this->services = $services + $this->services;
1✔
178
                return $this;
1✔
179
        }
180

181

182
        /** @return array<string, mixed> */
183
        protected function getDefaultParameters(): array
184
        {
185
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
1✔
186
                $last = end($trace);
1✔
187
                $debugMode = static::detectDebugMode();
1✔
188
                $loaderRc = class_exists(ClassLoader::class)
1✔
189
                        ? new \ReflectionClass(ClassLoader::class)
1✔
UNCOV
190
                        : null;
×
191
                $rootDir = class_exists(InstalledVersions::class) && ($tmp = InstalledVersions::getRootPackage()['install_path'] ?? null)
1✔
192
                        ? rtrim(Nette\Utils\FileSystem::normalizePath($tmp), '\/')
1✔
UNCOV
193
                        : null;
×
194
                $baseUrl = new Statement('trim($this->getByType(?)->getUrl()->getBaseUrl(), "/")', [Nette\Http\IRequest::class]);
1✔
195
                return [
196
                        'appDir' => isset($trace[1]['file']) ? dirname($trace[1]['file']) : null,
1✔
197
                        'wwwDir' => isset($last['file']) ? dirname($last['file']) : null,
1✔
198
                        'vendorDir' => $loaderRc ? dirname($loaderRc->getFileName(), 2) : null,
1✔
199
                        'rootDir' => $rootDir,
1✔
200
                        'debugMode' => $debugMode,
1✔
201
                        'productionMode' => !$debugMode,
1✔
202
                        'consoleMode' => PHP_SAPI === 'cli',
203
                        'baseUrl' => $baseUrl,
1✔
204
                ];
205
        }
206

207

208
        /**
209
         * Enables Tracy debugger and configures it for the current mode.
210
         */
211
        public function enableTracy(?string $logDirectory = null, ?string $email = null): void
212
        {
213
                if (!class_exists(Tracy\Debugger::class)) {
×
214
                        throw new Nette\NotSupportedException('Tracy not found, do you have `tracy/tracy` package installed?');
×
215
                }
216

UNCOV
217
                Tracy\Debugger::$strictMode = true;
×
UNCOV
218
                Tracy\Debugger::enable(!$this->staticParameters['debugMode'], $logDirectory, $email);
×
UNCOV
219
                Tracy\Bridges\Nette\Bridge::initialize();
×
UNCOV
220
                if (class_exists(Latte\Bridges\Tracy\BlueScreenPanel::class)) {
×
UNCOV
221
                        Latte\Bridges\Tracy\BlueScreenPanel::initialize();
×
222
                }
223
        }
224

225

226
        /** @deprecated use enableTracy() */
227
        public function enableDebugger(?string $logDirectory = null, ?string $email = null): void
228
        {
UNCOV
229
                $this->enableTracy($logDirectory, $email);
×
230
        }
231

232

233
        /**
234
         * Creates RobotLoader for automatic class discovery and caching.
235
         * @throws Nette\NotSupportedException if RobotLoader is not available
236
         */
237
        public function createRobotLoader(): Nette\Loaders\RobotLoader
238
        {
239
                if (!class_exists(Nette\Loaders\RobotLoader::class)) {
1✔
UNCOV
240
                        throw new Nette\NotSupportedException('RobotLoader not found, do you have `nette/robot-loader` package installed?');
×
241
                }
242

243
                $loader = new Nette\Loaders\RobotLoader;
1✔
244
                $loader->setTempDirectory($this->getCacheDirectory() . '/nette.robotLoader');
1✔
245
                $loader->setAutoRefresh($this->staticParameters['debugMode']);
1✔
246

247
                if (isset($this->defaultExtensions['application'])) {
1✔
248
                        $this->defaultExtensions['application'][1][1] = null;
1✔
249
                        $this->defaultExtensions['application'][1][3] = $loader;
1✔
250
                }
251

252
                return $loader;
1✔
253
        }
254

255

256
        /**
257
         * Adds a configuration file path or configuration array.
258
         * @param  string|array<string, mixed>  $config
259
         */
260
        public function addConfig(string|array $config): static
1✔
261
        {
262
                $this->configs[] = $config;
1✔
263
                return $this;
1✔
264
        }
265

266

267
        /**
268
         * Returns system DI container.
269
         */
270
        public function createContainer(bool $initialize = true): DI\Container
1✔
271
        {
272
                $class = $this->loadContainer();
1✔
273
                $container = new $class($this->dynamicParameters);
1✔
274
                foreach ($this->services as $name => $service) {
1✔
275
                        $container->addService($name, $service);
1✔
276
                }
277

278
                if ($initialize) {
1✔
279
                        $container->initialize();
1✔
280
                }
281

282
                return $container;
1✔
283
        }
284

285

286
        /**
287
         * Loads system DI container class and returns its name.
288
         * @return class-string<DI\Container>
289
         */
290
        public function loadContainer(): string
291
        {
292
                $loader = new DI\ContainerLoader(
1✔
293
                        $this->getCacheDirectory() . '/nette.configurator',
1✔
294
                        $this->staticParameters['debugMode'],
1✔
295
                );
296
                return $loader->load(
1✔
297
                        [$this, 'generateContainer'],
1✔
298
                        $this->generateContainerKey(),
1✔
299
                );
300
        }
301

302

303
        /**
304
         * @internal
305
         */
306
        public function generateContainer(DI\Compiler $compiler): void
1✔
307
        {
308
                $loader = $this->createLoader();
1✔
309
                $loader->setParameters($this->staticParameters);
1✔
310

311
                $compiler->addConfig(['parameters' => DI\Helpers::escape($this->defaultParameters)]);
1✔
312

313
                foreach ($this->configs as $config) {
1✔
314
                        if (is_string($config)) {
1✔
315
                                $compiler->loadConfig($config, $loader);
1✔
316
                        } else {
317
                                $compiler->addConfig($config);
1✔
318
                        }
319
                }
320

321
                $explicit = array_udiff_assoc($this->staticParameters, $this->defaultParameters, fn($a, $b) => $a === $b ? 0 : 1);
1✔
322
                $compiler->addConfig(['parameters' => DI\Helpers::escape($explicit)]);
1✔
323
                $compiler->setDynamicParameterNames(array_merge(array_keys($this->dynamicParameters), ['baseUrl']));
1✔
324

325
                $builder = $compiler->getContainerBuilder();
1✔
326
                $builder->addExcludedClasses($this->autowireExcludedClasses);
1✔
327

328
                foreach ($this->defaultExtensions as $name => $extension) {
1✔
329
                        [$class, $args] = is_string($extension)
1✔
330
                                ? [$extension, []]
1✔
331
                                : $extension;
1✔
332
                        if (class_exists($class)) {
1✔
333
                                $args = DI\Helpers::expand($args, $this->staticParameters);
1✔
334
                                $compiler->addExtension($name, (new \ReflectionClass($class))->newInstanceArgs($args));
1✔
335
                        }
336
                }
337

338
                Nette\Utils\Arrays::invoke($this->onCompile, $this, $compiler);
1✔
339
        }
1✔
340

341

342
        protected function createLoader(): DI\Config\Loader
343
        {
344
                return new DI\Config\Loader;
1✔
345
        }
346

347

348
        /** @return list<mixed> */
349
        protected function generateContainerKey(): array
350
        {
351
                return [
352
                        $this->staticParameters,
1✔
353
                        array_keys($this->dynamicParameters),
1✔
354
                        $this->configs,
1✔
355
                        PHP_VERSION_ID - PHP_RELEASE_VERSION, // minor PHP version
356
                        class_exists(ClassLoader::class) // composer update
1✔
357
                                ? filemtime((new \ReflectionClass(ClassLoader::class))->getFilename())
1✔
358
                                : null,
359
                ];
360
        }
361

362

363
        protected function getCacheDirectory(): string
364
        {
365
                if (empty($this->staticParameters['tempDir'])) {
1✔
366
                        throw new Nette\InvalidStateException('Set path to temporary directory using setTempDirectory().');
1✔
367
                }
368

369
                $dir = $this->staticParameters['tempDir'] . '/cache';
1✔
370
                Nette\Utils\FileSystem::createDir($dir);
1✔
371
                return $dir;
1✔
372
        }
373

374

375
        /**
376
         * Detects debug mode based on IP address or computer name matching.
377
         * @param  string|list<string>|null  $list  IP addresses or computer names whitelist
378
         */
379
        public static function detectDebugMode(string|array|null $list = null): bool
1✔
380
        {
381
                $addr = $_SERVER['REMOTE_ADDR'] ?? php_uname('n');
1✔
382
                $secret = is_string($_COOKIE[self::CookieSecret] ?? null)
1✔
383
                        ? $_COOKIE[self::CookieSecret]
1✔
384
                        : null;
1✔
385
                $list = is_string($list)
1✔
386
                        ? preg_split('#[,\s]+#', $list)
1✔
387
                        : (array) $list;
1✔
388
                if (!isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !isset($_SERVER['HTTP_FORWARDED'])) {
1✔
389
                        $list[] = '127.0.0.1';
1✔
390
                        $list[] = '::1';
1✔
391
                }
392

393
                return in_array($addr, $list, strict: true) || in_array("$secret@$addr", $list, strict: true);
1✔
394
        }
395
}
396

397

398
class_exists(Nette\Configurator::class);
1✔
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