Symfony service container not overridding child class - php

I am trying to override a service definition as per https://symfony.com/doc/2.4/components/dependency_injection/parentservices.html#overriding-parent-dependencies
Here are my configs:
//services.yaml
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/{Entity,Migrations,Tests}'
# 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
imports:
- {resource: 'clients.yaml'}
//clients.yaml
services:
App\Api\Client\AbstractClient:
abstract: true
arguments:
- '#serializer'
- '#httplug.client.app'
App\Api\Client\MyClient:
parent: App\Api\Client\AbstractClient
calls:
- [setApiHost, ['%client.api_url%']]
As you can see I have two classes, of which one is an abstract parent.
This appears to be setup correctly (successfully overridden):
Information for Service "App\Api\Client\AbstractClient"
=======================================================
---------------- ------------------------------------------
Option Value
---------------- ------------------------------------------
Service ID App\Api\Client\AbstractClient
Class App\Api\Client\AbstractClient
Tags -
Public yes
Synthetic no
Lazy no
Shared yes
Abstract yes
Autowired no
Autoconfigured no
Arguments Service(serializer)
Service(httplug.client.app.http_methods)
This class says its been autowired & autoconfigured but it has (correctly) inherited the arguments from my AbstractClient, but it has not implemented any setter injection definitions!
Information for Service "App\Api\Client\MyClient"
=========================================================
---------------- ------------------------------------------
Option Value
---------------- ------------------------------------------
Service ID App\Api\Client\MyClient
Class App\Api\Client\MyClient
Tags -
Public no
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired yes
Autoconfigured yes
Arguments Service(serializer)
Service(httplug.client.app.http_methods)
Can someone please guide me as to why my child class is not being correctly overridden?

Related

Symfony5 / Codeception: Service is not available in container

TL;DR
In Codeception test I am trying to $I->grabService(). Service works in controllers and has no custom config, but I get:
Fail Service App\Service\Car is not available in container
Full Story
I have a project with some Services which are basically classes, which do some processing. All the Services are accessible via service container. I am testing each class in functional suite (and some in unit) and everything worked fine till today.
So today I was adding a new Service and of course a test. I did:
root#9c80b567f681:/var/www/html# vendor/bin/codecept g:cest functional Service/Car
Test was created in /var/www/html/tests/functional/Service/CarCest.php
Test looks like this:
<?php
namespace App\Tests\Service;
use App\Service\Car;
use App\Tests\FunctionalTester;
class CarCest
{
public function _before(FunctionalTester $I)
{
$I->grabService(Car::class);
}
public function tryToTest(FunctionalTester $I)
{
}
}
Now I manually in PhpStorm create a new class. Class looks like this:
<?php
namespace App\Service;
class Car
{
}
This is output of my testing:
root#9c80b567f681:/var/www/html# vendor/bin/codecept run tests/functional/Service/CarCest.php
Codeception PHP Testing Framework v4.1.6
Powered by PHPUnit 9.2.6 by Sebastian Bergmann and contributors.
Running with seed:
App\Tests.functional Tests (1) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
✖ CarCest: Try to test (0.00s)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Time: 00:01.616, Memory: 34.00 MB
There was 1 failure:
---------
1) CarCest: Try to test
Test tests/functional/Service/CarCest.php:tryToTest
Step Grab service "App\Service\Car"
Fail Service App\Service\Car is not available in container
Scenario Steps:
1. $I->grabService("App\Service\Car") at tests/functional/Service/CarCest.php:12
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
TL;DR
Fail Service App\Service\Car is not available in container
Now most of other tests I have use the same concept: I get service in _before() and then test it. Everything passes except of any class I add today :) WTF?!?
BTW: If I replace $I->grabService(Car::class); with any other service created before, it works fine.
My services.yaml is the standard, out-of-the-box Symfony version. I always relied simply on the fact everything in src/* is already a service.
# 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:
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.
# 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']
I spent the whole morning installing/reinstalling/restarting PC... I am completely lost and stupid. Anybody has any idea ?
EDIT:
I noticed something very interesting. If I manually add the service to services.yml and set public: true, then I can use it from Codeception. But note, that I don't have to do this for any other services I created before.
TL;DR
Problem seems to be that Symfony removes all unused services upon container compilation. You can see the code here on symfony project git page.
After I noticed, that my service works correctly when it's explicitly set to public, I started digging around that and I stumbled across git issue, where someone had the same problem. Some more digging (and talking to people smarten than me) got me to the link posted on top of this answer.
BOOM! Only took like 4 days...

How to configure Doctrine Entity Manager as a public Symfony service?

I'd like to configure Doctrine Entity Manager as a public Symfony service.
I tried adding the following configuration blocks in services.yml (one at a time):
Doctrine\ORM\EntityManagerInterface:
public: true
# [OUTPUT] Error: Cannot instantiate interface Doctrine\ORM\EntityManagerInterface
Doctrine\ORM\EntityManager:
public: true
# [OUTPUT] Invalid service "Doctrine\ORM\EntityManager": its constructor must be public.
doctrine.orm.default_entity_manager:
public: true
# [OUTPUT] The definition for "doctrine.orm.default_entity_manager" has no class. If you intend to inject this service dynamically at runtime, please mark it as synthetic=true. If this is an abstract definition solely used by child definitions, please add abstract=true, otherwise specify a class to get rid of this error.
Judging by the output error messages, I suspect that the definitions are incomplete, but I didn't find anywhere the correct way to manually configure the Doctrine Entity Manager service.
As per Cerad's comment, I had to add alias: 'doctrine.orm.default_entity_manager'
to my first attempted configuration.
So the correct solution is:
Doctrine\ORM\EntityManagerInterface:
public: true
alias: 'doctrine.orm.default_entity_manager'

logging in symfony 2.3

I am trying to write my own messages to the log in Symfony 2.3, from anywhere, and not just the Controller (which I realize you can just do a "$this->get('logger')".
I've seen that in Symfony 1 you can use sfContext, but that class no longer seems to be a viable choice in 2.3.
Any help is appreciated.
Symfony2 has Service-oriented architecture (http://en.wikipedia.org/wiki/Service-oriented_architecture) and logger is one of service (by default Monolog). In controller you have access to service via $this->get('service_name'). Here is more info about service container: http://symfony.com/doc/current/book/service_container.html#what-is-a-service-container. If you wanna use logger in another service you have to define service and inject logger service. Example:
# section with defined service in your config.yml file (by default in config.yml)
services:
# your service name
my_service:
# your class name
class: Fully\Qualified\Loader\Class\Name
# arguments passed to service constructor. In this case #logger
arguments: ["#logger"]
# tags, info: http://symfony.com/doc/current/components/dependency_injection/tags.html
tags:
- { name: monolog.logger, channel: acme }
Additionally you should familiarize with dependency injection docs: http://symfony.com/doc/current/components/dependency_injection/index.html
I hope that helped. If not, please let me know where exactly you want to use logger.

Symfony 2.1 - Switch Monolog channel in controller

I want to log into a different file than the usual dev.log or prod.log
I know that this can be done with different channels and I used it in several services, but I'm not very clear about switching the Monolog channel in a controller.
In a service you just define the channel via the tags attribute in the service definition, but how can I do this in a controller or even better in a specific action?
I know that a possible solution would be this: Symfony 2 : Log into a specific file
But it seems overkill to define two new services just for logging to a custom file.
The only way to do this is to define your controller as a service and inject a custom logger with a custom channel.
Since the channels are created automatically there is currently no other way, but it's an interesting request and you're not the first, so I created an issue on MonologBundle to allow the definition of channels at the bundle configuration level. That way you could just fetch the proper logger from the controller using $this->get('monolog.logger.mychannel') (which you can already do if the channel exists, but not if you want a custom channel for the controller that nothing else uses).
Update:
As of symfony/monolog-bundle 2.4.0 you can define additional channels as:
monolog:
channels: ["foo", "bar"]
Then you can retrieve it as $this->get('monolog.logger.mychannel')
I know that this is an older post, but I ran into a similar need using symfony/monolog-bundle 2.1.x. I couldn't seem to find exactly what I needed in other threads, so I'm documenting my solution here, which was to create a logger container that used a custom channel.
In config.yml
monolog:
handlers:
user_actions:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%-user-actions.log"
level: info
channels: [user_actions]
In my bundle's services.yml
acme.logger.user_actions:
class: Acme\MyBundle\Monolog\UserActionsLogger
arguments: ['#logger']
tags:
- { name: monolog.logger, channel: user_actions }
In src/Acme/MyBundle/Monolog/UserActionsLogger.php
<?php
namespace Acme\MyBundle\Monolog;
class UserActionsLogger
{
public $logger;
public function __construct($logger)
{
$this->logger = $logger;
}
}
Then you can either inject the logger container into another service with:
acme.user.authenticationhandler:
class: %acme.user.authenticationhandler.class%
public: false
arguments: ['#router', '#security.context', '#acme.logger.user_actions']
Or, you could selectively use the logger container as a service in any controller:
$userActionsLogger = $this->get('acme.logger.user_actions');
Then you can access the actual logger by:
$userActionsLogger->logger->info('A thing happened!')
I am currently using symfony/monolog-bundle 2.3.0 and the following code works.
Configuration in config.yml
monolog:
handlers:
main:
type: stream
path: %kernel.logs_dir%/%kernel.environment%.log
level: info
doctrine:
type: stream
path: %kernel.logs_dir%/doctrine_%kernel.environment%.log
level: debug
channels: doctrine
On Controllers
$doctrineLogger = $this->get('monolog.logger.doctrine');
Hope it helps.

How to share the \Mongo instance between Doctrine ODM and MongoDbSessionHandler?

I'm trying to use MongoDB to store my sessions, and I need to get a reference to the \Mongo instance.
But apparently it's not declared as a service. Instead, doctrine creates it from within the wrapper.
So what can I do about it? I tried this:
services:
mongo.connection:
class: MongoDoctrine\MongoDB\Connection
factory_service: doctrine.odm.mongodb.document_manager
factory_method: getConnection
mongo:
class: Mongo
factory_service: mongo.connection
factory_method: getMongo
But sometimes it returns null, and it also conflicts with my logger preprocessor that adds the request_id to the logs, which I think has something to do with the session.
Any ideas?
Looking at the source for Doctrine\MongoDB\Connection, the getMongo() method simply returns the $mongo class property, which may or may not be initialized. If possible, you can call initialize() manually before you inject the connection. Given that you're already defining a service for the Connection wrapper, you should be able to do this:
services:
mongo.connection:
class: Doctrine\MongoDB\Connection
factory_service: doctrine.odm.mongodb.document_manager
factory_method: getConnection
calls:
- [initialize, []]
mongo:
class: Mongo
factory_service: mongo.connection
factory_method: getMongo
That would invoke the initialize() method between the container constructing the mongo.connection service from its factory method and it being returned.
Some additional points to note:
If mongo.connection is only going to be used once (to injected into mongo), you may prefer using anonymous service definitions in lieu of defining another service.
The ODM bundle already defines each connection as doctrine_mongodb.odm.%s_connection, using its name from the configuration in place of %s; however, that's not helpful if you need to add a method invocation to the service definition.
The latest version of the ODM bundle (for Symfony 2.1+) changed its service prefix from doctrine.odm.mongodb to doctrine_mongodb.odm, although a BC alias exists for the default document manager. It'd be wise to switch over to the new prefix, though.

Categories