Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Configuring Specific (Named) Arguments

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

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

We saw earlier that sometimes you only need to pass one argument... and the rest can be autowired. For MarkdownTransformer, we only need to configure the second argument:

... lines 1 - 5
services:
... lines 7 - 30
AppBundle\Service\MarkdownTransformer:
arguments: ['', '@doctrine_cache.providers.my_markdown_cache']
... lines 33 - 45

To allow the first to be continue to be autowired, we set it to an empty string. That works... but it's just weird. So, in Symfony 3.3, there's a better way.

In MarkdownTransformer, the argument names are $markdownParser and $cache:

... lines 1 - 7
class MarkdownTransformer
{
... lines 10 - 12
public function __construct(MarkdownParserInterface $markdownParser, Cache $cache)
{
... lines 15 - 16
}
... lines 18 - 33
}

The $cache argument is the one we need to configure. Back in services.yml, copy the cache service id, clear out the arguments, and add $cache: '@doctrine_cache.providers.my_markdown_cache':

... lines 1 - 5
services:
... lines 7 - 30
AppBundle\Service\MarkdownTransformer:
arguments:
$cache: '@doctrine_cache.providers.my_markdown_cache'
... lines 34 - 46

The dollar sign is the important part: it tells Symfony that we're actually configuring an argument by its name. $cache here must match $cache here.

And it works beautifully - refresh now! Everything is happy!

Named Arguments are Validated

But wait! Isn't this dangerous!? I mean, normally, if I were coding in MarkdownTransformer, I could safely rename this variable to $cacheDriver:

... lines 1 - 7
class MarkdownTransformer
{
... lines 10 - 12
public function __construct(MarkdownParserInterface $markdownParser, Cache $cacheDriver)
{
... line 15
$this->cache = $cacheDriver;
}
... lines 18 - 33
}

That change shouldn't make any difference to any code outside of this class.

But suddenly now... it does make a difference! The $cache in services.yml no longer matches the argument name. Isn't this a huge problem!? Actually, no. Yes, you will get an error - you can see it when you refresh. But this error will happen if you try to refresh any page across your entire system. Symfony validates all of your services and configuration. And as soon as it saw $cache in services.yml with no corresponding argument, it screamed.

So yes, changing the argument in your service class will break your app. But the error is unignorable: nothing works until you fix it. Update the configuration to $cacheDriver:

... lines 1 - 5
services:
... lines 7 - 30
AppBundle\Service\MarkdownTransformer:
arguments:
$cacheDriver: '@doctrine_cache.providers.my_markdown_cache'
... lines 34 - 46

And refresh again.

Back working! This is what makes Symfony's autowiring special. If there is any question or problem wiring the arguments to any service, Symfony throws an exception at compile team... meaning, it throws an exception when you try to refresh any page. This means no surprises: it's not possible to have an autowiring error on only one page and not notice it.

Leave a comment!

11
Login or Register to join the conversation
Default user avatar
Default user avatar Bruno Lima | posted 5 years ago | edited

Hi Ryan,

I was wondering if there are any differences using this:


AppBundle\Service\MarkdownTransformer:
    $cacheDriver: '@doctrine_cache.providers.my_markdown_cache'

instead of this:


AppBundle\Service\MarkdownTransformer:
    arguments:
        $cacheDriver: '@doctrine_cache.providers.my_markdown_cache'

since it looks like both works.

1 Reply

Yo Bruno Lima!

Wow, nice find! They're both identical actually! But in all of the 3.3 changes, I didn't even know this was one of them! So yea, use whichever you want. The shorter syntax is pretty cool looking :). The only downside is if you ever need to add more config (like tags), you'll then need to put the arguments under an arguments key. But, that's not too common.

Cheers!

1 Reply
Default user avatar

Great! Thank you.

Reply
Default user avatar
Default user avatar Vivek Sharma | posted 5 years ago | edited

Hi Ryan,

I got stucked somewhere when i was trying to do the same in my own code.
In my Container Class I'm passing two services as u can see,


 public function __construct(TwitterClient $transformerDriver,Rot13Transformer $status).

When i'm trying to pass the arguments inside it it throwing an error:

The service "AppBundle\Services\ServiceContainer" has a dependency on a non-existent service "AppBundle\Services\ServiceContainer".

YML CONFIG:


 AppBundle\Services\ServiceContainer:
    arguments:
        $transformerDriver: '@AppBundle\Services\ServiceContainer'

And when i'M trying to give service name in this format:


AppBundle\Services\ServiceContainer:
       arguments: ['@twitter_events_service', '@string_events_service']

