View source
<?php
namespace Drupal\supercache\Cache;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\SchemaObjectExistsException;
use Drupal\Core\Cache\CacheTagsChecksumInterface;
class DatabaseRawBackend implements CacheRawBackendInterface {
use RequestTimeTrait;
protected $bin;
protected $connection;
public function __construct(Connection $connection, $bin) {
$bin = 'rawcache_' . $bin;
$this->bin = $bin;
$this->connection = $connection;
$this
->refreshRequestTime();
}
public function get($cid) {
$cids = array(
$cid,
);
$cache = $this
->getMultiple($cids);
return reset($cache);
}
public function getMultiple(&$cids) {
$cid_mapping = array();
foreach ($cids as $cid) {
$cid_mapping[$this
->normalizeCid($cid)] = $cid;
}
$result = array();
try {
$result = $this->connection
->query('SELECT cid, data_serialized, data_string, data_int, data_float, expire, storage FROM {' . $this->connection
->escapeTable($this->bin) . '} WHERE cid IN ( :cids[] ) AND (expire > :expire OR expire = :expire_permanent) ORDER BY cid', array(
':cids[]' => array_keys($cid_mapping),
':expire' => (int) $this->requestTime,
':expire_permanent' => (int) CacheRawBackendInterface::CACHE_PERMANENT,
));
} catch (\Exception $e) {
}
$cache = array();
foreach ($result as $item) {
$item->cid = $cid_mapping[$item->cid];
$item = $this
->prepareItem($item);
if ($item) {
$cache[$item->cid] = $item;
}
}
$cids = array_diff($cids, array_keys($cache));
return $cache;
}
protected function prepareItem($cache) {
$valid = $cache->expire == CacheRawBackendInterface::CACHE_PERMANENT || $cache->expire >= $this->requestTime;
if (!$valid) {
return FALSE;
}
switch ($cache->storage) {
case 0:
if ($cache->data_serialized === NULL) {
return FALSE;
}
$cache->data = unserialize($cache->data_serialized);
break;
case 1:
$cache->data = $cache->data_string;
break;
case 2:
if ($cache->data_int === NULL) {
return FALSE;
}
$cache->data = (int) $cache->data_int;
break;
case 3:
if ($cache->data_float === NULL) {
return FALSE;
}
$cache->data = (double) $cache->data_float;
break;
default:
throw new \Exception("Storage type not supported. Somethign went wrong.");
}
unset($cache->data_serialized);
unset($cache->data_string);
unset($cache->data_int);
unset($cache->data_float);
unset($cache->storage);
unset($cache->expire);
return $cache;
}
public function set($cid, $data, $expire = CacheRawBackendInterface::CACHE_PERMANENT) {
$this
->setMultiple([
$cid => [
'data' => $data,
'expire' => $expire,
],
]);
}
public function setMultiple(array $items) {
$try_again = FALSE;
try {
$this
->doSetMultiple($items);
} catch (\Exception $e) {
if (!($try_again = $this
->ensureBinExists())) {
throw $e;
}
}
if ($try_again) {
$this
->doSetMultiple($items);
}
}
protected function prepareStorage($cid, $data, $expire) {
$fields = array(
'cid' => $this
->normalizeCid($cid),
'expire' => $expire,
);
$fields['data_serialized'] = NULL;
$fields['data_string'] = NULL;
$fields['data_int'] = NULL;
$fields['data_float'] = NULL;
if (is_bool($data) || is_int($data)) {
$fields['data_int'] = $data;
$fields['storage'] = 2;
}
elseif (is_float($data)) {
$fields['data_float'] = $data;
$fields['storage'] = 3;
}
elseif (is_string($data)) {
$fields['data_string'] = $data;
$fields['storage'] = 1;
}
else {
$fields['data_serialized'] = serialize($data);
$fields['storage'] = 0;
}
return $fields;
}
protected function doSetMultiple(array $items) {
$values = array();
foreach ($items as $cid => $item) {
$item += array(
'expire' => CacheRawBackendInterface::CACHE_PERMANENT,
);
$values[] = $this
->prepareStorage($cid, $item['data'], $item['expire']);
}
$query = $this->connection
->upsert($this->bin)
->key('cid')
->fields(array(
'cid',
'expire',
'data_serialized',
'data_string',
'data_int',
'data_float',
'storage',
));
foreach ($values as $fields) {
$query
->values(array_values($fields));
}
$query
->execute();
}
public function delete($cid) {
$this
->deleteMultiple(array(
$cid,
));
}
public function deleteMultiple(array $cids) {
$cids = array_values(array_map(array(
$this,
'normalizeCid',
), $cids));
try {
foreach (array_chunk($cids, 1000) as $cids_chunk) {
$this->connection
->delete($this->bin)
->condition('cid', $cids_chunk, 'IN')
->execute();
}
} catch (\Exception $e) {
if (!$this
->ensureBinExists()) {
$this
->catchException($e);
}
}
}
public function deleteAll() {
try {
$this->connection
->truncate($this->bin)
->execute();
} catch (\Exception $e) {
if (!$this
->ensureBinExists()) {
$this
->catchException($e);
}
}
}
public function garbageCollection() {
try {
$this->connection
->delete($this->bin)
->condition('expire', CacheRawBackendInterface::CACHE_PERMANENT, '<>')
->condition('expire', $this->requestTime, '<')
->execute();
} catch (\Exception $e) {
}
}
public function removeBin() {
try {
$this->connection
->schema()
->dropTable($this->bin);
} catch (\Exception $e) {
$this
->catchException($e);
}
}
public function counter($cid, $increment, $default = 0) {
$try_again = FALSE;
try {
$this
->doCounter($cid, $increment, $default);
} catch (\Exception $e) {
if (!($try_again = $this
->ensureBinExists())) {
throw $e;
}
}
if ($try_again) {
$this
->doCounter($cid, $increment, $default);
}
}
protected function doCounter($cid, $increment, $default = 0) {
$query = $this->connection
->update($this->bin);
$query
->condition('cid', $cid);
$query
->condition('data_int', NULL, 'IS NOT NULL');
$query
->expression('data_int', "data_int + {$increment}");
$result = 0;
try {
$result = $query
->execute();
} catch (\Exception $e) {
}
if ($result == 0) {
$query = $this->connection
->select($this->bin);
$query
->addField($this->bin, 'cid');
$query
->condition('cid', $this
->normalizeCid($cid));
$count = count($query
->execute()
->fetchAll());
if ($count == 1) {
throw new \Exception("Counter failed.");
}
$this
->counterSet($cid, $default);
}
}
public function counterMultiple(array $cids, $increment, $default = 0) {
foreach ($cids as $cid) {
$this
->counter($cid, $increment, $default);
}
}
public function counterSet($cid, $value) {
$this
->set($cid, (int) $value);
}
public function counterSetMultiple(array $items) {
foreach ($items as $cid => $item) {
$this
->counterSet($cid, (int) $item);
}
}
public function counterGet($cid) {
if ($result = $this
->get($cid)) {
return (int) $result->data;
}
return FALSE;
}
public function counterGetMultiple(array &$cids) {
$results = $this
->getMultiple($cids);
$counters = [];
foreach ($results as $cid => $item) {
$counters[$cid] = (int) $item->data;
}
return $counters;
}
protected function ensureBinExists() {
try {
$database_schema = $this->connection
->schema();
if (!$database_schema
->tableExists($this->bin)) {
$schema_definition = $this
->schemaDefinition();
$database_schema
->createTable($this->bin, $schema_definition);
return TRUE;
}
} catch (SchemaObjectExistsException $e) {
return TRUE;
}
return FALSE;
}
protected function catchException(\Exception $e, $table_name = NULL) {
if ($this->connection
->schema()
->tableExists($table_name ?: $this->bin)) {
throw $e;
}
}
protected function normalizeCid($cid) {
$cid_is_ascii = mb_check_encoding($cid, 'ASCII');
if (strlen($cid) <= 255 && $cid_is_ascii) {
return $cid;
}
$hash = Crypt::hashBase64($cid);
if (!$cid_is_ascii) {
return $hash;
}
return substr($cid, 0, 255 - strlen($hash)) . $hash;
}
public function schemaDefinition() {
$schema = array(
'description' => 'Storage for the cache API.',
'fields' => array(
'cid' => array(
'description' => 'Primary Key: Unique cache ID.',
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
'binary' => TRUE,
),
'data_serialized' => array(
'description' => 'Cache when data is serialized',
'type' => 'blob',
'not null' => FALSE,
'size' => 'big',
),
'data_string' => array(
'description' => 'Cache data when string.',
'type' => 'text',
'not null' => FALSE,
'size' => 'big',
),
'data_int' => array(
'description' => 'Cache data when integer.',
'type' => 'int',
'not null' => FALSE,
'size' => 'big',
),
'data_float' => array(
'description' => 'Cache data when float',
'type' => 'float',
'not null' => FALSE,
),
'expire' => array(
'description' => 'A Unix timestamp indicating when the cache entry should expire, or ' . CacheRawBackendInterface::CACHE_PERMANENT . ' for never.',
'type' => 'int',
'not null' => TRUE,
'size' => 'big',
'default' => 0,
),
'storage' => array(
'description' => 'A flag to indicate the storage type: 0 => serialized, 1 => string, 2 => integer, 3 => float',
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
),
),
'indexes' => array(
'expire' => array(
'expire',
),
),
'primary key' => array(
'cid',
),
);
return $schema;
}
}