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

nette / application / 19834954118

01 Dec 2025 07:31PM UTC coverage: 82.856% (-0.1%) from 82.997%
19834954118

push

github

dg
TemplateGenerator WIP

5 of 9 new or added lines in 2 files covered. (55.56%)

34 existing lines in 2 files now uncovered.

1938 of 2339 relevant lines covered (82.86%)

0.83 hits per line

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

79.59
/src/Bridges/ApplicationDI/ApplicationExtension.php
1
<?php
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
declare(strict_types=1);
9

10
namespace Nette\Bridges\ApplicationDI;
11

12
use Composer\Autoload\ClassLoader;
13
use Nette;
14
use Nette\Application\Attributes;
15
use Nette\Application\UI;
16
use Nette\DI\Definitions;
17
use Nette\Schema\Expect;
18
use Nette\Utils\Reflection;
19
use Tracy;
20
use function is_array, is_string, sprintf;
21

22

23
/**
24
 * Application extension for Nette DI.
25
 */
26
final class ApplicationExtension extends Nette\DI\CompilerExtension
27
{
28
        private readonly array $scanDirs;
29
        private int $invalidLinkMode;
30
        private array $checked = [];
31

32

33
        public function __construct(
1✔
34
                private readonly bool $debugMode = false,
35
                ?array $scanDirs = null,
36
                private readonly ?string $tempDir = null,
37
                private readonly ?Nette\Loaders\RobotLoader $robotLoader = null,
38
        ) {
39
                $this->scanDirs = (array) $scanDirs;
1✔
40
        }
1✔
41

42

43
        public function getConfigSchema(): Nette\Schema\Schema
44
        {
45
                return Expect::structure([
1✔
46
                        'debugger' => Expect::bool(),
1✔
47
                        'errorPresenter' => Expect::anyOf(
1✔
48
                                Expect::structure([
1✔
49
                                        '4xx' => Expect::string('Nette:Error')->dynamic(),
1✔
50
                                        '5xx' => Expect::string('Nette:Error')->dynamic(),
1✔
51
                                ])->castTo('array'),
1✔
52
                                Expect::string()->dynamic(),
1✔
53
                        )->firstIsDefault(),
1✔
54
                        'catchExceptions' => Expect::bool(false)->dynamic(),
1✔
55
                        'mapping' => Expect::anyOf(
1✔
56
                                Expect::string(),
1✔
57
                                Expect::arrayOf('string|array'),
1✔
58
                        ),
59
                        'aliases' => Expect::arrayOf('string'),
1✔
60
                        'scanDirs' => Expect::anyOf(
1✔
61
                                Expect::arrayOf('string')->default($this->scanDirs)->mergeDefaults(),
1✔
62
                                false,
1✔
63
                        )->firstIsDefault(),
1✔
64
                        'scanComposer' => Expect::bool(class_exists(ClassLoader::class)),
1✔
65
                        'scanFilter' => Expect::string('*Presenter'),
1✔
66
                        'silentLinks' => Expect::bool(),
1✔
67
                        'generateTemplateClasses' => Expect::bool(),
1✔
68
                ]);
69
        }
70

71

72
        public function loadConfiguration(): void
73
        {
74
                $config = $this->config;
1✔
75
                $builder = $this->getContainerBuilder();
1✔
76
                $builder->addExcludedClasses([UI\Presenter::class]);
1✔
77

78
                $this->invalidLinkMode = $this->debugMode
1✔
79
                        ? UI\Presenter::InvalidLinkTextual | ($config->silentLinks ? 0 : UI\Presenter::InvalidLinkWarning)
1✔
80
                        : UI\Presenter::InvalidLinkWarning;
1✔
81

82
                $application = $builder->addDefinition($this->prefix('application'))
1✔
83
                        ->setFactory(Nette\Application\Application::class);
1✔
84
                if ($config->catchExceptions || !$this->debugMode) {
1✔
85
                        $application->addSetup('$error4xxPresenter', [is_array($config->errorPresenter) ? $config->errorPresenter['4xx'] : $config->errorPresenter]);
1✔
86
                        $application->addSetup('$errorPresenter', [is_array($config->errorPresenter) ? $config->errorPresenter['5xx'] : $config->errorPresenter]);
1✔
87
                }
88

89
                $this->compiler->addExportedType(Nette\Application\Application::class);
1✔
90

91
                if ($this->debugMode && ($config->scanDirs || $this->robotLoader) && $this->tempDir) {
1✔
92
                        $touch = $this->tempDir . '/touch';
×
93
                        Nette\Utils\FileSystem::createDir($this->tempDir);
×
94
                        $this->getContainerBuilder()->addDependency($touch);
×
95
                }
96

97
                $presenterFactory = $builder->addDefinition($this->prefix('presenterFactory'))
1✔
98
                        ->setType(Nette\Application\IPresenterFactory::class)
1✔
99
                        ->setFactory(Nette\Application\PresenterFactory::class, [new Definitions\Statement(
1✔
100
                                Nette\Bridges\ApplicationDI\PresenterFactoryCallback::class,
101
                                [1 => $touch ?? null],
1✔
102
                        )]);
103

104
                if ($config->mapping) {
1✔
105
                        $presenterFactory->addSetup('setMapping', [
×
106
                                is_string($config->mapping) ? ['*' => $config->mapping] : $config->mapping,
×
107
                        ]);
108
                }
109

110
                if ($config->aliases) {
1✔
111
                        $presenterFactory->addSetup('setAliases', [$config->aliases]);
×
112
                }
113

114
                $builder->addDefinition($this->prefix('linkGenerator'))
1✔
115
                        ->setFactory(Nette\Application\LinkGenerator::class, [
1✔
116
                                1 => new Definitions\Statement([new Definitions\Statement('@Nette\Http\IRequest::getUrl'), 'withoutUserInfo']),
1✔
117
                        ]);
118

119
                if ($this->name === 'application') {
1✔
120
                        $builder->addAlias('application', $this->prefix('application'));
1✔
121
                        $builder->addAlias('nette.presenterFactory', $this->prefix('presenterFactory'));
1✔
122
                }
123
        }
1✔
124

125

126
        public function beforeCompile(): void
127
        {
128
                $builder = $this->getContainerBuilder();
1✔
129

130
                if ($this->config->debugger ?? $builder->getByType(Tracy\BlueScreen::class)) {
1✔
131
                        $builder->getDefinition($this->prefix('application'))
×
132
                                ->addSetup([self::class, 'initializeBlueScreenPanel']);
×
133
                }
134

135
                if ($this->debugMode && $this->config->generateTemplateClasses) {
1✔
NEW
136
                        $builder->getDefinition('latte.templateFactory')
×
NEW
137
                                ->setArgument('generate', true);
×
138
                }
139

140
                $all = [];
1✔
141

142
                foreach ($builder->findByType(Nette\Application\IPresenter::class) as $def) {
1✔
143
                        $all[$def->getType()] = $def;
1✔
144
                }
145

146
                $counter = 0;
1✔
147
                foreach ($this->findPresenters() as $class) {
1✔
148
                        $this->checkPresenter($class);
1✔
149
                        if (empty($all[$class])) {
1✔
150
                                $all[$class] = $builder->addDefinition($this->prefix((string) ++$counter))
1✔
151
                                        ->setType($class);
1✔
152
                        }
153
                }
154

155
                foreach ($all as $def) {
1✔
156
                        $def->addTag(Nette\DI\Extensions\InjectExtension::TagInject)
1✔
157
                                ->setAutowired(false);
1✔
158

159
                        if (is_subclass_of($def->getType(), UI\Presenter::class) && $def instanceof Definitions\ServiceDefinition) {
1✔
160
                                $def->addSetup('$invalidLinkMode', [$this->invalidLinkMode]);
1✔
161
                        }
162

163
                        $this->compiler->addExportedType($def->getType());
1✔
164
                }
165
        }
1✔
166

167

168
        /** @return string[] */
169
        private function findPresenters(): array
170
        {
171
                $config = $this->getConfig();
1✔
172

173
                if ($config->scanDirs) {
1✔
174
                        if (!class_exists(Nette\Loaders\RobotLoader::class)) {
1✔
175
                                throw new Nette\NotSupportedException("RobotLoader is required to find presenters, install package `nette/robot-loader` or disable option {$this->prefix('scanDirs')}: false");
×
176
                        }
177

178
                        $robot = new Nette\Loaders\RobotLoader;
1✔
179
                        $robot->addDirectory(...$config->scanDirs);
1✔
180
                        $robot->acceptFiles = [$config->scanFilter . '.php'];
1✔
181
                        if ($this->tempDir) {
1✔
182
                                $robot->setTempDirectory($this->tempDir);
×
183
                                $robot->refresh();
×
184
                        } else {
185
                                $robot->rebuild();
1✔
186
                        }
187
                } elseif ($this->robotLoader && $config->scanDirs !== false) {
1✔
188
                        $robot = $this->robotLoader;
1✔
189
                        $robot->refresh();
1✔
190
                }
191

192
                $classes = [];
1✔
193
                if (isset($robot)) {
1✔
194
                        $classes = array_keys($robot->getIndexedClasses());
1✔
195
                }
196

197
                if ($config->scanComposer) {
1✔
198
                        $rc = new \ReflectionClass(ClassLoader::class);
1✔
199
                        $classFile = dirname($rc->getFileName()) . '/autoload_classmap.php';
1✔
200
                        if (is_file($classFile)) {
1✔
201
                                $this->getContainerBuilder()->addDependency($classFile);
1✔
202
                                $classes = array_merge($classes, array_keys((fn($path) => require $path)($classFile)));
1✔
203
                        }
204
                }
205

206
                $presenters = [];
1✔
207
                foreach (array_unique($classes) as $class) {
1✔
208
                        if (
209
                                fnmatch($config->scanFilter, $class)
1✔
210
                                && class_exists($class)
1✔
211
                                && ($rc = new \ReflectionClass($class))
1✔
212
                                && $rc->implementsInterface(Nette\Application\IPresenter::class)
1✔
213
                                && !$rc->isAbstract()
1✔
214
                        ) {
215
                                $presenters[] = $rc->getName();
1✔
216
                        }
217
                }
218

219
                return $presenters;
1✔
220
        }
221

222

223
        /** @internal */
224
        public static function initializeBlueScreenPanel(
225
                Tracy\BlueScreen $blueScreen,
226
                Nette\Application\Application $application,
227
        ): void
228
        {
229
                $blueScreen->addPanel(function (?\Throwable $e) use ($application, $blueScreen): ?array {
×
230
                        $dumper = $blueScreen->getDumper();
231
                        return $e ? null : [
232
                                'tab' => 'Nette Application',
233
                                'panel' => '<h3>Requests</h3>' . $dumper($application->getRequests())
234
                                        . '<h3>Presenter</h3>' . $dumper($application->getPresenter()),
235
                        ];
236
                });
×
237
                if (
238
                        version_compare(Tracy\Debugger::Version, '2.9.0', '>=')
×
239
                        && version_compare(Tracy\Debugger::Version, '3.0', '<')
×
240
                ) {
241
                        $blueScreen->addFileGenerator(self::generateNewPresenterFileContents(...));
×
242
                }
243
        }
244

245

246
        public static function generateNewPresenterFileContents(string $file, ?string $class = null): ?string
247
        {
248
                if (!$class || !str_ends_with($file, 'Presenter.php')) {
×
249
                        return null;
×
250
                }
251

252
                $res = "<?php\n\ndeclare(strict_types=1);\n\n";
×
253

254
                if ($pos = strrpos($class, '\\')) {
×
255
                        $res .= 'namespace ' . substr($class, 0, $pos) . ";\n\n";
×
256
                        $class = substr($class, $pos + 1);
×
257
                }
258

259
                return $res . "use Nette;\n\n\nclass $class extends Nette\\Application\\UI\\Presenter\n{\n\$END\$\n}\n";
×
260
        }
261

262

263
        private function checkPresenter(string $class): void
1✔
264
        {
265
                if (!is_subclass_of($class, UI\Presenter::class) || isset($this->checked[$class])) {
1✔
266
                        return;
1✔
267
                }
268
                $this->checked[$class] = true;
1✔
269

270
                $rc = new \ReflectionClass($class);
1✔
271
                if ($rc->getParentClass()) {
1✔
272
                        $this->checkPresenter($rc->getParentClass()->getName());
1✔
273
                }
274

275
                foreach ($rc->getProperties() as $rp) {
1✔
276
                        if (($rp->getAttributes($attr = Attributes\Parameter::class) || $rp->getAttributes($attr = Attributes\Persistent::class))
1✔
277
                                && (!$rp->isPublic() || $rp->isStatic() || $rp->isReadOnly())
1✔
278
                        ) {
279
                                throw new Nette\InvalidStateException(sprintf('Property %s: attribute %s can be used only with public non-static property.', Reflection::toString($rp), $attr));
×
280
                        }
281
                }
282

283
                $re = $class::formatActionMethod('') . '.|' . $class::formatRenderMethod('') . '.|' . $class::formatSignalMethod('') . '.';
1✔
284
                foreach ($rc->getMethods() as $rm) {
1✔
285
                        if (preg_match("#^(?!handleInvalidLink)($re)#", $rm->getName()) && (!$rm->isPublic() || $rm->isStatic())) {
1✔
286
                                throw new Nette\InvalidStateException(sprintf('Method %s: this method must be public non-static.', Reflection::toString($rm)));
×
287
                        } elseif (preg_match('#^createComponent.#', $rm->getName()) && ($rm->isPrivate() || $rm->isStatic())) {
1✔
288
                                throw new Nette\InvalidStateException(sprintf('Method %s: this method must be non-private non-static.', Reflection::toString($rm)));
×
289
                        } elseif ($rm->getAttributes(Attributes\Requires::class, \ReflectionAttribute::IS_INSTANCEOF)
1✔
290
                                && !preg_match("#^$re|createComponent.#", $rm->getName())
1✔
291
                        ) {
292
                                throw new Nette\InvalidStateException(sprintf('Method %s: attribute %s can be used only with action, render, handle or createComponent methods.', Reflection::toString($rm), Attributes\Requires::class));
×
293
                        } elseif ($rm->getAttributes(Attributes\Deprecated::class) && !preg_match("#^$re#", $rm->getName())) {
1✔
294
                                throw new Nette\InvalidStateException(sprintf('Method %s: attribute %s can be used only with action, render or handle methods.', Reflection::toString($rm), Attributes\Deprecated::class));
×
295
                        }
296
                }
297
        }
1✔
298
}
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