Skip to content

Commit

Permalink
[Yaml] Add support for !php/enum *->value syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolas-grekas committed Jun 24, 2022
1 parent bbf25d6 commit f71e4ab
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 2 deletions.
5 changes: 5 additions & 0 deletions src/Symfony/Component/Yaml/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========

6.2
---

* Add support for `!php/enum` and `!php/enum *->value`

6.1
---

Expand Down
38 changes: 36 additions & 2 deletions src/Symfony/Component/Yaml/Inline.php
Expand Up @@ -432,7 +432,7 @@ private static function parseMapping(string $mapping, int $flags, int &$i = 0, a
throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping);
}

if ('!php/const' === $key) {
if ('!php/const' === $key || '!php/enum' === $key) {
$key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false);
$key = self::evaluateScalar($key, $flags);
}
Expand Down Expand Up @@ -623,6 +623,40 @@ private static function evaluateScalar(string $scalar, int $flags, array &$refer
throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}

return null;
case str_starts_with($scalar, '!php/enum'):
if (self::$constantSupport) {
if (!isset($scalar[11])) {
throw new ParseException('Missing value for tag "!php/enum".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}

$i = 0;
$enum = self::parseScalar(substr($scalar, 10), 0, null, $i, false);
if ($useValue = str_ends_with($enum, '->value')) {
$enum = substr($enum, 0, -7);
}
if (!\defined($enum)) {
throw new ParseException(sprintf('The enum "%s" is not defined.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}

$value = \constant($enum);

if (!$value instanceof \UnitEnum) {
throw new ParseException(sprintf('The string "%s" is not the name of a valid enum.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
if (!$useValue) {
return $value;
}
if (!$value instanceof \BackedEnum) {
throw new ParseException(sprintf('The enum "%s" defines no value next to its name.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}

return $value->value;
}
if (self::$exceptionOnInvalidType) {
throw new ParseException(sprintf('The string "%s" could not be parsed as an enum. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}

return null;
case str_starts_with($scalar, '!!float '):
return (float) substr($scalar, 8);
Expand Down Expand Up @@ -703,7 +737,7 @@ private static function parseTag(string $value, int &$i, int $flags): ?string
}

// Is followed by a scalar and is a built-in tag
if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) {
if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || \in_array($tag, ['str', 'php/const', 'php/enum', 'php/object'], true))) {
// Manage in {@link self::evaluateScalar()}
return null;
}
Expand Down
8 changes: 8 additions & 0 deletions src/Symfony/Component/Yaml/Tests/Fixtures/FooBackedEnum.php
@@ -0,0 +1,8 @@
<?php

namespace Symfony\Component\Yaml\Tests\Fixtures;

enum FooBackedEnum: string
{
case BAR = 'bar';
}
54 changes: 54 additions & 0 deletions src/Symfony/Component/Yaml/Tests/InlineTest.php
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Inline;
use Symfony\Component\Yaml\Tag\TaggedValue;
use Symfony\Component\Yaml\Tests\Fixtures\FooBackedEnum;
use Symfony\Component\Yaml\Tests\Fixtures\FooUnitEnum;
use Symfony\Component\Yaml\Yaml;

Expand Down Expand Up @@ -72,13 +73,45 @@ public function testParsePhpConstantThrowsExceptionWhenUndefined()
Inline::parse('!php/const WRONG_CONSTANT', Yaml::PARSE_CONSTANT);
}

public function testParsePhpEnumThrowsExceptionWhenUndefined()
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage('The enum "SomeEnum::Foo" is not defined');
Inline::parse('!php/enum SomeEnum::Foo', Yaml::PARSE_CONSTANT);
}

public function testParsePhpEnumThrowsExceptionWhenNotAnEnum()
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage('The string "PHP_INT_MAX" is not the name of a valid enum');
Inline::parse('!php/enum PHP_INT_MAX', Yaml::PARSE_CONSTANT);
}

public function testParsePhpEnumThrowsExceptionWhenNotBacked()
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage('The enum "Symfony\Component\Yaml\Tests\Fixtures\FooUnitEnum::BAR" defines no value next to its name');
Inline::parse('!php/enum Symfony\Component\Yaml\Tests\Fixtures\FooUnitEnum::BAR->value', Yaml::PARSE_CONSTANT);
}

public function testParsePhpConstantThrowsExceptionOnInvalidType()
{
$this->assertNull(Inline::parse('!php/const PHP_INT_MAX'));

$this->expectException(ParseException::class);
$this->expectExceptionMessageMatches('#The string "!php/const PHP_INT_MAX" could not be parsed as a constant.*#');
Inline::parse('!php/const PHP_INT_MAX', Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
}

public function testParsePhpEnumThrowsExceptionOnInvalidType()
{
$this->assertNull(Inline::parse('!php/enum SomeEnum::Foo'));

$this->expectException(ParseException::class);
$this->expectExceptionMessageMatches('#The string "!php/enum SomeEnum::Foo" could not be parsed as an enum.*#');
Inline::parse('!php/enum SomeEnum::Foo', Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
}

/**
* @dataProvider getTestsForDump
*/
Expand Down Expand Up @@ -589,6 +622,16 @@ public function testDumpUnitEnum()
$this->assertSame("!php/const Symfony\Component\Yaml\Tests\Fixtures\FooUnitEnum::BAR", Inline::dump(FooUnitEnum::BAR));
}

public function testParseUnitEnum()
{
$this->assertSame(FooUnitEnum::BAR, Inline::parse("!php/enum Symfony\Component\Yaml\Tests\Fixtures\FooUnitEnum::BAR", Yaml::PARSE_CONSTANT));
}

public function testParseBackedEnumValue()
{
$this->assertSame(FooBackedEnum::BAR->value, Inline::parse("!php/enum Symfony\Component\Yaml\Tests\Fixtures\FooBackedEnum::BAR->value", Yaml::PARSE_CONSTANT));
}

public function getDateTimeDumpTests()
{
$tests = [];
Expand Down Expand Up @@ -818,6 +861,17 @@ public function testPhpConstTagWithEmptyValue(string $value)
Inline::parse($value, Yaml::PARSE_CONSTANT);
}

/**
* @dataProvider phpConstTagWithEmptyValueProvider
*/
public function testPhpEnumTagWithEmptyValue(string $value)
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage('Missing value for tag "!php/enum" at line 1 (near "!php/enum").');

Inline::parse(str_replace('!php/const', '!php/enum', $value), Yaml::PARSE_CONSTANT);
}

public function phpConstTagWithEmptyValueProvider()
{
return [
Expand Down

0 comments on commit f71e4ab

Please sign in to comment.