8000 merged branch WouterJ/dump_xml (PR #8635) · symfony/symfony@7005cf5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7005cf5

Browse files
committed
merged branch WouterJ/dump_xml (PR #8635)
This PR was squashed before being merged into the master branch (closes #8635). Discussion ---------- [Config] Create XML Reference Dumper | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | no | Fixed tickets | - | License | MIT | Doc PR | - Only Yaml was supported. This PR adds support for XML. This makes it easier to test XML schema's (see symfony-cmf/menu-bundle#114 ), helps us at the docs with our configuration reference and helps others using XML with symfony. ## Todo - [x] Prototyped arrays don't work properly - [x] Add comments (see Yaml dumper) - [x] Add namespaces support ## Side effects I've moved the reference dumpers to their own namespace and renamed the original reference dumper to `YamlReferenceDumper`. The old one is kept for BC, but deprecated. /cc @dantleech Commits ------- 05e9ca7 [Config] Create XML Reference Dumper
2 parents c3728d2 + 05e9ca7 commit 7005cf5

File tree

8 files changed

+645
-200
lines changed

8 files changed

+645
-200
lines changed

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
namespace Symfony\Bundle\FrameworkBundle\Command;
1313

14-
use Symfony\Component\Config\Definition\ReferenceDumper;
14+
use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper;
15+
use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper;
1516
use Symfony\Component\Console\Input\InputArgument;
1617
use Symfony\Component\Console\Input\InputInterface;
1718
use Symfony\Component\Console\Output\OutputInterface;
@@ -21,6 +22,7 @@
2122
* A console command for dumping available configuration reference
2223
*
2324
* @author Kevin Bond <kevinbond@gmail.com>
25+
* @author Wouter J <waldio.webdesign@gmail.com>
2426
*/
2527
class ConfigDumpReferenceCommand extends ContainerDebugCommand
2628
{
@@ -32,7 +34,8 @@ protected function configure()
3234
$this
3335
->setName('config:dump-reference')
3436
->setDefinition(array(
35-
new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle or extension alias')
37+
new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle or extension alias'),
38+
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The format, either yaml or xml', 'yaml'),
3639
))
3740
->setDescription('Dumps default configuration for an extension')
3841
->setHelp(<<<EOF
@@ -47,6 +50,9 @@ protected function configure()
4750
or
4851
4952
<info>php %command.full_name% FrameworkBundle</info>
53+
54+
With the <info>format</info> option specifies the format of the configuration, this is either yaml
55+
or xml. When the option is not provided, yaml is used.
5056
EOF
5157
)
5258
;
@@ -118,7 +124,17 @@ protected function execute(InputInterface $input, OutputInterface $output)
118124

119125
$output->writeln($message);
120126

121-
$dumper = new ReferenceDumper();
122-
$output->writeln($dumper->dump($configuration));
127+
switch ($input->getOption('format')) {
128+
case 'yaml':
129+
$dumper = new YamlReferenceDumper();
130+
break;
131+
case 'xml':
132+
$dumper = new XmlReferenceDumper();
133+
break;
134+
default:
135+
throw new \InvalidArgumentException('Only the yaml and xml formats are supported.');
136+
}
137+
138+
$output->writeln($dumper->dump($configuration), $extension->getNamespace());
123139
}
124140
}

src/Symfony/Component/Config/Definition/ArrayNode.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ public function setXmlRemappings(array $remappings)
105105
$this->xmlRemappings = $remappings;
106106
}
107107

108+
/**
109+
* Gets the xml remappings that should be performed.
110+
*
111+
* @return array $remappings an array of the form array(array(string, string))
112+
*/
113+
public function getXmlRemappings()
114+
{
115+
return $this->xmlRemappings;
116+
}
117+
108118
/**
109119
* Sets whether to add default values for this array if it has not been
110120
* defined in any of the configuration files.
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Config\Definition\Dumper;
13+
14+
use Symfony\Component\Config\Definition\ConfigurationInterface;
15+
use Symfony\Component\Config\Definition\NodeInterface;
16+
use Symfony\Component\Config\Definition\ArrayNode;
17+
use Symfony\Component\Config\Definition\EnumNode;
18+
use Symfony\Component\Config\Definition\PrototypedArrayNode;
19+
20+
/**
21+
* Dumps a XML reference configuration for the given configuration/node instance.
22+
*
23+
* @author Wouter J <waldio.webdesign@gmail.com>
24+
*/
25+
class XmlReferenceDumper
26+
{
27+
private $reference;
28+
29+
public function dump(ConfigurationInterface $configuration, $namespace = null)
30+
{
31+
return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace);
32+
}
33+
34+
public function dumpNode(NodeInterface $node, $namespace = null)
35+
{
36+
$this->reference = '';
37+
$this->writeNode($node, 0, true, $namespace);
38+
$ref = $this->reference;
39+
$this->reference = null;
40+
41+
return $ref;
42+
}
43+
44+
/**
45+
* @param NodeInterface $node
46+
* @param integer $depth
47+
* @param boolean $root If the node is the root node
48+
* @param string $namespace The namespace of the node
49+
*/
50+
private function writeNode(NodeInterface $node, $depth = 0, $root = false, $namespace = null)
51+
{
52+
$rootName = ($root ? 'config' : $node->getName());
53+
$rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null));
54+
55+
// xml remapping
56+
if ($node->getParent()) {
57+
$remapping = array_filter($node->getParent()->getXmlRemappings(), function ($mapping) use ($rootName) {
58+
return $rootName === $mapping[1];
59+
});
60+
61+
if (count($remapping)) {
62+
list($singular, $plural) = current($remapping);
63+
$rootName = $singular;
64+
}
65+
}
66+
$rootName = str_replace('_', '-', $rootName);
67+
68+
$rootAttributes = array();
69+
$rootAttributeComments = array();
70+
$rootChildren = array();
71+
$rootComments = array();
72+
73+
if ($node instanceof ArrayNode) {
74+
$children = $node->getChildren();
75+
76+
// comments about the root node
77+
if ($rootInfo = $node->getInfo()) {
78+
$rootComments[] = $rootInfo;
79+
}
80+
81+
if ($rootNamespace) {
82+
$rootComments[] = 'Namespace: '.$rootNamespace;
83+
}
84+
85+
// render prototyped nodes
86+
if ($node instanceof PrototypedArrayNode) {
87+
array_unshift($rootComments, 'prototype');
88+
89+
if ($key = $node->getKeyAttribute()) {
90+
$rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key;
91+
}
92+
93+
$prototype = $node->getPrototype();
94+
95+
if ($prototype instanceof ArrayNode) {
96+
$children = $prototype->getChildren();
97+
} else {
98+
if ($prototype->hasDefaultValue()) {
99+
$prototypeValue = $prototype->getDefaultValue();
100+
} else {
101+
switch (get_class($prototype)) {
102+
case 'Symfony\Component\Config\Definition\ScalarNode':
103+
$prototypeValue = 'scalar value';
104+
break;
105+
106+
case 'Symfony\Component\Config\Definition\FloatNode':
107+
case 'Symfony\Component\Config\Definition\IntegerNode':
108+
$prototypeValue = 'numeric value';
109+
break;
110+
111+
case 'Symfony\Component\Config\Definition\BooleanNode':
112+
$prototypeValue = 'true|false';
113+
break;
114+
115+
case 'Symfony\Component\Config\Definition\EnumNode':
116+
$prototypeValue = implode('|', array_map('json_encode', $prototype->getValues()));
117+
break;
118+
119+
default:
120+
$prototypeValue = 'value';
121+
}
122+
}
123+
}
124+
}
125+
126+
// get attributes and elements
127+
foreach ($children as $child) {
128+
if (!$child instanceof ArrayNode) {
129+
// get attributes
130+
131+
// metadata
132+
$name = str_replace('_', '-', $child->getName());
133+
$value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world
134+
135+
// comments
136+
$comments = array();
137+
if ($info = $child->getInfo()) {
138+
$comments[] = $info;
139+
}
140+
141+
if ($example = $child->getExample()) {
142+
$comments[] = 'Example: '.$example;
143+
}
144+
145+
if ($child->isRequired()) {
146+
$comments[] = 'Required';
147+
}
148+
149+
if ($child instanceof EnumNode) {
150+
$comments[] = 'One of '.implode('; ', array_map('json_encode', $child->getValues()));
151+
}
152+
153+
if (count($comments)) {
154+
$rootAttributeComments[$name] = implode(";\n", $comments);
155+
}
156+
157+
// default values
158+
if ($child->hasDefaultValue()) {
159+
$value = $child->getDefaultValue();
160+
}
161+
162+
// append attribute
163+
$rootAttributes[$name] = $value;
164+
} else {
165+
// get elements
166+
$rootChildren[] = $child;
167+
}
168+
}
169+
}
170+
171+
// render comments
172+
173+
// root node comment
174+
if (count($rootComments)) {
175+
foreach ($rootComments as $comment) {
176+
$this->writeLine('<!-- '.$comment.' -->', $depth);
177+
}
178+
}
179+
180+
// attribute comments
181+
if (count($rootAttributeComments)) {
182+
foreach ($rootAttributeComments as $attrName => $comment) {
183+
$commentDepth = $depth + 4 + strlen($attrName) + 2;
184+
$commentLines = explode("\n", $comment);
185+
$multiline = (count($commentLines) > 1);
186+
$comment = implode(PHP_EOL.str_repeat(' ', $commentDepth), $commentLines);
187+
188+
if ($multiline) {
189+
$this->writeLine('<!--', $depth);
190+
$this->writeLine($attrName.': '.$comment, $depth + 4);
191+
$this->writeLine('-->', $depth);
192+
} else {
193+
$this->writeLine('<!-- '.$attrName.': '.$comment.' -->', $depth);
194+
}
195+
}
196+
}
197+
198+
// render start tag + attributes
199+
$rootIsVariablePrototype = isset($prototypeValue);
200+
$rootIsEmptyTag = (0 === count($rootChildren) && !$rootIsVariablePrototype);
201+
$rootOpenTag = '<'.$rootName;
202+
if (1 >= ($attributesCount = count($rootAttributes))) {
203+
if (1 === $attributesCount) {
204+
$rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes)));
205+
}
206+
207+
$rootOpenTag .= $rootIsEmptyTag ? ' />' : '>';
208+
209+
if ($rootIsVariablePrototype) {
210+
$rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
211+
}
212+
213+
$this->writeLine($rootOpenTag, $depth);
214+
} else {
215+
$this->writeLine($rootOpenTag, $depth);
216+
217+
$i = 1;
218+
219+
foreach ($rootAttributes as $attrName => $attrValue) {
220+
$attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue));
221+
222+
$this->writeLine($attr, $depth + 4);
223+
224+
if ($attributesCount === $i++) {
225+
$this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth);
226+
227+
if ($rootIsVariablePrototype) {
228+
$rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
229+
}
230+
}
231+
}
232+
}
233+
234+
// render children tags
235+
foreach ($rootChildren as $child) {
236+
$this->writeLine('');
237+
$this->writeNode($child, $depth + 4);
238+
}
239+
240+
// render end tag
241+
if (!$rootIsEmptyTag && !$rootIsVariablePrototype) {
242+
$this->writeLine('');
243+
244+
$rootEndTag = '</'.$rootName.'>';
245+
$this->writeLine($rootEndTag, $depth);
246+
}
247+
}
248+
249+
/**
250+
* Outputs a single config reference line
251+
*
252+
* @param string $text
253+
* @param int $indent
254+
*/
255+
private function writeLine($text, $indent = 0)
256+
{
257+
$indent = strlen($text) + $indent;
258+
$format = '%'.$indent.'s';
259+
260+
$this->reference .= sprintf($format, $text)."\n";
261+
}
262+
263+
/**
264+
* Renders the string conversion of the value.
265+
*
266+
* @param mixed $value
267+
*
268+
* @return string
269+
*/
270+
private function writeValue($value)
271+
{
272+
if ('%%%%not_defined%%%%' === $value) {
273+
return '';
274+
}
275+
276+
if (is_string($value) || is_numeric($value)) {
277+
return $value;
278+
}
279+
280+
if (false === $value) {
281+
return 'false';
282+
}
283+
284+
if (true === $value) {
285+
return 'true';
286+
}
287+
288+
if (null === $value) {
289+
return 'null';
290+
}
291+
292+
if (empty($value)) {
293+
return '';
294+
}
295+
296+
if (is_array($value)) {
297+
return implode(',', $value);
298+
}
299+
}
300+
}

0 commit comments

Comments
 (0)
0