phpsymfonysymfony4

How to properly use '%' for escaping YAML configuration in Symfony?


When it comes to creating bundles, it's often necessary to support some configuration for the developers who'll use the bundle.

In this case, I need the configuration's value to support %someText%, without Symfony trying to resolve this value as parameter.

Example: Incorrect Configuration

my_bundle_name:
    my_configuration: '%someText%'

So, I figured, I'll have to escape the % (so it looks like %%) to make things work - resulting in the value %%someText%%.

If some parameter value includes the % character, you need to escape it by adding another % so Symfony doesn't consider it a reference to a parameter name [...]

Example: No Error, but Unexpected Result

my_bundle_name:
    my_configuration: '%%someText%%'

While this solves the You have requested a non-existent parameter[...] error, the actual value is now %%someText%%. I expected Symfony to return the unsecaped value instead, i.e., %someText%.

Sure I could str_replace('%%', '%', '%%someText%%'), but this feels hack-ish.

How would I change the value to actually get %someText% instead?

Here's where the configuration is loaded:

// Namespace and use statements omitted for brevity
class MyBundleNameExtension extends ConfigurableExtension
{
    /**
     * @inheritDoc
     */
    protected function loadInternal(array $mergedConfig, ContainerBuilder $container)
    {
        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
        $loader->load('config.yml');

        p_r($mergedConfig["my_configuration"]); // Prints %%someText%%
    }
}

I've verified that using both ' and " yields the same result.


Solution

  • The Problem

    The problem was that the loadInternal was called before the parameters were processed, thus, leaving me with %%someText%%.

    The solution

    I created a SetupListener class like so:

    <?php
    
    // Namespace and use statements omitted
    
    class SetupListener
    {
        /** @var ParameterBagInterface $parameterBag */
        private $parameterBag;
    
        /** @var ContainerInterface $container */
        private $container;
    
        /**
         * SetupListener constructor.
         * @param ParameterBagInterface $parameterBag
         * @param ContainerInterface $container
         */
        public function __construct(ParameterBagInterface $parameterBag, ContainerInterface $container)
        {
            $this->parameterBag = $parameterBag;
            $this->container = $container;
        }
    
        /**
         * @param RequestEvent $event
         */
        public function onSetup(RequestEvent $event)
        {
            if($event->isMasterRequest() && !$event->getRequest()->isXmlHttpRequest()) {
                $var = $this->parameterBag->get("my_configuration");
                // Further processing...
            }
        }
    }
    

    Probably not the most elegant solution, but it works for me.