olvlvl / symfony-dependency-injection-proxy
Generate super tiny proxies for Symfony's dependency injection
Package info
github.com/olvlvl/symfony-dependency-injection-proxy
pkg:composer/olvlvl/symfony-dependency-injection-proxy
Requires
- php: >=8.4.1
- ext-json: *
- symfony/dependency-injection: ^8.1
Requires (Dev)
- phpstan/phpstan: ^2.3
- phpunit/phpunit: ^13.4
- symfony/config: ^8.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
This package provides a proxy generator for Symfony's dependency injection component that generates super tiny, super simple proxies, especially when compared to Symfony's default implementation. Here are some differences:
- Can proxy
finalclasses. - Can only proxy classes with interfaces.
- The generated proxies are self-contained.
- The package is ~10Kb and doesn't have dependencies.
- The package can be removed once the proxies have been generated.
If you're not familiar with proxy services, better have a look at Symfony's documentation before going any further.
Important
This package is no longer needed on PHP 8.4+ and Symfony 7.3+. It was created when Symfony
relied on ocramius/proxy-manager for lazy services, which generated large proxies and couldn't
proxy final classes. Symfony 6.2 moved to symfony/var-exporter, and Symfony 7.3 and later use
PHP 8.4's native lazy objects, so lazy: true now does everything this package does, and more:
| Case | Symfony's lazy services | This package |
|---|---|---|
final class implementing an interface |
Works | Works |
| Interface-typed service built by factory | Works | Works |
| Class without an interface | Works | Fails: unable to determine the interface |
| Generated code size | ~3.4 KB | ~2.8 KB |
The output of both is self-contained, and neither needs anything extra at runtime, since PHP provides the lazy-object support. Being limited to interfaces is a restriction of this package, not a feature. Unless you have a specific reason, remove it and use Symfony's built-in lazy services.
Projects on PHP below 8.4 or Symfony below 7.3 can still use an earlier version of this package.
Installation
composer require olvlvl/symfony-dependency-injection-proxy
How it works
The generator works with the following assumptions: the service we want to proxy implements an
interface, and services using that service expect that interface, following the dependency
inversion principle. Now, consider the following code, where an ExceptionHandler service
requires a logger implementing LoggerInterface:
<?php use Psr\Log\LoggerInterface; class ExceptionHandler { public function __construct(private LoggerInterface $logger) { } // … }
Imagine we're using Monolog as logger, and we have an expansive stream to set up. Why waste time building the logger for every request when it's seldom used? That's when we mark our service as lazy.
The following example demonstrates how we can mark our Psr\Log\LoggerInterface service as lazy (we
could use PHP code or XML just the same):
services: Psr\Log\LoggerInterface: class: Monolog\Logger lazy: true # …
The service can also use a factory:
services: Psr\Log\LoggerInterface: factory: 'LoggerFactory::build' lazy: true # …
Note: We don't have to define our service with a class, we could use logger instead of
Psr\Log\LoggerInterface just > the same, except we would have to define class for the factory
one.
Now let's see how to build our container.
Building the dependency injection container
The following code demonstrates how to build, compile, and dump a container:
<?php use olvlvl\SymfonyDependencyInjectionProxy\ProxyDumper; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; $builder = new ContainerBuilder(); // … // Here we load our config, or build the container using clever PHP calls. // We might even have some compiler passes to add. // … $builder->compile(); $dumper = new PhpDumper($builder); $dumper->setProxyDumper(new ProxyDumper()); /* @var string $containerFile */ file_put_contents($containerFile, $dumper->dump());
There you have it. We can use our container as usual and everything is awesome.
What if my lazy service implements multiple interfaces?
The basic interface resolver will have a hard time figuring out which interface to implement if a
service implements many. For instance, if a service was an instance of ArrayObject the following
exception would be thrown:
Don't know which interface to choose from for ArrayObject: IteratorAggregate, Traversable, ArrayAccess, Serializable, Countable.
We can specify the interface to implement using the lazy attribute:
ArrayObject: lazy: ArrayAccess
Continuous Integration
The project is continuously tested by GitHub Actions.
Code of Conduct
This project adheres to a Contributor Code of Conduct. By participating in this project and its community, you are expected to uphold this code.
Contributing
Please see CONTRIBUTING for details.
License
olvlvl/symfony-dependency-injection-proxy is released under the BSD-3-Clause.