-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[2.2][WIP] Cache Component #3211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Cache; | ||
|
||
/** | ||
* APC Cache implementation | ||
* | ||
* Very simple implementation that can be used as default with various Symfony components | ||
* that support caching, such as Validation, ClassLoader. | ||
* | ||
* @author Benjamin Eberlei <kontakt@beberlei.de> | ||
*/ | ||
class ApcCache implements CacheInterface | ||
{ | ||
public function __construct() | ||
{ | ||
if (!extension_loaded('apc')) { | ||
throw new \RuntimeException("You need the APC php extension installed to use this cache driver."); | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function fetch($id) | ||
{ | ||
return apc_fetch($id); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function contains($id) | ||
{ | ||
$found = false; | ||
|
||
apc_fetch($id, $found); | ||
|
||
return $found; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function save($id, $data, $lifeTime = 0) | ||
{ | ||
return (bool) apc_store($id, $data, (int) $lifeTime); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function delete($id) | ||
{ | ||
return apc_delete($id); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Cache; | ||
|
||
/** | ||
* Generic Caching interface that any caching provider can implement/provide. | ||
* | ||
* Used inside Symfony is suggested to be used by any third-party | ||
* bundle to allow central cache management. | ||
* | ||
* @author Benjamin Eberlei <kontakt@beberlei.de> | ||
*/ | ||
interface CacheInterface | ||
{ | ||
/** | ||
* Fetches an entry from the cache. | ||
* | ||
* @param string $id cache id The id of the cache entry to fetch. | ||
* @return string The cached data or FALSE, if no cache entry exists for the given id. | ||
*/ | ||
function fetch($id); | ||
|
||
/** | ||
* Test if an entry exists in the cache. | ||
* | ||
* @param string $id cache id The cache id of the entry to check for. | ||
* @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise. | ||
*/ | ||
function contains($id); | ||
|
||
/** | ||
* Puts data into the cache. | ||
* | ||
* @param string $id The cache id. | ||
* @param string $data The cache entry/data. | ||
* @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this cache entry (0 => infinite lifeTime). | ||
* @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise. | ||
*/ | ||
function save($id, $data, $lifeTime = 0); | ||
|
||
/** | ||
* Deletes a cache entry. | ||
* | ||
* @param string $id cache id | ||
* @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise. | ||
*/ | ||
function delete($id); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Symfony Cache | ||
|
||
This component provides a very simple cache interface to be used | ||
with other Symfony components the framework or third party bundles. | ||
Complexity in the cache interface is intentionaly left out to provide | ||
a common denominator that clients of this interface can rely on. | ||
|
||
A lightweight APC implementation is also shipped to support the most | ||
commonly available caching implementation. | ||
|
||
For other cache drivers or more complex caching needs you should | ||
use any cache provider that has an implementation of the | ||
`Symfony\Component\Cache\CacheInterface`. | ||
|
||
If you are using Doctrine\Common in your project you can use the | ||
delegate cache driver `Symfony\Component\Cache\DoctrineCache`. | ||
|
||
## Usage | ||
|
||
use Symfony\Component\Cache\ApcCache; | ||
|
||
$cache = new ApcCache(); | ||
$cache->save("key", "value"); | ||
|
||
if ($cache->contains("key")) { | ||
echo $cache->fetch("key"); | ||
} | ||
$cache->delete("key"); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"name": "symfony/cache", | ||
"type": "library", | ||
"description": "Symfony Cache Component", | ||
"keywords": [], | ||
"homepage": "http://symfony.com", | ||
"version": "2.1.0", | ||
"license": "MIT", | ||
"authors": [ | ||
{ | ||
"name": "Fabien Potencier", | ||
"email": "fabien@symfony.com" | ||
}, | ||
{ | ||
"name": "Symfony Community", | ||
"homepage": "http://symfony.com/contributors" | ||
} | ||
], | ||
"require": { | ||
"php": ">=5.3.2" | ||
}, | ||
"autoload": { | ||
"psr-0": { "Symfony\\Component\\Cache": "" } | ||
}, | ||
"target-dir": "Symfony/Component/Cache" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\ClassLoader; | ||
|
||
use Symfony\Component\Cache\CacheInterface; | ||
|
||
/** | ||
* CacheUniversalClassLoader implements a "universal" autoloader cached in any | ||
* Symfony Cache implementation | ||
* | ||
* It is able to load classes that use either: | ||
* | ||
* * The technical interoperability standards for PHP 5.3 namespaces and | ||
* class names (https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md); | ||
* | ||
* * The PEAR naming convention for classes (http://pear.php.net/). | ||
* | ||
* Classes from a sub-namespace or a sub-hierarchy of PEAR classes can be | ||
* looked for in a list of locations to ease the vendoring of a sub-set of | ||
* classes for large projects. | ||
* | ||
* Example usage: | ||
* | ||
* require 'vendor/symfony/src/Symfony/Component/Cache/CacheInterface.php'; | ||
* require 'vendor/symfony/src/Symfony/Component/Cache/ApcCache.php'; | ||
* require 'vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php'; | ||
* require 'vendor/symfony/src/Symfony/Component/ClassLoader/CacheUniversalClassLoader.php'; | ||
* | ||
* use Symfony\Component\ClassLoader\ApcUniversalClassLoader; | ||
* use Symfony\Component\Cache\ApcCache; | ||
* | ||
* $apc = new ApcCache(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this implies requiring the cache implementation and the interface first |
||
* $loader = new CacheUniversalClassLoader($cache, 'cache.prefix.'); | ||
* | ||
* // register classes with namespaces | ||
* $loader->registerNamespaces(array( | ||
* 'Symfony\Component' => __DIR__.'/component', | ||
* 'Symfony' => __DIR__.'/framework', | ||
* 'Sensio' => array(__DIR__.'/src', __DIR__.'/vendor'), | ||
* )); | ||
* | ||
* // register a library using the PEAR naming convention | ||
* $loader->registerPrefixes(array( | ||
* 'Swift_' => __DIR__.'/Swift', | ||
* )); | ||
* | ||
* // activate the autoloader | ||
* $loader->register(); | ||
* | ||
* In this example, if you try to use a class in the Symfony\Component | ||
* namespace or one of its children (Symfony\Component\Console for instance), | ||
* the autoloader will first look for the class under the component/ | ||
* directory, and it will then fallback to the framework/ directory if not | ||
* found before giving up. | ||
* | ||
* @author Fabien Potencier <fabien@symfony.com> | ||
* @author Kris Wallsmith <kris@symfony.com> | ||
* @author Benjamin Eberlei <kontakt@beberlei.de> | ||
* | ||
* @api | ||
*/ | ||
class CacheUniversalClassLoader extends UniversalClassLoader | ||
{ | ||
private $cache; | ||
private $prefix; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param string $prefix A prefix to create a namespace in APC | ||
* | ||
* @api | ||
*/ | ||
public function __construct(CacheInterface $cache, $prefix) | ||
{ | ||
$this->cache = $cache; | ||
$this->prefix = $prefix; | ||
} | ||
|
||
/** | ||
* Finds a file by class name while caching lookups to APC. | ||
* | ||
* @param string $class A class name to resolve to file | ||
*/ | ||
public function findFile($class) | ||
{ | ||
if (false === $file = $this->cache->fetch 10000 ($this->prefix.$class)) { | ||
$this->cache->save($this->prefix.$class, $file = parent::findFile($class)); | ||
} | ||
|
||
return $file; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Validator\Mapping\Cache; | ||
|
||
use Symfony\Component\Validator\Mapping\ClassMetadata; | ||
use Symfony\Component\Cache\CacheInterface as ComponentCacheInterface; | ||
|
||
/** | ||
* Persists ClassMetadata instances with Symfony Cache Component | ||
* | ||
* @author Benjamin Eberlei <kontakt@beberlei.de> | ||
*/ | ||
class SymfonyCache implements CacheInterface | ||
{ | ||
private $cache; | ||
private $prefix; | ||
|
||
public function __construct(ComponentCacheInterface $cache, $prefix) | ||
{ | ||
$this->cache = $cache; | ||
$this->prefix = $prefix; | ||
} | ||
|
||
/** | ||
* Returns whether metadata for the given class exists in the cache | ||
* | ||
* @param string $class | ||
*/ | ||
public function has($class) | ||
{ | ||
return $this->cache->contains($this->prefix.$class); | ||
} | ||
|
||
/** | ||
* Returns the metadata for the given class from the cache | ||
* | ||
* @param string $class Class Name | ||
* | ||
* @return ClassMetadata | ||
*/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should use |
||
public function read($class) | ||
{ | ||
return $this->cache->fetch($this->prefix.$class); | ||
} | ||
|
||
/** | ||
* Stores a class metadata in the cache | ||
* | ||
* @param ClassMetadata $metadata A Class Metadata | ||
*/ | ||
public function write(ClassMetadata $metadata) | ||
{ | ||
$this->cache->save($this->prefix.$metadata->getClassName(), $metadata); | ||
} | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there is an extra line at the end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree with the idea of returning
false
if not found: what if data is boolean?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if data is NULL? same problem, fetch($id, $found) is not supported by all cache drivers though.
Thats what ->contains is for. The problem here is that we have to find the most common denominator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exceptions make sense here imo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @alexandresalome disagreeing here, the first requirement should be the ability to store any type of value (this is one of the PR I have submitted against Doctrine common.)
So the interface could look like:
I think there is one more problem with providing an interface with both
fetch
andcontains
as it encourages flawed code asSo what about dropping
contains
?fetch
would returnstrue
when the value is in cache,false
when you really need thecontains
behaviorThe above example would become:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neither Memcache, Xcache nor Zends Cache support an API for checking if a key really exists or not. (Memcached does support it). An API that works by reference for the values is really unintuitive, an API interface should take expectations of users into account.
Throwing an exception is misusing exceptions as a control-flow and will lead to ugly code in practice. I guess
fetch($id, &$found)
does make sense and could get rid of contains(), but personally saving false/null/emptystring into a cache entry screams for trouble anyways.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rande see the discussion page as things like that have already been suggested. And IMO, users should not have to bother with an extra object wrapping the data to be able to store them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vicb
I commented in the pull request and it's somewhat relevant to this. The basic idea in the request was that I agreed with @stof that we should consider the CacheInterface and the DriverInterface as separate pieces. The reason I bring this up is that my answer depends on the approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tedivm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On object can be present and stale, at which point it would be considered a miss even though it still exists. If you let the higher level caching layer that the drives feed into make the decisions about what to do with stale data (reuse it, discard it, etc) then you have a very powerful tool. Stash has some examples of this.
To be clear, the TTL I'm referring to is the remaining TTL for that specific object. If the start TTL was 180 seconds, but only 20 seconds remain to expire, then it would be very useful to know that. Additionally it may be useful to know that it expired 120 minutes ago- using a datetime (or timestamp) internally for that expire date is important.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your first reply also answered my second question. That's a very interesting discussion, many thanks for you inputs ! I really need to find some time to take a deeper look at Stash