Right now I can call services from controllers in the following way:
$this -> get('myservice') -> doSomething();
But I want to do this according to the factory method pattern, like this:
ServiceFactory :: createMyservice() -> doSomething();
The problem is that I can't pass parameters with the second method, they're null.
services.yml:
parameters:
services:
myservice:
class: AppBundle\Service\Myservice
factory: [Appbundle\Factory\ServiceFactory, createMyservice]
arguments:
- "%kernel.root_dir%"
ServiceFactory.php:
<?php
namespace AppBundle\Factory;
use AppBundle\Service\MyService;
class ServiceFactory
{
public static function createMyservice($rootDir)
{
var_dump($rootDir); // NULL
$object = new Myservice($rootDir);
return $object;
}
}
What's wrong?
Related
What do I need to in order to make this work?
interface BaseServiceInterface {
public function getRecords();
}
class BaseService implements BaseServiceInterface{
public function getRecords(){
return "bla";
}
}
class SomeOtherService{
private $baseService;
public function __construct(BaseServiceInterface $baseService){
$this->baseService = $baseService;
}
}
my service.yml looks like this:
base_service:
class: AppBundle\Service\BaseService
autowire: true
When I try to run this I get:
Cannot autowire argument 1 for AppBundle\Service\SomeOtherService because the type-hinted class does not exist (Class BaseServiceInterface does not exist).
autowire does not work directly with interface.
you need to create the service alias to make it works.
services:
AppBundle\Service\BaseServiceInterface: '#AppBundle\Service\BaseService'
reference: https://symfony.com/doc/current/service_container/autowiring.html#working-with-interfaces
I am trying to create a service in Symfony2 to automatically pass Doctrine\ORM\EntityManager to __construct to avoid having to pass it each time I instantiate the class, i.e.
// use this
$TestClass= new TestClass;
// instead of this
$entityManager = $this->getDoctrine()->getEntityManager();
$TestClass= new TestClass($entityManager);
I created a class EntityManagerUser, tried to register it as a service and TestClass extends that.
services.yml is included, as another service works, and I've double-checked by adding (then removing) a syntax error.
I read the docs, this, this and this and I've ended up with the code below, which doesn't pass #doctrine.orm.entity_manager. However, the controller_listener service does receive #templating.
I've cleared cache via the console and manually deleted app/cache but I still see this error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Test\TestBundle\ServiceUser\EntityManagerUser::__construct() must be an instance of Test\TestBundle\ServiceUser\Doctrine\ORM\EntityManager, none given, called in D:\Documents\www\Test\live\src\Test\TestBundle\Controller\MyController.php on line 84 and defined in D:\Documents\www\Test\live\src\Test\TestBundle\ServiceUser\EntityManagerUser.php line 14
services.yml
services:
# this one doesn't throw an error and passes #templating to __construct
test.eventlistener.before_controller_listener:
class: Test\TestBundle\Eventlistener\BeforeControllerListener
arguments: [ #templating ]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
# the following one doesn't pass #doctrine.orm.entity_manager
test.service_user.entity_manager_user:
class: Test\TestBundle\ServiceUser\EntityManagerUser
arguments: [ #doctrine.orm.entity_manager ]
src/Test/TestBundle/ServiceUser/EntityManagerUser.php
namespace Test\TestBundle\ServiceUser;
use Doctrine\ORM\EntityManager;
class EntityManagerUser{
protected $entityManager;
public function __construct(EntityManager $entityManager){
$this->entityManager = $entityManager;
// N.B. it's not possible to do it this way:
// $this->entityManager = new EntityManager;
}
// also tried public function __construct($entityManager){
// and public function __construct(Doctrine\ORM\EntityManager $entityManager){
}
src/Test/TestBundle/Classes/TestClass.php
namespace Test\TestBundle\Classes\TestClass;
use Test\TestBundle\ServiceUser\EntityManagerUser;
class TestClass extends EntityManagerUser{
/* currently no functions */
}
In my controller, line 84
$test= new TestClass;
// I tested that this throws the same error, it does // $test= new EntityManagerUser;
What have I missed?
Services only get their arguments if they are called through the service constructor:
$this->get('test.service_user.entity_manager_user');
Declaring the class as a service doenst make a difference if you create a new class and extend the original.
What you could do is also declare this new class as a service and still have it extend the base class.
test.classes.test_class:
class: Test\TestBundle\Classes\TestClass\TestClass
arguments: [ #doctrine.orm.entity_manager ]
then you dont have to define the constructor in the extended class because it is the parent.
then get the class by doing:
$testClass = $this->get('test.classes.test_class');
//will be instanceof Test\TestBundle\Classes\TestClass\TestClass
I worked out how to do what I wanted, which was not define entityManager each time I was required in an instanciated class.
It made sense to rename EntityManagerUser to ContainerListener, a static class, and inject #service_container into it through services, so it can then also return other classes.
namespace Test\TestBundle\EventListener;
class ContainerListener{
static $container;
// knock out the parent::onKernelRequest function that we don't want
public function onKernelRequest($event){
return;
}
public function __construct($container){
self::$container = $container;
}
static function twig(){
return self::$container->get('twig');
}
static function entityManager(){
return self::$container->get('doctrine')->getEntityManager();
}
static function entityManagerConnection(){
$entityManager = self::$container->get('doctrine')->getEntityManager();
return $entityManager->getConnection();
}
}
services.yml
services:
test.event_listener.container_listener:
class: Test\TestBundle\EventListener\ContainerListener
arguments: [ #service_container ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
BaseClass.php gets entityManager
namespace Test\TestBundle\Class;
use Test\TestBundle\EventListener\ContainerListener;
class BaseClass{
public function __construct(){
$this->entityManager = ContainerListener::entityManager();
}
}
TestClass.php extends BaseClass as do others
class TestClass extends BaseClass(){
function someFunction(){
// etc etc
// $this->entityManager exists with no construct and without passing it
$stmt = $this->entityManager->getConnection()->prepare( $some_sql );
// etc etc
}
}
Somewhere in DefaultController.php
# nope # $entityManager = $this->getDoctrine()->getEntityManager();
# nope # $TestClass= new TestClass($entityManager);
$TestClass= new TestClass; # win!
i decided to create a service in symfony2.4 that can access me to the container in all of scopes in my project.
so i created a model:
namespace Nevec\RaxcidoBundle\Model;
class Base {
public static $container;
public function __construct() {
self::$container = $container;
}
}
and set this model as a service in resources/config/services.yml
parameters:
nevec_raxcido.core: Nevec\RaxcidoBundle\Model\Base
services:
nevec_raxcido.example:
class: %nevec_raxcido.core%
arguments: [#service_container]
now, as you know i should call this service in controllers like this:
$this->get("nevec_raxcido.example");
but i want to auto load this service, without call above command in controllers
the question is how can i automatically load a service after kernel boot in symfony2?
i found the solution, it seems that we should use listeners into services.yml like:
parameters:
nevec_raxcido.core: Nevec\RaxcidoBundle\Model\Base
services:
nevec_raxcido.example:
class: %nevec_raxcido.core%
arguments: [#service_container]
tags:
- {name: kernel.event_listener, event: kernel.request, method: onKernelRequest}
and this model:
<?php
namespace Nevec\RaxcidoBundle\Model;
class Base{
public static $container;
public function __construct($container) {
self::$container = $container;
}
public function onKernelRequest($event){
return;
}
}
so you can access to the container in all scopes of your application with this:
$container = Model\Base::$container;
Please don't use the service container. Use dependency injection.
class Service {
private $service;
public function __construct(SomeServiceInterface $someService){
$this -> service = $service;
}
}
and the yml:
services:
service1:
class: SOMENAMESPACE\Service
arguments: [#service2]
service2:
class: SOMENAMESPACE\SomeService
Now you can access SOMENAMESPACE\SomeService in SOMENAMESPACE\Service. And you can get the service in a controller via:
$this -> get('service1');
Let's say doctrine is your concrete service you want to inject.
Do this:
class Service {
private $em;
protected function getEm(ObjectManager $em){
$this -> em = $em;
}
}
services:
service1:
class: SOMENAMESPACE\Service
arguments: [#doctrine.orm.entity_manager]
Second part of the question: How to autoload? Pretty easy. Build a "BaseController" and extend it.
class BaseAppController extends Controller{
private $service;
protected function getService(){
if (!($this -> service instanceof SomeServiceInterface)) $this -> service = $this -> get('service');
return $this -> service;
}
}
access via $this -> getService()
I need to get doctrine working inside my helper, im trying to use like i normaly do in a controller:
$giftRepository = $this->getDoctrine( )->getRepository( 'DonePunctisBundle:Gift' );
But this gave me:
FATAL ERROR: CALL TO UNDEFINED METHOD
DONE\PUNCTISBUNDLE\HELPER\UTILITYHELPER::GETDOCTRINE() IN
/VAR/WWW/VHOSTS/PUNCTIS.COM/HTTPDOCS/SRC/DONE/PUNCTISBUNDLE/HELPER/UTILITYHELPER.PH
What Im missing here?
EDIT:
services file
services:
templating.helper.utility:
class: Done\PunctisBundle\Helper\UtilityHelper
arguments: [#service_container]
tags:
- { name: templating.helper, alias: utility }
Firts lines of helper file
<?php
namespace Done\PunctisBundle\Helper;
use Symfony\Component\Templating\Helper\Helper;
use Symfony\Component\Templating\EngineInterface;
class UtilityHelper extends Helper {
/*
* Dependency injection
*/
private $container;
public function __construct( $container )
{
$this->container = $container;
}
The problem here is that your Helper class is not container-aware; that is, it has no idea about all the services Symfony has loaded (monolog, twig, ...and doctrine).
You fix this by passing "doctrine" to it. This is called Dependency Injection, and is one of the core things that makes Symfony awesome. Here's how it works:
First, give your Helper class a place for the Doctrine service to live, and require it in the Helper's constructor:
class UtilityHelper
{
private $doctrine;
public function __construct($doctrine)
{
$this->doctrine = $doctrine;
}
public function doSomething()
{
// Do something here
}
}
Then, you use services.yml to define how Symfony should construct that instance of Helper:
services:
helper:
class: Done\PunctisBundle\Helper\UtilityHelper
arguments: [#doctrine]
In this case, #doctrine is a placeholder that means "insert the Doctrine service here".
So now, in your Controller, or in anything else that is container-aware, you can get access to Doctrine through the Helper class like this:
class SomeController()
{
public function someAction()
{
$this->get("helper")->doctrine->getRepository(...);
}
}
EDIT
After looking at your edit, it appears that you're injecting the entire service container into the Helper class. That's not a best practice -- you should only inject what you need. However, you can still do it:
services.yml
services:
helper:
class: Done\PunctisBundle\Helper\UtilityHelper
arguments: [#service_container]
UtilityHelper.php
class UtilityHelper
{
private $container;
public function __construct($container)
{
$this->container = $container;
}
public function doSomething()
{
// This won't work, because UtilityHelper doesn't have a getDoctrine() method:
// $this->getDoctrine()->getRepository(...)
// Instead, think about what you have access to...
$container = $this->container;
// Now, you need to get Doctrine
// This won't work... getDoctrine() is a shortcut method, available only in a Controller
// $container->getDoctrine()->getRepository(...)
$container->get("doctrine")->getRepository(...)
}
}
I've included a few comments there that highlight some common pitfalls. Hope this helps.
In Helper, Services etc you cannot use it like in actions.
You need to pass it like argument to youre Helper via service description in conf file(services.yml or *.xml).
Example:
services:
%service_name%:
class: %path_to_youre_helper_class%
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: %name% }
And dont forget catch it in __construct of youre Helper.
Example:
use Doctrine\ORM\EntityManager;
....
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
You can use it like:
public function myMethod()
{
$repo = $this->em->getRepository('DonePunctisBundle:Gift');
}
Using symfony2. I have a listener class that is attempting to call a method from a different class (a controller) like so:
$authenticate = new AuthenticationController();
$authenticate->isTokenValid($token);
And the controller isTokenValid method:
public function isTokenValid($token) {
$conn = $this->get('database_connection');
Is throwing the error
Fatal error: Call to a member function get() on a non-object in /home/content/24/9254124/html/newsite/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php on line 246
If i load the controller method the proper way (using routing in the url) it works fine.
Symfony2 uses Dependency Injection pattern, you have to inject container that holds all services (like database connection):
$authenticate = new AuthenticationController();
$authenticate->setContainer($this->container);
$authenticate->isTokenValid($token);
Of course I assume here that your listener class is ContainerAware
[+] To make your listener ContainerAware, pass #service_container to it (example form services.yml)
my.listener:
class: ACME\MyBundle\ListenerController
arguments: [ #service_container ]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
kernel.event_listener:
event: kernel.controller
and then in constructor of you listener class:
public function __construct($container = null){
$this->container = $container;
}
I'm adding another answer because what #dev-null-dweller suggests is a bad practice: in almost every case you better to inject only the services you need — not the whole container:
use Doctrine\DBAL\Connection;
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
my_listener:
arguments: [ #database_connection ]