2

I'm trying to convert a bundle to symfony 4 and need to update my ancient parameters.yml to the modern symfony 4 way of life. Basicall the bundle itself - shared across multiple apps - should have a configurable file under /config/packages/.

However I receive this error:

(1/1) InvalidArgumentException

There is no extension able to load the configuration for "ptmr" (in /var/www/html/ptmr/pws_ptmrio_dev/PtmrBundle/DependencyInjection/../../config/packages/ptmr.yaml). Looked for namespace "ptmr", found none

/PtmrBundle/DependencyInjection/PtmrExtension.php

<?php
namespace PtmrBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class PtmrExtension extends Extension
{

    public function load(array $configs, ContainerBuilder $container)
    {

        $configuration = new Configuration(true);
        $config = $this->processConfiguration($configuration, $configs);

        $loader = new YamlFileLoader(
            $container,
            new FileLocator(__DIR__ . '/../../config/packages')
        );

        $loader->load('ptmr.yaml');

    }


}

/PtmrBundle/DependencyInjection/Configuration.php

<?php
namespace PtmrBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{

    private $debug;

    public function  __construct($debug = true)
    {
        $this->debug = (bool) $debug;
    }

    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder('ptmr');

        $treeBuilder->getRootNode()
            ->children()
            ->arrayNode('twitter')
            ->children()
            ->integerNode('client_id')->end()
            ->scalarNode('client_secret')->end()
            ->end()
            ->end() // twitter
            ->end()
        ;

        return $treeBuilder;
    }
}

/config/packages/ptmr.yaml

ptmr:
  twitter:
    client_id: 123
    client_secret: your_secret

-- Note: The Bundle itself works.

I added this line to psr-4 in composer.json:

    "PtmrBundle\\": "PtmrBundle/"

This lines to config/routes/annotations.yml

ptmr_bundle:
    resource: ../PtmrBundle/Controller/
    type: annotation

These lines to config/services.yaml

services:
    ...

    PtmrBundle\:
        resource: '../PtmrBundle/*'
        exclude: '../PtmrBundle/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

    ...

    PtmrBundle\Controller\:
        resource: '../PtmrBundle/Controller'
        tags: ['controller.service_arguments']

And of course PtmrBundle/PtmrBundle.php

<?php

namespace PtmrBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class PtmrBundle extends Bundle
{

}

I'm following these instructions and I reaaaly do not see any errors. What am I missing? Symfony 4.2.

ptmr.io
  • 2,115
  • 4
  • 22
  • 34
  • did you add it to bundles.php? https://symfony.com/doc/current/bundles.html – myxaxa Apr 15 '19 at 15:59
  • Hi, yes of course :) the bundle itself works fine. When I remove the line $loader->load('ptmr.yaml'); everything compiles as expected - only the parameters are not available. – ptmr.io Apr 15 '19 at 16:00
  • Get rid of $loader->load('ptmr.yaml'); The contents of ptmr.yaml will be injected into $config. That won't fix your error but it will help. And trying to autowire your bundle in config/services.yaml typically does not end well. Comment out those lines as well until you get past the basic "no extension" message. – Cerad Apr 15 '19 at 16:06
  • While commenting out $loader->load(), it compiles correctly, however the parameters are not available then: The parameter "ptmr.twitter" must be defined. – ptmr.io Apr 15 '19 at 17:10
  • You are confusing parameters with config values. Two different concepts. Typically your extension would modify service definitions and inject your config values. If you want your config to actually be parameters then just make them parameters. If you really really really want to use a config and then expose some of the values as parameters then you can do it inside of your extension using ContainerBuilder::setParameter. But this would be very unusual. Configs are confusing. Keep reading the docs until it sinks in. – Cerad Apr 15 '19 at 19:25
  • @Cerad thank you. It would indeed be sufficient to just enter the values into the parameters section of services.yaml. However, for "academic purpose", what is wrong with the code above :)? – ptmr.io Apr 15 '19 at 20:22
  • The $loader->load('ptmr.yaml'); is used to load service and parameters defined within your bundle. You would never reach out from a bundle and try to get something from the application. Nor would you try to have the application reach into your bundle and autowire bundle services. Look at some of the Symfony bundles and see how they work. It can seem to be complicated. – Cerad Apr 15 '19 at 20:55
  • @Cerad thank you for explaining. I tried to put some of the configs into the bundle folders too, but it doesn't change anything. Moreover, how do other bundles "put" config files into the /config/packages/ folder then? (twig, swiftmailer, ...) – ptmr.io Apr 16 '19 at 06:05
  • The bundles themselves don't actually put anything in the config folders. Instead, a Symfony extension (called Flex) to composer takes care of this when the bundle is installed. It uses what they call a "recipe" for installing the bundle. Back in the old days, developers had to copy the config files themselves after installing a bundle. Still do for most bundles. – Cerad Apr 16 '19 at 12:39

