8000 [Config] Add `NodeDefinition::docUrl()` · symfony/symfony@a27e4aa · GitHub
[go: up one dir, main page]

Skip to content

Commit a27e4aa

Browse files
[Config] Add NodeDefinition::docUrl()
1 parent 79ea49c commit a27e4aa

File tree

14 files changed

+102
-7
lines changed

14 files changed

+102
-7
lines changed

src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ public function getConfigTreeBuilder(): TreeBuilder
2626
$treeBuilder = new TreeBuilder('debug');
2727

2828
$rootNode = $treeBuilder->getRootNode();
29-
$rootNode->children()
29+
$rootNode
30+
->docUrl('https://symfony.com/doc/{version:major}.{version:minor}/reference/configuration/debug.html', 'symfony/debug-bundle')
31+
->children()
3032
->integerNode('max_items')
3133
->info('Max number of displayed items past the first level, -1 means no limit.')
3234
->min(-1)

src/Symfony/Bundle/DebugBundle/composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@
1818
"require": {
1919
"php": ">=8.2",
2020
"ext-xml": "*",
21+
"composer-runtime-api": ">=2.1",
2122
"symfony/dependency-injection": "^6.4|^7.0",
2223
"symfony/http-kernel": "^6.4|^7.0",
2324
"symfony/twig-bridge": "^6.4|^7.0",
2425
"symfony/var-dumper": "^6.4|^7.0"
2526
},
2627
"require-dev": {
27-
"symfony/config": "^6.4|^7.0",
28+
"symfony/config": "^7.3",
2829
"symfony/web-profiler-bundle": "^6.4|^7.0"
2930
},
3031
"conflict": {

src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php

+13
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
104104
$io->title(
105105
\sprintf('Current configuration for %s', $name === $extensionAlias ? \sprintf('extension with alias "%s"', $extensionAlias) : \sprintf('"%s"', $name))
106106
);
107+
108+
$io->comment(\sprintf('Documentation at %s', $this->getDocUrl($extension, $container)));
107109
}
108110

109111
$io->writeln($this->convertToFormat([$extensionAlias => $config], $format));
@@ -269,4 +271,15 @@ private function getAvailableFormatOptions(): array
269271
{
270272
return ['txt', 'yaml', 'json'];
271273
}
274+
275+
private function getDocUrl(ExtensionInterface $extension, ContainerBuilder $container): ?string
276+
{
277+
$configuration = $extension instanceof ConfigurationInterface ? $extension : $extension->getConfiguration($container->getExtensionConfig($extension->getAlias()), $container);
278+
279+
return $configuration
280+
->getConfigTreeBuilder()
281+
->getRootNode()
282+
->getNode(true)
283+
->getAttribute('docUrl');
284+
}
272285
}

src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php

+19
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\Component\Console\Input\InputOption;
2424
use Symfony\Component\Console\Output\OutputInterface;
2525
use Symfony\Component\Console\Style\SymfonyStyle;
26+
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
2627
use Symfony\Component\Yaml\Yaml;
2728

