I'm looking for a concrete example of implementing DI with Symfony controllers... https://symfony.com/doc/3.4/controller/service.html hasn't been much help.
Config
search_service:
class: Acme\MyBundle\Services\SearchService
search_controller:
class: Acme\MyBundle\Controller\SearchController
arguments: ['#search_service']
Controller
// Acme/MyBundle/Controllers/SearchController.php
class SearchController extends Controller
{
public function __construct(SearchService $searchService)
{
$this->searchService = $searchService;
}
}
Gives me:
Type error: Argument 1 passed to Acme\\MyBundle\\Controller\\SearchController::__construct() must be an instance of Acme\\MyBundle\\Services\\SearchService, none given
Any help appreciated :)
Your controller does not work, because you don't have a namespace. So at the start, add correct namespace, but it will still be problematic to inject parameters with manual wiring, because you extend base controller.
Better just use autowiring, with that you won't need to define your dependencies from services.yml and it will work with controllers easily.
Here's example
# app/config/services.yml
services:
# default configuration for services in *this* file
_defaults:
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: false
# makes classes in src/AppBundle available to be used as services
# this creates a service per class whose id is the fully-qualified class name
AppBundle\:
resource: '../../src/AppBundle/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/AppBundle/{Entity,Repository}'
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
tags: ['controller.service_arguments']
ps. Also, I recommend to don't extend base controller at all, because in that way you get too much dependencies you actually don't need. Better to get twig, services and everything you need by wiring them.
I had to make the follow changes to get it working for my 3.4 installation:
Change resource relative path
Acme\MyBundle\Controller\:
resource: '../../Controller'
tags: ['controller.service_arguments']
Change 'name' of the controller to the full classname
Acme\MyBundle\Controller\SearchController:
class: Acme\MyBundle\Controller\SearchController
arguments: ['#search_service']
Related
Following the official documentation (here), I decided to split my services.yaml configuration file into several files for readability.
I then created a file config/services/doctrine_listeners.yaml containing this single service definition :
services:
_defaults:
autowire: true
autoconfigure: true
App\Listeners\BookListener:
tags:
- { name: doctrine.event_listener, event: prePersist }
And I import it like this in my config/services.yaml :
imports:
- { resource: 'services/doctrine_listeners.yaml' }
When proceeding like that, the listener is never instanciated. If I instead declare it directly inside the config/services.yaml it works.
This looks like a bug to me, did I miss something ?
The problem might be that the service is overwritten by the default service file. In it there is a PSR-4 service discovery for all classes in src:
# 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}'
This will also register your listeners and it looks like this will overwrite the previous configuration from inside your file.
I would recommend adding Listeners to the exclude section in services.yaml and then move service discovery for them to your file instead:
services:
_defaults:
autowire: true
autoconfigure: true
App\Listeners\:
resource: '../../src/Listeners/*'
App\Listeners\BookListener:
tags:
- { name: doctrine.event_listener, event: prePersist }
The most probable reason would be redundancy in adding your services, which doesn't allows to load the services. It would be great if you share you config/services.yml content here.
After upgrading to Symfony 3.4 from 2.8, I am attempting to get rid of warnings about using services from the container. One hang up is my controller all extend from an abstract controller which needs access to the monolog logger. I've decided to use autwiring for my controllers and have added a constructor in the base controller which has a LoggerInterface $logger as the only argument. In attempt to configure this once, I've added the $logger variable with a reference to the logger service under the bind section of services.yml.
However, I keep getting the error:
Unused binding "$logger" in service "security.authentication.failure_handler.secured_area.form_login"
I believe this error is supposed to appear only if no services have a constructor argument with that variable name. Now I know that my controllers all have this in the abstract class, as well as being part of some of my other services, so this seems wrong. How can I get rid of this error?
Here is what my services.yml looks like:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
bind:
$logger: "#logger"
$env: "%sys_env%"
_instanceof:
\Twig_Extension:
tags: ['twig.extension']
AppBundle\:
resource: '../../../../../../src/AppBundle/{Controller,Service,Twig}/*'
exclude: '../../../../../../src/AppBundle/Service/Exception/*'
# SECURITY ########################################################################
security.authentication.failure_handler:
class: AppBundle\Security\AuthenticationFailureHandler
autowire: false
arguments: ["#http_kernel", "#security.http_utils", {}, "#app.service.security", "#doctrine.orm.entity_manager", "#logger"]
tags:
- { name: 'monolog.logger', channel: 'security' }
UPDATE 1:
I noticed that in security.authentication.failure_handler I have a reference to one of my services: app.service.security. I forgot to declare that below, so I added the following to services.yml:
app.service.security:
class: AppBundle\Service\SecurityService
That got rid of the logger error, however now I'm seeing an error about the $env string variable:
Unused binding "$env" in service "security.authentication.failure_handler.secured_area.form_login".
I'm concerned that the error message is not the real error, and this is a red herring. The bind options seem a little flaky. Any advice appreciated...
UPDATE 2:
I've decided to get rid of the bind and instanceof config and am setting up the values manually, but now this is the error: Cannot autowire service "app.service.security": argument "$sysInfoService" of method "AppBundle\Service\SecurityService::__construct()" references class "AppBundle\Service\SystemInfoService" but no such service exists. You should maybe alias this class to the existing "app.service.system_info" service.
What's weird is that I believe I'm doing exactly what the error is suggesting to do; I've added aliases for the supposedly autowired service:
app.service.system_info:
class: AppBundle\Service\SystemInfoService
app.service.security:
class: AppBundle\Service\SecurityService
I do have some services which I manually declare with autowired: false in order to manually set the arguments. That should be ok, I think; you should be able to have autowired and manual wiring coexisting in the service container, right?
I have the following services.yaml file: # This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
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\Service\Processor\TestClauses:
public: true
App\Service\Processor\Factory:
arguments:
- 'App\Service\Processor\TestClauses'
-
- 'MilkProductionProcessor'
ie. I'm happy to autowire everything, but this one service that needs an array as input.
This does not seem to work unless I make all services public. My understanding of the documentation https://symfony.com/doc/4.1/service_container.html#public-versus-private-services is that I just have to make services public that I want to inject manually
The "root service" is injected to a command. When I run this command:
1) With the services.yaml as is I get
[WARNING] Some commands could not be registered:
In Factory.php line 15:
Argument 1 passed to App\Service\Processor\Factory::__construct() must impl
ement interface App\Service\Processor\TestClausesInterface, string given, c
alled in /home/jochen/projects/freshagenda/symfony/var/cache/dev/Container7
4x3zkp/getProcessFilesCommandService.php on line 16
There are no commands defined in the "app" namespace.
Did you mean this?
doctrine:mapping
2) When I make services:_defaults:public true
it moves forward
App\ServiceThatNeedsArrayAsInput:
arguments:
$array: ...
Everything else can be autowired and autoconfigured. In constructor of the ServiceThatNeedsArrayAsInput you should receive that $array from arguments - difference from earlier versions is that you explicitly say to which variable you wish to bind argument defined in services.yml
// ServiceThatNeedsArrayAsInput.php
public function __construct(array $array) {} // Only array from arguments
public function __construct(array $array, AutowiredService $service) {} // Just add it here and DI will autoinject it, no need to change services.yml
Your example
I'm not entirely sure what are you trying to do here but if you want to inject autowired services, there's no need to define it explicitly in services.yml - check the above example - you only need to add Classname to constructor.
App\Service\Processor\Factory:
arguments:
$array: ['MilkProductionProcessor']
I'm using symfony version 3.4.8.
The configuration works in symfony 3.3, but in symfony 3.4 I get:
Cannot autowire service "ProblemBundle\EntityManager\ProblemManager": argument "$paginator" of metho
d "__construct()" references interface "Knp\Component\Pager\PaginatorInterface" but no such service
exists. Did you create a class that implements this interface?
ProblemManager.php
public function (PaginatorInterface $page){}
app/config/services.yml
# Learn more about services, parameters and containers at
# https://symfony.com/doc/current/service_container.html
parameters:
#parameter_name: value
services:
# default configuration for services in *this* file
_defaults:
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: true
# makes classes in src/AppBundle available to be used as services
# this creates a service per class whose id is the fully-qualified class name
# add more services, or override services that need manual wiring
# AppBundle\Service\ExampleService:
# arguments:
# $someArgument: 'some_value'
UserBundle\Controller\:
resource: '../../src/UserBundle/Controller'
public: true
autowire: true
tags: ['controller.service_arguments']
UserBundle\EntityManager\UserManager:
autowire: true
ProblemBundle\EntityManager\ProblemManager:
autowire: true
public: true
How can I configure autowiring for all (including third party bundle)?
Instruct the Dependency Injection component which implementation would you like to use, for instance:
Knp\Component\Pager\PaginatorInterface:
public: true
alias: Your\Solid\Implementation
Should you want to use another implementation, it's a matter of changing this single configuration line.
You can still inject Non-Autowireable arguments by service tags, in your case it would be
ProblemBundle\EntityManager\ProblemManager:
autowire: true
public: true
arguments:
$page: "#knp_paginator.injectable"
see: https://github.com/KnpLabs/KnpPaginatorBundle
I have a simple Listener example like the one of
https://symfony.com/doc/current/event_dispatcher.html#content_wrapper
The example is 1:1
services.yml is the same
services:
# default configuration for services in *this* file
_defaults:
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: false
# makes classes in src/AppBundle available to be used as services
# this creates a service per class whose id is the fully-qualified class name
# AppBundle\:
# resource: '../../src/AppBundle/*'
# # you can exclude directories or files
# # but if a service is unused, it's removed anyway
# exclude: '../../src/AppBundle/{Entity,Repository,Tests}'
AppBundle\:
resource: '../../src/AppBundle/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/AppBundle/{Entity,Repository}'
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
# AppBundle\Controller\:
# resource: '../../src/AppBundle/Controller'
# public: true
# tags: ['controller.service_arguments']
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
But the Listener is listed as "Not Called Listeners"
What am I doing wrong ?
A class can be auto-tagged if it implements a given interface - this is the case for the EventSubscriber example. If a listener does not have a hint to the container builder (an interface, or a class that it extends), then there's no way to know it should be tagged as a listener, or for which events.
You will likely want to explicitly tag the listener in your configuration, as the example does.
# app/config/services.yml
services:
AppBundle\EventListener\ExceptionListener:
tags:
- { name: kernel.event_listener, event: kernel.exception }
The subscriber can be deduced, as it has an explicit mapping between an event - getSubscribedEvents(), like KernelEvents::EXCEPTION and the class method to be run.
As it says on The Symfony 3.3 DI Container Changes Explained
This does not work for all tags. Many tags have required attributes, like event listeners, where you also need to specify the event name and method in your tag. Autoconfigure works only for tags without any required tag attributes