Symfony Service Expressions: Do things you thought Impossible
Written by weaverryan, and Leannapelham
Did you know you can do this with Symfony's service container?
# app/config/services.yml
services:
app_user_manager:
class: AppBundle\Service\UserManager
arguments:
- "@=service('doctrine').getRepository('AppBundle:User')"
# ... other arguments
The @=
means that you're using Symfony's Expression Language,
which let's you mix dynamic logic into your normally-static service definitions.
Normally, if you want to inject a repository, you need to register it as a service first, using a factory. And while that's fine (and probably better if you're injecting the factory a lot), using the expression language is well, kinda cool.
In the compiled container, it beautifully generates with exactly the code you would've used directly::
// app/cache/appDevDebugProjectContainer.php
protected function getAppUserManagerService()
{
return $this->services['app_user_manager'] = new AppBundle\Service\UserManager(
$this->get('doctrine')->getRepository('AppBundle:User')
);
}
And from an object-oriented, dependency-injection perspective, that feels good, and makes up for the slightly-wonky service expression syntax. The expression language isn't new, but I think a lot of people don't really know its power.
Passing Database Configuration as a Service Argument
How about reading configuration from the database and passing those as arguments? Normally, you'd need create a service that reads the configuration and inject the whole thing in:
services:
# some service that can read configuration values from the database
app_configuration_reader:
class: AppBundle\Config\ConfigurationReader
arguments: ["@doctrine.orm_entity_manager"]
# some service that helps email things
app_mailer:
class: AppBundle\Service\Mailer:
arguments:
- "@mailer"
- "@app_configuration_reader"
Suppose our app_mailer
service needs some database configuration value
called email_from_username
, which it uses as the from
address when
sending emails. To accomplish this, you'd usually need inject the entire
app_configuration_reader
service (the service you create to read config
values). But with expressions, you can inject only what you need:
services:
# some service that can read configuration values from the database
app_configuration_reader:
class: AppBundle\Config\ConfigurationReader
arguments: ["@doctrine.orm_entity_manager"]
# some service that helps email things
app_mailer:
class: AppBundle\Service\Mailer:
arguments:
- "@mailer"
- "@=service('app_configuration_reader').get('email_from_username')"
And in addition to the service()
function you also have a parameter
function, and all the normal syntax (including if
statement logic) from
the Expression Language. See Using the Expression Language for a few
more details about using it with services.
Now, go do something cool with this :).
9 Comments
stupid symfony with its stupid yaml... why making life harder by limiting configuration with yml??? what's so bad in plain php arrays???
Hey ivorobioff ,
I'm sorry that you upset. But what do you mean about "limiting configuration with yml"? If we're talking about plain PHP arrays - there's no limitation, you can do the same in YAML, but YAML is read clearer because you don't need to worry about opening and closing brackets, semi-colons at the end, arrow operators or reserved words like 'array()'. I don't think YAML makes our life harder, maybe just in some odd cases, but there's always a trade-off. Anyway, it IS possible to use XML or even plain PHP files instead of YAML for your configuration, and you can find these alternative configuration examples in Symfony docs.
Cheers!
The Symfony documentation doesn't go into great detail about how to use Expression Language as an argument to a service. This article was helpful, but it could perhaps go further into the topic and explain it in more detail.
Hey Tom!
Sure :). What would you like to know - what parts are still not so clear?
Cheers!
When I stumbled across this article, I had been trying to find a way to conditionally pass an argument into a service, depending on the current environment. I knew I could have used "%kernel.environment%" as the argument and then tested it inside the service class, but the test wouldn't have been relevant to the class.
Helpfully for me, you mention that using `@=` invokes Symfony's Expression Language; from pouring over the Symfony docs, I'd had the impression that you could only use `@=parameter()`, `@=service()`, or `@=container`. But I found that you could write something (essentially useless) like `@=true ? 'yes' : 'no'`. That led me to a better understanding of this concept and I ended up doing something like `@=parameter('kernel.environment') === 'prod' ? true : false`.
So your article really helped me, thanks. But still I just thought it could cover this topic a bit more than the actual Symfony docs do, and give a few more examples of what is possible using each of the methods provided.
Thanks for sharing Tom - this makes great sense, and I bet your comment (at the very least) will help some other people - the expression language indeed is more flexible than I'm really showing here!
Interesting! That's Powerful.