Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2795d95

Browse filesBrowse files
committed
feature #59762 [Config] Add NodeDefinition::docUrl() (alexandre-daubois)
This PR was merged into the 7.3 branch. Discussion ---------- [Config] Add `NodeDefinition::docUrl()` | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | - | License | MIT Adding such information would allow extensions and bundles to provide even more info with a documentation "one click away". The primary goal is to use this feature in conjunction with #58771, allowing to dump a ``@see` https://symfony.com/doc/...` right next to the configuration array shape. Commits ------- 3c7fce2 [Config] Add `NodeDefinition::docUrl()`
2 parents 59d8c63 + 3c7fce2 commit 2795d95
Copy full SHA for 2795d95

File tree

Expand file treeCollapse file tree

14 files changed

+104
-7
lines changed
Filter options
Expand file treeCollapse file tree

14 files changed

+104
-7
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php
+3-1Lines changed: 3 additions & 1 deletion
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/DebugBundle/composer.json
+2-1Lines changed: 2 additions & 1 deletion
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php
+15Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ 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+
if ($docUrl = $this->getDocUrl($extension, $container)) {
109+
$io->comment(\sprintf('Documentation at %s', $docUrl));
110+
}
107111
}
108112

109113
$io->writeln($this->convertToFormat([$extensionAlias => $config], $format));
@@ -269,4 +273,15 @@ private function getAvailableFormatOptions(): array
269273
{
270274
return ['txt', 'yaml', 'json'];
271275
}
276+
277+
private function getDocUrl(ExtensionInterface $extension, ContainerBuilder $container): ?string
278+
{
279+
$configuration = $extension instanceof ConfigurationInterface ? $extension : $extension->getConfiguration($container->getExtensionConfig($extension->getAlias()), $container);
280+
281+
return $configuration
282+
->getConfigTreeBuilder()
283+
->getRootNode()
284+
->getNode(true)
285+
->getAttribute('docUrl');
286+
}
272287
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php
+19Lines changed: 19 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+1Lines changed: 1 addition & 0 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php
+1Lines changed: 1 addition & 0 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/composer.json
+1-1Lines changed: 1 addition & 1 deletion
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php
+3-1Lines changed: 3 additions & 1 deletion
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/TwigBundle/composer.json
+1-1Lines changed: 1 addition & 1 deletion
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/Configuration.php
+3-1Lines changed: 3 additions & 1 deletion
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/WebProfilerBundle/composer.json
+2-1Lines changed: 2 additions & 1 deletion
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php
+21Lines changed: 21 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Tests/Definition/Builder/NodeDefinitionTest.php
+31Lines changed: 31 additions & 0 deletions
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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.