Skip to content

Commit

Permalink
merging in the 1.1.0 update
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryan Kanner committed May 5, 2017
2 parents 097ada0 + 21b401f commit 936cc19
Show file tree
Hide file tree
Showing 5 changed files with 601 additions and 14 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ $result = dfm_get_transient( 'sample_transient', '' );

## Transient Modifier
The transient modifier, (second parameter passed to the `dfm_get_transient` function) is used in a variety of different ways throughout this library. For a transient stored in metadata, it will be used as the object ID the transient is attached to. It will be used when using the `get_metadata()` and `save_metadata()` functions, so it is crucial that it is passed for transients stored in metadata. For global transients, it can be used to store variations of the same type of transient. It will append the `$modifier` to the end of the transient key. This way you could store and retrieve different variations of the same transient that are mostly the same without registering a whole new transient. You can use the modifier to change the data saved to the transient by using it to alter your logic in your callback (the modifier is passed as the only argument to your callback function).

## Debugging
To help with debugging, you can set a constant in your codebase called `DFM_TRANSIENTS_HOT_RELOAD` and set it to `true` to enable "hot reload" mode. This will essentially make it so that transient data will be regenerated every time it is called. This is handy if you are working on adding a transient, and want it to keep regenerating while you are working on it. This saves the need from manually deleting it from your database, or setting an extremely short timeout. **NOTE:** This constant should only ever be used on a development environment. Using this on production could cause serious performance issues depending on the data you are storing in your transients.

## Retries
Since version 1.1.0 there is a retry facilitation system for DFM Transients. This is helpful if you are storing data from an external API, and want to serve stale data if the API is down. To use this feature, all you have to do is return `false` or a `wp_error` object in your transient callback if your remote request failed. This will then store the stale expired data back into the transient, and will use an expiration timeout that increases exponentially every time it fails to fetch the data. Essentially it stores a `failed` value in the cache for each transient, and adds one to the value every time the retry method runs. It then mulitplies this number by its self to figure out how many minutes it should set the expiration to. For example, if the fetch has failed 5 times, it will set the timeout to 25 minutes, and will retry again after that.

## Contributing
To contribute to this repo, please fork it and submit a pull request. If there is a larger feature you would like to see, or something you would like to discuss, please open an issue.
## Copyright
Expand Down
7 changes: 6 additions & 1 deletion dfm-transients.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Plugin Name: Transient Control
* Plugin URI: https://github.com/dfmedia/DFM-Transients
* Description: Better control for transients
* Version: 1.0.1
* Version: 1.1.0
* Author: Ryan Kanner, Digital First Media
* License: MIT
*/
Expand All @@ -23,3 +23,8 @@
if ( is_admin() ) {
require_once( plugin_dir_path( __FILE__ ) . 'includes/admin/class-dfm-transient-admin.php' );
}

