I'm creating a control panel application that has a base bundle with some basic functionality and specific bundles for advanced and specific functionality.
for example the base bundle handles user authentication and holds all the template assets and other bundles add functionalities to config different parts of the operating system.
I need to be able to add menu links in the layout of the base bundle to each of the other bundles. and I prefer to do it in each bundles configuration so I can mix and match features for different clients.
I read all about Compiler Passes, Extensions and dependency injection with no luck. is there correct of doing it ?
If you are using Twig this should do the trick...
{% render "DifferentBundle:ControllerName:functionalityName" with {'argument_name': 3} %}
You should have a functionalityNameAction method in your DifferentBundle controller for this to work.
Take a look at the Creating and using Templates - Embedding Controllers section in the doc.
Hope it helps.
Just in case anyone has a similar problem, here is how I achieved this:
I created a service in my BaseBundle that implements the __get , __set, __isset and __unset magic methods and has an extra append method. it stores variables in a static variable inside the class.
I then added Listeners to all my bundles :
namespace Mbs\OtherBundle\Listener;
use Mbs\BaseBundle\Services\GlobalVars;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class ControllerListener
{
protected $_global_vars;
public function __construct(GlobalVars $global_vars)
{
$this->_global_vars = $global_vars;
}
public function onKernelController(FilterControllerEvent $event)
{
$this->_global_vars->append('bundles', 'mbs.other');
}
}
This is my services.yml for one of the bundles. GlobalVars is the class I mentioned earlier.
services:
mbs.base_controller_listener:
class: Mbs\OtherBundle\Listener\ControllerListener
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
arguments: [ #mbs.global_vars ]
Related
In my application, I use Symfony 4. I want Symfony to search for controllers in two directories: A and B. I found something like this:
controllers:
resource: '../src/DirectoryA/Controller/'
type: annotation
, but it only works for one directory. How can I have Symfony to search for controllers in two directories?
Regards
In your config/services.yaml
App\DirectoryA\Controller\: # assuming you have namespace like that
resource: '../src/DirectoryA/Controller'
tags: ['controller.service_arguments']
App\DirectoryB\Controller\: # assuming you have namespace like that
resource: '../src/DirectoryB/Controller'
tags: ['controller.service_arguments']
This will add next directory for service arguments. Thats answers your questions based In directory, what you have posted is routing file, in there would be similiar
controllers_a:
resource: '../src/DirectoryA/Controller/'
type: annotation
controllers_b:
resource: '../src/DirectoryB/Controller/'
type: annotation
The accepted answer is of course completely correct.
However, once you move from having one controller directory to multiple directories, updating your services.yaml file can be a bit of a pain. Even having to have directories specifically for controllers can be limiting.
Here is an alternate approach which allows creating controllers wherever you want and automatically tagging them.
Start with an empty controller interface for tagging.
interface ControllerInterface {}
Now have all your controllers implement the interface
class Controller1 implements ControllerInterface { ...
class Controller2 implements ControllerInterface { ...
And then adjust the kernel to automatically tag all your controller interface classes with the controller tag.
# src/Kernel.php
protected function build(ContainerBuilder $container)
{
$container->registerForAutoconfiguration(ControllerInterface::class)
->addTag('controller.service_arguments')
;
}
And presto. You can create your controllers wherever you want with nothing in services.yaml.
Update:
If you would like to avoid editing Kernel.php then you can use the _instanceof functionality in your services.yaml file.
#config/services.yaml
services:
_instanceof:
App\Contract\ControllerInterface:
tags: ['controller.service_arguments']
Another Update: As long as your controller extends Symfony's AbstractController then no additional tagging is needed. You can even delete the default controller lines in the default services.yaml file if you want.
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();
A lot of my services rely on a PDO connection to an external database (for reasons specific to my application, it made sense to use this strategy over using Doctrine). To start a PDO connection, each services needs a data source name, username and password. This leaves my services.yml containing much of the same arguments for each service:
#AppBundle\Resources\config\services.yml
# ...
QueryDataBuilderHelper:
class: AppBundle\Services\QueryDataBuilderHelper
arguments: [ "%database_host%", "%database_user%", "%database_password%" ]
ZipCodeClass:
class: AppBundle\Services\ZipCodeClass
arguments: [ "%database_host%", "%database_user%", "%database_password%" ]
# ...
Is it possible to define the connection somewhere and reference it in all services without passing parameters to each one?
The Symfony cookbook recommends using parent services and extending the parent services. When I try to use the subclass, the subclass doesn't pull the arguments of the superclass:
#AppBundle\Resources\config\services.yml
DBConnectionHelper:
class: AppBundle\Services\DBConnectionHelper
arguments: [ "%database_host%", "%database_user%", "%database_password%" ]
DBSubClass:
class: AppBundle\Services\DBSubClass
parent: DBConnectionHelper
arguments: [ "%unrelated_parameter%" ]
//AppBundle\Services\SuperClassService.php
namespace AppBundle\Services;
class DBConnectionHelper
{
public function __construct($dsn, $user, $password){
$this->DB_connection = new \PDO($dsn, $user, $password);
}
}
//AppBundle\Services\DBSubClass.php
namespace AppBundle\Services;
class DBSubClass extends DBConnectionHelper
{
public function __construct($unrelated_param){
//Calling parent::__construct() here will require the parameters again.
//Which is what I am trying to avoid...
// Outputs a notice that DB_Connection isn't set.
var_dump($this->DB_Connection());
$this->unrelated_param = $unrelated_param;
}
}
I've used the constructor here instead of setters because these dependancies are not optional. The Symfony docs suggest that setters should only be used when dependancies are optional.
What you need to use here is called an Abstract Service. Luckily for you, Symfony is providing a way to achieve this. The options you're looking for are abstract and parent. You can read the entire chapter here.
I will try to briefly explain the example from documentation, so that you can grasp the idea.
# ...
services:
# ...
mail_manager:
abstract: true
calls:
- [setMailer, ["#my_mailer"]]
- [setEmailFormatter, ["#my_email_formatter"]]
newsletter_manager:
class: "NewsletterManager"
parent: mail_manager
greeting_card_manager:
class: "GreetingCardManager"
parent: mail_manager
Basically what you need to do is this:
Create an abstract class where you will define all common properties/methods.
Then configure these common method calls in your service file accordingly and add the option abstract. Since your service is an abstract one it means that it cannot be instantiated, so class option here is omitted.
After that create a service like you always do, and don't forget to extend the abstract class. Then register that service in your service.yml file and add the parent option to it, by setting the name of your abstract service.
Repeat the step above as many times as you wish for each child service and you should be good to go.
If you have any questions, leave a comment. Hope this can help you out.
Rather than extend your DBConnectionHelper class, you could inject it as a dependancy via your constructor.
//AppBundle\Services\DBSubClass.php
namespace AppBundle\Services;
class DBSubClass
{
private $dbConnectionHelper;
public function __construct($dbConnectionHelper, $unrelated_param)
{
$this->dbConnectionHelper = $dbConnectionHelper;
$this->unrelated_param = $unrelated_param;
}
}
You can then get your connection using $this->dbConnectionHelper->DB_Connection().
You would also need to configure your service as:
DBSubClass:
class: AppBundle\Services\DBSubClass
arguments: [ "#DBConnectionHelper", "%unrelated_parameter%" ]
In a Symfony2 project, when you use a Controller, you can access Doctrine by calling getDoctrine() on this, i.e.:
$this->getDoctrine();
In this way, I can access the repository of such a Doctrine Entity.
Suppose to have a generic PHP class in a Symfony2 project. How can I retrieve Doctrine ?
I suppose that there is such a service to get it, but I don't know which one.
You can register this class as a service and inject whatever other services into it. Suppose you have GenericClass.php as follows:
class GenericClass
{
public function __construct()
{
// some cool stuff
}
}
You can register it as service (in your bundle's Resources/config/service.yml|xml usually) and inject Doctrine's entity manager into it:
services:
my_mailer:
class: Path/To/GenericClass
arguments: [doctrine.orm.entity_manager]
And it'll try to inject entity manager to (by default) constructor of GenericClass. So you just have to add argument for it:
public function __construct($entityManager)
{
// do something awesome with entity manager
}
If you are not sure what services are available in your application's DI container, you can find out by using command line tool: php app/console container:debug and it'll list all available services along with their aliases and classes.
After checking the symfony2 docs i figured out how to pass your service
in a custom method to break the default behavior.
Rewrite your configs like this:
services:
my_mailer:
class: Path/To/GenericClass
calls:
- [anotherMethodName, [doctrine.orm.entity_manager]]
So, the Service is now available in your other method.
public function anotherMethodName($entityManager)
{
// your magic
}
The Answer from Ondrej is absolutely correct, I just wanted to add this piece of the puzzle to this thread.
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.