1<?php 2/** 3 * This program is free software; you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation; either version 2 of the License, or 6 * (at your option) any later version. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License along 14 * with this program; if not, write to the Free Software Foundation, Inc., 15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 * http://www.gnu.org/copyleft/gpl.html 17 * 18 * @file 19 * @author Ori Livneh 20 */ 21 22/** 23 * APC-backed and APCu-backed function memoization 24 * 25 * This class provides memoization for pure functions. A function is pure 26 * if its result value depends on nothing other than its input parameters 27 * and if invoking it does not cause any side-effects. 28 * 29 * The first invocation of the memoized callable with a particular set of 30 * arguments will be delegated to the underlying callable. Repeat invocations 31 * with the same input parameters will be served from APC or APCu. 32 * 33 * @par Example: 34 * @code 35 * $memoizedStrrev = new MemoizedCallable( 'range' ); 36 * $memoizedStrrev->invoke( 5, 8 ); // result: array( 5, 6, 7, 8 ) 37 * $memoizedStrrev->invokeArgs( array( 5, 8 ) ); // same 38 * MemoizedCallable::call( 'range', array( 5, 8 ) ); // same 39 * @endcode 40 * 41 * @since 1.27 42 */ 43class MemoizedCallable { 44 45 /** @var callable */ 46 private $callable; 47 48 /** @var string Unique name of callable; used for cache keys. */ 49 private $callableName; 50 51 /** @var int */ 52 private $ttl; 53 54 /** 55 * @throws InvalidArgumentException if $callable is not a callable. 56 * @param callable $callable Function or method to memoize. 57 * @param int $ttl TTL in seconds. Defaults to 3600 (1hr). Capped at 86400 (24h). 58 */ 59 public function __construct( $callable, $ttl = 3600 ) { 60 if ( !is_callable( $callable, false, $this->callableName ) ) { 61 throw new InvalidArgumentException( 62 'Argument 1 passed to MemoizedCallable::__construct() must ' . 63 'be an instance of callable; ' . gettype( $callable ) . ' given' 64 ); 65 } 66 67 if ( $this->callableName === 'Closure::__invoke' ) { 68 throw new InvalidArgumentException( 'Cannot memoize unnamed closure' ); 69 } 70 71 $this->callable = $callable; 72 $this->ttl = min( max( $ttl, 1 ), 86400 ); 73 } 74 75 /** 76 * Fetch the result of a previous invocation from APC or APCu. 77 * 78 * @param string $key 79 * @param bool &$success 80 * @return bool 81 */ 82 protected function fetchResult( $key, &$success ) { 83 $success = false; 84 if ( function_exists( 'apc_fetch' ) ) { 85 return apc_fetch( $key, $success ); 86 } elseif ( function_exists( 'apcu_fetch' ) ) { 87 return apcu_fetch( $key, $success ); 88 } 89 return false; 90 } 91 92 /** 93 * Store the result of an invocation in APC or APCu. 94 * 95 * @param string $key 96 * @param mixed $result 97 */ 98 protected function storeResult( $key, $result ) { 99 if ( function_exists( 'apc_store' ) ) { 100 apc_store( $key, $result, $this->ttl ); 101 } elseif ( function_exists( 'apcu_store' ) ) { 102 apcu_store( $key, $result, $this->ttl ); 103 } 104 } 105 106 /** 107 * Invoke the memoized function or method. 108 * 109 * @throws InvalidArgumentException If parameters list contains non-scalar items. 110 * @param array $args Parameters for memoized function or method. 111 * @return mixed The memoized callable's return value. 112 */ 113 public function invokeArgs( array $args = [] ) { 114 foreach ( $args as $arg ) { 115 if ( $arg !== null && !is_scalar( $arg ) ) { 116 throw new InvalidArgumentException( 117 'MemoizedCallable::invoke() called with non-scalar ' . 118 'argument' 119 ); 120 } 121 } 122 123 $hash = md5( serialize( $args ) ); 124 $key = __CLASS__ . ':' . $this->callableName . ':' . $hash; 125 $success = false; 126 $result = $this->fetchResult( $key, $success ); 127 if ( !$success ) { 128 $result = ( $this->callable )( ...$args ); 129 $this->storeResult( $key, $result ); 130 } 131 132 return $result; 133 } 134 135 /** 136 * Invoke the memoized function or method. 137 * 138 * Like MemoizedCallable::invokeArgs(), but variadic. 139 * 140 * @param mixed ...$params Parameters for memoized function or method. 141 * @return mixed The memoized callable's return value. 142 */ 143 public function invoke( ...$params ) { 144 return $this->invokeArgs( $params ); 145 } 146 147 /** 148 * Shortcut method for creating a MemoizedCallable and invoking it 149 * with the specified arguments. 150 * 151 * @param callable $callable 152 * @param array $args 153 * @param int $ttl 154 * @return mixed 155 */ 156 public static function call( $callable, array $args = [], $ttl = 3600 ) { 157 $instance = new self( $callable, $ttl ); 158 return $instance->invokeArgs( $args ); 159 } 160} 161