Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Tagging Services (and having Fun!)

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $6.00

We need to somehow tell Twig about our fun new Twig extension. To do that, first, register it as a service. The name doesn't matter, so how about app.markdown_extension. Set the class, but skip arguments: we don't have any yet, so this is optional:

... lines 1 - 5
services:
... lines 7 - 10
app.markdown_extension:
class: AppBundle\Twig\MarkdownExtension
... lines 13 - 15

Now, this service is a bit different: it's not something that we intend to use directly in our controller, like app.markdown_transformer. Instead, we simply want Twig to know about our service. We somehow need to raise our hand and say:

Oh, oh oh! This service is special - this service is a Twig Extension!

We do that by adding a tag. The syntax is weird, so stay with me: Add tags:, then under that a dash and a set of curly-braces. Inside, set name to twig.extension:

... lines 1 - 5
services:
... lines 7 - 10
app.markdown_extension:
class: AppBundle\Twig\MarkdownExtension
tags:
- { name: twig.extension }

And that's it.

Real quick - make sure it works. Refresh! Boom! We see a pretty awesome-looking upper-case string.

What are these Tags???

Tags are the way to hook your services into different parts of the core system. When Symfony creates the twig service, it looks for all services in the container that are tagged with twig.extension. It then configures these as extensions on twig.

Google for "Symfony dependency injection tags": there's an awesome reference section on Symfony.com called The Dependency Injection Tags. It lists every tag that can be used to hook into core Symfony. And if you're tagging a service, well, you're probably doing something really cool.

For example, if you want to register an event listener and actually hook into Symfony's boot process, you create a service and then tag it with kernel.event_listener. You won't memorize all of these: you just need to understand their purpose. Someday, you'll read some docs or watch a tutorial here that will tell you to tag a service. I want you to understand what that tag is actually doing.

Finish the Extension

Let's finish our extension: we need to parse this through markdown. We find ourselves in a familiar position: we're inside a service and need access to some other service: MarkdownTransformer. Dependency injection!

Add public function __construct() with a MarkdownTransformer argument. I'll hold option+enter and select "Initialize fields" as a shortcut. Again, this just added the property for me and assigned it in __construct():

... lines 1 - 6
class MarkdownExtension extends \Twig_Extension
{
private $markdownTransformer;
public function __construct(MarkdownTransformer $markdownTransformer)
{
$this->markdownTransformer = $markdownTransformer;
}
... lines 15 - 31
}

Go Deeper!

Watch our PhpStorm Course to learn about these great shortcuts.

In parseMarkdown(), return $this->markdownTransformer->parse() and pass it $str:

... lines 1 - 6
class MarkdownExtension extends \Twig_Extension
{
... lines 9 - 22
public function parseMarkdown($str)
{
return $this->markdownTransformer->parse($str);
}
... lines 27 - 31
}

The last step is to update our service in services.yml. Add arguments: ['@app.markdown_transformer']:

... lines 1 - 5
services:
... lines 7 - 10
app.markdown_extension:
... lines 12 - 14
arguments: ['@app.markdown_transformer']

Refresh! And it's working. Now, let me show you a shortcut.

Leave a comment!

25
Login or Register to join the conversation
Tom Avatar

I'm getting a really strange error:

Cannot autowire service "AppBundle\Service\MarkdownTransformer": argument "$cache" of method "__construct()" references interface "Doctrine\Common\Cache\Cache" but no such service exists. You should maybe alias this interface to one of these existing services: "annotations.filesystem_cache", "annotations.cache", "doctrine_cache.providers.doctrine.orm.default_metadata_cache", "doctrine_cache.providers.doctrine.orm.default_result_cache", "doctrine_cache.providers.doctrine.orm.default_query_cache", "doctrine_cache.providers.my_markdown_cache".

I've tried adding this 'Knp\Bundle\MarkdownBundle\MarkdownParserInterface: '@markdown.parser'' into services.yaml but it doesn't work.

Please help.

Reply
MolloKhan Avatar MolloKhan | SFCASTS | Tom | posted 4 years ago | edited

Hey Tom

You have to define that argument of the MarkdownTransformer service in your "services.yml" file, the service name you should use is '@doctrine_cache.providers.my_markdown_cache'

Cheers!

Reply
Alexandre P. Avatar
Alexandre P. Avatar Alexandre P. | MolloKhan | posted 4 years ago

I have same error but Diego's answer doesn't solve my problem...

services.yml : https://pastebin.com/WKttLXd3

How can I solve this problem ? Thank you

Reply
Alexandre P. Avatar

I modified my services.yml by removing everything that doesn't appear in the tutorial. It works....

But why does it work?

Reply

Hey Alexandre P.

Wow it's cool that you solved issue! It works because you disabled autowiring feature. It's enabled by default in your symfony version, and you will learn about it in next chapter!

