Skip to content
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

Invalidate static caches for all calls using internal API #297

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions includes/RestfulStaticCacheController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

/**
* @file
* Contains RestfulStaticCacheController
*/

class RestfulStaticCacheController implements \RestfulStaticCacheControllerInterface {

/**
* @var array
*
* The cache ID registry.
*/
protected $cids = array();

/**
* @var string
*
* The cache key prefix.
*/
protected $prefix;

/**
* Constructor
*/
public function __construct() {
$this->prefix = 'restful_' . mt_rand() . '_';
}

/**
* {@inheritdoc}
*/
public function get($cid, $default = NULL) {
$this->registerCid($cid);
return drupal_static($this->prefix . $cid, $default);
}

/**
* {@inheritdoc}
*/
public function set($cid, $value) {
$val = &drupal_static($this->prefix . $cid);
$val = $value;
}

/**
* {@inheritdoc}
*/
public function clear($cid) {
drupal_static_reset($this->prefix . $cid);
}

/**
* {@inheritdoc}
*/
public function clearAll() {
foreach ($this->getCids() as $cid) {
$this->clear($cid);
}
}

/**
* Register cache ID. The registry is used by clearAll.
*
* @param string $cid
* The cache ID to register.
*/
protected function registerCid($cid) {
if (!in_array($cid, $this->cids)) {
$this->cids[] = $cid;
}
}

/**
* Cache ID accessor.
*
* @return array
* The cache IDs.
*/
public function getCids() {
return $this->cids;
}

}
49 changes: 49 additions & 0 deletions includes/RestfulStaticCacheControllerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/**
* @file
* Contains RestfulStaticCacheControllerInterface
*/

interface RestfulStaticCacheControllerInterface {

/**
* Gets the static cache.
*
* @param string $cid
* The cache key to use.
* @param mixed $default
* The default value in case there is no static cache.
*
* @return mixed
* The cached value.
*/
public function get($cid, $default = NULL);

/**
* Sets the static cache.
*
* @param string $cid
* The cache key to use.
* @param mixed $value
* The value to set.
*
* @return mixed
* The cached value.
*/
public function set($cid, $value);

/**
* Clear a particular cache value.
*
* @param string $cid
* The cache ID to clear.
*/
public function clear($cid);

/**
* Clear all registered cache values.
*/
public function clearAll();

}
56 changes: 40 additions & 16 deletions plugins/restful/RestfulBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ abstract class RestfulBase extends \RestfulPluginBase implements \RestfulInterfa
*/
protected $valueMetadata = array();

/**
* Static cache controller.
*
* @var \RestfulStaticCacheController
*/
public $staticCache;

/**
* Get the cache id parameters based on the keys.
*
Expand Down Expand Up @@ -344,6 +351,7 @@ public function __construct(array $plugin, \RestfulAuthenticationManager $auth_m
if ($rate_limit = $this->getPluginKey('rate_limit')) {
$this->setRateLimitManager(new \RestfulRateLimitManager($this->getPluginKey('resource'), $rate_limit));
}
$this->staticCache = new \RestfulStaticCacheController();
}

/**
Expand Down Expand Up @@ -513,14 +521,15 @@ public function getResourceName() {
* definition.
*/
public function getVersion() {
$version = &drupal_static(__CLASS__ . '::' . __FUNCTION__);
$version = $this->staticCache->get(__CLASS__ . '::' . __FUNCTION__);
if (isset($version)) {
return $version;
}
$version = array(
'major' => $this->getPluginKey('major_version'),
'minor' => $this->getPluginKey('minor_version'),
);
$this->staticCache->set(__CLASS__ . '::' . __FUNCTION__, $version);
return $version;
}

