10000 [Dependency Injection] Optional parameter containing % character. · Issue #50469 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Dependency Injection] Optional parameter containing % character. #50469

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
infinitum11 opened this issue May 30, 2023 · 0 comments
Closed

[Dependency Injection] Optional parameter containing % character. #50469

infinitum11 opened this issue May 30, 2023 · 0 comments

Comments

@infinitum11
Copy link
infinitum11 commented May 30, 2023

Symfony version(s) affected

6.2.11

Description

The ParameterNotFoundException exception is thrown when:

  1. an explicitly wired service has two or more optional parameters where the 1st optional parameter contains a "%" character.
  2. the 2nd optional parameter is explicitly set (while the 1st is omitted) in the ~src/config/service.yaml.

How to reproduce

  1. Install a fresh Symfony app.
  2. Create a service that accepts two (or more) optional parameters. The 1st optional parameter must contain a "%" character.
# ~src/Service/Shouter.php
<?php declare(strict_types=1);
namespace App\Service;

class Shouter
{
    public function __construct(
        private readonly string $format = '%s%s',
        private readonly string $endMark = '!',
    ) {}

    public function shout(string $str): string
    {
        return sprintf($this->format, $str, $this->endMark);
    }
}
  1. Wire the service.
# ~config/services.yaml
parameters:

services:
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/Entity/'
            - '../src/Service/'
            - '../src/Kernel.php'
    shouter:
        class: App\Service\Shouter
        arguments:
            $endMark: '!'
  1. Create a dummy controller to see in action.
# ~src/Controller/HomeController.php
<?php declare(strict_types=1);
namespace App\Controller;

use App\Service\Shouter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class HomeController extends AbstractController
{
    #[Route('/', 'app_shouter')]
    public function home(#[Autowire(service: 'shouter')] Shouter $shouter): Response
    {
        return new Response($shouter->shout('John'));
    }
}
Stack Trace

Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException:
You have requested a non-existent parameter "s".

at var/cache/dev/ContainerMAWqtVy/App_KernelDevDebugContainer.php:1841
at ContainerMAWqtVy\App_KernelDevDebugContainer->getParameter()
(var/cache/dev/ContainerMAWqtVy/getShouterService.php:22)
at ContainerMAWqtVy\getShouterService::do()
(var/cache/dev/ContainerMAWqtVy/App_KernelDevDebugContainer.php:417)
at ContainerMAWqtVy\App_KernelDevDebugContainer->load()
(vendor/symfony/dependency-injection/Container.php:382)
at Symfony\Component\DependencyInjection\Container->getService()
(vendor/symfony/dependency-injection/Argument/ServiceLocator.php:40)
at Symfony\Component\DependencyInjection\Argument\ServiceLocator->get()
(vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php:84)
at Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver->resolve()
(vendor/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php:60)
at Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver->resolve()
(vendor/symfony/http-kernel/Controller/ArgumentResolver.php:54)
at Symfony\Component\HttpKernel\Controller\ArgumentResolver->getArguments()
(vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php:40)
at Symfony\Component\HttpKernel\Controller\TraceableArgumentResolver->getArguments()
(vendor/symfony/http-kernel/HttpKernel.php:155)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw()
(vendor/symfony/http-kernel/HttpKernel.php:74)
at Symfony\Component\HttpKernel\HttpKernel->handle()
(vendor/symfony/http-kernel/Kernel.php:184)
at Symfony\Component\HttpKernel\Kernel->handle()
(vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php:35)
at Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner->run()
(vendor/autoload_runtime.php:29)
at require_once('/home/xxx/projects/php/symfony_sandbox/vendor/autoload_runtime.php')
(public/index.php:5)

Compiled container I think **''.$container->getParameter('s').'s',** is not something we expect to see here.
<?php

namespace ContainerMAWqtVy;

use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;

/**
 * @internal This class has been auto-generated by the Symfony Dependency Injection Component.
 */
class getShouterService extends App_KernelDevDebugContainer
{
    /**
     * Gets the private 'shouter' shared autowired service.
     *
     * @return \App\Service\Shouter
     */
    public static function do($container, $lazyLoad = true)
    {
        include_once \dirname(__DIR__, 4).'/src/Service/Shouter.php';

        return $container->privates['shouter'] = new \App\Service\Shouter(''.$container->getParameter('s').'s', '!');
    }
}

Possible Solution

No response

Additional Context

If we set the 1st optional parameter explicitly, then everything works fine. However, when a service has a lot of optional parameters it becomes cumbersome.

    shouter:
        class: App\Service\Shouter
        arguments:
            $format: '%%s%%s'
            $endMark: '!'

In case when we have access to that service we could write optional parameters in the way how '%' is escaped in the config i.e. with double '%%' private readonly string $format = '%%s%%s', but this won't help with 3rd party libraries:

    shouter:
        class: App\Service\Shouter
        arguments:
            $endMark: '!'
    public function __construct(
        private readonly string $format = '%%s%%s',
        private readonly string $endMark = '!',
    ) {}
nicolas-grekas added a commit that referenced this issue May 30, 2023
…lt values (MatTheCat)

This PR was merged into the 5.4 branch.

Discussion
----------

[DependencyInjection] Escape `%` from parameter-like default values

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

When string default values are autowired, they shouldn’t reference parameters.

This PR tries to address the issue by escaping them in the `AutowirePass`.

Commits
-------

bb4eeb0 [DependencyInjection] Escape `%` from parameter-like default values
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

4 participants
0