I am able to use the new autowire feature of Symfony2 to inject a service into a service. However, I cannot inject a service into a controller. What am I not doing/doing wrong?
This is my services.yml file:
services:
home_controller:
class: AppBundle\Controller\HomeController
autowire: true
This is my ServiceConsumerDemo:
namespace AppBundle\Services;
class ServiceConsumerDemo {
private $serviceDemo;
public function __construct(ServiceDemo $serviceDemo) {
$this->serviceDemo = $serviceDemo;
}
public function getMessage(){
return $this->serviceDemo->helloWorld();
}
}
This is ServiceDemo:
namespace AppBundle\Services;
class ServiceDemo
{
public function helloWorld(){
return "hello, world!";
}
}
This is HomeController (which works):
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class HomeController extends Controller
{
/**
* #Route("/")
*
*/
public function indexAction(Request $request)
{
$message[0] = $this->get('service_consumer_demo')->getMessage();
return new \Symfony\Component\HttpFoundation\JsonResponse($message);
}
}
This is HomeController which does not work
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use AppBundle\Services\ServiceConsumerDemo;
class HomeController extends Controller
{
private $serviceConsumerDemo;
public function __construct(ServiceConsumerDemo $serviceConsumerDemo) {
$this->serviceConsumerDemo = $serviceConsumerDemo;
}
/**
* #Route("/", name="homepage")
*
*/
public function indexAction(Request $request)
{
$message[0] = $this->serviceConsumerDemo->getMessage();
return new \Symfony\Component\HttpFoundation\JsonResponse($message);
}
}
This is the error thrown:
Catchable Fatal Error: Argument 1 passed to
AppBundle\Controller\HomeController::__construct() must be an instance
of AppBundle\Services\ServiceConsumerDemo, none given,
How do I access the Services in the Controller? I understand I need to declare the controller as a service. So I'm mentioning the controller in the services.yml file. Is there anything else that I need to do?
By default, controllers are not considered as services, so you can't use autowiring with them.
However, it's a simple enough fix - add a #Route annotation on the class (i.e. not on an action method in the class) with a service definition.
From the documentation:
The #Route annotation on a controller class can also be used to assign
the controller class to a service so that the controller resolver will
instantiate the controller by fetching it from the DI container
instead of calling new PostController() itself.
For example:
/**
* #Route(service="app_controller")
*/
class AppController extends Controller
{
private $service;
public function __construct(SomeService $service)
{
$this->service = $service;
}
/** #Route("/") */
public function someAction()
{
$this->service->doSomething();
}
}
and in your config:
app_controller:
class: AppBundle\Controller\AppController
autowire: true
Symfony 3.3+ May 2017 UPDATE!
Symfony 3.3 allows native way to approach this.
# app/config/services.yml
services:
_defaults:
autowire: true # all services in this config are now autowired
App\: # no more manual registration of similar groups of services
resource: ../{Controller}
This means App\Controller\SomeController is autowired by default and to be found in app/Controller/SomeController.php file.
You can read:
more about _defaults here
and more about PSR-4 service registration
Thanks to autowiring in Symfony 2.8+ I could create bundle, that adds autowiring even for controllers: Symplify/ControllerAutowire
You can read more about this in the article.
Usage is as simple as:
class YourController
{
public function __construct(SomeDependency $someDependency)
{
// ...
}
}
Nothing more is required.
You do not pass service to your controller in services.yml. Change it to:
services:
service_demo:
class: AppBundle\Services\ServiceDemo
service_consumer_demo:
class: AppBundle\Services\ServiceConsumerDemo
autowire: true
home_controller:
class: AppBundle\Controller\HomeController
autowire: true
arguments:
service: "#service_consumer_demo"
Related
I'm trying to use a parameter that i've set in service.yaml as such :
parameters:
app.path.users_profile_picture: uploads/users/profile-picture
Directly from within a User Entity, And I can't figure out how to do this ?
I'm not sure of what you're trying to do or why, it does look like there may be a better way of achieving what you want.
That being said, it is possible to use a parameter from your services.yaml in an entity, but you can't inject it directly in the entity's constructor. If you're using Doctrine you can inject it by subscribing to the postLoad event, which is dispatched after an entity is constructed by the entityManager.
Let's say you have a User entity with an attribute for the services.yaml param you want tu use:
<?php
namespace App\Entity;
private $serviceArgument;
class User
{
/**
* #return mixed
*/
public function getServiceArgument()
{
return $this->serviceArgument;
}
/**
* #param mixed $serviceArgument
*/
public function setServiceArgument($serviceArgument): void
{
$this->serviceArgument = $serviceArgument;
}
}
You need an EventSubscriber so you can do stuff on the postLoad event:
<?php
namespace App\EventSubscriber;
use App\Entity\User;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class UserSubscriber implements EventSubscriberInterface
{
protected $serviceArgument;
// Inject param from services.yaml
public function __construct($serviceArgument)
{
$this->serviceArgument = $serviceArgument;
}
// Listen to postLoad event
public static function getSubscribedEvents()
{
return array(
Events::postLoad,
);
}
// Set the services.yaml param on the entity
public function postLoad(LifecycleEventArgs $args)
{
$user = $args->getObject();
if ($user instanceof User) {
$user->setServiceArgument($this->serviceArgument);
}
}
}
Then in your services.yaml you need to configure the subscriber and pass the param you want:
# config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
App\EventSubscriber\UserSubscriber:
tags:
- { name: doctrine.event_listener, event: postLoad }
arguments:
$serviceArgument: 'Argument from my services.yaml'
Now you can use your param in your entity (User::getServiceArgument()).
Plea
I have read the "Service Container" entry--and nearly every linked entry--in the Symfony 4 docs. I have tackled this issue on two separate occasions spending 4-6 hours each time. I clearly just don't get it...please help!
Want
I want to use dependency injection to use a service (SessionInterface) in a model (User) but I do not want to use the model constructor as then I would need to inject the dependency each time I hydrate and instantiate the model. I think I should be able to use method call and setter injection to meet this criteria.
Code
services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
Model
namespace App\Model\System\User;
use App\Model\System\Client\Client;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\User\{EquatableInterface, UserInterface};
class User implements EquatableInterface, UserInterface
{
private $session;
public function __construct(array $properties)
{
// stuff
}
public function clientAssumed(): Client
{
return $this->session->get('foo', 'bar');
}
/**
* #required
*
* #param SessionInterface $session
*/
public function setSession(SessionInterface $session)
{
$this->session = $session;
}
}
Controller
namespace App\Controller\Core;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
class CoreBase extends AbstractController
{
/**
* Displays authenticated User landing page.
*
* #Route("/home", name="core_home")
*
* #param Security $security
*
* #return Response
*/
public function landing(Security $security): Response
{
$data = [
'client' => $security->getUser()->clientAssumed(),
];
return $this->render('core/landing.html.twig', $data);
}
}
Service Information
php bin/console debug:container 'App\Model\System\User\User'
.
Information for Service "App\Model\System\User\User"
====================================================
---------------- ----------------------------
Option Value
---------------- ----------------------------
Service ID App\Model\System\User\User
Class App\Model\System\User\User
Tags -
Calls setSession
Public no
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired yes
Autoconfigured yes
---------------- ----------------------------
Result
Too few arguments to function App\Model\System\User\User::setSession(), 0 passed in /srv/http/wwwroot/gw4/src/Controller/Core/CoreBase.php on line 25 and exactly 1 expected
I have a problem with my application after I moved it to docker container. I newbie with Symfony and I cannot understand where the problem is.
After I want to call my UserController index action my browser throws an error:
Controller "App\Controller\UserController" has required constructor arguments and does not exist in the container. Did you forget to define such a service?
Too few arguments to function App\Controller\UserController::__construct(), 0 passed in /projekty/taskit/vendor/symfony/http-kernel/Controller/ControllerResolver.php on line 133 and exactly 1 expected
My services.yaml file:
parameters:
locale: 'en'
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\Entity\UserRepositoryInterface: '#App\DTO\UserAssembler'
UserController.php file
<?php
namespace App\Controller;
use App\Service\UserService;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
/**
* Class UserController
* #package App\Controller
*/
class UserController extends AbstractController
{
/**
* #var UserService
*/
private $userService;
/**
* #param UserService $userService
*/
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
/**
* #Route("/users/", name="users_index")
*/
public function index()
{
$users = $this->userService->findAll();
return $this->render('user/index.html.twig', array('users' => $users));
}
}
And my UserService.php file code
<?php
namespace App\Service;
use App\Entity\UserRepositoryInterface;
use Doctrine\ORM\EntityNotFoundException;
/**
* Class UserService
* #package App\Service
*/
class UserService
{
/**
* #var UserRepositoryInterface
*/
private $userRepository;
/**
* #param UserRepositoryInterface $userRepository
*/
public function __construct(UserRepositoryInterface $userRepository)
{
$this->userRepository = $userRepository;
}
/**
* #return array
* #throws EntityNotFoundException
*/
public function findAll() : array
{
$users = $this->userRepository->findAll();
if (is_null($users)) {
throw new EntityNotFoundException('Table users is empty');
}
return $users;
}
}
It seems to me you are doing a lot of unnecessary things, which are already part of the framework. Like your service, what does it do? You can call your repositories directly from your controller. And in your controller, you are declaring variables and constructing which is also unnecessary. Just use dependency injection in your function in the controller. This little bit of code should work and replace both your controller and your service (which again, you can get rid of).
<?php
namespace App\Controller;
use App\Repository\UserRepository;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
/**
* Class UserController
* #package App\Controller
*/
class UserController extends AbstractController
{
/**
* #Route("/users/", name="users_index")
*/
public function index(UserRepository $userRepository)
{
$users = $this->userRepository->findAll();
return $this->render('user/index.html.twig', array('users' => $users));
}
}
I wanted to set password automatically when one register the form. So I use REGISTRATION_INITIALIZE to trigger the event. Unfortunately it's not working.
Listener:
<?php
namespace Acme\UserBundle\EventListener;
use Doctrine\ORM\EntityManager;
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\FOSUserEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RegistrationListener implements EventSubscriberInterface
{
/**
* #var EntityManager
*/
private $em;
/**
* #param \Doctrine\ORM\EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit',
);
}
public function onRegistrationInit(UserEvent $userEvent)
{
$user = $userEvent->getUser();
$user->setPassword('abcdeffffff');
}
Services:
#src/Acme/UserBundle/Resources/config/services.yml
services:
acme_user.registration:
class: Acme\UserBundle\EventListener\RegistrationListener
arguments:
entityManager: "#doctrine.orm.entity_manager"
tags:
- { name: kernel.event_subscriber}
So the password is not setting and it shows the password should not be blank.
What am I doing wrong? Any help!
EDIT:
The problem was I was defining service at the wrong place.
Instead of src/Acme/UserBundle/Resources/config/services.yml, It should be app/config/services.yml.
I saw src/Acme/UserBundle/Resources/config/services.ymlin http://symfony.com/doc/master/bundles/FOSUserBundle/controller_events.html, But was not working for me!
Maybe I am wrong, but I think your service definition uses features from Symfony 3.3 but you are using Symfony 3.2.8, e.g.
New in version 3.3: The ability to configure an argument by its name ($adminEmail) was added in Symfony 3.3. Previously, you could configure it only by its index (2 in this case) or by using empty quotes for the other arguments.
Try updating your service definition to:
#src/Acme/UserBundle/Resources/config/services.yml
services:
acme_user.registration:
class: Acme\UserBundle\EventListener\RegistrationListener
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: kernel.event_subscriber}
And a remark: as you are actually using a subscriber you should rename the class RegistrationListener to RegistrationSubscriber.
I have a paginator class which should get the limit out of the config.xml and the current page from the controller.
How do I define the limit in config.xml and how can I access it outside the controller?
Regards
Define your paginator class as DIC service and add service_container as a service e.g
//paginator class
//namespace definitions
use Symfony\Component\DependencyInjection\ContainerInterface;
class Paginator{
/**
* #var Symfony\Component\DependencyInjection\ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container){
$this->container = $container;
}
public function yourPaginationMethod(){
$limit = $this->container->getParameter("limit.conf.parameter");
//rest of the method
}
}
And then in you services.yml of your bundle.
#services.yml
paginator_service:
class: FQCN\Of\Your\PaginatorClass
arguments: [#service_container]
And in your controller you can get access of the Paginator in following way.
//in controller method
$paginator = $this->get('paginator_service');
For more info about it you can check Service container section of Symfony Documentation.