2829
/**
@@ -123,6 +124,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
123124
$message .= \sprintf(' at path "%s"', $path);
124125
}
125126

127+
if ($docUrl = $this->getExtensionDocUrl($extension)) {
128+
$message .= \sprintf(' (see %s)', $docUrl);
129+
}
130+
126131
switch ($format) {
127132
case 'yaml':
128133
$io->writeln(\sprintf('# %s', $message));
@@ -182,4 +187,18 @@ private function getAvailableFormatOptions(): array
182187
{
183188
return ['yaml', 'xml'];
184189
}
190+
191+
private function getExtensionDocUrl(ConfigurationInterface|ConfigurationExtensionInterface $extension): ?string
192+
{
193+
$kernel = $this->getApplication()->getKernel();
194+
$container = $this->getContainerBuilder($kernel);
195+
196+
$configuration = $extension instanceof ConfigurationInterface ? $extension : $extension->getConfiguration($container->getExtensionConfig($extension->getAlias()), $container);
197+
198+
return $configuration
199+
->getConfigTreeBuilder()
200+
->getRootNode()
201+
->getNode(true)
202+
->getAttribute('docUrl');
203+
}
185204
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public function getConfigTreeBuilder(): TreeBuilder
7575
$rootNode = $treeBuilder->getRootNode();
7676

7777
$rootNode
78+
->docUrl('https://symfony.com/doc/{version:major}.{version:minor}/reference/configuration/framework.html', 'symfony/framework-bundle')
7879
->beforeNormalization()
7980
->ifTrue(fn ($v) => !isset($v['assets']) && isset($v['templating']) && class_exists(Package::class))
8081
->then(function ($v) {

src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public function getConfigTreeBuilder(): TreeBuilder
5555
$rootNode = $tb->getRootNode();
5656

5757
$rootNode
58+
->docUrl('https://symfony.com/doc/{version:major}.{version:minor}/reference/configuration/security.html', 'symfony/security-bundle')
5859
->beforeNormalization()
5960
->always()
6061
->then(function ($v) {

src/Symfony/Bundle/SecurityBundle/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"composer-runtime-api": ">=2.1",
2121
"ext-xml": "*",
2222
"symfony/clock": "^6.4|^7.0",
23-
"symfony/config": "^6.4|^7.0",
23+
"symfony/config": "^7.3",
2424
"symfony/dependency-injection": "^6.4.11|^7.1.4",
2525
"symfony/event-dispatcher": "^6.4|^7.0",
2626
"symfony/http-kernel": "^6.4|^7.0",

src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ public function getConfigTreeBuilder(): TreeBuilder
3232
$treeBuilder = new TreeBuilder('twig');
3333
$rootNode = $treeBuilder->getRootNode();
3434

35-
$rootNode->beforeNormalization()
35+
$rootNode
36+
->docUrl('https://symfony.com/doc/{version:major}.{version:minor}/reference/configuration/twig.html', 'symfony/twig-bundle')
37+
->beforeNormalization()
3638
->ifTrue(fn ($v) => \is_array($v) && \array_key_exists('exception_controller', $v))
3739
->then(function ($v) {
3840
if (isset($v['exception_controller'])) {

src/Symfony/Bundle/TwigBundle/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"require": {
1919
"php": ">=8.2",
2020
"composer-runtime-api": ">=2.1",
21-
"symfony/config": "^6.4|^7.0",
21+
"symfony/config": "^7.3",
2222
"symfony/dependency-injection": "^6.4|^7.0",
2323
"symfony/twig-bridge": "^6.4|^7.0",
2424
"symfony/http-foundation": "^6.4|^7.0",

src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/Configuration.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ public function getConfigTreeBuilder(): TreeBuilder
3131
{
3232
$treeBuilder = new TreeBuilder('web_profiler');
3333

34-
$treeBuilder->getRootNode()
34+
$treeBuilder
35+
->getRootNode()
36+
->docUrl('https://symfony.com/doc/{version:major}.{version:minor}/reference/configuration/web_profiler.html', 'symfony/web-profiler-bundle')
3537
->children()
3638
->arrayNode('toolbar')
3739
->info('Profiler toolbar configuration')

src/Symfony/Bundle/WebProfilerBundle/composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
],
1818
"require": {
1919
"php": ">=8.2",
20-
"symfony/config": "^6.4|^7.0",
20+
"composer-runtime-api": ">=2.1",
21+
"symfony/config": "^7.3",
2122
"symfony/framework-bundle": "^6.4|^7.0",
2223
"symfony/http-kernel": "^6.4|^7.0",
2324
"symfony/routing": "^6.4|^7.0",

src/Symfony/Component/Config/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Add `ExprBuilder::ifFalse()`
88
* Add support for info on `ArrayNodeDefinition::canBeEnabled()` and `ArrayNodeDefinition::canBeDisabled()`
99
* Allow using an enum FQCN with `EnumNode`
10+
* Add `NodeDefinition::docUrl()`
1011

1112
7.2
1213
---

src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php

+21
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Config\Definition\Builder;
1313

14+
use Composer\InstalledVersions;
1415
use Symfony\Component\Config\Definition\BaseNode;
1516
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
1617
use Symfony\Component\Config\Definition\NodeInterface;
@@ -76,6 +77,26 @@ public function example(string|array $example): static
7677
return $this->attribute('example', $example);
7778
}
7879

80+
/**
81+
* Sets the documentation URI, as usually put in the "@see" tag of a doc block. This
82+
* can either be a URL or a file path. You can use the placeholders {package},
83+
* {version:major} and {version:minor} in the URI.
84+
*
85+
* @return $this
86+
*/
87+
public function docUrl(string $uri, ?string $package = null): static
88+
{
89+
if ($package) {
90+
preg_match('/^(\d+)\.(\d+)\.(\d+)/', InstalledVersions::getVersion($package) ?? '', $m);
91+
}
92+
93+
return $this->attribute('docUrl', strtr($uri, [
94+
'{package}' => $package ?? '',
95+
'{version:major}' => $m[1] ?? '',
96+
'{version:minor}' => $m[2] ?? '',
97+
]));
98+
}
99+
79100
/**
80101
* Sets an attribute on the node.
81102
*

src/Symfony/Component/Config/Tests/Definition/Builder/NodeDefinitionTest.php

+31
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,35 @@ public function testSetPathSeparatorChangesChildren()
3535

3636
$parentNode->setPathSeparator('/');
3737
}
38+
39+
public function testDocUrl()
40+
{
41+
$node = new ArrayNodeDefinition('node');
42+
$node->docUrl('https://example.com/doc/{package}/{version:major}.{version:minor}', 'phpunit/phpunit');
43+
44+
$r = new \ReflectionObject($node);
45+
$p = $r->getProperty('attributes');
46+
47+
$this->assertMatchesRegularExpression('~^https://example.com/doc/phpunit/phpunit/\d+\.\d+$~', $p->getValue($node)['docUrl']);
48+
}
49+
50+
public function testDocUrlWithoutPackage()
51+
{
52+
$node = new ArrayNodeDefinition('node');
53+
$node->docUrl('https://example.com/doc/empty{version:major}.empty{version:minor}');
54+
55+
$r = new \ReflectionObject($node);
56+
$p = $r->getProperty('attributes');
57+
58+
$this->assertSame('https://example.com/doc/empty.empty', $p->getValue($node)['docUrl']);
59+
}
60+
61+
public function testUnknownPackageThrowsException()
62+
{
63+
$this->expectException(\OutOfBoundsException::class);
64+
$this->expectExceptionMessage('Package "phpunit/invalid" is not installed');
65+
66+
$node = new ArrayNodeDefinition('node');
67+
$node->docUrl('https://example.com/doc/{package}/{version:major}.{version:minor}', 'phpunit/invalid');
68+
}
3869
}

0 commit comments

Comments
 (0)
0