Symfony 4 services ignored when declared inside a resource file - symfony

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.

Related

Symfony Ignored by DI in load configurations

At first my configuration file looked like this:
# config/services.yaml
services:
_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.
App\:
resource: '../src/*'
exclude: '../src/{DBAL/*,Migrations,Form,Tests,Kernel.php,Entity/BaseEntity.php,Repository/BaseRepository.php}'
App\EventDispatcher\Event\Api\EntityEvent:
class: App\EventDispatcher\Event\Api\EntityEvent
public: true
I transferred some configurations to another file:
# config/services.yaml
imports:
- { resource: 'services/new_config_file.yaml' } # <------ NEW
services:
_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.
App\:
resource: '../src/*'
exclude: '../src/{DBAL/*,Migrations,Form,Tests,Kernel.php,Entity/BaseEntity.php,Repository/BaseRepository.php}'
# THIS BLOCK IS MOVED
# App\EventDispatcher\Event\Api\EntityEvent:
# class: App\EventDispatcher\Event\Api\EntityEvent
# public: true
In new config file:
# config/services/new_config_file.yaml
services:
_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.
App\:
resource: '../../src/*'
exclude: '../../src/{DBAL/*,Migrations,Form,Tests,Kernel.php,Entity/BaseEntity.php,Repository/BaseRepository.php}'
App\EventDispatcher\Event\Api\EntityEvent:
class: App\EventDispatcher\Event\Api\EntityEvent
public: true
As a result, I get an error like:
{
"status": "error",
"message": "The controller for URI \"/api/broker/\" is not callable. The \"App\\EventDispatcher\\Event\\Api\\EntityEvent\" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead."
}
I understand that only parameters can be imported? In the source code, it checks that there should be a services key
I have found a solution. Namely, after a deep study of the source symfony-config и symfony-yaml
Their import algorithm is as follows:
First, all files are imported (the import construct is fulfilled)
Then the final configuration file itself is parsed.
Where am I wrong?
I was mistaken in the line
App\:
resource: '../../src/*'
exclude: '../../src/{DBAL/*,Migrations,Form,Tests,Kernel.php,Entity/BaseEntity.php,Repository/BaseRepository.php}'
It had to be imported in the very first file (in my case, in the config / services / new_config_file.yaml file). I have this line imported in each file
Because of this, the following occurred:
The services file was imported first (config/services/new_config_file.yaml).
In my case, this is this:
App\EventDispatcher\Event\Api\EntityEvent:
class: App\EventDispatcher\Event\Api\EntityEvent
public: true
Then in the file config/services.yaml
This was done:
App\:
resource: '../src/*'
exclude: '../src/{DBAL/*,Migrations,Form,Tests,Kernel.php,Entity/BaseEntity.php,Repository/BaseRepository.php}'
And she frayed the previous service import that I needed, because resource: '../src/*' It re-imports this service only with public: true Here we described that by default:
services:
_defaults:
...
public: false
Be careful!

Symfony 4.1 - manually wire single service requires _defaults: public: true

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']

Using DI with Symfony Controller

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']

Symfony _defaults in bundles

I'm a newbie in Symfony and have a few questions about the dependency injection, particularly about Symfony 3.3
Can the new _defaults block be declared for every single bundle separately or it's global and defined in the hosting app?
Can I use the new things like autowiring in my reusable bundles or I have to declare all the bundle's services separately?
It's per file declaration. You can also override it at single service definitions. E.g.
# app/config/services.yml
services:
_defaults:
autowire: true
App\SomeService:
autowire: false
In short: yes.
I feel you're asking how to combine all this features together and what is the best practise for it. Saying that, I'll extend my answer with multi-bundle example of service definitions.
Application with 2 Bundles would look like this
imports:
- { resource: "../../src/FirstBundle/config/services.yml" }
- { resource: "../../src/SecondBundle/config/services.yml" }
services:
_defaults:
autowire: true
App\SomeService:
autowire: false
With first bundle:
# src/FirstBundle/config/services.yml
services:
_defaults:
autowire: true
App\FirstBundle\:
resource: ../..
And second bundle:
# src/SecondBundle/config/services.yml
services:
_defaults:
autowire: true
App\SecondBundle\:
resource: ../..
One Extra Tip
Also, you can improve first file to just single line import thanks to glob patterns.
I use it in practise like this:
imports:
- { resource: "../../src/**/config/services.yml" }
services:
_defaults:
autowire: true
App\SomeService:
autowire: false
You can read more about Symfony 3.3 dependency-injection features in this post with before/after config examples.

Symfony 3.3.9 Listener not calles

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

Resources