1

I'm trying to migrate my custom bundle from symfony 3 to symfony 6.2 support, but are stuck with a InvalidConfigurationException.

I was following this and this documentation, but I appear to be missing a vital part.

src/SchoenefHtmlToPdfBundle.php:

<?php

namespace Schoenef\HtmlToPdfBundle;

use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

class SchoenefHtmlToPdfBundle extends AbstractBundle
{

}

src/DependencyInjection/SchoenefHtmlToPdfExtension.php:

namespace Schoenef\HtmlToPdfBundle\DependencyInjection;

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

class SchoenefHtmlToPdfExtension extends Extension
{

    /**
     * {@inheritdoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../../config'));
        $loader->load('services.yml');
    }

}

src/DependencyInjection/Configuration.php

<?php
namespace Schoenef\HtmlToPdfBundle\DependencyInjection;

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

/**
 * This is the class that validates and merges configuration from your app/config files
 * this is testing the configuration in the following manner:
 * html2pdf:
 *   provider: defualt pdfrocket
 *   timeout: default 20
 *   apikey: required
 *
 * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
 */
class Configuration implements ConfigurationInterface
{

    const pageSizes = ['A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9', 'B0', 'B1', 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9', 'C5E', 'Comm10E', 'DLE', 'Executive', 'Folio', 'Ledger', 'Legal', 'Letter', 'Tabloid'];
    const PROVIDER_PDF_ROCKET = 'pdfrocket';

    const KEY_PROVIDER = 'provider';
    const KEY_TIMEOUT = 'timeout';
    const KEY_APIKEY = 'apikey';
    const KEY_DEFAULT_OPTIONS = 'default_options';

    const OPTION_DPI = 'dpi';
    const OPTION_SHRINKING = 'shrinking';
    const OPTION_IMAGE_QUALITY = 'image_quality';
    const OPTION_PAGE_SIZE = 'page_size';
    const OPTION_ZOOM = 'zoom';
    const OPTION_JS_DELAY = 'js_delay';

    /**
     * {@inheritdoc}
     */
    public function getConfigTreeBuilder(): TreeBuilder
    {
        $treeBuilder = new TreeBuilder('schoenef_html_to_pdf');

        $treeBuilder->getRootNode()
            ->children()
                ->enumNode(self::KEY_PROVIDER)->values([self::PROVIDER_PDF_ROCKET])->defaultValue(self::PROVIDER_PDF_ROCKET)->end()
                ->integerNode(self::KEY_TIMEOUT)->defaultValue(20)->end()
                ->scalarNode(self::KEY_APIKEY)->isRequired()->end()
                ->arrayNode(self::KEY_DEFAULT_OPTIONS)
                    ->children()
                        ->integerNode(self::OPTION_DPI)->end()
                        ->floatNode(self::OPTION_ZOOM)->end()
                        ->integerNode(self::OPTION_JS_DELAY)->end()
                        ->booleanNode(self::OPTION_SHRINKING)->defaultTrue()->end()
                        ->integerNode(self::OPTION_IMAGE_QUALITY)->end()
                        ->enumNode(self::OPTION_PAGE_SIZE)->values(self::pageSizes)->end()
                    ->end()
                ->end()
            ->end();
        return $treeBuilder;
    }
}

Anybody can see the probably very obvious mistake? I only get:

Unrecognized options "provider, timeout, apikey, default_options" under "schoenef_html_to_pdf". Available options are "".

Thx a lot!

2
  • I copied your configuration class to a fresh 6.2 app and it all seems to work as expected. Try sticking a dd() in Extension::load just to make sure it is getting called. You would probably get a different error if it was not but that is all I can think of. Unless you are perhaps editing the wrong project directory. Commented Dec 6, 2022 at 16:32
  • 1
    Hah. I just noticed you extended AbstractBundle instead of Bundle. I changed my test case and got the same problem as you did. Not sure if that was intended behaviour or not. So go back to extending Bundle or replace your extension and configuration classes with AbstractBundle's loadExtension and configure methods. Commented Dec 6, 2022 at 19:13

1 Answer 1

3

Symfony 6.1 introduced a new AbstractBundle class. With AbstractBundle you no longer need individual Extension and Configuration classes. What I had not realized until seeing this question is that AbstractBundle is incompatible with the older Bundle class.

So the short answer to this question is to extend the original Bundle class and everything should work as before. The Bundle class itself is not going anywhere.

use Symfony\Component\HttpKernel\Bundle\Bundle;

class SchoenefHtmlToPdfBundle extends Bundle

A somewhat longer answer would be to go ahead and take advantage of the AbstractBundle's capability. Something like:

class CeradMyBundle extends AbstractBundle
{
  const pageSizes = ['A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9', 'B0', 'B1', 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9', 'C5E', 'Comm10E', 'DLE', 'Executive', 'Folio', 'Ledger', 'Legal', 'Letter', 'Tabloid'];
  const PROVIDER_PDF_ROCKET = 'pdfrocket';

  const KEY_PROVIDER = 'provider';
  const KEY_TIMEOUT = 'timeout';
  const KEY_APIKEY = 'apikey';
  const KEY_DEFAULT_OPTIONS = 'default_options';

  const OPTION_DPI = 'dpi';
  const OPTION_SHRINKING = 'shrinking';
  const OPTION_IMAGE_QUALITY = 'image_quality';
  const OPTION_PAGE_SIZE = 'page_size';
  const OPTION_ZOOM = 'zoom';
  const OPTION_JS_DELAY = 'js_delay';

  public function configure(DefinitionConfigurator $definition): void
  {
    $definition->rootNode()
      ->children()
        ->enumNode(self::KEY_PROVIDER)->values([self::PROVIDER_PDF_ROCKET])->defaultValue(self::PROVIDER_PDF_ROCKET)->end()
        ->integerNode(self::KEY_TIMEOUT)->defaultValue(20)->end()
        ->scalarNode(self::KEY_APIKEY)->isRequired()->end()
        ->arrayNode(self::KEY_DEFAULT_OPTIONS)
            ->children()
                ->integerNode(self::OPTION_DPI)->end()
                ->floatNode(self::OPTION_ZOOM)->end()
                ->integerNode(self::OPTION_JS_DELAY)->end()
                ->booleanNode(self::OPTION_SHRINKING)->defaultTrue()->end()
                ->integerNode(self::OPTION_IMAGE_QUALITY)->end()
                ->enumNode(self::OPTION_PAGE_SIZE)->values(self::pageSizes)->end()
            ->end()
        ->end()
    ->end();
  }
  public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
  {
    $container->import('../config/services.yaml');
  }
}

You might also consider moving the configuration stuff into it's own config/definition.php file.

Sign up to request clarification or add additional context in comments.

3 Comments

Thx, I was absolutely not aware of this difference! I tried to make the documentation more clear: github.com/symfony/symfony-docs/pull/17534
Thank you all! Here I got to work with AbstractBundle doing the stuffs with the loadExtension method. Symfony is a very good framework, but it is a pity that its documentation is so superficial in many points. It took me almost 2 and a half days to figure out how to make something as simple as exposing a service in a bundle work.
@chuckedw The best documentation for this sort of thing is the source code itself. Most of what I have learned about bundles comes from examining the core bundles (Framework, Twig, Doctrine etc.) themselves. The assumption is that people maintaining these bundles tend to know what they are doing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.