Cheers!

Reply
Peter-K Avatar
Peter-K Avatar Peter-K | posted 5 years ago

Hi all,
I dont understand why I have always difficult/different scenarios.

I have a SoftwareSetting entity/table.I store version number in database and other values.

Now in my twig base template in a footer i want to just show version number. I could hardcode but then there is no point to have it in DB,or pass it to every template but what I am looking for is a global variable.

This SoftwareSetting entity/table has also other values like isModuleAvailable.

Now if isModuleAvailable is true then I show a module name in navbar otherwise it is disabled.

Again I could pass this value to every template but I have 50 routes where I would have to pass this. Save would be for version.

Admin has a functionality to change any of these settings.

I was reading about this: https://twig.symfony.com/do...

Then other user said stay away from global variables.

Thing is that I need these 2 variables on every route and I am too lazy to attach/pass it from controller to twig template.

Reply
Peter-K Avatar
Peter-K Avatar Peter-K | Peter-K | posted 5 years ago | edited

ok I dont know if this is a good practice but I am using globals here is how I am doing it so check it out. Might help you.

// twig extension


namespace AppBundle\Twig;
use Doctrine\ORM\EntityManager;

class SoftwareExtension extends \Twig_Extension
{
    protected $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function getGlobals()
    {
        return [
            'version' => $this->getVersion()->getValue(),
        ];
    }

    public function getVersion()
    {
        return $this->em->getRepository('AppBundle:Software')->findOneBy([
            'lookupValue' => 'software_version'
        ]);
    }
}

// register service in services.yml


    app.software_settings:
        class: AppBundle\Twig\SoftwareExtension
        arguments: ["@doctrine.orm.entity_manager"]

// software entity


namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="software")
 */
class Software
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     */
    private $lookupValue;

    /**
     * @ORM\Column(type="text")
     */
    private $description;

    /**
     * @ORM\Column(type="string")
     */
    private $value;

and then in my base.html.twig I can call just {{ version }} and this variable is accessible in all your templates and you do not have to pass it to twig from controller.

Hope that helps someone

Reply

Hey Peter K.!

Yea, I know this exact situation, and you're 100% correct to think that "passing the variables into Twig from a controller" is not the right path - that would be totally crazy :). Globals are a fine way to handle this - your templating section is one of the only places where using globals just makes sense. However, I solve this problem (which I have quite commonly) in a slightly different way: by adding custom functions or filters to my Twig extension. It's basically the same thing - just a matter of taste. So, I would have something like`
{{ get_version() }}`
in my templates. I like the functions because they're more flexible (arguments when necessary) and it's more clear where they come from (when I see a global variable, I think "is a controller passing that?").

Cheers!

Reply
Peter-K Avatar

Thanks for the feedback you are right I will change it to functions.

Off topic how do you format code here in comments? I have pasted it directly from phpStorm to keep format and you can see how ugly it looks above.

Reply

Hey Peter K.

You have to wrap your code between <'pre'><'code'><'/code'><'/pre'> (Remove single quotes)

Have a nice day :)

1 Reply
Default user avatar
Default user avatar Eni Shima | posted 5 years ago

Hi all . I have a problem when adding twig extension . The project give me some error as below :

(3/3) FileLoaderLoadException
The file "/var/www/html/aqua_note/app/config/services.yml" does not contain valid YAML in /var/www/html/aqua_note/app/config/services.yml (which is being imported from "/var/www/html/aqua_note/app/config/config.yml").
in FileLoader.php (line 179)
at FileLoader->doImport('/var/www/html/aqua_note/app/config/services.yml', null, false, '/var/www/html/aqua_note/app/config/config.yml')
in FileLoader.php (line 101)
at FileLoader->import('services.yml', null, false, '/var/www/html/aqua_note/app/config/config.yml')
in YamlFileLoader.php (line 184)

Please does anyone help me

Reply

Hey Eni Shima

Look's like your services.yml file has a syntax error, could you show it here? so I can help you debugging

Cheers!

Reply
Default user avatar
Default user avatar Eni Shima | MolloKhan | posted 5 years ago | edited

Hi MolloKhan .Yes here is my services.yml . I added public: true in app.markdown_extension: but the problem still exist

# Learn more about services, parameters and containers at
# https://symfony.com/doc/cur...
parameters:
#parameter_name: value

services:
# default configuration for services in *this* file
_defaults:
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: false

# makes classes in src/AppBundle available to be used as services
# this creates a service per class whose id is the fully-qualified class name
AppBundle\:
resource: '../../src/AppBundle/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/AppBundle/{Entity,Repository,Tests}'

# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']

# add more services, or override services that need manual wiring
# AppBundle\Service\ExampleService:
# arguments:
# $someArgument: 'some_value'

