Skip to content

Commit e308f36

Browse files
committed
added DI extensions auto-discovery
1 parent 335681e commit e308f36

3 files changed

Lines changed: 108 additions & 1 deletion

File tree

composer.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@
6161
"extra": {
6262
"branch-alias": {
6363
"dev-master": "4.0-dev"
64+
},
65+
"nette": {
66+
"di-extensions": {
67+
"constants": "Nette\\Bootstrap\\Extensions\\ConstantsExtension",
68+
"php": "Nette\\Bootstrap\\Extensions\\PhpIniExtension"
69+
}
6470
}
6571
},
6672
"config": {

src/Bootstrap/Configurator.php

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ class Configurator
7979
/** @var array<string, mixed> */
8080
private array $defaultParameters;
8181

82+
/** @var string[] extension names to exclude from auto-discovery */
83+
private array $excludeExtensions = [];
84+
private bool $autoDiscovery = true;
85+
8286

8387
public function __construct()
8488
{
@@ -173,6 +177,21 @@ public function addServices(array $services): static
173177
}
174178

175179

180+
/**
181+
* Disables auto-discovery of extensions from installed packages.
182+
* Without arguments disables completely, with arguments disables only specified extensions.
183+
*/
184+
public function excludeExtension(string ...$extensions): static
185+
{
186+
if (count($extensions) === 0) {
187+
$this->autoDiscovery = false;
188+
} else {
189+
$this->excludeExtensions = array_merge($this->excludeExtensions, $extensions);
190+
}
191+
return $this;
192+
}
193+
194+
176195
/** @return array<string, mixed> */
177196
protected function getDefaultParameters(): array
178197
{
@@ -258,6 +277,30 @@ public function addConfig(string|array $config): static
258277
}
259278

260279

280+
/**
281+
* Discovers extensions from installed Composer packages.
282+
* Reads extra.nette.di-extensions from vendor/composer/installed.json
283+
* @return array<string, string|array{class: class-string, args: array}>
284+
*/
285+
protected function discoverExtensions(): array
286+
{
287+
$vendorDir = $this->staticParameters['vendorDir'] ?? null;
288+
if (!$vendorDir || !is_file($installedJson = $vendorDir . '/composer/installed.json')) {
289+
return [];
290+
}
291+
292+
$installed = json_decode(Nette\Utils\FileSystem::read($installedJson), true);
293+
$extensions = [];
294+
foreach ($installed['packages'] ?? [] as $package) {
295+
foreach ($package['extra']['nette']['di-extensions'] ?? [] as $name => $def) {
296+
$extensions[$name] = is_string($def) ? $def : [$def['class'], $def['args'] ?? []];
297+
}
298+
}
299+
300+
return $extensions;
301+
}
302+
303+
261304
/**
262305
* Returns system DI container.
263306
*/
@@ -319,7 +362,10 @@ public function generateContainer(DI\Compiler $compiler): void
319362
$builder = $compiler->getContainerBuilder();
320363
$builder->addExcludedClasses($this->autowireExcludedClasses);
321364

322-
foreach ($this->defaultExtensions as $name => $extension) {
365+
$extensions = $this->autoDiscovery ? $this->discoverExtensions() : [];
366+
$extensions = array_merge($extensions, $this->defaultExtensions);
367+
$extensions = array_diff_key($extensions, $this->excludeExtensions);
368+
foreach ($extensions as $name => $extension) {
323369
[$class, $args] = is_string($extension)
324370
? [$extension, []]
325371
: $extension;
@@ -351,6 +397,7 @@ protected function generateContainerKey(): array
351397
class_exists(ClassLoader::class) // composer update
352398
? filemtime((new \ReflectionClass(ClassLoader::class))->getFilename())
353399
: null,
400+
$this->autoDiscovery ? $this->excludeExtensions : null,
354401
];
355402
}
356403

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Configurator::excludeExtension()
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Bootstrap\Configurator;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
test('excludeExtension() disables extension from defaultExtensions', function () {
17+
$configurator = new Configurator;
18+
$configurator->setTempDirectory(getTempDir());
19+
$configurator->excludeExtension('application');
20+
21+
$container = $configurator->createContainer();
22+
23+
// application extension should not be registered
24+
Assert::false($container->hasService('application'));
25+
Assert::false($container->hasService('nette.presenterFactory'));
26+
27+
// other extensions should still work
28+
Assert::true($container->hasService('httpRequest'));
29+
});
30+
31+
32+
test('excludeExtension() without arguments disables all auto-discovery', function () {
33+
$configurator = new Configurator;
34+
$configurator->setTempDirectory(getTempDir());
35+
$configurator->excludeExtension();
36+
37+
// defaultExtensions should still be registered
38+
$container = $configurator->createContainer();
39+
Assert::true($container->hasService('application'));
40+
Assert::true($container->hasService('httpRequest'));
41+
});
42+
43+
44+
test('excludeExtension() can disable multiple extensions', function () {
45+
$configurator = new Configurator;
46+
$configurator->setTempDirectory(getTempDir());
47+
$configurator->excludeExtension('application', 'mail');
48+
49+
$container = $configurator->createContainer();
50+
51+
Assert::false($container->hasService('application'));
52+
Assert::false($container->hasService('nette.mailer'));
53+
Assert::true($container->hasService('httpRequest'));
54+
});

0 commit comments

Comments
 (0)