|
8 | 8 | /**
|
9 | 9 | * The current system version.
|
10 | 10 | */
|
11 |
| -define('VERSION', '7.7'); |
| 11 | +define('VERSION', '7.8'); |
12 | 12 |
|
13 | 13 | /**
|
14 | 14 | * Core API compatibility.
|
@@ -225,6 +225,195 @@ define('REGISTRY_WRITE_LOOKUP_CACHE', 2);
|
225 | 225 | */
|
226 | 226 | define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*');
|
227 | 227 |
|
| 228 | +/** |
| 229 | + * Provides a caching wrapper to be used in place of large array structures. |
| 230 | + * |
| 231 | + * This class should be extended by systems that need to cache large amounts |
| 232 | + * of data and have it represented as an array to calling functions. These |
| 233 | + * arrays can become very large, so ArrayAccess is used to allow different |
| 234 | + * strategies to be used for caching internally (lazy loading, building caches |
| 235 | + * over time etc.). This can dramatically reduce the amount of data that needs |
| 236 | + * to be loaded from cache backends on each request, and memory usage from |
| 237 | + * static caches of that same data. |
| 238 | + * |
| 239 | + * Note that array_* functions do not work with ArrayAccess. Systems using |
| 240 | + * DrupalCacheArray should use this only internally. If providing API functions |
| 241 | + * that return the full array, this can be cached separately or returned |
| 242 | + * directly. However since DrupalCacheArray holds partial content by design, it |
| 243 | + * should be a normal PHP array or otherwise contain the full structure. |
| 244 | + * |
| 245 | + * Note also that due to limitations in PHP prior to 5.3.4, it is impossible to |
| 246 | + * write directly to the contents of nested arrays contained in this object. |
| 247 | + * Only writes to the top-level array elements are possible. So if you |
| 248 | + * previously had set $object['foo'] = array(1, 2, 'bar' => 'baz'), but later |
| 249 | + * want to change the value of 'bar' from 'baz' to 'foobar', you cannot do so |
| 250 | + * a targeted write like $object['foo']['bar'] = 'foobar'. Instead, you must |
| 251 | + * overwrite the entire top-level 'foo' array with the entire set of new |
| 252 | + * values: $object['foo'] = array(1, 2, 'bar' => 'foobar'). Due to this same |
| 253 | + * limitation, attempts to create references to any contained data, nested or |
| 254 | + * otherwise, will fail silently. So $var = &$object['foo'] will not throw an |
| 255 | + * error, and $var will be populated with the contents of $object['foo'], but |
| 256 | + * that data will be passed by value, not reference. For more information on |
| 257 | + * the PHP limitation, see the note in the official PHP documentation at· |
| 258 | + * http://php.net/manual/en/arrayaccess.offsetget.php on |
| 259 | + * ArrayAccess::offsetGet(). |
| 260 | + * |
| 261 | + * By default, the class accounts for caches where calling functions might |
| 262 | + * request keys in the array that won't exist even after a cache rebuild. This |
| 263 | + * prevents situations where a cache rebuild would be triggered over and over |
| 264 | + * due to a 'missing' item. These cases are stored internally as a value of |
| 265 | + * NULL. This means that the offsetGet() and offsetExists() methods |
| 266 | + * must be overridden if caching an array where the top level values can |
| 267 | + * legitimately be NULL, and where $object->offsetExists() needs to correctly |
| 268 | + * return (equivalent to array_key_exists() vs. isset()). This should not |
| 269 | + * be necessary in the majority of cases. |
| 270 | + * |
| 271 | + * Classes extending this class must override at least the |
| 272 | + * resolveCacheMiss() method to have a working implementation. |
| 273 | + * |
| 274 | + * offsetSet() is not overridden by this class by default. In practice this |
| 275 | + * means that assigning an offset via arrayAccess will only apply while the |
| 276 | + * object is in scope and will not be written back to the persistent cache. |
| 277 | + * This follows a similar pattern to static vs. persistent caching in |
| 278 | + * procedural code. Extending classes may wish to alter this behaviour, for |
| 279 | + * example by overriding offsetSet() and adding an automatic call to persist(). |
| 280 | + * |
| 281 | + * @see SchemaCache |
| 282 | + */ |
| 283 | +abstract class DrupalCacheArray implements ArrayAccess { |
| 284 | + |
| 285 | + /** |
| 286 | + * A cid to pass to cache_set() and cache_get(). |
| 287 | + */ |
| 288 | + private $cid; |
| 289 | + |
| 290 | + /** |
| 291 | + * A bin to pass to cache_set() and cache_get(). |
| 292 | + */ |
| 293 | + private $bin; |
| 294 | + |
| 295 | + /** |
| 296 | + * An array of keys to add to the cache at the end of the request. |
| 297 | + */ |
| 298 | + protected $keysToPersist = array(); |
| 299 | + |
| 300 | + /** |
| 301 | + * Storage for the data itself. |
| 302 | + */ |
| 303 | + protected $storage = array(); |
| 304 | + |
| 305 | + /** |
| 306 | + * Constructor. |
| 307 | + * |
| 308 | + * @param $cid |
| 309 | + * The cid for the array being cached. |
| 310 | + * @param $bin |
| 311 | + * The bin to cache the array. |
| 312 | + */ |
| 313 | + public function __construct($cid, $bin) { |
| 314 | + $this->cid = $cid; |
| 315 | + $this->bin = $bin; |
| 316 | + |
| 317 | + if ($cached = cache_get($this->cid, $this->bin)) { |
| 318 | + $this->storage = $cached->data; |
| 319 | + } |
| 320 | + } |
| 321 | + |
| 322 | + public function offsetExists($offset) { |
| 323 | + return $this->offsetGet($offset) !== NULL; |
| 324 | + } |
| 325 | + |
| 326 | + public function offsetGet($offset) { |
| 327 | + if (isset($this->storage[$offset]) || array_key_exists($offset, $this->storage)) { |
| 328 | + return $this->storage[$offset]; |
| 329 | + } |
| 330 | + else { |
| 331 | + return $this->resolveCacheMiss($offset); |
| 332 | + } |
| 333 | + } |
| 334 | + |
| 335 | + public function offsetSet($offset, $value) { |
| 336 | + $this->storage[$offset] = $value; |
| 337 | + } |
| 338 | + |
| 339 | + public function offsetUnset($offset) { |
| 340 | + unset($this->storage[$offset]); |
| 341 | + } |
| 342 | + |
| 343 | + /** |
| 344 | + * Flags an offset value to be written to the persistent cache. |
| 345 | + * |
| 346 | + * If a value is assigned to a cache object with offsetSet(), by default it |
| 347 | + * will not be written to the persistent cache unless it is flagged with this |
| 348 | + * method. This allows items to be cached for the duration of a request, |
| 349 | + * without necessarily writing back to the persistent cache at the end. |
| 350 | + * |
| 351 | + * @param $offset |
| 352 | + * The array offset that was request. |
| 353 | + * @param $persist |
| 354 | + * Optional boolean to specify whether the offset should be persisted or |
| 355 | + * not, defaults to TRUE. When called with $persist = FALSE the offset will |
| 356 | + * be unflagged so that it will not written at the end of the request. |
| 357 | + */ |
| 358 | + protected function persist($offset, $persist = TRUE) { |
| 359 | + $this->keysToPersist[$offset] = $persist; |
| 360 | + } |
| 361 | + |
| 362 | + /** |
| 363 | + * Resolves a cache miss. |
| 364 | + * |
| 365 | + * When an offset is not found in the object, this is treated as a cache |
| 366 | + * miss. This method allows classes implementing the interface to look up |
| 367 | + * the actual value and allow it to be cached. |
| 368 | + * |
| 369 | + * @param $offset |
| 370 | + * The offset that was requested. |
| 371 | + * |
| 372 | + * @return |
| 373 | + * The value of the offset, or NULL if no value was found. |
| 374 | + */ |
| 375 | + abstract protected function resolveCacheMiss($offset); |
| 376 | + |
| 377 | + /** |
| 378 | + * Immediately write a value to the persistent cache. |
| 379 | + * |
| 380 | + * @param $cid |
| 381 | + * The cache ID. |
| 382 | + * @param $bin |
| 383 | + * The cache bin. |
| 384 | + * @param $data |
| 385 | + * The data to write to the persistent cache. |
| 386 | + * @param $lock |
| 387 | + * Whether to acquire a lock before writing to cache. |
| 388 | + */ |
| 389 | + protected function set($cid, $data, $bin, $lock = TRUE) { |
| 390 | + // Lock cache writes to help avoid stampedes. |
| 391 | + // To implement locking for cache misses, override __construct(). |
| 392 | + $lock_name = $cid . ':' . $bin; |
| 393 | + if (!$lock || lock_acquire($lock_name)) { |
| 394 | + if ($cached = cache_get($cid, $bin)) { |
| 395 | + $data = $cached->data + $data; |
| 396 | + } |
| 397 | + cache_set($cid, $data, $bin); |
| 398 | + if ($lock) { |
| 399 | + lock_release($lock_name); |
| 400 | + } |
| 401 | + } |
| 402 | + } |
| 403 | + |
| 404 | + public function __destruct() { |
| 405 | + $data = array(); |
| 406 | + foreach ($this->keysToPersist as $offset => $persist) { |
| 407 | + if ($persist) { |
| 408 | + $data[$offset] = $this->storage[$offset]; |
| 409 | + } |
| 410 | + } |
| 411 | + if (!empty($data)) { |
| 412 | + $this->set($this->cid, $data, $this->bin); |
| 413 | + } |
| 414 | + } |
| 415 | +} |
| 416 | + |
228 | 417 | /**
|
229 | 418 | * Start the timer with the specified name. If you start and stop the same
|
230 | 419 | * timer multiple times, the measured intervals will be accumulated.
|
@@ -2532,6 +2721,55 @@ function ip_address() {
|
2532 | 2721 | * If true, the schema will be rebuilt instead of retrieved from the cache.
|
2533 | 2722 | */
|
2534 | 2723 | function drupal_get_schema($table = NULL, $rebuild = FALSE) {
|
| 2724 | + static $schema; |
| 2725 | + |
| 2726 | + if ($rebuild || !isset($table)) { |
| 2727 | + $schema = drupal_get_complete_schema($rebuild); |
| 2728 | + } |
| 2729 | + elseif (!isset($schema)) { |
| 2730 | + $schema = new SchemaCache(); |
| 2731 | + } |
| 2732 | + |
| 2733 | + if (!isset($table)) { |
| 2734 | + return $schema; |
| 2735 | + } |
| 2736 | + if (isset($schema[$table])) { |
| 2737 | + return $schema[$table]; |
| 2738 | + } |
| 2739 | + else { |
| 2740 | + return FALSE; |
| 2741 | + } |
| 2742 | +} |
| 2743 | + |
| 2744 | +/** |
| 2745 | + * Extends DrupalCacheArray to allow for dynamic building of the schema cache. |
| 2746 | + */ |
| 2747 | +class SchemaCache extends DrupalCacheArray { |
| 2748 | + |
| 2749 | + public function __construct() { |
| 2750 | + // Cache by request method. |
| 2751 | + parent::__construct('schema:runtime:' . $_SERVER['REQUEST_METHOD'] == 'GET', 'cache'); |
| 2752 | + } |
| 2753 | + |
| 2754 | + protected function resolveCacheMiss($offset) { |
| 2755 | + $complete_schema = drupal_get_complete_schema(); |
| 2756 | + $value = isset($complete_schema[$offset]) ? $complete_schema[$offset] : NULL; |
| 2757 | + $this->storage[$offset] = $value; |
| 2758 | + $this->persist($offset); |
| 2759 | + return $value; |
| 2760 | + } |
| 2761 | +} |
| 2762 | + |
| 2763 | +/** |
| 2764 | + * Get the whole database schema. |
| 2765 | + * |
| 2766 | + * The returned schema will include any modifications made by any |
| 2767 | + * module that implements hook_schema_alter(). |
| 2768 | + * |
| 2769 | + * @param $rebuild |
| 2770 | + * If true, the schema will be rebuilt instead of retrieved from the cache. |
| 2771 | + */ |
| 2772 | +function drupal_get_complete_schema($rebuild = FALSE) { |
2535 | 2773 | static $schema = array();
|
2536 | 2774 |
|
2537 | 2775 | if (empty($schema) || $rebuild) {
|
@@ -2573,18 +2811,13 @@ function drupal_get_schema($table = NULL, $rebuild = FALSE) {
|
2573 | 2811 | if (!empty($schema) && (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL)) {
|
2574 | 2812 | cache_set('schema', $schema);
|
2575 | 2813 | }
|
| 2814 | + if ($rebuild) { |
| 2815 | + cache_clear_all('schema:', 'cache', TRUE); |
| 2816 | + } |
2576 | 2817 | }
|
2577 | 2818 | }
|
2578 | 2819 |
|
2579 |
| - if (!isset($table)) { |
2580 |
| - return $schema; |
2581 |
| - } |
2582 |
| - elseif (isset($schema[$table])) { |
2583 |
| - return $schema[$table]; |
2584 |
| - } |
2585 |
| - else { |
2586 |
| - return FALSE; |
2587 |
| - } |
| 2820 | + return $schema; |
2588 | 2821 | }
|
2589 | 2822 |
|
2590 | 2823 | /**
|
|
0 commit comments