app.markdown_transformer:
class: AppBundle\Service\MarkdownTransformer
arguments: ['@markdown.parser', '@doctrine_cache.providers.my_markdown_cache']
public: true

app.markdown_extension:
class: AppBundle\Twig\MarkdownExtension
tags:
- { name: twig.extension }
arguments: ['@app.markdown_transformer']
public: true

Reply

Could you upload it to https://pastebin.com/ or any other platform, please? It's hard to see the problem and it lost the identation.

As a first guess, I think removing the first line "parameters:" might fix it

Cheers!

Reply
Default user avatar

I have removed the "parameter" but it haven change . Here is the file services.yaml :https://pastebin.com/QW05QRF5 .

Reply

This is strange, your services.yml file look's good (or I just can't detect the error), try to not importing it and see if the error goes away, just to be 100% sure this file is the problem, if that's not the case, check that all classes exists, and their namespaces are correct.

Reply
Default user avatar

I have done all that the tutorial have said but when we try to add the twig extension , it gives error . The error is as below : PHP Fatal error: Uncaught Symfony\Component\DependencyInjection\Exception\AutowiringFailedException: Cannot autowire service "AppBundle\Service\MarkdownTransformer": argument "$markdownParser" of method "__construct()" references interface "Knp\Bundle\MarkdownBundle\MarkdownParserInterface" but no such service exists. You should maybe alias this interface to one of these existing services: "markdown.parser.min", "markdown.parser.light", "markdown.parser.medium", "markdown.parser.max", "markdown.parser.flavored". in /var/www/html/aqua_note/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php:297
Stack trace:
#0 /var/www/html/aqua_note/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php(223): Symfony\Component\DependencyInjection\Compiler\AutowirePass->autowireMethod(Object(ReflectionMethod), Array)
#1 /var/www/html/aqua_note/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php(146): Symfony\Component\DependencyInj in /var/www/html/aqua_note/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php on line 297

.
I think the problem is on the MarkdownExtension.php class , but i haven't guess yet what it is . anyway this is are my 2 classes :https://pastebin.com/7vRnaKnp and https://pastebin.com/pWAhHGnY . Please can you help me

Reply

I believe you are running on Symfony 3.3, right? The dependency injection service has changed a bit in that version.
What you need to do is create an alias for MarkdownParserInterface, like this:


// services.yml
services: 
    Knp\Bundle\MarkdownBundle\MarkdownParserInterface: '@markdown.parser'

And now, Symfony will be able to autowire any service that depends on MarkdownParserInterface

if you want to learn more about how all this works, you want watch our fabulous tutorial of Symfony 3.3 https://knpuniversity.com/screencast/symfony-3.3

Cheers!

2 Reply
Default user avatar
Default user avatar Rich Wilx | posted 5 years ago | edited

uh oh... disaster struck and I need help figuring out what's gone wrong please.

So, I'm working through the tutorial and then all of a sudden I get "SQL STAT[HY000] [2002] No connection could be made beacuase the target machine actively refused it.

The parameters.yml file looks like this:


parameters:
    database_host: localhost
    database_port: null
    database_name: aqua_note
    database_user: root
    database_password: null
    secret: ae45f37c009b8329a818831d99130

I've not touched the Genus.php file (there are no errors that I can see)

I checked in the console:
λ mysql -u root aqua_note
ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost' (10061)

I checked in the C:\wamp64\bin\mysql\mysql5.7.19\data\aqua_note folder and sure enough there is a nice collection of files. So the data is there.

Now, I'm stuck.... I must have missed something obvious along the way but I cannot figure out what.

Advice please?

Reply
Default user avatar

<noob>
In future, start the database (navigate to the myql folder and run mysqld
</noob>

Reply

Hey Rich,

I'm glad you got it working quicker than I replied to you, nice work! I see you're on Windows and using Wamp server. Looks like you have to start MySQL server manually, because on Linux servers it's started in the background after installation. And thank you for sharing it with our other Windows users!

Cheers!

Reply
Mike P. Avatar
Mike P. Avatar Mike P. | posted 5 years ago

I have SF 3.3.6 and it even works without registering it inside services.yml. Is autoloading twig extensions a new feature?
If I try to register it via services.yml it works as well. So isn't it necessary anymore to include the twig extension in services.yml?

Reply

Hey Mike,

Yes, it's a new feature. Actually, Twig 2.x allow to autoregister Twig extensions, so probably you're on the new Twig 2.x :) If so - you don't need to register it manually now. Btw, we have written a blog post how to avoid such kind of problems when you're on Symfony 3.3: http://knpuniversity.com/bl... - I think it could be interesting for you.

Cheers!

1 Reply
Mike P. Avatar

Thanks Victor for your blazing fast answer! You're a tutorial god!

Reply

Hahaha, thanks :)

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}
userVoice