2 Answers2

2

I found the answer after all. To make your own config/bundle.yaml parameters file, simply do:

Step 1: Create a file in your bundle DependencyInjection/{BundleNameWithoutBundle}Extension.php, e.g. for MyBundle > MyExtension.php

<?php
namespace MyBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;

class MyExtension extends \Symfony\Component\DependencyInjection\Extension\Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        $container->setParameter('my', $config);
    }
}

Also see How to Load Service Configuration inside a Bundle

Step 2: Make a configuration file that provides a schema for your .yaml file DependencyInjection/Configuration.php

<?php

namespace MyBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder('my');

        $treeBuilder->getRootNode()
            ->children()
                ->variableNode('project_name')->end()
            ->end()
        ;

        return $treeBuilder;
    }
}

Also see How to Create Friendly Configuration for a Bundle

Step 3: Mirror the Configuration.php in your config/my.yaml

my:
  project_name: "My Name"

Now the parameter my is available (set in MyExtension Class). Simply get it in your Controller like:

class IndexController extends AbstractController
{
    public function indexAction(ParameterBagInterface $parameterBag)
    {
        ...
        dump($parameterBag->get('ptmr'));
        return $this->render('index.html.twig');
    }

}

Note: Most bundles go further and manipulate the bundles config xml files, which is out of scope for a simple question like this. A simple example on how to do this is the KnpPaginatorBundle which is not overly complicated to understand for settings parameters.

Personal Note: It seems to me, the Symfony docs are overly complicated and should provide a simple example. You got to know a lot of nomenclature and it's hard to learn it, especially compared to other well documented symfony chapters.

ptmr.io
  • 2,115
  • 4
  • 22
  • 34
0

You are attempting to load configuration for your bundle before Symfony is aware of your bundle. Your bundle class must be loaded and configured before you can parse configuration under the ptmr property.

Typically your bundle would load __DIR__.'/../Resources/config/services.yaml which contains service and parameters definitions. Outside of your bundle, in your application config/ dir, you would then load configuration under the ptmr property.

emarref
  • 1,286
  • 9
  • 18
  • @Malcom thank you. Could you give an example? Looking e.g. at the FOSUserBundle, how do they manage this? https://github.com/FriendsOfSymfony/FOSUserBundle/tree/master/Resources/config – ptmr.io Apr 16 '19 at 06:08
  • @ptmr.io - Just for info, the FOSUserBundle is not a good example of anything even though it is the mostly widely used third party bundles. Instead, look at some of the Symfony bundles such as the Framework bundle or the SecurityBundle or perhaps the MonologBundle. In addition, create a fresh project and try to add your own bundle following the docs focusing only on configuration. – Cerad Apr 16 '19 at 12:35
  • There are a few steps going on here that are implicitly handled by the bundle when you add it to your bundles file. When the container boots, it iterates over all registered bundles and finds the extension class by convention. If found, the extension load method is called. Here, you add container configuration from your Resources/config dir to set up services and parameters in the container. Later, a compiler pass finds your bundle's Configuration class, and augments the top-level keys the container knows about. Adding ptmr to its list of known keys such as services and parameters. – emarref Apr 16 '19 at 22:00
  • To answer the question in your comment though, the [load method on the Extension class](https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/DependencyInjection/FOSUserExtension.php#L49) is used to load all/some of those xml files in the Resources/config dir. The [Configuration class](https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/DependencyInjection/Configuration.php) is used to augment the container by adding the `fos_user` tree for configuration files in `config/` to use. You'll notice that `fos_user` configuration is used nowhere in the bundle itself. – emarref Apr 16 '19 at 22:16