In Symfony 3.4, they made all services private by default, which means you can no longer call $this->get('my_service_id')
in your controllers to quickly obtain a service.
This change was made because direct usage of services from the container is considered a bad practice. That's why controllers allow services to be injected using Type Hinting in their methods and constructors.
The only remaining inconvenience is that when running tests, we get the following:
> bin/phpunit
# Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: The "App\Service\S" 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.
Problem
We want to create a service and test it before integrating it with the rest of the project (standard TDD approach).
I have a repository R
that implements the RInterface
interface. The RInterface
interface is used in the S
service (type hinted constructor). The S
service is used in the C
controller (again, as a constructor parameter).
When we run the test for the S
service...
class STest extends KernelTestCase
{
public function testGetItems(): void
{
self::bootKernel();
$container = self::$kernel->getContainer();
$service = $container->get(S::class);
// ... other
}
}
We see the following message:
> bin/phpunit
# Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: The "App\Service\S" 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.
Reason
The service can be integrated when certain conditions are met.
To check if the S
service is present in the test container, we type the following command:
> bin/console debug:container 'App\Service\S' --env=test
Here is the message we will see:
Information for Service "App\Core\Service\S"
=========================================================
---------------- ---------------------------------
Option Value
---------------- ---------------------------------
Service ID App\Service\S
Class App\Service\S
Tags -
Public no
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired yes
Autoconfigured yes
---------------- ---------------------------------
As mentioned in the received message when running the test, the S
service appears as private (Public: no
). It's important to note that due to how the Symfony container works, unused services are removed from the container. This means that if you have a private service not used by any other service, Symfony removes it, and you cannot retrieve it from the container.
Solution
The solution is to explicitly define the S
service as public
so that Symfony does not remove it. The most appropriate solution would be to create a public alias only in the test environment for the service you want to test.
# config/services_test.yaml
services:
test_alias.service:s:
alias: 'App\Service\S'
public: true
Good work 👨💻
Top comments (5)
you litteraly save my night Thank u .
Instead of doing that - you can use self::getContainer('private service') in your test class. This is simpler 👍🏼
@lyrixx Certainly, the solution you indicated is the fastest and simplest.
I tried to indicate in my article the most appropriate solution, which is to create a public alias only in the test environment for the single service you want to test.
I didn't understand your article point then :) There is a documentation page for getting private service in test env : symfony.com/doc/current/testing.ht... and this is how everyone should do.
@lyrixx the part of the documentation you mentioned contains this note:
The solution I provided in the article is also valid for these cases.