// CLI Commands
if ( defined( 'WP_CLI' ) && true === WP_CLI ) {
require_once( plugin_dir_path( __FILE__ ) . 'includes/cli.php' );
}
158 changes: 146 additions & 12 deletions includes/class-dfm-transients.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ class DFM_Transients {
* @var string
* @access private
*/
private $modifier = '';
public $modifier = '';

/**
* The storage key for the transient
*
* @var string
* @access private
*/
private $key = '';
public $key = '';

/**
* Lock stored in transient.
Expand All @@ -62,6 +62,14 @@ class DFM_Transients {
*/
private $lock_key = '';

/**
* Flag for if we are attempting a retry
*
* @var $doing_retry bool
* @access private
*/
private $doing_retry = false;

/**
* DFM_Transients constructor.
*
Expand Down Expand Up @@ -125,7 +133,7 @@ public function set( $data ) {
}

if ( false === $data || is_wp_error( $data ) ) {
return;
$this->facilitate_retry();
}

switch ( $this->transient_object->cache_type ) {
Expand All @@ -147,6 +155,37 @@ public function set( $data ) {

}

/**
* This method handles the deletion of a transient
*
* @return WP_Error|void
* @access public
*/
public function delete() {

if ( ! isset( $this->transient_object ) ) {
return new WP_Error( 'invalid-transient', __( 'You are trying to retrieve a transient that doesn\'t exist', 'dfm-transients' ) );
}

switch( $this->transient_object->cache_type ) {
case 'transient':
$this->delete_from_transient();
break;
case 'post_meta':
$this->delete_from_metadata( 'post' );
break;
case 'term_meta':
$this->delete_from_metadata( 'term' );
break;
case 'user_meta':
$this->delete_from_metadata( 'user' );
break;
default:
new WP_Error( 'invalid-cache-type', __( 'When registering your transient, you used an invalid cache type. Valid options are transient, post_meta, term_meta.', 'dfm-transients' ) );
}

}

/**
* Locks the ability to update the transient data. This will prevent race conditions.
*
Expand Down Expand Up @@ -206,7 +245,11 @@ private function get_from_transient() {

$data = get_transient( $this->key );

if ( false === $data ) {
if ( false === $data || ( defined( 'DFM_TRANSIENTS_HOT_RELOAD' ) && true === DFM_TRANSIENTS_HOT_RELOAD ) ) {

if ( true === $this->doing_retry ) {
return false;
}
$data = call_user_func( $this->transient_object->callback, $this->modifier );
$this->set( $data );
} elseif ( $this->is_expired( $data ) && ! $this->is_locked() ) {
Expand Down Expand Up @@ -238,8 +281,18 @@ private function get_from_transient() {
private function get_from_meta( $type ) {

$data = get_metadata( $type, $this->modifier, $this->key, true );

$data_exists = true;

if ( empty( $data ) ) {
$data_exists = metadata_exists( $type, $this->modifier, $this->key );
}

if ( false === $data_exists || ( defined( 'DFM_TRANSIENTS_HOT_RELOAD' ) && true === DFM_TRANSIENTS_HOT_RELOAD ) ) {

if ( false === $data ) {
if ( true === $this->doing_retry ) {
return false;
}
$data = call_user_func( $this->transient_object->callback, $this->modifier );
$this->set( $data );
} elseif ( $this->is_expired( $data ) && ! $this->is_locked() ) {
Expand Down Expand Up @@ -308,6 +361,82 @@ private function save_to_metadata( $data, $type ) {

}

/**
* Deletes a transient stored in the default transient storage engine
*
* @access private
* @uses delete_transient()
* @return void
*/
private function delete_from_transient() {
delete_transient( $this->key );
}

/**
* Deletes a transient stored in metadata
*
* @param string $type The object type related to the metadata
* @uses delete_metadata()
* @return void
* @access private
*/
private function delete_from_metadata( $type ) {
delete_metadata( $type, $this->modifier, $this->key );
}

/**
* If a callback function fails to return the correct data, this will store the stale data back into the
* transient, and then set the expiration of the data at an exponential scale, so we are not constantly
* retrying to get the data (if an API is down or something).
*
* @access private
* @return void
*/
private function facilitate_retry() {

// Set flag while doing a retry to prevent infinite loops.
$this->doing_retry = true;

// Retrieve the stale data.
$current_data = $this->get();

// If there is nothing already stored for the transient, bail.
if ( false === $current_data ) {
return;
}

// Store the expiration set when registering the transient. Our timeout should not exceed this number.
$max_expiration = $this->transient_object->expiration;

// Retrieve the cache fail amount from the cache
$failed_num = wp_cache_get( $this->key . '_failed', 'dfm_transients_retry' );

// Default to 1 failure if there's nothing set, or it's set to zero. This is so it doesn't mess with
// the `pow` func.
if ( false === $failed_num || 0 === $failed_num ) {
$failures = 1;
} else {
$failures = $failed_num;
}

// Generate the new expiration time. This essentially just muliplies the amount of failures by itself, and
// then multiplies it by one minute to get the expiration, so if it is retrying it for the 5th time, it will
// do 5*5 (which is 25) so it will set the retry to 25 minutes.
$new_expiration = ( pow( $failures, 2 ) * MINUTE_IN_SECONDS );

// Only set the new expiration if it's less than the original registered expiration.
if ( $new_expiration < $max_expiration ) {
$this->transient_object->expiration = $new_expiration;
}

// Save the stale data with the new expiration
$this->set( $current_data );

// Add 1 to the the failures in the cache.
wp_cache_set( $this->key . '_failed', ( $failures + 1 ), 'dfm_transients_retry', DAY_IN_SECONDS );

}

/**
* Hashes storage key
*
Expand Down Expand Up @@ -341,12 +470,17 @@ private function cache_key() {
$key = $this->hash_key( $key );
}

if ( 'post_meta' === $this->transient_object->cache_type || 'term_meta' === $this->transient_object->cache_type ) {
$key = $this->prefix . $key;
}

if ( 'transient' === $this->transient_object->cache_type && ! empty( $this->modifier ) ) {
$key = $key . '_' . $this->modifier;
switch( $this->transient_object->cache_type ) {
case 'post_meta':
case 'term_meta':
case 'user_meta':
$key = $this->prefix . $key;
break;
case 'transient':
if ( ! empty( $this->modifier ) ) {
$key = $key . '_' . $this->modifier;
}
break;
}

return $key;
Expand Down Expand Up @@ -390,7 +524,7 @@ private function should_soft_expire() {
* @return bool
*/
private function is_expired( $data ) {
if ( '' !== $this->transient_object->expiration && is_array( $data ) && $data['expiration'] < time() ) {
if ( ! empty( $this->transient_object->expiration ) && is_array( $data ) && $data['expiration'] < time() ) {
return true;
} else {
return false;
Expand Down
Loading

0 comments on commit 936cc19

Please sign in to comment.