Skip to content

[DependencyInjection][Yaml] Add support for flags parsing with binary_flags type and tag #57522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

7.2
---

* Add `binary_flags` parameter and argument types in `XmlFileLoader`

7.1
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,10 @@ private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file
case 'constant':
$arguments[$key] = \constant(trim($arg->nodeValue));
break;
case 'binary_flags':
$constants = \array_map(\constant(...), \array_map(trim(...), \explode('|', $arg->nodeValue)));
$arguments[$key] = \array_reduce($constants, static fn ($carry, $item) => $carry | $item, 0);
break;
default:
$arguments[$key] = XmlUtils::phpize($trim ? trim($arg->nodeValue) : $arg->nodeValue);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@
<xsd:enumeration value="string" />
<xsd:enumeration value="constant" />
<xsd:enumeration value="binary" />
<xsd:enumeration value="binary_flags" />
</xsd:restriction>
</xsd:simpleType>

Expand All @@ -354,6 +355,7 @@
<xsd:enumeration value="string" />
<xsd:enumeration value="constant" />
<xsd:enumeration value="binary" />
<xsd:enumeration value="binary_flags" />
<xsd:enumeration value="iterator" />
<xsd:enumeration value="closure" />
<xsd:enumeration value="service_closure" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Symfony\Component\DependencyInjection\Tests\Fixtures;

class FooClassWithFlags
{
public const FLAG_A = 2;
public const FLAG_B = 4;
public const FLAG_C = 8;

public function __construct(public int $flags)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="my_flags" type="binary_flags">Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithFlags::FLAG_A</parameter>
</parameters>
<services>
<service id="service_container" class="Symfony\Component\DependencyInjection\ContainerInterface" public="true" synthetic="true"/>
<service id="Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithFlags" class="Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithFlags" public="true">
<argument type="binary_flags">Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithFlags::FLAG_A | Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithFlags::FLAG_C</argument>
</service>
</services>
</container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="service_container" class="Symfony\Component\DependencyInjection\ContainerInterface" public="true" synthetic="true"/>
<service id="Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithFlags" class="Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithFlags" public="true">
<argument type="binary_flags">Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithFlags::WRONG_FLAG</argument>
</service>
</services>
</container>
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarInterface;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute;
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithFlags;
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum;
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooWithAbstractArgument;
use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy;
Expand Down Expand Up @@ -964,6 +965,26 @@ public function testInvalidEnumeration()
$loader->load('services_with_invalid_enumeration.xml');
}

public function testBinaryFlags()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('services_with_binary_flags.xml');
$container->compile();

$definition = $container->getDefinition(FooClassWithFlags::class);
$this->assertSame([FooClassWithFlags::FLAG_A | FooClassWithFlags::FLAG_C], $definition->getArguments());
}

public function testInvalidBinaryFlags()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));

$this->expectException(\Error::class);
$loader->load('services_with_invalid_binary_flags.xml');
}

public function testInstanceOfAndChildDefinition()
{
$container = new ContainerBuilder();
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Yaml/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CHANGELOG
---

* Deprecate parsing duplicate mapping keys whose value is `null`
* Add support for binary flags with the `!!binary_flags` tag

7.1
---
Expand Down
31 changes: 31 additions & 0 deletions src/Symfony/Component/Yaml/Inline.php
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,15 @@ private static function evaluateScalar(string $scalar, int $flags, array &$refer
return (float) substr($scalar, 8);
case str_starts_with($scalar, '!!binary '):
return self::evaluateBinaryScalar(substr($scalar, 9));
case str_starts_with($scalar, '!!binary_flags '):
if (self::$constantSupport) {
return self::evaluateBinaryFlags(substr($scalar, 15));
}
if (self::$exceptionOnInvalidType) {
throw new ParseException(\sprintf('The string "%s" could not be parsed as binary flags. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}

return null;
}

throw new ParseException(\sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename);
Expand Down Expand Up @@ -797,6 +806,28 @@ public static function evaluateBinaryScalar(string $scalar): string
return base64_decode($parsedBinaryData, true);
}

public static function evaluateBinaryFlags(string $flags): int
{
$constants = \explode('|', $flags);
$result = 0;

foreach ($constants as $rawConstant) {
$rawConstant = \trim($rawConstant);
if (!\defined($rawConstant)) {
throw new ParseException(\sprintf('The constant "%s" is not defined.', $rawConstant), self::$parsedLineNumber + 1, $flags, self::$parsedFilename);
}

$constant = \constant($rawConstant);
if (!\is_int($constant)) {
throw new ParseException(\sprintf('The constant "%s" is not an integer.', $rawConstant), self::$parsedLineNumber + 1, $flags, self::$parsedFilename);
}

$result |= $constant;
}

return $result;
}

private static function isBinaryString(string $value): bool
{
return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value);
Expand Down
36 changes: 36 additions & 0 deletions src/Symfony/Component/Yaml/Tests/InlineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,42 @@ public function testParsePhpEnumThrowsExceptionOnInvalidType()
Inline::parse('!php/enum SomeEnum::Foo', Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
}

public function testParseSingleBinaryFlag()
{
$this->assertSame(Yaml::PARSE_CONSTANT, Inline::parse('!!binary_flags Symfony\Component\Yaml\Yaml::PARSE_CONSTANT', Yaml::PARSE_CONSTANT));
}

public function testParseMultipleBinaryFlags()
{
$this->assertSame(Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS, Inline::parse('!!binary_flags Symfony\Component\Yaml\Yaml::PARSE_CONSTANT | Symfony\Component\Yaml\Yaml::PARSE_CUSTOM_TAGS', Yaml::PARSE_CONSTANT));
}

public function testParseBinaryFlagsThrowsExceptionWhenUndefined()
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage('The constant "WRONG_CONSTANT" is not defined');
Inline::parse('!!binary_flags WRONG_CONSTANT', Yaml::PARSE_CONSTANT);
}

public function testParseBinaryFlagsThrowsExceptionWhenNotAnInt()
{
$this->expectException(ParseException::class);
$this->expectExceptionMessage('The constant "DIRECTORY_SEPARATOR" is not an integer');
Inline::parse('!!binary_flags DIRECTORY_SEPARATOR', Yaml::PARSE_CONSTANT);
}

public function testParseBinaryFlagsWithoutFlags()
{
$this->assertNull(Inline::parse('!!binary_flags PHP_INT_MAX'));
}

public function testParseBinaryFlagsWithoutOptionToParseConstants()
{
$this->expectException(ParseException::class);
$this->expectExceptionMessageMatches('/The string "!!binary_flags PHP_INT_MAX" could not be parsed as binary flags.*/');
Inline::parse('!!binary_flags PHP_INT_MAX', Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
}

/**
* @dataProvider getTestsForDump
*/
Expand Down