Description
Symfony version(s) affected
6.2.11
Description
The ParameterNotFoundException exception is thrown when:
- an explicitly wired service has two or more optional parameters where the 1st optional parameter contains a "%" character.
- the 2nd optional parameter is explicitly set (while the 1st is omitted) in the
~src/config/service.yaml
.
How to reproduce
- Install a fresh Symfony app.
- 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);
}
}
- 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: '!'
- 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 = '!',
) {}