Say we have a Symfony bundle that contains a service having a dependency defined by an interface (Strategy pattern). I also have several implementations of the interface (ie. strategies) defined in the same bundle. Each strategy may also have various further dependencies that my require further configuration. So my services.yml in the bundle may look like that:
services:
my_service:
class: MyBundle\Services\MyService
arguments: ['#my_strategy']
my_strategy_service_1:
class: MyBundle\Services\MyStrategyService1
my_strategy_service_2:
class: MyBundle\Services\MyStrategyService2
argument: [#my_memcache]
And I can choose the implementation in the config.yml in the application.
services:
my_strategy:
alias: my_strategy_service_2
my_memcache:
class: Memcached
calls:
- [ addServer, [%memcache_host%, %memcache_port%] ]
Let's stick to the example and let's change the implementation. If I write in the config.yml in the application:
services:
my_strategy:
alias: my_strategy_service_1
it still, unfortunately requires me to define "my_memcache" service, even if it's not used this time, but only defined in the bundle. How can I deal with the situation? Can I enforce checking the dependencies for "my_strategy_service_2" only if it's really used?
Usually I implement a "register" between context and strategy use:
Define all possibile strategies (services) and I tag them
Define a register were I have injected all possibile strategies (services) by fetching them through tags (with a compiler pass)
Retrieve the strategy through the register (either by a configuration parameter injected or by passing it when calling the function to lookup for correct strategy)
Use the strategy
Hope it's clear
Related
I want to pass the EntityManager instance into the constructor of my controller, using this code:
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Doctrine\ORM\EntityManager;
class UserController extends Controller
{
public function __construct( EntityManager $entityManager )
{
// do some stuff with the entityManager
}
}
I do the constructor injection by putting the parameters into the service.yml file:
parameters:
# parameter_name: value
services:
# service_name:
# class: AppBundle\Directory\ClassName
# arguments: ["#another_service_name", "plain_value", "%parameter_name%"]
app.user_controller:
class: AppBundle\Controller\UserController
arguments: ['#doctrine.orm.entity_manager']
the service.yml is included in the config.yml and when I run
php bin/console debug:container app.user_controller
I get:
Information for Service "app.user_controller"
=============================================
------------------ -------------------------------------
Option Value
------------------ -------------------------------------
Service ID app.user_controller
Class AppBundle\Controller\UserController
Tags -
Public yes
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired no
Autowiring Types -
------------------ -------------------------------------
However, calling a route which is mapped to my controller, I get:
FatalThrowableError in UserController.php line 17: Type error:
Argument 1 passed to
AppBundle\Controller\UserController::__construct() must be an instance
of Doctrine\ORM\EntityManager, none given, called in
/home/michel/Documents/Terminfinder/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php
on line 202
I cant figure out, why the EntityManager is not getting injected?
When using the base classController.php the Container is usually auto-wired by the framework in theControllerResolver.
Basically you are trying to mix up how things actually work.
To solve your problem you basically have two solutions:
Do no try to inject the dependency but fetch it directly from the Container from within your action/method.
public function listUsers(Request $request)
{
$em = $this->container->get('doctrine.orm.entity_manager');
}
Create a controller manually but not extend the Controller base class; and set ip up as a service
To go a bit further on this point, some people will advise to do not use the default Controller provided by Symfony.
While I totally understand their point of view, I'm slightly more moderated on the subject.
The idea behind injecting only the required dependencies is to avoid and force people to have thin controller, which is a good thing.
However, with a little of auto-determination, using the existing shortcut is much simpler.
A Controller / Action is nothing more but the glue between your Views and your Domain/Models.
Prevent yourself from doing too much in your Controller using the ContainerAware facility.
A Controller can thrown away without generate business changes in your system.
Since 2017 and Symfony 3.3+, there is native support for controllers as services.
You can keep your controller the way it is, since you're using constructor injection correctly.
Just modify your services.yml:
# app/config/services.yml
services:
_defaults:
autowire: true
AppBundle\:
resouces: ../../src/AppBundle
It will:
load all controllers and repositories as services
autowire contructor dependencies (in your case EntityManager)
Step further: repositories as services
Ther were many question on SO regarding Doctrine + repository + service + controller, so I've put down one general answer to a post. Definitelly check if you prefer constructor injection and services over static and service locators.
Did you use following pattern to call the controller AppBundle:Default:index? if yes that should be the problem. If you want to use controller as a service you have to use the pattern: app.controller_id:indexAction which uses the id of the service to load the controller.
Otherwise it will try to create an instance of the class without using the service container.
For more information see the symfony documentation about this topic https://symfony.com/doc/current/controller/service.html
The entity manager is available in a controller without needing to inject it. All it takes is:
$em = $this->getDoctrine()->getManager();
I need to know how to override the trans method in symfony3.
In symfony2 project we used to override the parameter for the translator class
parameters:
translator.class: Acme\HelloBundle\Translation\Translator
We started using symfony3 and try to apply the same approach but unfortunately the parameter was removed and the class path is written directly into the service.
You can override an existing definition (e.g. when applying the Decorator pattern). This kind of decoration is supported by the Dependency Injection Container of Symfony as described in the doc. As example:
services:
app.mailer:
class: AppBundle\Mailer
# this replaces the old app.mailer definition with the new one, the
# old definition is lost
app.mailer:
class: AppBundle\DecoratingMailer
and:
services:
# ...
app.decorating_mailer:
class: AppBundle\DecoratingMailer
decorates: app.mailer
arguments: ['#app.decorating_mailer.inner']
public: false
Hope this help
What is the meaning of public and tags at the declaration of extension Twig?
http://symfony.com/doc/current/cookbook/templating/twig_extension.html#register-an-extension-as-a-service
# app/config/services.yml
services:
app.twig_extension:
class: AppBundle\Twig\AppExtension
public: false
tags:
- { name: twig.extension }
What is public and tags? What I can I use it?
In the Symfony Docs Marking Services as Public / Private:
What makes private services special is that, if they are only injected
once, they are converted from services to inlined instantiations (e.g.
new PrivateThing()). This increases the container's performance.
Now that the service is private, you should not fetch the service
directly from the container:
Basically private services are only available for injection into other services. You wouldn't be able to fetch the service in your controller action for instance.
$container->get('app.twig_extension'); // would fail
Additional information:
Tagging is just used for grouping and manipulating the same kind of services before compilation of the service container. That's it, nothing special. Here are very useful posts:
Symfony2 service container: how to make your service use tags (this post has been included to the official documentation given below)
DependencyInjection component
For example, if you create your own class (by extending the base Twig_Extension class), declare it as a service and tag it as twig.extension and clear the caches (with warming up), Twig compiler pass class (TwigEnvironmentPass class) automatically registers it and recognize it like its own extensions, which you can use in your twig template files..
I have an application that uses Symfony components.
Everything went great until I hit a wall when I was needed to add Symfony's request object to dependency injection container.
Here's my config:
parameters:
config.project: %project_cfg%
config.module: %module_cfg%
config.mysql: %mysql_cfg%
config.couch: %couch_cfg%
request: %http_request%
services:
request:
class: Symfony\Component\HttpFoundation\Request
calls:
- [createFromGlobals]
db:
class: App\Core\AlarisDb
arguments: ["%mysql_cfg%"]
func:
class: App\Core\AlarisFunctions
calls:
- [setTree, ["#tree"]]
tree:
class: App\Core\AlarisTree
Everything works perfect, except that request is not created properly, because in PHP it should be called like this:
$request = Request::createFromGlobals();
Whereas YAML config calls it as object's method. Is there a way to tell it to call it as a static method?
I might be wrong but I think this needs a factory call rather than a "calls" call, like..
request:
class: Symfony\Component\HttpFoundation\Request
factory_class: Symfony\Component\HttpFoundation\Request
factory_method: createFromGlobals
For more on factories see the docs.
I think that "Synthetic Services" is what you are looking for:
Synthetic services are services that are injected into the container
instead of being created by the container.
More: http://symfony.com/doc/current/components/dependency_injection/advanced.html#synthetic-services
I have a already working Twig extension in my Symfony2 app:
namespace Company\MyBundle\Service;
class MyExtension extends \Twig_Extension
{
// ...
}
I now want to create a Twig function, which itselfs takes some data and renders a partial template. But my question is: how do I get a new templating instance in my twig extension service?
Here is my current config:
services:
twig.extension.my_extensions:
class: Company\MyBundle\Service\TwigExtension
tags:
- { name: twig.extension }
If I now add arguments: [#templating] to the config, I get an (understandable) circular reference exception.
It seems one of the recommended simple ways is to inject the container directly and retrieve the templating engine from there. As you've seen, injecting in the templating engine directly causes a circular reference exception.
So, inject in #service_container and you should be good. This seems to be the approach taken by bundles such as the FOSFacebookBundle as well.