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%" ]
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 have a few logic classes implemented as services. They do some similar stuff, so I wanted to setup a hierarchy:
namespace \Company\Bundle\Service;
abstract class ParentLogic {
protected $user;
public function __construct($security_token_storage) {
$this->user = $security_token_storage->getToken()->getUser();
}
}
class ChildLogic extends ParentLogic {
}
Here is how I have the services setup
services:
parentlogic:
abstract: true
class: Company\Bundle\Service\ParentLogic
arguments: [#security.token_storage]
childlogic:
class: Company\Bundle\Service\ChildLogic
parent: parentlogic
however when I try to use the childlogic service in a controller
namespace Company\Bundle\Controller;
class TestController {
static public function getLogicService() {
return $this->get('childlogic');
}
}
I get an error saying the argument to the constructor is missing:
Warning: Missing argument 1 for Company\Bundle\Service\parentlogic::__construct(), called in /file/path/to/company/app/cache/dev/classes.php on line 2220 and defined
Is this possible? If so what am I doing wrong, or better how is it done correctly?
What Cerad said in the comment is probably true. Constructor arguments are not used on parent services. We have a similar setup with controllers where we have an AbstractController as a parent service and concrete controllers that inherit from AbstractController. However in our service definition we add any dependencies to the parent using setter-calls:
services:
abstract_controller:
class: Bundle\Controller\AbstractController
abstract: true
calls:
- [setSomething, ['#something']]
- [setAnotherThing, ['#anotherThing']]
concrete_controller:
class: Bundle\Controller\ConcreteController
parent: abstract_controller
Works like a charm.
I reckon if you wish to use constructor arguments instead you should specify them explicitly in your child service definitions as well since on creation of the child service the parent constructor will be called.
i searh and read a lot of same question but ever i got the same error :/
I create a service:
parameters:
tbackend.report.class: T\BackendBundle\Entity\Report
services:
tbackend.entity.report:
class: %tbackend.report.class%
arguments: ["%kernel.root_dir%"]
And i has this in T\BackendBundle\Entity\Report:
public function __construct($rootDir){
$this->rootDir = $rootDir;
}
When i try to create new Report(); i receive this msg:
Warning: Missing argument 1 for T\BackendBundle\Entity\Report::__construct(), called in /var/www/test/t_study/src/T/BackendBundle/Entity/ReportRepository.php on line 62 and defined
Considerations: i know the services.yml is called, i has more services in this file and all work ok (Loginhandlers, etc), i only add one more (tbackend.entity.report)
What is wrong with that? :( I dont know if need more for know about the problem. I follow symfony2 service container guide
http://symfony.com/doc/master/book/service_container.html
Basically I try not to use DIR in the Entity when moving files
Ty
When instantiating a class, you use normal PHP. Symfony isn't some magic that hooks into the instantiating process of PHP to automatically inject things in the constructor.
If you want to get a service, you either have to inject the service in the class you need it or you have the containe rin the class (for instance, in the controller) and retrieve the service from the container.
$report = $this->container->get('tbackend.entity.report');
or: (which is a much better practice in all cases except from controllers)
class WhereINeedTheReport
{
private $report;
public function __construct(Report $report)
{
$this->report = $report;
}
}
services:
# ...
where_i_need_the_report:
class: ...
arguments: ["#tbackend.entity.report"]
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 regular class in my Symfony2 project:
class RangeColumn extends Column{
//...
}
Now inside this class is a render function, in which I'd like to use Twig or the Translation Service of Symfony2 to render a specific template. How do I access this services in a proper way?
Code example:
<?php
class MyRegularClass
{
private $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
public function myFunction()
{
$this->translator->trans('sentence_to_translate');
}
}
And if you want your class to become a service:
In your services.yml file located in your bundle,
parameters:
my_regular_class.class: Vendor\MyBundle\Classes\MyRegularClass
services:
mybundle.classes.my_regular_class:
class: %my_regular_class.class%
arguments: [#translator]
For more details, see the chapter about the Symfony2 Service Container
Use dependency injection. It's a really simple concept.
You should simply pass (inject) needed services to your class.
If dependencies are obligatory pass them in a constructor. If they're optional use setters.
You might go further and delegate construction of your class to the dependency injection container (make a service out of it).
Services are no different from your "regular" class. It's just that their construction is delegated to the container.