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

nette / bootstrap / 25406830765

05 May 2026 10:55PM UTC coverage: 90.541% (+0.3%) from 90.278%
25406830765

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 array_diff_key($staticParameters, $defaultParameters) AFTER
configs as authoritative explicit overrides.

Bookkeeping: setters (setTempDirectory, setDebugMode) route through
addStaticParameters(), which removes the touched keys from $defaultParameters.
That preserves the rule "what user explicitly set wins over configs" even
when the explicit value coincidentally equals the auto-detected default.

The container cache key includes the explicit set so two configurators
with identical $staticParameters but different "user-touched" keys
compile to distinct containers.

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

12 existing lines in 2 files now uncovered.

134 of 148 relevant lines covered (90.54%)

0.91 hits per line

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

90.7
/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
                return $this->addStaticParameters([
1✔
107
                        'debugMode' => $value,
1✔
108
                        'productionMode' => !$value, // compatibility
1✔
109
                ]);
110
        }
111

112

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

118

119
        /**
120
         * Sets path to temporary directory.
121
         */
122
        public function setTempDirectory(string $path): static
1✔
123
        {
124
                return $this->addStaticParameters(['tempDir' => $path]);
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
                $this->defaultParameters = array_diff_key($this->defaultParameters, $params);
1✔
157
                return $this;
1✔
158
        }
159

160

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

171

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

182

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

208

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

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

226

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

233

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

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

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

253
                return $loader;
1✔
254
        }
255

256

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

267

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

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

283
                return $container;
1✔
284
        }
285

286

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

303

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

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

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

322
                $explicit = array_diff_key($this->staticParameters, $this->defaultParameters);
1✔
323
                $compiler->addConfig(['parameters' => DI\Helpers::escape($explicit)]);
1✔
324
                $compiler->setDynamicParameterNames(array_merge(array_keys($this->dynamicParameters), ['baseUrl']));
1✔
325

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

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

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

342

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

348

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

364

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

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

376

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

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

399

400
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