symfony 4.1: no such service exists - php

So I've created a new Symfony 4.1 project last week and have been working on it without any issues. Until yesterday when I started using some third party dependencies. I installed the Guzzle Client by using the composer require guzzlehttp/guzzle. This installed the package just fine as far as I can see.
However, when I then try to inject this service into a constructor in my code (an event or command), I get the error message: Cannot autowire service "App\xxx\Command\testCommand": argument "$client" of method "__construct()" references class "GuzzleHttp\Client" but no such service exists.
The Guzzle service is not the only one that doesn't work, this is just an example. The weird thing to me is that there are a lot of dependencies that I have that work just fine like the Doctrine DocumentManager, JMS serializer and NelmioApiDocBundle.
What I've tried so far (and actually semi-solves the problem) is adding the service name to my services.yaml file:
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.
GuzzleHttp\Client: <-- Adding this makes it work flawlessly.
The problem I have with this solution is that I would end up adding hundreds of services to my services.yaml file just to get them to work. It seems to me like this is not the optimal solution.
So my question is how I could resolve this in the most elegant way. Is this something I'm doing wrong in my project? Is it something that has to be resolved on the vendors side? And if so, how could it be made to work with Symfony 4.1?

composer require GuzzleHttp\Client
It seems like you are trying to require a class, not a package. I was not aware composer is smart enough to recognize a package by it's provided classes, so I checked... it can't, only gives package name suggestions.
Make sure you understand what is a composer package.
The Guzzle service is not the only one that doesn't work, this is just an example.
You don't have a Guzzle service. You only have the guzzle library with it's classes. Services require a service definition, like the one you wrote.
The other services like Doctrine you mentioned are working, because they are bundles, which provide service definitions.
Either you use a bundle, or you use a library, and hook it up yourself.
In the case of guzzle, I would not use a bundle. Then again, I would not use guzzle directly anymore, but HTTPlug. (bundle: https://github.com/php-http/HttplugBundle, library: https://github.com/php-http/httplug)

In my case, I just needed to re-generate the optimized autoload files.
So using composer dump-autoload solved the issue for me.

Related

How Symfony container collects vendor classes?

I am working on very old legacy code, mostly procedural. Trying to improve it. Rewriting applications is impossible right now. The plan is to add a few libraries which would help organize things and improve that way.
I added a Symfony dependency-injection component in order to do that. It would provide the possibility to fetch needed services with its dependency easy.
I watched symfonycast tutorial on how to play with container. And with that knowledge, I managed to write a simple loader to start the container and to use services made by me. It is simple, it guesses FQCN based on file path, and then uses reflection to get dependencies. But I can not figure out how to load vendor classes, because here you can not guess namespace that way. :)
The question is: What exactly Symfony uses to load classes from the vendor folder, does it reads composer.json files to see namespaces, does it uses some composer feature, or something else?
Loading classes is different than instancing services.
The first can in fact use regular composer facilities to discover vendored classes in a legacy project like yours, even if they weren't installed with composer. This uses the standard php autoload mechanism with some added magic.
To include the, let's say lib/ legacy directory in the discoverable files you would add the following to composer.json:
"autoload": {
"classmap": ["lib/"]
}
And then run composer dump-autoload. Note that by including vendor/autoload.php in your legacy files you could even forego the require directives for your dependencies and rely on composer as well. This can be a path for migrating them to composer-managed dependencies, too.
Service instancing requires not only being able to locate the classes themselves, but also their respective dependencies so the container can create the object tree automatically. This usually involves hand-writing service definition files: classes in the vendor/ folder are not automatically registered as services. A bundle (or your own definitions) enables support for an specific library.
Take for instance the Mailer component: you can use it as a standalone library, but for framework integration (which includes service definitions and depen) you'd need to install Mailer bundle as well.
The exception where automatic service registration applies (when using symfony framework, not the standalone dependency injection component) is for files under src/. During container compilation, services.yaml is loaded and the ContainerConfigurator with help from FileLoader, looks for *.php files the directories configured as a resource, creating service definitions for them.
I guess you could do a similar thing for your legacy dependencies in a CompilerPass by using a similar technique or by trying to leverage the composer classmap but, specially if your legacy dependencies do not follow a PSR loading standard, I'd advise against it, since it can pull in tests, example files, etc.

Make private and removed service available in tests

I'm trying to make vatin-bundle compatible with Symfony 6.
But the tests fail
The "validator" 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.
It seems this is new in Symfony 6
The container in static::getContainer() is actually a special test container. It gives you access to both the public services and the non-removed private services services.
What is the preferred way to make validator available in tests again?
The only way I found is creating my own alias like
services:
myvalidator:
alias: validator
public: true
and use the new alias. Is there a better way?
If the service is removed, then it's no longer accessible no matter what. It's not a matter of visibility, the service is no longer there. So you need to prevent the service from being removed: creating an alias is the best and simplest way to go about it.
This has been confirmed by maintainers here.
You can create the alias only during testing, and still access the original service. (e.g. ->get('validator'). Once the alias is created, the original service is no longer removed.
I don't think this is something really new in Symfony 6, but it's bee a thing since Symfony 4.4. Although it's true that now on Symfony 6, since it's removes previously deprecated behaviour, things could have changed.

Systematic Symfony 4 lazy service loading or annotation?

It's possible to lazy-load a service in Symfony, but:
Is it possible to make it the default way to load services
without having to configure it for all service?
Is there a possibility to annotate a service for lazy-loading?
Regarding the lazy loading. There are multiple ways you can solve this:
For one there is the new Service Locator that was introduced in 3.3. The documentation explicitly says:
Sometimes, a service needs access to several other services without being sure that all of them will actually be used. In those cases, you may want the instantiation of the services to be lazy. However, that's not possible using the explicit dependency injection since services are not all meant to be lazy (see Lazy Services).
The referenced article on Lazy Service describes another way to solve this which requires an additional dependency ocramius/proxy-manager which will then make it possible to mark services as lazy: true in your DI config.
I don't know if I understand the first question correctly, but if you don't want to manually configure dependencies you can rely on the autowiring feature that also received major changes in Symfony 3.3 and 3.4. You can only make the autowring a default per configuration file, but using the PSR-4 autodiscovery can basically autowire your whol src directory if you want to with some manual intervention, e.g. for parameters.

Extending of Symfony container

is it possible overwrite/extend Symfony\Component\DependencyInjection\Container::get() method? I want automatic creating service, when it is not contain in container, but class of service exists.
For example:
Name of service is My.MyBundle.Model.FooRepository
Service with this name doesnt exists, but when i call:
$container->get('My.MyBundle.Model.FooRepository');
check class_exists for \My\MyBundle\Model\FooRepository and when its exists, add to container and return it. Dependencies of this new services will be resolve by kutny/autowiring-bundle.
This feature can be extended only for some namespaces or interfaces and in production enviroment can be cached, but for developing will be great helper.
Any idea?
This is not directly answering your question but maybe it's answering your need: if you want to have "auto-wiring" inside your Symfony project, you can use PHP-DI inside Symfony. PHP-DI is an alternative container that can do auto-wiring (which Symfony does not).
Have a look at the Symfony 2 integration documentation to see if it can fit your bill.

Symfony2 service structure

I am having a hard time understanding Symfony2 services. I have read lots of stuff everywhere (including some here in SO) but none seems to fully explain it.
Suppose I have a bundle A and a separated bundle B. I want B functionality available to the A bundle. I want to inject B in the service container so A will be able to use it.
Which bundle should have a Services directory? Which one should have a configuration file? Both if needed? And where the Extension goes? Why?
Bundle B will require an Extension in order to load it's services.xml file.
Bundle B will require an entry in it's services.xml file to define the service.
Bundle B will have the Services directory containing your service class which exposes the desired functionality.
Bundle A does not require anything special. It will be able to use the container to access the service exposed by Bundle B. Just needs to know the service id.
It's confusing until you make a few services.
Read these two questions and my answers to them first:
Symfony2 conceptual issue: general bundles vs. specific ones,
Should everything really be a bundle in Symfony 2?
Assuming you're talking about app specific bundles, I suggest having one bundle only and keep services out it. Then, you could register your services in several ways:
Directly in the config.yml,
Creating an extension class in your AppBundle, or
Via annotations from JMSDiExtraBundle — this is what I prefer personally.

Categories