Skip to content

Commit

Permalink
Improve the documentation for lazy injection
Browse files Browse the repository at this point in the history
List of improvements:

- the introduction is a bit clearer and clears possible confusions
- the example is more concrete
- a new section explains that this feature should be used scarcely
- documented how to optimize performances for this feature
  • Loading branch information
mnapoli committed Nov 12, 2017
1 parent 5704c3a commit e2049c8
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 39 deletions.
89 changes: 52 additions & 37 deletions doc/lazy-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,45 @@ current_menu: lazy-injection

# Lazy injection

With PHP-DI all objects that you define in the container are lazily created, i.e. they are created once they are requested or injected somewhere. However that is sometimes not "lazy" enough and you would like the objects to be created only when they are actually used (e.g. when we call a method on them). This is what this section is about.
This feature should not be confused with lazy initialization of objects: **PHP-DI always creates objects only when they are requested or injected somewhere.**

Please understand that this is an advanced use case and that, unless you are certain of what you are doing, you probably don't need to use this technique.
Lazy injection goes further than this: it allows to defer the creation of an object's dependencies to the moment when they are actually used, not before.

Consider the following example:
**Warning: this feature should only be used exceptionally, please read the "When to use" section at the end of this page.**

## Example

```php
<?php
class Foo
class ProductExporter
{
private $a;
private $b;
private $pdfWriter;
private $csvWriter;

public function __construct(Foo $a, Bar $b)
public function __construct(PdfWriter $pdfWriter, CsvWriter $csvWriter)
{
$this->a = $a;
$this->b = $b;
$this->pdfWriter = $pdfWriter;
$this->csvWriter = $csvWriter;
}

public function doSomething()
public function exportToPdf()
{
$this->a->doStuff();
$this->pdfWriter->write(...);
}

public function doSomethingElse()
public function exportToCsv()
{
$this->b->doStuff();
$this->csvWriter->write(...);
}
}
```

`a` is used only when `doSomething()` is called, and `b` only when `doSomethingElse()` is called.
$productExporter = $container->get(ProductExporter::class);
$productExporter->exportToCsv();
```

You may wonder then: why injecting `a` **and** `b` if they may not be used? Especially if creating those objects is heavy in time or memory.
In this example the `exportToPdf()` is not called. `PdfWriter` is initialized and injected in the class but it's never used.

That's where lazy injection can help.
**If** `PdfWriter` was costly to initialize (for example if it has a lot of dependencies or if it does expensive things in the constructor) lazy injection can help to avoid instantiating the object **until it is used**.

## How it works

Expand All @@ -53,9 +56,7 @@ The proxy is a special kind of object that **looks and behaves exactly like the

Creating a proxy is complex. For this, PHP-DI relies on [ProxyManager](https://github.com/Ocramius/ProxyManager), the (amazing) library used by Doctrine, Symfony and Zend.

### Example

For the simplicity of the example, we will not inject a lazy object, but we will ask the container to return one:
Let's illustrate that with an example. For the sake of simplicity we will not inject a lazy object but we will ask the container to return one:

```php
class Foo
Expand Down Expand Up @@ -84,18 +85,22 @@ You can define an object as "lazy". If it is injected as a dependency, then a pr

### Installation

Lazy injection requires the [Ocramius/ProxyManager](https://github.com/Ocramius/ProxyManager) library. This library is not installed by default with PHP-DI, you need to require it in your `composer.json`:
Lazy injection requires the [Ocramius/ProxyManager](https://github.com/Ocramius/ProxyManager) library. This library is not installed by default with PHP-DI, you need to require it:

```json
{
"require": {
"php-di/php-di": "*",
"ocramius/proxy-manager": "~2.0"
}
}
```
````
composer require ocramius/proxy-manager
````

### PHP configuration file

```php
<?php

Then run `composer update`.
return [
'foo' => DI\create('MyClass')
->lazy(),
];
```

### Annotations

Expand All @@ -115,13 +120,23 @@ class MyClass
$containerPHP->set('foo', \DI\create('MyClass')->lazy());
```

### PHP configuration file
## When to use

```php
<?php
Lazy injection requires to create proxy objects for each object you declare as `lazy`. It is not recommended to use this feature more that a few times in one application.

return [
'foo' => DI\create('MyClass')
->lazy(),
];
While proxies are extremely optimized, they are only worth it if the object you define as lazy has a constructor that takes some time (e.g. connects to a database, writes to a file, etc.).

## Optimizing performances

PHP-DI needs to generate proxies of the classes you mark as "*lazy*".

By default those proxies are generated on every HTTP request, this is good for development but not for production.

In production you should generate proxies to file:

```php
// Enable writing proxies to file in the tmp/proxies directory
$containerBuilder->writeProxiesToFile(true, __DIR__ . '/tmp/proxies');
```

You will need to clear the directory every time you deploy to avoid keeping outdated proxies.
4 changes: 4 additions & 0 deletions doc/performances.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,7 @@ return $object;
All the compiled definitions will be dumped into a PHP class (the compiled container) which will be written to a file (for example `CompiledContainer.php`).

At runtime, the container builder will see that the file `CompiledContainer.php` exists and will load it (instead of loading the definition files). That PHP file may contain a lot of code but PHP's opcode cache will cache that class in memory (remember to use opcache in production). When a definition needs to be resolved, PHP-DI will simply execute the compiled code and return the created instance.

## Optimizing lazy injection

If you are using the [Lazy Injection](lazy-injection.md) feature you should read the section ["Optimizing performances" of the guide](lazy-injection.md#optimizing-performances).
8 changes: 6 additions & 2 deletions src/ContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ public function build()
* - in production you should clear that directory every time you deploy
* - in development you should not compile the container
*
* @see http://php-di.org/doc/performances.html
*
* @param string $directory Directory in which to put the compiled container.
* @param string $containerClass Name of the compiled class. Customize only if necessary.
* @param string $containerParentClass Name of the compiled container parent class. Customize only if necessary.
Expand Down Expand Up @@ -245,8 +247,10 @@ public function ignorePhpDocErrors(bool $bool) : ContainerBuilder
/**
* Configure the proxy generation.
*
* For dev environment, use writeProxiesToFile(false) (default configuration)
* For production environment, use writeProxiesToFile(true, 'tmp/proxies')
* For dev environment, use `writeProxiesToFile(false)` (default configuration)
* For production environment, use `writeProxiesToFile(true, 'tmp/proxies')`
*
* @see http://php-di.org/doc/lazy-injection.html
*
* @param bool $writeToFile If true, write the proxies to disk to improve performances
* @param string|null $proxyDirectory Directory where to write the proxies
Expand Down

0 comments on commit e2049c8

Please sign in to comment.