Skip to content

Commit

Permalink
Fix #376 Replace Doctrine Cache with PSR-16
Browse files Browse the repository at this point in the history
  • Loading branch information
mnapoli committed Apr 17, 2017
1 parent 3c2b882 commit fd69d27
Show file tree
Hide file tree
Showing 15 changed files with 144 additions and 130 deletions.
6 changes: 6 additions & 0 deletions change-log.md
Expand Up @@ -2,21 +2,27 @@

## 6.0

This is the complete change log. You can also read the [migration guide](doc/migration/6.0.md) for upgrading.

Improvements:

- [#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
- The container can now be built without parameters: `new Container()`
- [#242](https://github.com/PHP-DI/PHP-DI/issues/242) Error in case a definition is not indexed by a string
- [#376](https://github.com/PHP-DI/PHP-DI/issues/376) [PSR-16](https://github.com/php-fig/simple-cache) support for the cache system (which replaces Doctrine's Cache)

BC breaks:

- PHP 7 or greater is required
- `DI\object()` has been removed, use `DI\create()` or `DI\autowire()` instead
- The deprecated `DI\link()` helper was removed, used `DI\get()` instead
- The cache system has been moved from Doctrine Cache to PSR-16 (the simple cache standard)
- The exception `DI\Definition\Exception\DefinitionException` was renamed to `DI\Definition\Exception\InvalidDefinition`

Be also aware that internal classes or interfaces may have changed.

## 5.4.1

- [PSR-11](http://www.php-fig.org/psr/) compliance
Expand Down
3 changes: 1 addition & 2 deletions composer.json
Expand Up @@ -26,13 +26,13 @@
"php": ">=7.0.0",
"container-interop/container-interop": "~1.2",
"psr/container": "~1.0",
"psr/simple-cache": "^1.0",
"php-di/invoker": "^1.3.2",
"php-di/phpdoc-reader": "^2.0.1"
},
"require-dev": {
"phpunit/phpunit": "~5.0",
"mnapoli/phpunit-easymock": "~0.2.0",
"doctrine/cache": "~1.4",
"doctrine/annotations": "~1.2",
"ocramius/proxy-manager": "~2.0.2"
},
Expand All @@ -44,7 +44,6 @@
"psr/container-implementation": "^1.0"
},
"suggest": {
"doctrine/cache": "Install it if you want to use the cache (version ~1.4)",
"doctrine/annotations": "Install it if you want to use annotations (version ~1.2)",
"ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)"
}
Expand Down
8 changes: 4 additions & 4 deletions doc/container-configuration.md
Expand Up @@ -7,23 +7,23 @@ current_menu: container-configuration

## Development environment

PHP-DI's container is preconfigured for "plug'n'play", i.e. development environment. You can start using it simply like so:
PHP-DI's container is preconfigured for "plug and play", i.e. development environment. You can start using it simply like so:

```php
$container = ContainerBuilder::buildDevContainer();
// same as
// or even simpler
$container = new Container();
```

By default, PHP-DI will have [Autowiring](definition.md) enabled (annotations are disabled by default).
By default, PHP-DI will have [Autowiring](definition.md) enabled ([annotations](annotations.md) are disabled by default).

## Production environment

In production environment, you will of course favor speed:

```php
$builder = new \DI\ContainerBuilder();
$builder->setDefinitionCache(new Doctrine\Common\Cache\ApcCache());
$builder->setDefinitionCache(/* a cache */);
$builder->writeProxiesToFile(true, 'tmp/proxies');

$container = $builder->build();
Expand Down
21 changes: 21 additions & 0 deletions doc/migration/6.0.md
Expand Up @@ -37,6 +37,27 @@ If you have multiple configuration files, for example if you have built a module

The `DI\link()` function helper was deprecated in 5.0. It is now completely removed. Use `DI\get()` instead.

## Caching

PHP-DI 5 was using the Doctrine Cache library for caching definitions. Since then, PSR-6 and PSR-16 have standardized caching in PHP.

PHP-DI 6 now uses PSR-16 (the simple cache standard) instead of Doctrine. The migration should be easy:

- you can replace completely doctrine/cache with another PHP library, like the [symfony/cache](http://symfony.com/doc/current/components/cache.html) component

```php
$cache = new Symfony\Component\Cache\Simple\ApcuCache();
$containerBuilder->setDefinitionCache($cache);
```

- or else you can use the `Symfony\Component\Cache\Adapter\DoctrineAdapter` class from the [symfony/cache](http://symfony.com/doc/current/components/cache.html) component to keep using the Doctrine cache while also making it compatible with PSR-16

```php
$doctrineCache = /* your current Doctrine cache */
$cache = new Symfony\Component\Cache\Adapter\DoctrineAdapter($doctrineCache);
$containerBuilder->setDefinitionCache($cache);
```

## Internal changes

If you were overriding or extending some internal classes of PHP-DI, be aware that they may have changed.
50 changes: 22 additions & 28 deletions doc/performances.md
Expand Up @@ -9,47 +9,34 @@ current_menu: performances

PHP-DI uses the [definitions](definition.md) you configured to instantiate classes.

Reading those definitions (and, if enabled, reading annotations or autowiring) on each request can be avoided by using a cache. The caching system PHP-DI uses is the [Doctrine Cache](http://doctrine-common.readthedocs.org/en/latest/reference/caching.html) library.
Reading those definitions (and, if enabled, reading [autowiring](autowiring.md) or [annotations](annotations.md)) on each request can be avoided by using a cache.

PHP-DI is compatible with all [PSR-16](https://github.com/php-fig/simple-cache) caches (PSR-16 is a standard for PHP cache systems). To choose which library you want to use, you can have a look at [Packagist](https://packagist.org/providers/psr/simple-cache-implementation). We recommend using Symfony's cache component: [symfony/cache](https://github.com/symfony/cache).

### Setup

The Doctrine Cache library is not installed by default with PHP-DI, you need to install it with Composer:
There is no cache system installed by default with PHP-DI, you need to install it with Composer. The examples below use the Symfony cache component.

```json
{
"require": {
...
"doctrine/cache": "~1.4"
}
}
```
composer require symfony/cache
```

Then you can then pass a cache instance to the container builder:
You can then pass a cache instance to the container builder:

```php
$containerBuilder->setDefinitionCache(new Doctrine\Common\Cache\ApcCache());
$cache = new Symfony\Component\Cache\Simple\ApcuCache();
$containerBuilder->setDefinitionCache($cache);
```

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. The only cache you might use in development is the `ArrayCache` because it doesn't persist data between requests.
Heads up: do not use a cache in a development environment, else all the changes you make to the definitions (annotations, configuration files, etc.) may not be taken into account. The only cache you may use in development is `DI\Cache\ArrayCache` (which is the only cache implementation provided by PHP-DI) because it doesn't persist data between requests.

### Cache types

The cache implementation is provided by Doctrine (because it works very well) and supports the following adapters:

- `ArrayCache` (in memory, lifetime of the request)
- `ApcCache` (requires the APC or APCu extension)
- `MemcacheCache` (requires the memcache extension)
- `MemcachedCache` (requires the memcached extension)
- `RedisCache` (requires the phpredis extension)
- `FilesystemCache` (not optimal for high concurrency)
- `PhpFileCache`: incompatible with data stored by PHP-DI
- `WinCacheCache` (requires the wincache extension)
- `XcacheCache` (requires the xcache extension)
- `ZendDataCache` (requires Zend Server Platform)
Depending on the cache library you will choose, it will provide adapters to different kind of backends, for example: APCu, Memcache, Redis, Filesystem, etc.

Read the [Doctrine documentation](http://docs.doctrine-project.org/projects/doctrine-common/en/latest/reference/caching.html) for more details.
Here is the list of caches Symfony Cache supports: [supported adapters](http://symfony.com/doc/current/components/cache.html#available-simple-cache-psr-16-classes).

The recommended cache is the `ApcCache`.
In production environments, **caches based on APCu are recommended**. Given PHP-DI's architecture, there will be a cache request for each container entry you get. Remote caches (like Redis or Memcache) will most certainly have a latency too high for such low level calls and will not be efficient.

### Cache prefixes

Expand All @@ -60,8 +47,15 @@ Conflicts can also happen if an application runs on different "environments" (e.
To avoid this situation, you should use a cache "prefix": each installation of your app has a unique ID, and this ID is used to prefix cache keys
to avoid collisions.

You can also add your application's version to the cache prefix so that on each new deployment the cache for the old version is discarded.

For example with Symfony Cache:

```php
$cache = new Doctrine\Common\Cache\ApcCache();
$cache->setNamespace('MyApplication');
$environment = 'prod'; // or 'dev'
$appVersion = '...';
$namespace = 'MySuperApplication-' . $environment . '-' . $appVersion;

$cache = new Symfony\Component\Cache\Simple\ApcuCache($namespace);
$containerBuilder->setDefinitionCache($cache);
```
64 changes: 39 additions & 25 deletions src/DI/Cache/ArrayCache.php
Expand Up @@ -2,76 +2,90 @@

namespace DI\Cache;

use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\ClearableCache;
use Doctrine\Common\Cache\FlushableCache;
use Psr\SimpleCache\CacheInterface;

/**
* Simple implementation of a cache based on an array.
*
* This implementation can be used instead of Doctrine's ArrayCache for
* better performances (because simpler implementation).
* The code is based on Doctrine's ArrayCache provider.
*
* The code is based on Doctrine's ArrayCache provider:
* @see \Doctrine\Common\Cache\ArrayCache
* @link www.doctrine-project.org
* @link www.doctrine-project.org
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ArrayCache implements Cache, FlushableCache, ClearableCache
class ArrayCache implements CacheInterface
{
/**
* @var array
*/
private $data = [];

public function fetch($id)
public function get($key, $default = null)
{
// isset() is required for performance optimizations, to avoid unnecessary function calls to array_key_exists.
if (isset($this->data[$id]) || array_key_exists($id, $this->data)) {
return $this->data[$id];
if (isset($this->data[$key]) || array_key_exists($key, $this->data)) {
return $this->data[$key];
}

return false;
return $default;
}

public function contains($id)
public function has($key)
{
// isset() is required for performance optimizations, to avoid unnecessary function calls to array_key_exists.
return isset($this->data[$id]) || array_key_exists($id, $this->data);
return isset($this->data[$key]) || array_key_exists($key, $this->data);
}

public function save($id, $data, $lifeTime = 0)
public function set($key, $value, $ttl = 0)
{
$this->data[$id] = $data;
$this->data[$key] = $value;

return true;
}

public function delete($id)
public function delete($key)
{
unset($this->data[$id]);
unset($this->data[$key]);

return true;
}

public function getStats()
public function clear()
{
return null;
$this->data = [];

return true;
}

public function flushAll()
public function getMultiple($keys, $default = null)
{
$this->data = [];
$values = [];
foreach ($keys as $key) {
$values[$key] = $this->get($key, $default);
}

return $values;
}

public function setMultiple($values, $ttl = null)
{
foreach ($values as $key => $value) {
$this->data[$key] = $value;
}

return true;
}

public function deleteAll()
public function deleteMultiple($keys)
{
return $this->flushAll();
foreach ($keys as $key) {
unset($this->data[$key]);
}

return true;
}
}
8 changes: 4 additions & 4 deletions src/DI/ContainerBuilder.php
Expand Up @@ -11,9 +11,9 @@
use DI\Definition\Source\ReflectionBasedAutowiring;
use DI\Definition\Source\SourceChain;
use DI\Proxy\ProxyFactory;
use Doctrine\Common\Cache\Cache;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use Psr\SimpleCache\CacheInterface;

/**
* Helper to create and configure a Container.
Expand Down Expand Up @@ -52,7 +52,7 @@ class ContainerBuilder
private $ignorePhpDocErrors = false;

/**
* @var Cache
* @var CacheInterface
*/
private $cache;

Expand Down Expand Up @@ -204,10 +204,10 @@ public function ignorePhpDocErrors($bool)
/**
* Enables the use of a cache for the definitions.
*
* @param Cache $cache Cache backend to use
* @param CacheInterface $cache Cache backend to use
* @return ContainerBuilder
*/
public function setDefinitionCache(Cache $cache)
public function setDefinitionCache(CacheInterface $cache)
{
$this->ensureNotLocked();

Expand Down
15 changes: 6 additions & 9 deletions src/DI/Definition/Source/CachedDefinitionSource.php
Expand Up @@ -4,7 +4,7 @@

use DI\Definition\CacheableDefinition;
use DI\Definition\Definition;
use Doctrine\Common\Cache\Cache;
use Psr\SimpleCache\CacheInterface;

/**
* Caches another definition source.
Expand All @@ -25,11 +25,11 @@ class CachedDefinitionSource implements DefinitionSource
private $source;

/**
* @var Cache
* @var CacheInterface
*/
private $cache;

public function __construct(DefinitionSource $source, Cache $cache)
public function __construct(DefinitionSource $source, CacheInterface $cache)
{
$this->source = $source;
$this->cache = $cache;
Expand All @@ -55,10 +55,7 @@ public function getDefinition($name)
return $definition;
}

/**
* @return Cache
*/
public function getCache()
public function getCache() : CacheInterface
{
return $this->cache;
}
Expand All @@ -73,7 +70,7 @@ private function fetchFromCache($name)
{
$cacheKey = self::CACHE_PREFIX . $name;

$data = $this->cache->fetch($cacheKey);
$data = $this->cache->get($cacheKey, false);

if ($data !== false) {
return $data;
Expand All @@ -92,6 +89,6 @@ private function saveToCache($name, Definition $definition = null)
{
$cacheKey = self::CACHE_PREFIX . $name;

$this->cache->save($cacheKey, $definition);
$this->cache->set($cacheKey, $definition);
}
}
2 changes: 1 addition & 1 deletion tests/IntegrationTest/CacheTest.php
Expand Up @@ -2,8 +2,8 @@

namespace DI\Test\IntegrationTest;

use DI\Cache\ArrayCache;
use DI\ContainerBuilder;
use Doctrine\Common\Cache\ArrayCache;

/**
* Test caching.
Expand Down

0 comments on commit fd69d27

Please sign in to comment.