Expand Down Expand Up @@ -702,21 +711,12 @@ public function process($path = '', array $request = array(), $method = \Restful
$this->setMethod($method);
$this->setPath($path);
$this->setRequest($request);
// Override the range with the value in the URL.
if (!empty($request['range'])) {
$url_params = $this->getPluginKey('url_params');
if (!$url_params['range']) {
throw new \RestfulBadRequestException('The range parameter has been disabled in server configuration.');
}

if (!ctype_digit((string) $request['range']) || $request['range'] < 1) {
throw new \RestfulBadRequestException('"Range" property should be numeric and higher than 0.');
}
if ($request['range'] < $this->getRange()) {
// If there is a valid range property in the request override the range.
$this->setRange($request['range']);
}
}
// Clear all static caches from previous requests.
$this->staticCache->clearAll();

// Override the range with the value in the URL.
$this->overrideRange();

$version = $this->getVersion();
$this->setHttpHeaders('X-API-Version', 'v' . $version['major'] . '.' . $version['minor']);
Expand Down Expand Up @@ -1163,7 +1163,7 @@ protected function clearRenderedCache(array $context = array()) {
protected function generateCacheId(array $context = array()) {
// For performance reasons create the request part and cache it, then add
// the context part.
$request_cid = &drupal_static(__CLASS__ . '::' . __FUNCTION__);
$request_cid = $this->staticCache->get(__CLASS__ . '::' . __FUNCTION__);
if (!isset($request_cid)) {
// Get the cache ID from the selected params. We will use a complex cache
// ID for smarter invalidation. The cache id will be like:
Expand All @@ -1182,6 +1182,7 @@ protected function generateCacheId(array $context = array()) {
$cid_params = static::addCidParams($request);
}
$request_cid = $cid . implode('::', $cid_params);
$this->staticCache->set(__CLASS__ . '::' . __FUNCTION__, $request_cid);
}
// Now add the context part to the cid
$cid_params = static::addCidParams($context);
Expand Down Expand Up @@ -1506,4 +1507,27 @@ protected static function notImplementedCrudOperation($operation) {
throw new \RestfulNotImplementedException(format_string('The "@method" method is not implemented in class @class.', array('@method' => $operation, '@class' => __CLASS__)));
}

/**
* Overrides the range parameter with the URL value if any.
*
* @throws RestfulBadRequestException
*/
protected function overrideRange() {
$request = $this->getRequest();
if (!empty($request['range'])) {
$url_params = $this->getPluginKey('url_params');
if (!$url_params['range']) {
throw new \RestfulBadRequestException('The range parameter has been disabled in server configuration.');
}

if (!ctype_digit((string) $request['range']) || $request['range'] < 1) {
throw new \RestfulBadRequestException('"Range" property should be numeric and higher than 0.');
}
if ($request['range'] < $this->getRange()) {
// If there is a valid range property in the request override the range.
$this->setRange($request['range']);
}
}
}

}
4 changes: 2 additions & 2 deletions plugins/restful/RestfulDataProviderDbQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -408,14 +408,14 @@ public function remove($id) {
public function mapDbRowToPublicFields($row) {
if ($this->getMethod() == \RestfulInterface::GET) {
// For read operations cache the result.
$output = &drupal_static(__CLASS__ . '::' . __FUNCTION__ . '::' . $this->getUniqueId($row));
$output = $this->staticCache->get(__CLASS__ . '::' . __FUNCTION__ . '::' . $this->getUniqueId($row));
if (isset($output)) {
return $output;
}
}
else {
// Clear the cache if the request is not GET.
drupal_static_reset(__CLASS__ . '::' . __FUNCTION__ . '::' . $this->getUniqueId($row));
$this->staticCache->clear(__CLASS__ . '::' . __FUNCTION__ . '::' . $this->getUniqueId($row));
}
// Loop over all the defined public fields.
foreach ($this->getPublicFields() as $public_field_name => $info) {
Expand Down
2 changes: 1 addition & 1 deletion plugins/restful/RestfulEntityBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ protected function getTargetTypeFromEntityReference(\EntityMetadataWrapper $wrap
* The value if found, or NULL if bundle not defined.
*/
protected function getValueFromResource(EntityMetadataWrapper $wrapper, $property, $resource, $public_field_name = NULL, $host_id = NULL) {
$handlers = &drupal_static(__FUNCTION__, array());
$handlers = $this->staticCache->get(__CLASS__ . '::' . __FUNCTION__, array());

if (!$entity = $wrapper->value()) {
return;
Expand Down
2 changes: 2 additions & 0 deletions restful.info
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ files[] = includes/RestfulManager.php
files[] = includes/RestfulPluginBase.php
files[] = includes/RestfulPluginInterface.php
files[] = includes/RestfulRateLimit.php
files[] = includes/RestfulStaticCacheController.php
files[] = includes/RestfulStaticCacheControllerInterface.php

; Exceptions
files[] = exceptions/RestfulBadRequestException.php
Expand Down
6 changes: 0 additions & 6 deletions tests/RestfulHookMenuTestCase.test
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ class RestfulHookMenuTestCase extends RestfulCurlBaseTestCase {

$node1 = node_load($node1->nid);
$this->assertEqual($node1->title, 'new title', 'HTTP method was overriden.');


}

/**
Expand Down Expand Up @@ -126,30 +124,26 @@ class RestfulHookMenuTestCase extends RestfulCurlBaseTestCase {
$this->assertEqual($handler->getVersion(), array('major' => 1, 'minor' => 1), 'api/v1.1/articles resolves 1.1');

// 2. my-api/v1/articles yields version 1.6 (update to last 1.x version)
drupal_static_reset('RestfulBase::getVersion');
drupal_static_reset('RestfulBase::getVersionFromRequest');
$handler = restful_get_restful_handler_for_path('api/v1/articles');
$this->assertEqual($handler->getVersion(), array('major' => 1, 'minor' => 6), 'api/v1.6/articles resolves 1.6');

// 3. my-api/articles with header X-API-Version: v1.1 yields 1.1
// Fake the HTTP header.
$_SERVER['HTTP_X_API_VERSION'] = 'v1.1';
drupal_static_reset('RestfulBase::getVersion');
drupal_static_reset('RestfulBase::getVersionFromRequest');
$handler = restful_get_restful_handler_for_path('api/articles');
$this->assertEqual($handler->getVersion(), array('major' => 1, 'minor' => 1), 'api/articles resolves 1.1');

// 4. my-api/articles with header X-API-Version: v1 yields 1.6
// Fake the HTTP header.
$_SERVER['HTTP_X_API_VERSION'] = 'v1';
drupal_static_reset('RestfulBase::getVersion');
drupal_static_reset('RestfulBase::getVersionFromRequest');
drupal_static_reset('restful_get_restful_handler_for_path');
$handler = restful_get_restful_handler_for_path('api/articles');
$this->assertEqual($handler->getVersion(), array('major' => 1, 'minor' => 6), 'api/articles resolves 1.6');

// 5. my-api/articles without header X-API-Version yields 2.0
drupal_static_reset('RestfulBase::getVersion');
drupal_static_reset('RestfulBase::getVersionFromRequest');
drupal_static_reset('restful_get_restful_handler_for_path');
unset($_SERVER['HTTP_X_API_VERSION']);
Expand Down
4 changes: 0 additions & 4 deletions tests/RestfulRenderCacheTestCase.test
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ class RestfulRenderCacheTestCase extends DrupalWebTestCase {

// Test that cache is being generated correctly.
// Get the articles.
drupal_static_reset('RestfulBase::generateCacheId');
drupal_static_reset('RestfulBase::getVersion');
drupal_static_reset('RestfulBase::getVersionFromRequest');
$pre_cache_results = $handler->get();
// Make sure some cache entries are generated.
Expand Down Expand Up @@ -84,9 +82,7 @@ class RestfulRenderCacheTestCase extends DrupalWebTestCase {
// Test cache segmentation with request params.
// Rebuild caches.
$cache->clear('*', TRUE);
drupal_static_reset('RestfulBase::generateCacheId');
$results_wo_request = $handler->get();
drupal_static_reset('RestfulBase::generateCacheId');
$results_w_request = $handler->get('', array('fields' => 'id,label'));
// Check that the cached results for the entity without request array are
// different than with the request array.
Expand Down