8000 [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
Closed
@infinitum11

Description

@infinitum11

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 = '!',
    ) {}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0