Skip to content

Instantly share code, notes, and snippets.

@mms-uret
Created September 25, 2018 08:23
Show Gist options
  • Save mms-uret/0f7d488df11f29912ca3cd64ec54b7dd to your computer and use it in GitHub Desktop.
Save mms-uret/0f7d488df11f29912ca3cd64ec54b7dd to your computer and use it in GitHub Desktop.
PdoTagAwareAdapter for ezp
<?php
namespace SRF\CMSSiteBundle\Cache;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\ParameterType;
use Doctrine\ORM\EntityManager;
use PDO;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\InvalidArgumentException;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
class PdoTagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, ResettableInterface
{
use ProxyTrait;
/** @var AdapterInterface */
private $pool;
/** @var Connection */
private $connection;
/** @var CacheItem[] */
private $deferred = [];
/** @var \Closure */
private $getTagsFromCacheItem;
public function __construct(AdapterInterface $pool, Connection $connection)
{
$this->pool = $pool;
$this->connection = $connection;
$this->getTagsFromCacheItem = \Closure::bind(
function (CacheItem $item) {
return $item->tags;
},
null,
CacheItem::class
);
}
/**
* @return CacheItem
*/
public function getItem($key)
{
foreach ($this->getItems(array($key)) as $item) {
return $item;
}
}
/**
* @return \Traversable|CacheItem[]
*/
public function getItems(array $keys = array())
{
return $this->pool->getItems($keys);
}
/**
* Confirms if the cache contains specified cache item.
*
* Note: This method MAY avoid retrieving the cached value for performance reasons.
* This could result in a race condition with CacheItemInterface::get(). To avoid
* such situation use CacheItemInterface::isHit() instead.
*
* @param string $key
* The key for which to check existence
*
* @throws invalidArgumentException
* If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
* MUST be thrown
*
* @return bool
* True if item exists in the cache, false otherwise
*/
public function hasItem($key)
{
return $this->pool->hasItem($key);
}
/**
* Deletes all items in the pool.
*
* @return bool
* True if the pool was successfully cleared. False if there was an error.
*/
public function clear()
{
return $this->pool->clear();
}
/**
* Removes the item from the pool.
*
* @param string $key
* The key to delete
*
* @throws invalidArgumentException
* If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
* MUST be thrown
*
* @return bool
* True if the item was successfully removed. False if there was an error.
*/
public function deleteItem($key)
{
return $this->pool->deleteItem($key);
}
/**
* Removes multiple items from the pool.
*
* @param string[] $keys
* An array of keys that should be removed from the pool
*
* @throws invalidArgumentException
* If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException
* MUST be thrown
*
* @return bool
* True if the items were successfully removed. False if there was an error.
*/
public function deleteItems(array $keys)
{
return $this->pool->deleteItems($keys);
}
/**
* Persists a cache item immediately.
*
* @param cacheItemInterface $item
* The cache item to save
*
* @return bool
* True if the item was successfully persisted. False if there was an error.
*/
public function save(CacheItemInterface $item)
{
if (!$item instanceof CacheItem) {
return false;
}
$this->deferred[$item->getKey()] = $item;
return $this->commit();
}
/**
* Sets a cache item to be persisted later.
*
* @param cacheItemInterface $item
* The cache item to save
*
* @return bool
* False if the item could not be queued or if a commit was attempted and failed. True otherwise.
*/
public function saveDeferred(CacheItemInterface $item)
{
if (!$item instanceof CacheItem) {
return false;
}
$this->deferred[$item->getKey()] = $item;
return true;
}
/**
* Persists any deferred cache items.
*
* @return bool
* True if all not-yet-saved items were successfully saved or there were none. False otherwise.
*/
public function commit()
{
$f = $this->getTagsFromCacheItem;
$statement = $this->connection->prepare('INSERT IGNORE INTO srf_cache_tag (cache_key, cache_tag) VALUES (:cache_key, :cache_tag)');
foreach ($this->deferred as $key => $item) {
$this->pool->saveDeferred($item);
$statement->bindValue(':cache_key', $key);
foreach ($f($item) as $tag) {
if ($this->shouldICare($tag)) {
$statement->bindValue(':cache_tag', $tag);
try {
$statement->execute();
} catch (\Exception $e) {
//so sorry, so sad
}
}
}
}
$this->pool->commit();
$this->deferred = [];
return true;
}
/**
* Invalidates cached items using tags.
*
* @param string[] $tags An array of tags to invalidate
*
* @return bool True on success
*
* @throws InvalidArgumentException When $tags is not valid
*/
public function invalidateTags(array $tags)
{
$tags = array_unique($tags);
$tags = array_filter($tags, function($key) {
return $this->shouldICare($key);
});
$taintedHash = uniqid();
$this->connection->executeUpdate('UPDATE srf_cache_tag SET tainted_hash = ? WHERE cache_tag IN (?)',
array($taintedHash, $tags),
array(ParameterType::STRING, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
);
$stmt = $this->connection->executeQuery('SELECT DISTINCT cache_key FROM srf_cache_tag WHERE tainted_hash = ?',
array($taintedHash),
array(ParameterType::STRING)
);
$stmt->execute();
$keys = $stmt->fetchAll(PDO::FETCH_COLUMN);
if ($this->pool->deleteItems($keys)) {
$this->connection->executeUpdate('DELETE FROM srf_cache_tag WHERE tainted_hash = ?',
array($taintedHash),
array(ParameterType::STRING)
);
}
return true;
}
private function shouldICare($key) {
return preg_match('/^content-[0-9]+$/', $key);
}
public function __destruct()
{
$this->commit();
}
}
@andrerom
Copy link

Thanks for sharing!

Wondering if some sort of bulk insert would help here at all to avoid the loops of loops for insert, ref:
INSERT IGNORE INTO srf_cache_tag (cache_key, cache_tag) VALUES (:cache_key1, :cache_tag1), (:cache_key2, :cache_tag2), ...

But as there is nothing yet native in doctrine for that we would need to deal with it ourselves to not exceed limits on number of value pairs to insert. And maybe find better wya to bind the values then what I gave in example above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment