Permalink
Browse files

Merge c8df984 into 0f0da2a

  • Loading branch information...
mnapoli committed Jan 9, 2018
2 parents 0f0da2a + c8df984 commit 460f70c0bf92c9dcb9b020ddc31b06f580fece8b
View
@@ -6,17 +6,18 @@ This is the complete change log. You can also read the [migration guide](doc/mig
Improvements:
- [#494](https://github.com/PHP-DI/PHP-DI/pull/494) The container can now be compiled for optimum performances in production
- [#294](https://github.com/PHP-DI/PHP-DI/issues/294), [#349](https://github.com/PHP-DI/PHP-DI/issues/349), [#449](https://github.com/PHP-DI/PHP-DI/pull/449): `DI\object()` has been replaced by more specific and less ambiguous helpers:
- `DI\create()` creates an object, overrides autowiring and previous definitions
- `DI\autowire()` autowires an object and allows to override specific constructor and method parameters
- [#494](https://github.com/PHP-DI/PHP-DI/pull/494) The container can now be compiled for optimum performances in production
- The container can now be built without parameters: `new Container()`
- Definitions can be nested:
- [#490](https://github.com/PHP-DI/PHP-DI/issues/490) Definitions can be nested in arrays (by [@yuloh](https://github.com/yuloh))
- [#501](https://github.com/PHP-DI/PHP-DI/issues/501) & [#540](https://github.com/PHP-DI/PHP-DI/issues/540) Autowire definitions can be nested in other definitions
- [#487](https://github.com/PHP-DI/PHP-DI/issues/487) & [#540](https://github.com/PHP-DI/PHP-DI/issues/540) Closures are now handled as factories when they are nested in other definitions
- [#242](https://github.com/PHP-DI/PHP-DI/issues/242) Error in case a definition is not indexed by a string
- [#505](https://github.com/PHP-DI/PHP-DI/pull/505) Debug container entries
- [#564](https://github.com/PHP-DI/PHP-DI/pull/564) Caching was made almost entirely obsolete by the container compilation, however there is still a caching system entirely rebuilt over APCu for covering the last cases that compilation could not address (see [php-di.org/doc/performances.html](http://php-di.org/doc/performances.html))
Fixes:
@@ -30,6 +31,7 @@ BC breaks:
- PHP 7 or greater is required and HHVM is no longer supported
- `DI\object()` has been removed, use `DI\create()` or `DI\autowire()` instead
- [#409](https://github.com/PHP-DI/PHP-DI/issues/409): Scopes are removed, read more in the [scopes](doc/scopes.md) documentation.
- Caching was replaced by compiling the container: `ContainerBuilder::setDefinitionCache()` was removed, use `ContainerBuilder::enableCompilation()` instead.
- [#463](https://github.com/PHP-DI/PHP-DI/issues/463) & [#485](https://github.com/PHP-DI/PHP-DI/issues/485): Container-interop support was removed, PSR-11 is used instead (by [@juliangut](https://github.com/juliangut))
- The deprecated `DI\link()` helper was removed, used `DI\get()` instead
- [#484](https://github.com/PHP-DI/PHP-DI/pull/484) The deprecated `\DI\Debug` class has been removed. Definitions can be cast to string directly
View
@@ -80,9 +80,9 @@ Read more details and alternatives in the [scopes](../scopes.md) documentation.
## Caching
Caching has been removed completely in favor of the much faster alternative: compiling the container (see the section about compiling the container).
Caching has been almost entirely removed in favor of a much faster alternative: compiling the container (see the section about compiling the container below).
As such, the `ContainerBuilder::setDefinitionCache()` method was removed. In your code you can remove that line (and maybe compile the container instead).
As such, the `ContainerBuilder::setDefinitionCache()` method was removed. In your code you can remove that line (and compile the container instead). Read the ["performances" guide](../performances.md) for more information.
## Compiling the container
View
@@ -7,7 +7,7 @@ current_menu: performances
## A note about caching
Since PHP-DI 6.0 there is no caching system anymore. Instead the container can be compiled (see below), which allows for even better performances than caching.
PHP-DI 4 and 5 relied a lot on caching. With PHP-DI 6 the main vector for optimization is now to compile the container into highly optimized code (see below). Compiling the container is simpler and faster.
## Compiling the container
@@ -52,8 +52,6 @@ if (/* is production */) {
}
```
As a side note, do not confuse "development environment" with your automated tests. You are encouraged to run your automated tests (PHPUnit, Behat, etc.) on a system as close to your production setup (which means with the container compiled).
### Optimizing for compilation
As you can read in the "*How it works*" section, PHP-DI will take all the definitions it can find and compile them. That means that definitions like **autowired classes that are not listed in the configuration cannot be compiled** since PHP-DI doesn't know about them.
@@ -73,13 +71,9 @@ return [
You do not need to configure them (autowiring will still take care of that) but at least now PHP-DI will know about those classes and will compile their definitions.
Currently PHP-DI does not traverse directories to find autowired or annotated classes automatically. It also does not resolve [wildcard definitions](php-definitions.md#wildcards) when it is compiled.
Please note that the following definitions are not compiled (yet):
- [wildcard definitions](php-definitions.md#wildcards)
Currently PHP-DI does not traverse directories to find autowired or annotated classes automatically.
Those definitions will still work perfectly, they will simply not get a performance boost when using a compiled container.
It also does not resolve [wildcard definitions](php-definitions.md#wildcards) during the compilation process. Those definitions will still work perfectly, they will simply not get a performance boost when using a compiled container.
On the other hand factory definitions (either defined with closures or with class factories) are supported in the compiled container. However please note that if you are using closures as factories:
@@ -117,3 +111,29 @@ At runtime, the container builder will see that the file `CompiledContainer.php`
## 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).
## Caching
Compiling the container is the most efficient solution, but it has some limits. The following cases are not optimized:
- autowired (or annotated) classes that are not declared in the configuration
- wildcard definitions
- usage of `Container::make()` or `Container::injectOn()` (because those are not using the compiled code)
If you make heavy use of those features, and if it slows down your application you can enable the caching system. The cache will ensure annotations or the reflection is not read again on every request.
The cache relies on APCu directly because it is the only cache system that makes sense (very fast to write and read). Other caches are not good options, this is why PHP-DI does not use PSR-6 or PSR-16 for this cache.
To enable the cache:
```php
$containerBuilder = new \DI\ContainerBuilder();
if (/* is production */) {
$containerBuilder->enableDefinitionCache();
}
```
Heads up:
- do not use a cache in a development environment, else changes you make to the definitions (annotations, configuration files, etc.) may not be taken into account
- clear the APCu cache on each deployment in production (to avoid using a stale cache)
View
@@ -13,6 +13,7 @@
use DI\Definition\Resolver\DefinitionResolver;
use DI\Definition\Resolver\ResolverDispatcher;
use DI\Definition\Source\DefinitionArray;
use DI\Definition\Source\MutableDefinitionSource;
use DI\Definition\Source\ReflectionBasedAutowiring;
use DI\Definition\Source\SourceChain;
use DI\Invoker\DefinitionParameterResolver;
@@ -43,7 +44,7 @@ class Container implements ContainerInterface, FactoryInterface, InvokerInterfac
protected $resolvedEntries = [];
/**
* @var SourceChain
* @var MutableDefinitionSource
*/
private $definitionSource;
@@ -53,11 +54,11 @@ class Container implements ContainerInterface, FactoryInterface, InvokerInterfac
private $definitionResolver;
/**
* Map of definitions that are already looked up.
* Map of definitions that are already fetched (local cache).
*
* @var array
*/
private $definitionCache = [];
private $fetchedDefinitions = [];
/**
* Array of entries being resolved. Used to avoid circular dependencies and infinite loops.
@@ -93,7 +94,7 @@ class Container implements ContainerInterface, FactoryInterface, InvokerInterfac
* @param ContainerInterface $wrapperContainer If the container is wrapped by another container.
*/
public function __construct(
SourceChain $definitionSource = null,
MutableDefinitionSource $definitionSource = null,
ProxyFactory $proxyFactory = null,
ContainerInterface $wrapperContainer = null
) {
@@ -143,11 +144,12 @@ public function get($name)
private function getDefinition($name)
{
if (!array_key_exists($name, $this->definitionCache)) {
$this->definitionCache[$name] = $this->definitionSource->getDefinition($name);
// Local cache that avoids fetching the same definition twice
if (!array_key_exists($name, $this->fetchedDefinitions)) {
$this->fetchedDefinitions[$name] = $this->definitionSource->getDefinition($name);
}
return $this->definitionCache[$name];
return $this->fetchedDefinitions[$name];
}
/**
@@ -365,7 +367,7 @@ protected function setDefinition(string $name, Definition $definition)
if (array_key_exists($name, $this->resolvedEntries)) {
unset($this->resolvedEntries[$name]);
}
$this->definitionCache = []; //Completely clear definitionCache
$this->fetchedDefinitions = []; // Completely clear this local cache
$this->definitionSource->addDefinition($definition);
}
View
@@ -10,6 +10,7 @@
use DI\Definition\Source\DefinitionSource;
use DI\Definition\Source\NoAutowiring;
use DI\Definition\Source\ReflectionBasedAutowiring;
use DI\Definition\Source\SourceCache;
use DI\Definition\Source\SourceChain;
use DI\Proxy\ProxyFactory;
use InvalidArgumentException;
@@ -93,6 +94,11 @@ class ContainerBuilder
*/
private $compileToDirectory;
/**
* @var bool
*/
private $sourceCache = false;
/**
* Build a container configured for the dev environment.
*/
@@ -143,6 +149,14 @@ public function build()
// Mutable definition source
$source->setMutableDefinitionSource(new DefinitionArray([], $autowiring));
if ($this->sourceCache) {
if (!SourceCache::isSupported()) {
throw new \Exception('APCu is not enabled, PHP-DI cannot use it as a cache');
}
// Wrap the source with the cache decorator
$source = new SourceCache($source);
}
$proxyFactory = new ProxyFactory($this->writeProxiesToFile, $this->proxyDirectory);
$this->locked = true;
@@ -313,6 +327,34 @@ public function addDefinitions($definitions) : self
return $this;
}
/**
* Enables the use of APCu to cache definitions.
*
* You must have APCu enabled to use it.
*
* Before using this feature, you should try these steps first:
* - enable compilation if not already done (see `enableCompilation()`)
* - if you use autowiring or annotations, add all the classes you are using into your configuration so that
* PHP-DI knows about them and compiles them
* Once this is done, you can try to optimize performances further with APCu. It can also be useful if you use
* `Container::make()` instead of `get()` (`make()` calls cannot be compiled so they are not optimized).
*
* Remember to clear APCu on each deploy else your application will have a stale cache. Do not enable the cache
* in development environment: any change you will make to the code will be ignored because of the cache.
*
* @see http://php-di.org/doc/performances.html
*
* @return $this
*/
public function enableDefinitionCache() : self
{
$this->ensureNotLocked();
$this->sourceCache = true;
return $this;
}
/**
* Are we building a compiled container?
*/
@@ -0,0 +1,77 @@
<?php
namespace DI\Definition\Source;
use DI\Definition\AutowireDefinition;
use DI\Definition\Definition;
use DI\Definition\ObjectDefinition;
/**
* Decorator that caches another definition source.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class SourceCache implements DefinitionSource, MutableDefinitionSource
{
/**
* @var string
*/
const CACHE_KEY = 'php-di.definitions.';
/**
* @var DefinitionSource
*/
private $cachedSource;
public function __construct(DefinitionSource $cachedSource)
{
$this->cachedSource = $cachedSource;
}
public function getDefinition(string $name)
{
$definition = apcu_fetch(self::CACHE_KEY . $name);
if ($definition === false) {
$definition = $this->cachedSource->getDefinition($name);
// Update the cache
if ($this->shouldBeCached($definition)) {
apcu_store(self::CACHE_KEY . $name, $definition);
}
}
return $definition;
}
/**
* Used only for the compilation so we can skip the cache safely.
*/
public function getDefinitions() : array
{
return $this->cachedSource->getDefinitions();
}
public static function isSupported() : bool
{
return function_exists('apcu_fetch')
&& ini_get('apc.enabled')
&& ! ('cli' === PHP_SAPI && ! ini_get('apc.enable_cli'));
}
public function addDefinition(Definition $definition)
{
throw new \LogicException('You cannot set a definition at runtime on a container that has caching enabled. Doing so would risk caching the definition for the next execution, where it might be different. You can either put your definitions in a file, remove the cache or ->set() a raw value directly (PHP object, string, int, ...) instead of a PHP-DI definition.');
}
private function shouldBeCached(Definition $definition = null) : bool
{
return
// Cache missing definitions
($definition === null)
// Object definitions are used with `make()`
|| ($definition instanceof ObjectDefinition)
// Autowired definitions cannot be all compiled and are used with `make()`
|| ($definition instanceof AutowireDefinition);
}
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace DI\Test\IntegrationTest;
use DI\ContainerBuilder;
use function DI\create;
use DI\Definition\ObjectDefinition;
use DI\Definition\Source\SourceCache;
class CacheTest extends BaseContainerTest
{
public function setUp()
{
parent::setUp();
if (! SourceCache::isSupported()) {
$this->markTestSkipped('APCu extension is required');
}
apcu_clear_cache();
}
/**
* @test
*/
public function cached_definitions_should_be_overridable()
{
$builder = new ContainerBuilder();
$builder->enableDefinitionCache();
$builder->addDefinitions([
'foo' => 'bar',
]);
$container = $builder->build();
$this->assertEquals('bar', $container->get('foo'));
$container->set('foo', 'hello');
$this->assertEquals('hello', $container->get('foo'));
}
/**
* @test
*/
public function compiled_entries_should_not_be_put_in_cache()
{
$builder = new ContainerBuilder();
$builder->enableCompilation(self::COMPILATION_DIR, self::generateCompiledClassName());
$builder->enableDefinitionCache();
$builder->addDefinitions([
'foo' => create(\stdClass::class),
]);
$container = $builder->build();
$container->get('foo');
$cachedDefinition = apcu_fetch(SourceCache::CACHE_KEY . 'foo');
self::assertFalse($cachedDefinition);
}
/**
* @test
*/
public function non_compiled_entries_should_be_put_in_cache()
{
$builder = new ContainerBuilder();
$builder->enableCompilation(self::COMPILATION_DIR, self::generateCompiledClassName());
$builder->enableDefinitionCache();
$container = $builder->build();
$container->get(\stdClass::class);
$cachedDefinition = apcu_fetch(SourceCache::CACHE_KEY . \stdClass::class);
self::assertInstanceOf(ObjectDefinition::class, $cachedDefinition);
}
}
Oops, something went wrong.

0 comments on commit 460f70c

Please sign in to comment.