Error: The service "AppBundle\Services\ServiceContainer" has a dependency on a non-existent service "twitter_events_service".

As u told first search the container and then use that value as a argument:-but when i do debug:container i am unable to find the service.

So i went to appDevContainer:- Here they are using something like this:-

return $this->services['AppBundle\Services\ServiceContainer'] = new \AppBundle\Services\ServiceContainer(new \AppBundle\Services\TwitterClient(), new \AppBundle\Test\Rot13Transformer());

But when i get into your project appDevCotainer File :- they are using services in this format:


return $this->services['app.markdown_transformer'] = new \AppBundle\Service\MarkdownTransformer($this->get('markdown.parser'));

And one more thing if i remove all the config from services.yml its working fine. ??

Reply

Hey Vivek Sharma

You have a subtle error, look at the service you are injecting to ServiceContainer


 AppBundle\Services\ServiceContainer:
    arguments:
        $transformerDriver: '@AppBundle\Services\ServiceContainer'

You are saying that $transformerDriver is an instance of ServiceContainer, but `$transformerDriver is a property of ServiceContainer

When you remove everything, I think it is working because you have type-hinted your arguments, so autowire + autoconfigure can do all the work

Have a nice day

Reply

Hi Ryan,

In my services.yml, '@service_container' is marked with a warnng in PhpStorm. The warning is "expect instance of: Symfony\Component\DependancyInjection\Container Provided instance of service id does not match"
Is this warning related to the autowiring? How do you fix it? Thanks.

Reply

Hey Scott,

Hm, what typehint do you have for this argument in your service? I wonder about FQCN of this class. Looks like you misprint namespace, it should be "Symfony\Component\DependencyInjection\Container", or probably better use interface instead "Symfony\Component\DependencyInjection\ContainerInterface", notice "Dependency" not "Dependancy".

Cheers!

Reply

Sorry, <em>"Dependancy"</em> was a typo. Here is the class:


namespace AppBundle\Service;

use Doctrine\ORM\EntityManager;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class Utils
{
  /** @var EntityManager */
  private $em;
  /** @var Container */
  private $container;

  function __construct(EntityManager $em, Container $container)
  {
    $this->em = $em;
    $this->container = $container;
  }

Here is the entry in services.yml:


  AppBundle\Service\Utils:
    arguments: [ '@doctrine.orm.entity_manager', '@service_container' ]
Reply
Victor Avatar Victor | SFCASTS | Skylar | posted 5 years ago | edited

Hey Scott,

Hm, looks correct, so you can just ignore it :) I think it can be a bug in PhpStorm inspection. Well, I did a bit of debugging:
bin/console debug:container | grep service_container

And I see that "service_container" service points to Symfony\Component\DependencyInjection\ContainerInterface, so try to use the interface instead in __construct():


// ...
use Symfony\Component\DependencyInjection\ContainerInterface;
// ...

class Utils
{
  public function __construct(EntityManager $em, ContainerInterface $container)
  {
    // ...
  }
}

Probably it will fix the warning. Actually, better always use interfaces if possible for type-hints.

Cheers!

Reply

Wow, that worked! Thank you very much.
For my education, why do you think it is better to use interfaces instead of the actual class?

Reply

Hey Scott,

Because interfaces gives you more flexibility! If you use an interface for a type-hint - you can pass *any* object which implements this interface. But if you type-hint with a real object - you are allowed to pass only this object, or any object which extends this object. Actually, it's not too important, but you would see the difference if you have a few classes which implements the same interfaces. For example, let's say we have CacheInterface and a few classes which implements this interface like RedisCache and LocalCache. If you type-hint with CacheInterface - you can easily switch your cache provider and even don't need to change the type-hint.

If you wonder more about the difference between Classes, Abstract classes, Interfaces, Traits, etc. and their use cases - take a look at our https://knpuniversity.com/s... course.

Cheers!

1 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.3.0-RC1", // v3.3.0-RC1
        "doctrine/orm": "^2.5", // 2.7.5
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.7
        "symfony/monolog-bundle": "^3.1", // v3.2.0
        "symfony/polyfill-apcu": "^1.0", // v1.23.0
        "sensio/distribution-bundle": "^5.0", // v5.0.25
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.29
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.4
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.4
        "knplabs/knp-markdown-bundle": "^1.4", // 1.7.1
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
        "stof/doctrine-extensions-bundle": "^1.2" // v1.3.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.7
        "symfony/phpunit-bridge": "^3.0", // v3.4.47
        "nelmio/alice": "^2.1", // v2.3.6
        "doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
    }
}
userVoice