Issue #402896 by berdir, catch, andypost, dmitrig01, chx, pounard, sun, sdboyer: introduce DrupalCacheArray and use it for drupal_get_schema().
parent
3a9a4ee0e6
commit
b9f87d85b5
|
@ -225,6 +225,195 @@ define('REGISTRY_WRITE_LOOKUP_CACHE', 2);
|
|||
*/
|
||||
define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*');
|
||||
|
||||
/**
|
||||
* Provides a caching wrapper to be used in place of large array structures.
|
||||
*
|
||||
* This class should be extended by systems that need to cache large amounts
|
||||
* of data and have it represented as an array to calling functions. These
|
||||
* arrays can become very large, so ArrayAccess is used to allow different
|
||||
* strategies to be used for caching internally (lazy loading, building caches
|
||||
* over time etc.). This can dramatically reduce the amount of data that needs
|
||||
* to be loaded from cache backends on each request, and memory usage from
|
||||
* static caches of that same data.
|
||||
*
|
||||
* Note that array_* functions do not work with ArrayAccess. Systems using
|
||||
* DrupalCacheArray should use this only internally. If providing API functions
|
||||
* that return the full array, this can be cached separately or returned
|
||||
* directly. However since DrupalCacheArray holds partial content by design, it
|
||||
* should be a normal PHP array or otherwise contain the full structure.
|
||||
*
|
||||
* Note also that due to limitations in PHP prior to 5.3.4, it is impossible to
|
||||
* write directly to the contents of nested arrays contained in this object.
|
||||
* Only writes to the top-level array elements are possible. So if you
|
||||
* previously had set $object['foo'] = array(1, 2, 'bar' => 'baz'), but later
|
||||
* want to change the value of 'bar' from 'baz' to 'foobar', you cannot do so
|
||||
* a targeted write like $object['foo']['bar'] = 'foobar'. Instead, you must
|
||||
* overwrite the entire top-level 'foo' array with the entire set of new
|
||||
* values: $object['foo'] = array(1, 2, 'bar' => 'foobar'). Due to this same
|
||||
* limitation, attempts to create references to any contained data, nested or
|
||||
* otherwise, will fail silently. So $var = &$object['foo'] will not throw an
|
||||
* error, and $var will be populated with the contents of $object['foo'], but
|
||||
* that data will be passed by value, not reference. For more information on
|
||||
* the PHP limitation, see the note in the official PHP documentation at·
|
||||
* http://php.net/manual/en/arrayaccess.offsetget.php on
|
||||
* ArrayAccess::offsetGet().
|
||||
*
|
||||
* By default, the class accounts for caches where calling functions might
|
||||
* request keys in the array that won't exist even after a cache rebuild. This
|
||||
* prevents situations where a cache rebuild would be triggered over and over
|
||||
* due to a 'missing' item. These cases are stored internally as a value of
|
||||
* NULL. This means that the offsetGet() and offsetExists() methods
|
||||
* must be overridden if caching an array where the top level values can
|
||||
* legitimately be NULL, and where $object->offsetExists() needs to correctly
|
||||
* return (equivalent to array_key_exists() vs. isset()). This should not
|
||||
* be necessary in the majority of cases.
|
||||
*
|
||||
* Classes extending this class must override at least the
|
||||
* resolveCacheMiss() method to have a working implementation.
|
||||
*
|
||||
* offsetSet() is not overridden by this class by default. In practice this
|
||||
* means that assigning an offset via arrayAccess will only apply while the
|
||||
* object is in scope and will not be written back to the persistent cache.
|
||||
* This follows a similar pattern to static vs. persistent caching in
|
||||
* procedural code. Extending classes may wish to alter this behaviour, for
|
||||
* example by overriding offsetSet() and adding an automatic call to persist().
|
||||
*
|
||||
* @see SchemaCache
|
||||
*/
|
||||
abstract class DrupalCacheArray implements ArrayAccess {
|
||||
|
||||
/**
|
||||
* A cid to pass to cache_set() and cache_get().
|
||||
*/
|
||||
private $cid;
|
||||
|
||||
/**
|
||||
* A bin to pass to cache_set() and cache_get().
|
||||
*/
|
||||
private $bin;
|
||||
|
||||
/**
|
||||
* An array of keys to add to the cache at the end of the request.
|
||||
*/
|
||||
protected $keysToPersist = array();
|
||||
|
||||
/**
|
||||
* Storage for the data itself.
|
||||
*/
|
||||
protected $storage = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param $cid
|
||||
* The cid for the array being cached.
|
||||
* @param $bin
|
||||
* The bin to cache the array.
|
||||
*/
|
||||
public function __construct($cid, $bin) {
|
||||
$this->cid = $cid;
|
||||
$this->bin = $bin;
|
||||
|
||||
if ($cached = cache_get($this->cid, $this->bin)) {
|
||||
$this->storage = $cached->data;
|
||||
}
|
||||
}
|
||||
|
||||
public function offsetExists($offset) {
|
||||
return $this->offsetGet($offset) !== NULL;
|
||||
}
|
||||
|
||||
public function offsetGet($offset) {
|
||||
if (isset($this->storage[$offset]) || array_key_exists($offset, $this->storage)) {
|
||||
return $this->storage[$offset];
|
||||
}
|
||||
else {
|
||||
return $this->resolveCacheMiss($offset);
|
||||
}
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value) {
|
||||
$this->storage[$offset] = $value;
|
||||
}
|
||||
|
||||
public function offsetUnset($offset) {
|
||||
unset($this->storage[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags an offset value to be written to the persistent cache.
|
||||
*
|
||||
* If a value is assigned to a cache object with offsetSet(), by default it
|
||||
* will not be written to the persistent cache unless it is flagged with this
|
||||
* method. This allows items to be cached for the duration of a request,
|
||||
* without necessarily writing back to the persistent cache at the end.
|
||||
*
|
||||
* @param $offset
|
||||
* The array offset that was request.
|
||||
* @param $persist
|
||||
* Optional boolean to specify whether the offset should be persisted or
|
||||
* not, defaults to TRUE. When called with $persist = FALSE the offset will
|
||||
* be unflagged so that it will not written at the end of the request.
|
||||
*/
|
||||
protected function persist($offset, $persist = TRUE) {
|
||||
$this->keysToPersist[$offset] = $persist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a cache miss.
|
||||
*
|
||||
* When an offset is not found in the object, this is treated as a cache
|
||||
* miss. This method allows classes implementing the interface to look up
|
||||
* the actual value and allow it to be cached.
|
||||
*
|
||||
* @param $offset
|
||||
* The offset that was requested.
|
||||
*
|
||||
* @return
|
||||
* The value of the offset, or NULL if no value was found.
|
||||
*/
|
||||
abstract protected function resolveCacheMiss($offset);
|
||||
|
||||
/**
|
||||
* Immediately write a value to the persistent cache.
|
||||
*
|
||||
* @param $cid
|
||||
* The cache ID.
|
||||
* @param $bin
|
||||
* The cache bin.
|
||||
* @param $data
|
||||
* The data to write to the persistent cache.
|
||||
* @param $lock
|
||||
* Whether to acquire a lock before writing to cache.
|
||||
*/
|
||||
protected function set($cid, $data, $bin, $lock = TRUE) {
|
||||
// Lock cache writes to help avoid stampedes.
|
||||
// To implement locking for cache misses, override __construct().
|
||||
$lock_name = $cid . ':' . $bin;
|
||||
if (!$lock || lock_acquire($lock_name)) {
|
||||
if ($cached = cache_get($cid, $bin)) {
|
||||
$data = $cached->data + $data;
|
||||
}
|
||||
cache_set($cid, $data, $bin);
|
||||
if ($lock) {
|
||||
lock_release($lock_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$data = array();
|
||||
foreach ($this->keysToPersist as $offset => $persist) {
|
||||
if ($persist) {
|
||||
$data[$offset] = $this->storage[$offset];
|
||||
}
|
||||
}
|
||||
if (!empty($data)) {
|
||||
$this->set($this->cid, $data, $this->bin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the timer with the specified name. If you start and stop the same
|
||||
* timer multiple times, the measured intervals will be accumulated.
|
||||
|
@ -2532,6 +2721,55 @@ function ip_address() {
|
|||
* If true, the schema will be rebuilt instead of retrieved from the cache.
|
||||
*/
|
||||
function drupal_get_schema($table = NULL, $rebuild = FALSE) {
|
||||
static $schema;
|
||||
|
||||
if ($rebuild || !isset($table)) {
|
||||
$schema = drupal_get_complete_schema($rebuild);
|
||||
}
|
||||
elseif (!isset($schema)) {
|
||||
$schema = new SchemaCache();
|
||||
}
|
||||
|
||||
if (!isset($table)) {
|
||||
return $schema;
|
||||
}
|
||||
if (isset($schema[$table])) {
|
||||
return $schema[$table];
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends DrupalCacheArray to allow for dynamic building of the schema cache.
|
||||
*/
|
||||
class SchemaCache extends DrupalCacheArray {
|
||||
|
||||
public function __construct() {
|
||||
// Cache by request method.
|
||||
parent::__construct('schema:runtime:' . $_SERVER['REQUEST_METHOD'] == 'GET', 'cache');
|
||||
}
|
||||
|
||||
protected function resolveCacheMiss($offset) {
|
||||
$complete_schema = drupal_get_complete_schema();
|
||||
$value = isset($complete_schema[$offset]) ? $complete_schema[$offset] : NULL;
|
||||
$this->storage[$offset] = $value;
|
||||
$this->persist($offset);
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the whole database schema.
|
||||
*
|
||||
* The returned schema will include any modifications made by any
|
||||
* module that implements hook_schema_alter().
|
||||
*
|
||||
* @param $rebuild
|
||||
* If true, the schema will be rebuilt instead of retrieved from the cache.
|
||||
*/
|
||||
function drupal_get_complete_schema($rebuild = FALSE) {
|
||||
static $schema = array();
|
||||
|
||||
if (empty($schema) || $rebuild) {
|
||||
|
@ -2573,18 +2811,13 @@ function drupal_get_schema($table = NULL, $rebuild = FALSE) {
|
|||
if (!empty($schema) && (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL)) {
|
||||
cache_set('schema', $schema);
|
||||
}
|
||||
if ($rebuild) {
|
||||
cache_clear_all('schema:', 'cache', TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($table)) {
|
||||
return $schema;
|
||||
}
|
||||
elseif (isset($schema[$table])) {
|
||||
return $schema[$table];
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue