Symfony5 / Codeception: Service is not available in container - php

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...

Related

autowire Predis Interface in symfony

i wanna use ClientInterface in my class constructor and i give an error :
Cannot autowire service "App\Service\DelayReportService": argument "$client" of method "__construct()" references interface "Predis\ClientInterface" but no such service exists. Did you create a class that implements this interface?
seems to be i should add it manually to services.yml i added it like :
Predis\ClientInterface: '#Predis\Client'
and now i give this error:
You have requested a non-existent service "Predis\Client".
what is the solution and why symfony itself dont handle it?
you seem to be confused about how to define a service... which isn't surprising tbh
look here
https://symfony.com/doc/5.4/service_container.html#explicitly-configuring-services-and-arguments
for example
services:
App\Service\Concerns\IDevJobService:
class: App\Tests\Service\TestDevJobService
autowire: true
public: true
where
IDevJobService is an INTERFACE
and
TestDevJobService
is the actual implementation that will be auto injected
using # inside the yaml files is done to reference a service that has already been defined ELSEWHERE
https://symfony.com/doc/5.4/service_container.html#service-parameters
you probably want to watch symfonycasts services tutorial (I am not affiliated and I havent watched it myself yet (sure wish I did)).
EDIT
Predis\Client is a 3rd party class. It isn't in your App namespace or in your src folder. Symfony checks the src folder for class that it will then make to a service. See services.yaml there is a comment there, look for exclude and resource. And I'm not sure, even if you autoload it, that you can then just do #Predis\Client to reference an existing service.
be sure as well to debug your config using
php bin/console debug:autowiring
under linux you could do as well php bin/console debug:autowiring | grep Predis to find it more quickly (if it is there at all)

Resolving Controller Services in Sylius/Symfony

Hoes does Symfony resolve the Sylius service sylius.controller.shop_user service to the controller class file Sylius\Bundle\UserBundle\Controller\UserController.
My understanding is that sylius.controller.shop_user is a service, and that in Symfony there will be a corresponding service configuration. This service configuration will tell Symfony which class to use when it needs to instantiate the service.
However, I can't seem to find a sylius.controller.shop_user configuration in the Sylius source configuration anywhere. There's just references to this service in routing files
#File: src/Sylius/Bundle/ShopBundle/Resources/config/routing/ajax/user.yml
sylius_shop_ajax_user_check_action:
path: /check
methods: [GET]
defaults:
_controller: sylius.controller.shop_user:showAction
_format: json
_sylius:
repository:
method: findOneByEmail
arguments:
email: $email
serialization_groups: [Secured]
or in on-disk container cache files.
var/cache/dev/srcKernelDevDebugContainer.xml
1798: <parameter key="sylius.controller.shop_user.class">Sylius\Bundle\UserBundle\Controller\UserController</parameter>
15230: <service id="sylius.controller.shop_user" class="Sylius\Bundle\UserBundle\Controller\UserController" public="true">
So how does Symfony know to instantiate the right class for this service?
Is there configuration I'm not seeing? Some Symfony magic that auto-generates the class? Some other mysterious third thing where I don't know what I don't know?
I don't have any specific task in mind, I'm just trying to get a feel for how Sylius and Symfony work under the hood.
The controller service is defined based on ResourceBundle's configuration in Sylius\Bundle\ResourceBundle\DependencyInjection\Driver\AbstractDriver::addController. This driver is called when loading a bundle.
Services with the name sylius.controller.[entity-name] are part of the
Sylius
entity resource system. As best I can tell, when you define your new doctrine entities
in a specific way and
register them as a Sylius resource, Sylius will
automatically generate these controller services based on your
configuration.
The actual line of code that defines these services
is here.
#File: src/Sylius/Bundle/ResourceBundle/DependencyInjection/Driver/AbstractDriver.php
/* ... */
$container->setDefinition($metadata->getServiceId('controller'), $definition);
/* ... */
The
Sylius\Bundle\ResourceBundle\DependencyInjection\Driver\AbstractDriver
class is a (as of 1.3) a base class for the
Sylius\Bundle\ResourceBundle\DependencyInjection\Driver\Doctrine\DoctrineORMDriver
class. How this class ends up being used is by Symfony is unclear, but is
fortunately beyond the scope of this answer.

Yii2 Codeception invalid routing?

I am trying to set-up acceptance tests using Codeception in Yii2.
So far so good with regards to installation, but I am having a route issue.
When I do:
codeception run acceptance
I get this feedback:
1) Failed to ensure login page works in LoginCept (./acceptance/LoginCept.php)
Step I fill field "input[name="LoginForm[username]"]",""
Fail Form field by Label or CSS element with 'input[name="LoginForm[username]"]' was not found.
Scenario Steps:
3. $I->fillField("input[name="LoginForm[username]"]","")
2. // I am going to submit login form with no data
1. $I->amOnPage("/backend/web/index-test.php/")
The input with name LoginForm[username] exists on the page, but apparantly Codeception is not getting the correct page.
Should /backend/web/index-test.php also have the approot path in it? When I request approot/backend/web/index-test.php in the browser it all works fine.
Thanks for any pointers.
Alex
UPDATE: hereby the contents of acceptance.suite.yml:
# Codeception Test Suite Configuration
# suite for acceptance tests.
# perform tests in browser using the Selenium-like tools.
# powered by Mink (http://mink.behat.org).
# (tip: that's what your customer will see).
# (tip: test your ajax and javascript by one of Mink drivers).
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
class_name: AcceptanceTester
modules:
enabled:
- PhpBrowser
- tests\codeception\common\_support\FixtureHelper
# you can use WebDriver instead of PhpBrowser to test javascript and ajax.
# This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium
# "restart" option is used by the WebDriver to start each time per test-file new session and cookies,
# it is useful if you want to login in your app in each test.
# - WebDriver
config:
PhpBrowser:
# PLEASE ADJUST IT TO THE ACTUAL ENTRY POINT WITHOUT PATH INFO
url: http://localhost:8080
# WebDriver:
# url: http://localhost:8080
# browser: firefox
# restart: true
UPDATE BASED ON COMMENTS BELOW:
I'm lost. I tried hardcoding the path, and even tried hardcoding the localhost URL, but then I get this response:
$I->amOnPage("/backend/web/index-test.php/localhost/www/yii2KickDish/backend/web")
which clearly is a bogus URL....so how can I get Codeception to resolve to the right location?
I got the same issue.
Seems like the "amOnPage" method build into the AcceptanceTesterActions trait isnt working well with yii2 url pattern.
That's how i solved this.
Create a class MainTester extending from AcceptanceTester
namespace tests\codeception\master\Step\Acceptance;
use tests\codeception\master\AcceptanceTester;
class MainTester extends AcceptanceTester
{
public function amOnPage($url)
{
$page = \Yii::$app->getUrlManager()->createUrl($url);
return parent::amOnPage($page);
}
}
Then in my Cest class
use tests\codeception\master\Step\Acceptance\MainTester as AcceptanceTester;
class TestClassCest
{
public function testMethod(AcceptanceTester $I)
{
$I->amOnPage('/example/something');
}
}

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.

Categories