Memoizable.php
4.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
<?php
/**
* This trait provides an easy way to memoize object method results.
*
* To enable memoization for an object, you need to use this trait and call the `enable_memoization` method.
* You can call the `clear_memoization_cache` method to clear the cache.
*
* How to use:
*
* Example without args:
*
* public function test() {
* return $this->memoize(
* function() {
* return rand( 1, 1000 );
* }
* );
* }
*
* Result:
*
* $obj->test(); // 123
* $obj->test(); // 123
*
* With args: (the args are be used as a cache key too)
*
* public function test( $arg ) {
* return $this->memoize(
* function() use ( $arg ) {
* return $arg . rand( 1, 1000 );
* }
* );
* }
*
* Result:
*
* $obj->test( 'a' ); // a234
* $obj->test( 'a' ); // a234
* $obj->test( 'b' ); // b456
* $obj->test( 'b' ); // b456
* $obj->test( 'c' ); // c789
*
* The passed function will be called only once and the result will be cached.
*
* Limitations:
*
* It was tested with simple public methods only.
* It does not support static methods.
* It does not support closures.
*
* The reason of it is that for such functionality we need to use some kind of global cache (not per object).
* Once we need to support static methods or closures, we can improve this trait.
*
* @since 4.6.0
*
* @package LearnDash\Core
*/
/** NOTICE: This code is currently under development and may not be stable.
* Its functionality, behavior, and interfaces may change at any time without notice.
* Please refrain from using it in production or other critical systems.
* By using this code, you assume all risks and liabilities associated with its use.
* Thank you for your understanding and cooperation.
**/
namespace LearnDash\Core\Traits;
/**
* A trait to memoize object method results.
*
* @since 4.6.0
*/
trait Memoizable {
/**
* The memoization cache.
*
* @since 4.6.0
*
* @var array<string, mixed>
*/
private $memoized = array();
/**
* An indicator of whether memoization is enabled. Default false.
*
* @since 4.6.0
*
* @var bool
*/
private $memoization_enabled = false;
/**
* Enables the memoization cache.
*
* @since 4.6.0
*
* @return void
*/
public function enable_memoization(): void {
$this->memoization_enabled = true;
}
/**
* Disables the memoization cache.
*
* @since 4.6.0
*
* @return void
*/
public function disable_memoization(): void {
$this->memoization_enabled = false;
}
/**
* Returns whether the memoization cache is enabled.
*
* @since 4.6.0
*
* @return bool
*/
public function memoization_is_enabled(): bool {
return $this->memoization_enabled;
}
/**
* Clears the memoization cache.
*
* @since 4.6.0
*
* @return void
*/
public function clear_memoization_cache(): void {
$this->memoized = array();
}
/**
* Memoizes the result of a callback.
*
* @since 4.6.0
*
* @template CallbackReturnType The callback output value type.
*
* @param callable(): CallbackReturnType $callback The callback to memoize.
*
* @return CallbackReturnType The result of the callback or the memoized result.
*/
protected function memoize( callable $callback ) {
$trace = debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, 2 );
$zero_stack = $trace[0];
$trace = $trace[1]; // 0 is the memoize method, 1 is the calling method.
$args = $trace['args'] ?? array();
if (
! $this->memoization_enabled
|| '{closure}' === $trace['function'] // Is closure.
|| empty( $trace['class'] ) // Is not a method call.
|| empty( $trace['type'] ) // Is not a method call.
|| '::' === $trace['type'] // Is static call.
) {
return call_user_func( $callback, $args );
}
$normalized_arguments = array_map(
function ( $argument ) {
return is_object( $argument ) ? spl_object_hash( $argument ) : $argument;
},
$args
);
$callback_hash = md5(
$trace['class'] . $trace['function'] . serialize( $normalized_arguments ) . ( $zero_stack['line'] ?? 0 )
);
if ( ! array_key_exists( $callback_hash, $this->memoized ) ) {
$this->memoized[ $callback_hash ] = call_user_func( $callback, $args );
}
return $this->memoized[ $callback_hash ];
}
}