Completed
Branch master (939199)
by
unknown
39:35
created

includes/libs/MemoizedCallable.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * APC-backed and APCu-backed function memoization
4
 *
5
 * This class provides memoization for pure functions. A function is pure
6
 * if its result value depends on nothing other than its input parameters
7
 * and if invoking it does not cause any side-effects.
8
 *
9
 * The first invocation of the memoized callable with a particular set of
10
 * arguments will be delegated to the underlying callable. Repeat invocations
11
 * with the same input parameters will be served from APC or APCu.
12
 *
13
 * @par Example:
14
 * @code
15
 * $memoizedStrrev = new MemoizedCallable( 'range' );
16
 * $memoizedStrrev->invoke( 5, 8 );  // result: array( 5, 6, 7, 8 )
17
 * $memoizedStrrev->invokeArgs( array( 5, 8 ) );  // same
18
 * MemoizedCallable::call( 'range', array( 5, 8 ) );  // same
19
 * @endcode
20
 *
21
 * This program is free software; you can redistribute it and/or modify
22
 * it under the terms of the GNU General Public License as published by
23
 * the Free Software Foundation; either version 2 of the License, or
24
 * (at your option) any later version.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29
 * GNU General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU General Public License along
32
 * with this program; if not, write to the Free Software Foundation, Inc.,
33
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
34
 * http://www.gnu.org/copyleft/gpl.html
35
 *
36
 * @file
37
 * @author Ori Livneh
38
 * @since 1.27
39
 */
40
class MemoizedCallable {
41
42
	/** @var callable */
43
	private $callable;
44
45
	/** @var string Unique name of callable; used for cache keys. */
46
	private $callableName;
47
48
	/**
49
	 * Constructor.
50
	 *
51
	 * @throws InvalidArgumentException if $callable is not a callable.
52
	 * @param callable $callable Function or method to memoize.
53
	 * @param int $ttl TTL in seconds. Defaults to 3600 (1hr). Capped at 86400 (24h).
54
	 */
55
	public function __construct( $callable, $ttl = 3600 ) {
56
		if ( !is_callable( $callable, false, $this->callableName ) ) {
57
			throw new InvalidArgumentException(
58
				'Argument 1 passed to MemoizedCallable::__construct() must ' .
59
				'be an instance of callable; ' . gettype( $callable ) . ' given'
60
			);
61
		}
62
63
		if ( $this->callableName === 'Closure::__invoke' ) {
64
			// Differentiate anonymous functions from one another
65
			$this->callableName .= uniqid();
66
		}
67
68
		$this->callable = $callable;
69
		$this->ttl = min( max( $ttl, 1 ), 86400 );
0 ignored issues
show
The property ttl does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
70
	}
71
72
	/**
73
	 * Fetch the result of a previous invocation from APC or APCu.
74
	 *
75
	 * @param string $key
76
	 * @param bool &$success
77
	 */
78
	protected function fetchResult( $key, &$success ) {
79
		$success = false;
80
		if ( function_exists( 'apc_fetch' ) ) {
81
			return apc_fetch( $key, $success );
82
		} elseif ( function_exists( 'apcu_fetch' ) ) {
83
			return apcu_fetch( $key, $success );
84
		}
85
		return false;
86
	}
87
88
	/**
89
	 * Store the result of an invocation in APC or APCu.
90
	 *
91
	 * @param string $key
92
	 * @param mixed $result
93
	 */
94
	protected function storeResult( $key, $result ) {
95
		if ( function_exists( 'apc_store' ) ) {
96
			apc_store( $key, $result, $this->ttl );
97
		} elseif ( function_exists( 'apcu_store' ) ) {
98
			apcu_store( $key, $result, $this->ttl );
99
		}
100
	}
101
102
	/**
103
	 * Invoke the memoized function or method.
104
	 *
105
	 * @throws InvalidArgumentException If parameters list contains non-scalar items.
106
	 * @param array $args Parameters for memoized function or method.
107
	 * @return mixed The memoized callable's return value.
108
	 */
109
	public function invokeArgs( array $args = [] ) {
110
		foreach ( $args as $arg ) {
111
			if ( $arg !== null && !is_scalar( $arg ) ) {
112
				throw new InvalidArgumentException(
113
					'MemoizedCallable::invoke() called with non-scalar ' .
114
					'argument'
115
				);
116
			}
117
		}
118
119
		$hash = md5( serialize( $args ) );
120
		$key = __CLASS__ . ':' . $this->callableName . ':' . $hash;
121
		$success = false;
122
		$result = $this->fetchResult( $key, $success );
123
		if ( !$success ) {
124
			$result = call_user_func_array( $this->callable, $args );
125
			$this->storeResult( $key, $result );
126
		}
127
128
		return $result;
129
	}
130
131
	/**
132
	 * Invoke the memoized function or method.
133
	 *
134
	 * Like MemoizedCallable::invokeArgs(), but variadic.
135
	 *
136
	 * @param mixed ...$params Parameters for memoized function or method.
137
	 * @return mixed The memoized callable's return value.
138
	 */
139
	public function invoke() {
140
		return $this->invokeArgs( func_get_args() );
141
	}
142
143
	/**
144
	 * Shortcut method for creating a MemoizedCallable and invoking it
145
	 * with the specified arguments.
146
	 *
147
	 * @param callable $callable
148
	 * @param array $args
149
	 * @param int $ttl
150
	 */
151
	public static function call( $callable, array $args = [], $ttl = 3600 ) {
152
		$instance = new self( $callable, $ttl );
153
		return $instance->invokeArgs( $args );
154
	}
155
}
156