8000 feature #17408 [Cache] Symfony PSR-6 implementation (nicolas-grekas) · symfony/symfony@cc84be9 · GitHub
[go: up one dir, main page]

Skip to content

Commit cc84be9

Browse files
committed
feature #17408 [Cache] Symfony PSR-6 implementation (nicolas-grekas)
This PR was merged into the 3.1-dev branch. Discussion ---------- [Cache] Symfony PSR-6 implementation | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - From the provided README.md: This component provides a strict [PSR-6](http://www.php-fig.org/psr/psr-6/) implementation for adding cache to your applications. It is designed to have a low overhead so that caching is fastest. It ships with a few caching adapters for the most widespread and suited to caching backends. It also provides a `doctrine/cache` proxy adapter to cover more advanced caching needs. Todo: - [ ] add more tests - [ ] add a FilesystemAdapter Commits ------- 91e482a [Cache] Symfony PSR-6 implementation
2 parents caae21c + 91e482a commit cc84be9

21 files changed

+1138
-2
lines changed

.travis.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
$packages[$package->name][$package->version] = $package;
4141

42-
$versions = file_get_contents('https://packagist.org/packages/'.$package->name.'.json');
42+
$versions = @file_get_contents('https://packagist.org/packages/'.$package->name.'.json') ?: '{"package":{"versions":[]}}';
4343
$versions = json_decode($versions);
4444

4545
foreach ($versions->package->versions as $version => $package) {

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ install:
5151
- if [[ $deps != skip ]]; then COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi;
5252
- if [[ $deps != skip && $deps ]]; then php .travis.php $TRAVIS_COMMIT_RANGE $TRAVIS_BRANCH $COMPONENTS; fi;
5353
- if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then SYMFONY_VERSION=$(git ls-remote --heads | grep -o '/[1-9].*' | tail -n 1 | sed s/.//); else SYMFONY_VERSION=$(cat composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9.]*'); fi;
54-
- if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then git fetch origin $SYMFONY_VERSION; git checkout -m FETCH_HEAD; fi;
54+
- if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then git fetch origin $SYMFONY_VERSION; git checkout -m FETCH_HEAD; COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi;
5555
- if [[ $deps = high && ${SYMFONY_VERSION%.*} != $(git show $(git ls-remote --heads | grep -FA1 /$SYMFONY_VERSION | tail -n 1):composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9]*' | head -n 1) ]]; then LEGACY=,legacy; fi;
5656
- export COMPOSER_ROOT_VERSION=$SYMFONY_VERSION.x-dev;
5757
- if [[ ! $deps ]]; then composer update --prefer-dist; else export SYMFONY_DEPRECATIONS_HELPER=weak; fi;

composer.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"php": ">=5.5.9",
2020
"doctrine/common": "~2.4",
2121
"twig/twig": "~1.23|~2.0",
22+
"psr/cache": "~1.0",
9E19
2223
"psr/log": "~1.0",
2324
"symfony/polyfill-apcu": "~1.0,>=1.0.2",
2425
"symfony/polyfill-intl-icu": "~1.0",
@@ -30,6 +31,7 @@
3031
"replace": {
3132
"symfony/asset": "self.version",
3233
"symfony/browser-kit": "self.version",
34+
"symfony/cache": "self.version",
3335
"symfony/class-loader": "self.version",
3436
"symfony/config": "self.version",
3537
"symfony/console": "self.version",
@@ -74,6 +76,8 @@
7476
"symfony/yaml": "self.version"
7577
},
7678
"require-dev": {
79+
"cache/integration-tests": "^0.6",
80+
"doctrine/cache": "~1.6",
7781
"doctrine/data-fixtures": "1.0.*",
7882
"doctrine/dbal": "~2.4",
7983
"doctrine/orm": "~2.4,>=2.4.5",

phpunit.xml.dist

Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener">
5555
<arguments>
5656
<array>
57+
<element><string>Cache\IntegrationTests</string></element>
58+
<element><string>Doctrine\Common\Cache</string></element>
5759
<element><string>Symfony\Component\HttpFoundation</string></element>
5860
</array>
5961
</arguments>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
composer.lock
2+
phpunit.xml
3+
vendor/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Adapter;
13+
14+
use Psr\Cache\CacheItemInterface;
15+
use Psr\Cache\CacheItemPoolInterface;
16+
use Symfony\Component\Cache\CacheItem;
17+
use Symfony\Component\Cache\Exception\InvalidArgumentException;
18+
19+
/**
20+
* @author Nicolas Grekas <p@tchwork.com>
21+
*/
22+
abstract class AbstractAdapter implements CacheItemPoolInterface
23+
{
24+
private $namespace;
25+
private $deferred = array();
26+
private $createCacheItem;
27+
private $mergeByLifetime;
28+
29+
protected function __construct($namespace = '', $defaultLifetime = 0)
30+
{
31+
$this->namespace = $namespace;
32+
$this->createCacheItem = \Closure::bind(
33+
function ($key, $value, $isHit) use ($defaultLifetime) {
34+
$item = new CacheItem();
35+
$item->key = $key;
36+
$item->value = $value;
37+
$item->isHit = $isHit;
38+
$item->defaultLifetime = $defaultLifetime;
39+
40+
return $item;
41+
},
42+
$this,
43+
CacheItem::class
44+
);
45+
$this->mergeByLifetime = \Closure::bind(
46+
function ($deferred, $namespace) {
47+
$byLifetime = array();
48+
49+
foreach ($deferred as $key => $item) {
50+
if (0 <= $item->lifetime) {
51+
$byLifetime[(int) $item->lifetime][$namespace.$key] = $item->value;
52+
}
53+
}
54+
55+
return $byLifetime;
56+
},
57+
$this,
58+
CacheItem::class
59+
);
60+
}
61+
62+
/**
63+
* Fetches several cache items.
64+
*
65+
* @param array $ids The cache identifiers to fetch.
66+
*
67+
* @return array The corresponding values found in the cache.
68+
*/
69+
abstract protected function doFetch(array $ids);
70+
71+
/**
72+
* Confirms if the cache contains specified cache item.
73+
*
74+
* @param string $id The identifier for which to check existence.
75+
*
76+
* @return bool True if item exists in the cache, false otherwise.
77+
*/
78+
abstract protected function doHave($id);
79+
80+
/**
81+
* Deletes all items in the pool.
82+
*
83+
* @return bool True if the pool was successfully cleared, false otherwise.
84+
*/
85+
abstract protected function doClear();
86+
87+
/**
88+
* Removes multiple items from the pool.
89+
*
90+
* @param array $ids An array of identifiers that should be removed from the pool.
91+
*
92+
* @return bool True if the items were successfully removed, false otherwise.
93+
*/
94+
abstract protected function doDelete(array $ids);
95+
96+
/**
97+
* Persists several cache items immediately.
98+
*
99+
* @param array $values The values to cache, indexed by their cache identifier.
100+
* @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning.
101+
*
102+
* @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not.
103+
*/
104+
abstract protected function doSave(array $values, $lifetime);
105+
106+
/**
107+
* {@inheritdoc}
108+
*/
109+
public function getItem($key)
110+
{
111+
$id = $this->getId($key);
112+
113+
if ($this->deferred) {
114+
$this->commit();
115+
}
116+
if (isset($this->deferred[$key])) {
117+
return $this->deferred[$key];
118+
}
119+
120+
$f = $this->createCacheItem;
121+
$isHit = false;
122+
$value = null;
123+
124+
foreach ($this->doFetch(array($id)) as $value) {
125+
$isHit = true;
126+
}
127+
128+
return $f($key, $value, $isHit);
129+
}
130+
131+
/**
132+
* {@inheritdoc}
133+
*/
134+
public function getItems(array $keys = array())
135+
{
136+
if ($this->deferred) {
137+
$this->commit();
138+
}
139+
$f = $this->createCacheItem;
140+
$ids = array();
141+
$items = array();
142+
143+
foreach ($keys as $key) {
144+
$id = $this->getId($key);
145+
146+
if (isset($this->deferred[$key])) {
147+
$items[$key] = $this->deferred[$key];
148+
} else {
149+
$ids[$key] = $id;
150+
}
151+
}
152+
153+
$values = $this->doFetch($ids);
154+
155+
foreach ($ids as $key => $id) {
156+
$isHit = isset($values[$id]);
157+
$items[$key] = $f($key, $isHit ? $values[$id] : null, $isHit);
158+
}
159+
160+
return $items;
161+
}
162+
163+
/**
164+
* {@inheritdoc}
165+
*/
166+
public function hasItem($key)
167+
{
168+
if ($this->deferred) {
169+
$this->commit();
170+
}
171+
172+
return $this->doHave($this->getId($key));
173+
}
174+
175+
/**
176+
* {@inheritdoc}
177+
*/
178+
public function clear()
179+
{
180+
$this->deferred = array();
181+
182+
return $this->doClear();
183+
}
184+
185+
/**
186+
* {@inheritdoc}
187+
*/
188+
public function deleteItem($key)
189+
{
190+
return $this->deleteItems(array($key));
191+
}
192+
193+
/**
194+
* {@inheritdoc}
195+
*/
196+
public function deleteItems(array $keys)
197+
{
198+
$ids = array();
199+
200+
foreach ($keys as $key) {
201+
$ids[] = $this->getId($key);
202+
unset($this->deferred[$key]);
203+
}
204+
205+
return $this->doDelete($ids);
206+
}
207+
208+
/**
209+
* {@inheritdoc}
210+
*/
211+
public function save(CacheItemInterface $item)
212+
{
213+
if (!$item instanceof CacheItem) {
214+
return false;
215+
}
216+
$key = $item->getKey();
217+
$this->deferred[$key] = $item;
218+
$this->commit();
219+
220+
return !isset($this->deferred[$key]);
221+
}
222+
223+
/**
224+
* {@inheritdoc}
225+
*/
226+
public function saveDeferred(CacheItemInterface $item)
227+
{
228+
if (!$item instanceof CacheItem) {
229+
return false;
230+
}
231+
try {
232+
$item = clone $item;
233+
} catch (\Error $e) {
234+
} catch (\Exception $e) {
235+
}
236+
if (isset($e)) {
237+
@trigger_error($e->__toString());
238+
239+
return false;
240+
}
241+
$this->deferred[$item->getKey()] = $item;
242+
243+
return true;
244+
}
245+
246+
/**
247+
* {@inheritdoc}
248+
*/
249+
public function commit()
250+
{
251+
$f = $this->mergeByLifetime;
252+
$ko = array();
253+
$namespaceLen = strlen($this->namespace);
254+
255+
foreach ($f($this->deferred, $this->namespace) as $lifetime => $values) {
256+
if (true === $ok = $this->doSave($values, $lifetime)) {
257+
continue;
258+
}
259+
if (false === $ok) {
260+
$ok = array_keys($values);
261+
}
262+
foreach ($ok as $failedId) {
263+
$key = substr($failedId, $namespaceLen);
264+
$ko[$key] = $this->deferred[$key];
265+
}
266+
}
267+
268+
return !$this->deferred = $ko;
269+
}
270+
271+
public function __destruct()
272+
{
273+
if ($this->deferred) {
274+
$this->commit();
275+
}
276+
}
277+
278+
private function getId($key)
279+
{
280+
if (!is_string($key)) {
281+
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given', is_object($key) ? get_class($key) : gettype($key)));
282+
}
283+
if (!isset($key[0])) {
284+
throw new InvalidArgumentException('Cache key length must be greater than zero');
285+
}
286+
if (isset($key[strcspn($key, '{}()/\@:')])) {
287+
throw new InvalidArgumentException('Cache key contains reserved characters {}()/\@:');
288+
}
289+
290+
return $this->namespace.$key;
291+
}
292+
}