Symfony League CommonMarkdown customize ExternalLinksExtension

437 Views Asked by At

How is the correct way to customize the ExternalLinksExtension in the services.yml on Symfony 4/5/6.1?

according to official documentation ExternalLinksExtension it is assumed that the configuration parameters must be passed to the Enviroment. However, the Enviroment is not a service and is not instantiated as a service, even if it is declared as such. It is instantiated directly from the function that initializes the CommonMarkdown bundle, where empty arguments are passed on to it.

By making a custom extension there is also no way to modify the configuration of the current environment. Inheriting from ExtensionInterface does not give you such access.

The current way I do it is to copy all the content of the native extension ExternalLinksExtension and in the configureSchema method merge the configuration of the current environment with custom arguments.

class ExternalLinksExtension implements ConfigurableExtensionInterface
{

    public function configureSchema(ConfigurationBuilderInterface $builder): void
    {
        $applyOptions = [
            ExternalLinkProcessor::APPLY_NONE,
            ExternalLinkProcessor::APPLY_ALL,
            ExternalLinkProcessor::APPLY_INTERNAL,
            ExternalLinkProcessor::APPLY_EXTERNAL,
        ];

        $builder->addSchema('external_link', Expect::structure([
            'internal_hosts' => Expect::type('string|string[]'),
            'open_in_new_window' => Expect::bool(false),
            'html_class' => Expect::string()->default(''),
            'nofollow' => Expect::anyOf(...$applyOptions)->default(ExternalLinkProcessor::APPLY_NONE),
            'noopener' => Expect::anyOf(...$applyOptions)->default(ExternalLinkProcessor::APPLY_EXTERNAL),
            'noreferrer' => Expect::anyOf(...$applyOptions)->default(ExternalLinkProcessor::APPLY_EXTERNAL),
        ]));

        //this is where I pass custom arfuments
        $builder->merge([
            'external_link' => [
                'internal_hosts' => ["localhost"],
                'open_in_new_window' => true,
                'html_class' => 'external-link',
                'nofollow' => '',
                'noopener' => 'external',
                'noreferrer' => 'external',
            ]
        ]);
    }

    public function register(EnvironmentBuilderInterface $environment): void
    {
        $environment->addEventListener(DocumentParsedEvent::class, new ExternalLinkProcessor($environment->getConfiguration()), -50);
    }
}

This way it works for me, but I believe that it is not convenient to have to be copying all the extensions that you want to customize or that you must make this type of hacks, there should be another more direct method of configuration.

In my service.yml file

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.


    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/Entity/'
            - '../src/Kernel.php'
            - '../src/Tests/'

    # controllers are imported separately to make sure services can be injected
    # as action arguments even if you don't extend any base controller class
    App\Controller\:
        resource: '../src/Controller/'
        tags: ['controller.service_arguments']

     
    League\CommonMark\Environment\Environment:
        public: true
        arguments:
            -
                external_link:
                    -
                        internal_hosts: [ "localhost" ]
                        open_in_new_window: true
                        html_class: "external-link"
                        nofollow: ""
                        noopener: "external"
                        noreferrer: "external"


    League\CommonMark\Extension\Table\TableExtension:
        tags:
            - { name: twig.markdown.league_extension }

    League\CommonMark\Extension\Autolink\AutolinkExtension:
        tags:
            - { name: twig.markdown.league_extension }

    League\CommonMark\Extension\Strikethrough\StrikethroughExtension:
        tags:
            - { name: twig.markdown.league_extension }

    App\Service\League\CommonMark\ExternalLinksExtension:
        arguments: ... ##arguments here also doesnt work with the native extension
        tags:
            - { name: twig.markdown.league_extension }

the twig docs, mentions that the extensions must go with the tag twig.markdown.league_extension

2

There are 2 best solutions below

0
On

According to this discussion and the PR that followed, since Twig 3.3.5, the Twig/Extra-Bundle contains a factory that simply relies on declaring the desired extensions in services.yaml, with the specific tag twig.markdown.league_extension:

# services.yaml
services:
    League\CommonMark\Extension\Table\ExternalLinksExtension:
        tags: [ 'twig.markdown.league_extension' ]

This worked for me, with TableExtension and my twig/extra-bundle v3.3.8.

0
On

I couldnt find a way to use the configuration in symfony either. lmeyer mentioned a workaround where a custom factory class twig.markdown.league_common_mark_converter_factory is created in which you can customize the config (and also inlcude settings of the extensions): https://github.com/twigphp/Twig/issues/3725#issuecomment-1367948462