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

Skip to content

[DependencyInjection] Unable to inject parameters containing array of enums into service #49505

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
imba28 opened this issue Feb 23, 2023 · 4 comments

Comments

@imba28
Copy link

imba28 commented Feb 23, 2023

Symfony version(s) affected

^6.2.0

Description

Symfony 6.2 introduced enums as service parameters: https://symfony.com/blog/new-in-symfony-6-2-improved-enum-support
One of the examples includes defining an array of enums as a parameter:

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\Entity\BlogPost;

return static function (ContainerConfigurator $container) {
    $container->parameters()
        // ...

        ->set('app.some_parameter', SomeEnum::Foo)
        ->set('app.another_parameter', [SomeEnum::Foo, SomeEnum::Bar]);
};

Although, when you try to inject said parameter into a service, the container produces a warning when it's asked to instantiate the service:

Warning: Undefined array key "app.another_parameter"

How to reproduce

  1. Define an array of enum as a service parameter, as stated in the documentation.
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\SomeEnum;

return static function (ContainerConfigurator $container) {
    $container->parameters()
        // ...

        ->set('app.some_parameter', SomeEnum::Foo)
        ->set('app.another_parameter', [SomeEnum::Foo, SomeEnum::Bar]);
};
  1. Next, create a class and inject both of the parameters into the constructor method:
namespace App\Service;

use App\SomeEnum;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

final class DemoService
{
    public function __construct(
        #[Autowire('%app.some_parameter%')]
        private SomeEnum $enum,

        #[Autowire('%app.another_parameter%')]
        private array $enums
    )
    {
        dd($this->enum, $this->enums);
    }
}
  1. Ask the container to instantiate DemoService.
  2. While app.some_parameter can be resolved, the container will warn about the absence of app.another_parameter.

I created a repo demonstrating this issue: See https://github.com/imba28/symfony-enum-array-parameters-bug for a complete example

Possible Solution

I think this is due to a bug in the class PhpDumper. Whenever a parameter contains an enum, it is marked as a dynamic parameter that must be resolved using $container->getParameter():

$hasEnum = false;
$export = $this->exportParameters([$value], '', 12, $hasEnum);
$export = explode('0 => ', substr(rtrim($export, " ]\n"), 2, -1), 2);
if ($hasEnum || preg_match("/\\\$this->(?:getEnv\('(?:[-.\w\\\\]*+:)*+\w++'\)|targetDir\.'')/", $export[1])) {
$dynamicPhp[$key] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]);
} else {

However, the routine dumping the parameters lacks an equivalent check:

private function dumpParameter(string $name): string
{
if ($this->container->hasParameter($name)) {
$value = $this->container->getParameter($name);
$dumpedValue = $this->dumpValue($value, false);
if (!$value || !\is_array($value)) {
return $dumpedValue;
}
if (!preg_match("/\\\$this->(?:getEnv\('(?:[-.\w\\\\]*+:)*+\w++'\)|targetDir\.'')/", $dumpedValue)) {
return sprintf('$this->parameters[%s]', $this->doExport($name));
}
}
return sprintf('$this->getParameter(%s)', $this->doExport($name));
}

For that reason, the generated file var/cache/dev/ContainerXYZ/getDemoServiceService.php includes

return $container->privates['App\\Service\\DemoService'] = new \App\Service\DemoService(\App\SomeEnum::Foo, $container->parameters['app.another_parameter']);

instead of

return $container->privates['App\\Service\\DemoService'] = new \App\Service\DemoService(\App\SomeEnum::Foo, $container->getParameter('app.another_parameter'));

which eventually fails, since app.another_parameter has been previously marked as a dynamic parameter and thus is not part of $container->parameters.

Possible fix

Recursively traverse $value and determine if there's an enum hidden somewhere and place the check in this condition: https://github.com/symfony/symfony/blob/6.2/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php#L1928

If it does, dump $this->getParameter(%s) instead of $this->parameters[%s].

Additional Context

No response

@imba28 imba28 added the Bug label Feb 23, 2023
@fancyweb fancyweb changed the title [Dependency Injection] Unable to inject parameters containing array of enums into service [DependencyInjection] Unable to inject parameters containing array of enums into service Feb 28, 2023
@fancyweb
Copy link
Contributor

Thanks for the great report.
Instead of trying to fix it, could we rechallenge #44868 (comment) @nicolas-grekas and simplify the code?

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Feb 28, 2023

Challenge accepted: I asked to PHP core devs, and they confirmed listing enums in array prevents opcache from putting said array in shared memory. I still recommend splitting as advised on that comment.

@stof
Copy link
Member

stof commented Feb 28, 2023

The issue is that #48045 added support for adding enums in parameters in loaders and configurators, without adding tests to ensure they work everywhere else in the component. So this was not caught when doing other refactorings.

@fancyweb
Copy link
Contributor

fancyweb commented Mar 1, 2023

Should we reuse exportParameters() in dumpParameter() just to check for enums then? Create a new private method? Just foreach recursively with a goto in dumpParameter()?

nicolas-grekas added a commit that referenced this issue Mar 2, 2023
…s (fancyweb)

This PR was merged into the 5.4 branch.

Discussion
----------

[DependencyInjection] Fix dumping array of enums parameters

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | #49505
| License       | MIT
| Doc PR        | -

Commits
-------

97c5874 [DependencyInjection] Fix dumping array of enums parameters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants