How Symfony container collects vendor classes?

Viewed 54

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?

1 Answers

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.

Related