Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5a38804

Browse files
feature #21194 [Yaml] Add tags support (GuilhemN)
This PR was squashed before being merged into the 3.3-dev branch (closes #21194). Discussion ---------- [Yaml] Add tags support | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | #21185 | License | MIT | Doc PR | This PR adds custom tags support to the Yaml component. Symfony tags (`!!binary`, `!str`, etc.) are still managed in the parser to have a lighter diff but we'll be able to convert them later if we want to. The primary addition of this PR is the `TagInterface`: ```php interface TagInterface { public function construct(mixed $value): mixed; } ``` It can be used to register custom tags. An example that could be used to convert [the syntax `=iterator`](#20907 (comment)) to a tag: ```php final class IteratorTag implements TagInterface { public function construct(mixed $value): mixed { return new IteratorArgument($value); } } $parser = new Parser(['iterator' => new IteratorTag()]); ``` If you think this is too complex, @nicolas-grekas [proposed an alternative](#21185 (comment)) to my proposal externalizing this support by introducing a new class `TaggedValue`. Commits ------- 4744107 [Yaml] Add tags support
2 parents 10c3fc2 + 4744107 commit 5a38804

File tree

10 files changed

+369
-88
lines changed

10 files changed

+369
-88
lines changed

src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
namespace Symfony\Component\DependencyInjection\Dumper;
1313

1414
use Symfony\Component\Yaml\Dumper as YmlDumper;
15+
use Symfony\Component\Yaml\Tag\TaggedValue;
1516
use Symfony\Component\DependencyInjection\Alias;
17+
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1618
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1719
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1820
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -251,10 +253,16 @@ private function dumpCallable($callable)
251253
*/
252254
private function dumpValue($value)
253255
{
254-
if ($value instanceof IteratorArgument) {
255-
$value = array('=iterator' => $value->getValues());
256-
} elseif ($value instanceof ClosureProxyArgument) {
257-
$value = array('=closure_proxy' => $value->getValues());
256+
if ($value instanceof ArgumentInterface) {
257+
if ($value instanceof IteratorArgument) {
258+
$tag = 'iterator';
259+
} elseif ($value instanceof ClosureProxyArgument) {
260+
$tag = 'closure_proxy';
261+
} else {
262+
throw new RuntimeException(sprintf('Unspecified Yaml tag for type "%s".', get_class($value)));
263+
}
264+
265+
return new TaggedValue($tag, $this->dumpValue($value->getValues()));
258266
}
259267

260268
if (is_array($value)) {

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\Component\Config\Resource\FileResource;
2424
use Symfony\Component\Yaml\Exception\ParseException;
2525
use Symfony\Component\Yaml\Parser as YamlParser;
26+
use Symfony\Component\Yaml\Tag\TaggedValue;
2627
use Symfony\Component\Yaml\Yaml;
2728
use Symfony\Component\ExpressionLanguage\Expression;
2829

@@ -516,7 +517,7 @@ protected function loadFile($file)
516517
}
517518

518519
try {
519-
$configuration = $this->yamlParser->parse(file_get_contents($file), Yaml::PARSE_CONSTANT);
520+
$configuration = $this->yamlParser->parse(file_get_contents($file), Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS);
520521
} catch (ParseException $e) {
521522
throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $file), 0, $e);
522523
}
@@ -567,42 +568,42 @@ private function validate($content, $file)
567568
/**
568569
* Resolves services.
569570
*
570-
* @param string|array $value
571+
* @param mixed $value
571572
*
572-
* @return array|string|Reference
573+
* @return array|string|Reference|ArgumentInterface
573574
*/
574575
private function resolveServices($value)
575576
{
576-
if (is_array($value)) {
577-
if (array_key_exists('=iterator', $value)) {
578-
if (1 !== count($value)) {
579-
throw new InvalidArgumentException('Arguments typed "=iterator" must have no sibling keys.');
580-
}
581-
if (!is_array($value = $value['=iterator'])) {
582-
throw new InvalidArgumentException('Arguments typed "=iterator" must be arrays.');
583-
}
584-
$value = new IteratorArgument(array_map(array($this, 'resolveServices'), $value));
585-
} elseif (array_key_exists('=closure_proxy', $value)) {
586-
if (1 !== count($value)) {
587-
throw new InvalidArgumentException('Arguments typed "=closure_proxy" must have no sibling keys.');
588-
}
589-
if (!is_array($value = $value['=closure_proxy']) || array(0, 1) !== array_keys($value)) {
590-
throw new InvalidArgumentException('Arguments typed "=closure_proxy" must be arrays of [@service, method].');
577+
if ($value instanceof TaggedValue) {
578+
$argument = $value->getValue();
579+
if ('iterator' === $value->getTag()) {
580+
if (!is_array($argument)) {
581+
throw new InvalidArgumentException('"!iterator" tag only accepts sequences.');
591582
}
592-
if (!is_string($value[0]) || !is_string($value[1]) || 0 !== strpos($value[0], '@') || 0 === strpos($value[0], '@@')) {
593-
throw new InvalidArgumentException('Arguments typed "=closure_proxy" must be arrays of [@service, method].');
583+
584+
return new IteratorArgument(array_map(array($this, 'resolveServices'), $argument));
585+
}
586+
if ('closure_proxy' === $value->getTag()) {
587+
if (!is_array($argument) || array(0, 1) !== array_keys($argument) || !is_string($argument[0]) || !is_string($argument[1]) || 0 !== strpos($argument[0], '@') || 0 === strpos($argument[0], '@@')) {
588+
throw new InvalidArgumentException('"!closure_proxy" tagged values must be arrays of [@service, method].');
594589
}
595-
if (0 === strpos($value[0], '@?')) {
596-
$value[0] = substr($value[0], 2);
590+
591+
if (0 === strpos($argument[0], '@?')) {
592+
$argument[0] = substr($argument[0], 2);
597593
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
598594
} else {
599-
$value[0] = substr($value[0], 1);
595+
$argument[0] = substr($argument[0], 1);
600596
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
601597
}
602-
$value = new ClosureProxyArgument($value[0], $value[1], $invalidBehavior);
603-
} else {
604-
$value = array_map(array($this, 'resolveServices'), $value);
598+
599+
return new ClosureProxyArgument($argument[0], $argument[1], $invalidBehavior);
605600
}
601+
602+
throw new InvalidArgumentException(sprintf('Unsupported tag "!%s".', $value->getTag()));
603+
}
604+
605+
if (is_array($value)) {
606+
$value = array_map(array($this, 'resolveServices'), $value);
606607
} elseif (is_string($value) && 0 === strpos($value, '@=')) {
607608
return new Expression(substr($value, 2));
608609
} elseif (is_string($value) && 0 === strpos($value, '@')) {

src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\DependencyInjection\ContainerBuilder;
1515
use Symfony\Component\DependencyInjection\Dumper\YamlDumper;
1616
use Symfony\Component\Yaml\Yaml;
17+
use Symfony\Component\Yaml\Parser;
1718

1819
class YamlDumperTest extends \PHPUnit_Framework_TestCase
1920
{
@@ -62,8 +63,10 @@ public function testDumpAutowireData()
6263
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services24.yml', $dumper->dump());
6364
}
6465

65-
private function assertEqualYamlStructure($yaml, $expected, $message = '')
66+
private function assertEqualYamlStructure($expected, $yaml, $message = '')
6667
{
67-
$this->assertEquals(Yaml::parse($expected), Yaml::parse($yaml), $message);
68+
$parser = new Parser();
69+
70+
$this->assertEquals($parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS), $parser->parse($yaml, Yaml::PARSE_CUSTOM_TAGS), $message);
6871
}
6972
}

src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,12 @@ services:
109109
factory: ['@factory_simple', getInstance]
110110
lazy_context:
111111
class: LazyContext
112-
arguments: [{ '=iterator': [foo, '@foo.baz', { '%foo%': 'foo is %foo%', foobar: '%foo%' }, true, '@service_container'] }]
112+
arguments: [!iterator [foo, '@foo.baz', { '%foo%': 'foo is %foo%', foobar: '%foo%' }, true, '@service_container']]
113113
lazy_context_ignore_invalid_ref:
114114
class: LazyContext
115-
arguments: [{ '=iterator': ['@foo.baz', '@?invalid'] }]
115+
arguments: [!iterator ['@foo.baz', '@?invalid']]
116116
closure_proxy:
117117
class: BarClass
118-
arguments: [{ '=closure_proxy': ['@closure_proxy', getBaz] }]
118+
arguments: [!closure_proxy ['@closure_proxy', getBaz]]
119119
alias_for_foo: '@foo'
120120
alias_for_alias: '@foo'

src/Symfony/Component/DependencyInjection/composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"psr/container": "^1.0"
2121
},
2222
"require-dev": {
23-
"symfony/yaml": "~3.2",
23+
"symfony/yaml": "~3.3",
2424
"symfony/config": "~3.3",
2525
"symfony/expression-language": "~2.8|~3.0"
2626
},
@@ -31,8 +31,8 @@
3131
"symfony/proxy-manager-bridge": "Generate service proxies to lazy load them"
3232
},
3333
"conflict": {
34-
"symfony/config": "<3.3",
35-
"symfony/yaml": "<3.2"
34+
"symfony/yaml": "<3.3",
35+
"symfony/config": "<3.3"
3636
},
3737
"provide": {
3838
"psr/container-implementation": "1.0"

0 commit comments

Comments
 (0)