Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
78.95% |
30 / 38 |
|
63.64% |
7 / 11 |
CRAP | |
0.00% |
0 / 1 |
StubObject | |
81.08% |
30 / 37 |
|
63.64% |
7 / 11 |
20.19 | |
0.00% |
0 / 1 |
__construct | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 | |||
isRealObject | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
unstub | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
_call | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
_newObject | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
__call | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
_get | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
__get | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
_set | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
__set | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
_unstub | |
76.92% |
10 / 13 |
|
0.00% |
0 / 1 |
4.20 |
1 | <?php |
2 | |
3 | // phpcs:disable MediaWiki.Commenting.FunctionComment.ObjectTypeHintReturn |
4 | // phpcs:disable MediaWiki.Commenting.FunctionComment.ObjectTypeHintParam |
5 | |
6 | /** |
7 | * Delayed loading of global objects. |
8 | * |
9 | * This program is free software; you can redistribute it and/or modify |
10 | * it under the terms of the GNU General Public License as published by |
11 | * the Free Software Foundation; either version 2 of the License, or |
12 | * (at your option) any later version. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | * GNU General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU General Public License along |
20 | * with this program; if not, write to the Free Software Foundation, Inc., |
21 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
22 | * http://www.gnu.org/copyleft/gpl.html |
23 | * |
24 | * @file |
25 | */ |
26 | |
27 | namespace MediaWiki\StubObject; |
28 | |
29 | use LogicException; |
30 | use Wikimedia\ObjectFactory\ObjectFactory; |
31 | |
32 | /** |
33 | * Class to implement stub globals, which are globals that delay loading the |
34 | * their associated module code by deferring initialisation until the first |
35 | * method call. |
36 | * |
37 | * Note on reference parameters: |
38 | * |
39 | * If the called method takes any parameters by reference, the __call magic |
40 | * here won't work correctly. The solution is to unstub the object before |
41 | * calling the method. |
42 | * |
43 | * Note on unstub loops: |
44 | * |
45 | * Unstub loops (infinite recursion) sometimes occur when a constructor calls |
46 | * another function, and the other function calls some method of the stub. The |
47 | * best way to avoid this is to make constructors as lightweight as possible, |
48 | * deferring any initialisation which depends on other modules. As a last |
49 | * resort, you can use MediaWiki\StubObject\StubObject::isRealObject() to break the loop, but as a |
50 | * general rule, the stub object mechanism should be transparent, and code |
51 | * which refers to it should be kept to a minimum. |
52 | * |
53 | * @newable |
54 | */ |
55 | class StubObject { |
56 | /** @var null|string */ |
57 | protected $global; |
58 | |
59 | /** @var null|string */ |
60 | protected $class; |
61 | |
62 | /** @var null|callable */ |
63 | protected $factory; |
64 | |
65 | /** @var array */ |
66 | protected $params; |
67 | |
68 | /** |
69 | * @stable to call |
70 | * |
71 | * @param string|null $global Name of the global variable. |
72 | * @param string|callable|null $class Name of the class of the real object |
73 | * or a factory function to call |
74 | * @param array $params Parameters to pass to constructor of the real object. |
75 | */ |
76 | public function __construct( $global = null, $class = null, $params = [] ) { |
77 | $this->global = $global; |
78 | if ( is_callable( $class ) ) { |
79 | $this->factory = $class; |
80 | } else { |
81 | $this->class = $class; |
82 | } |
83 | $this->params = $params; |
84 | } |
85 | |
86 | /** |
87 | * Returns a bool value whenever $obj is a stub object. Can be used to break |
88 | * a infinite loop when unstubbing an object. |
89 | * |
90 | * @param object $obj Object to check. |
91 | * @return bool True if $obj is not an instance of MediaWiki\StubObject\StubObject class. |
92 | */ |
93 | public static function isRealObject( $obj ) { |
94 | return is_object( $obj ) && !$obj instanceof self; |
95 | } |
96 | |
97 | /** |
98 | * Unstubs an object, if it is a stub object. Can be used to break a |
99 | * infinite loop when unstubbing an object or to avoid reference parameter |
100 | * breakage. |
101 | * |
102 | * @param object &$obj Object to check. |
103 | * @return void |
104 | */ |
105 | public static function unstub( &$obj ) { |
106 | if ( $obj instanceof self ) { |
107 | $obj = $obj->_unstub( 'unstub', 3 ); |
108 | } |
109 | } |
110 | |
111 | /** |
112 | * Function called if any function exists with that name in this object. |
113 | * It is used to unstub the object. Only used internally, PHP will call |
114 | * self::__call() function and that function will call this function. |
115 | * This function will also call the function with the same name in the real |
116 | * object. |
117 | * |
118 | * @param string $name Name of the function called |
119 | * @param array $args Arguments |
120 | * @return mixed |
121 | */ |
122 | // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore |
123 | public function _call( $name, $args ) { |
124 | $this->_unstub( $name, 5 ); |
125 | return call_user_func_array( [ $GLOBALS[$this->global], $name ], $args ); |
126 | } |
127 | |
128 | /** |
129 | * Create a new object to replace this stub object. |
130 | * @return object |
131 | */ |
132 | // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore |
133 | public function _newObject() { |
134 | $params = $this->factory |
135 | ? [ 'factory' => $this->factory ] |
136 | : [ 'class' => $this->class ]; |
137 | |
138 | // ObjectFactory::getObjectFromSpec accepts an array, not just a callable (phan bug) |
139 | // @phan-suppress-next-line PhanTypeInvalidCallableArraySize |
140 | return ObjectFactory::getObjectFromSpec( $params + [ |
141 | 'args' => $this->params, |
142 | 'closure_expansion' => false, |
143 | ] ); |
144 | } |
145 | |
146 | /** |
147 | * Function called by PHP if no function with that name exists in this |
148 | * object. |
149 | * |
150 | * @param string $name Name of the function called |
151 | * @param array $args Arguments |
152 | * @return mixed |
153 | */ |
154 | public function __call( $name, $args ) { |
155 | return $this->_call( $name, $args ); |
156 | } |
157 | |
158 | /** |
159 | * Wrapper for __get(), similar to _call() above |
160 | * |
161 | * @param string $name Name of the property to get |
162 | * @return mixed |
163 | */ |
164 | // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore |
165 | public function _get( $name ) { |
166 | $this->_unstub( "__get($name)", 5 ); |
167 | return $GLOBALS[$this->global]->$name; |
168 | } |
169 | |
170 | /** |
171 | * Function called by PHP if no property with that name exists in this |
172 | * object. |
173 | * |
174 | * @param string $name Name of the property to get |
175 | * @return mixed |
176 | */ |
177 | public function __get( $name ) { |
178 | return $this->_get( $name ); |
179 | } |
180 | |
181 | /** |
182 | * Wrapper for __set(), similar to _call() above |
183 | * |
184 | * @param string $name Name of the property to set |
185 | * @param mixed $value New property value |
186 | */ |
187 | // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore |
188 | public function _set( $name, $value ) { |
189 | $this->_unstub( "__set($name)", 5 ); |
190 | $GLOBALS[$this->global]->$name = $value; |
191 | } |
192 | |
193 | /** |
194 | * Function called by PHP if no property with that name exists in this |
195 | * object. |
196 | * |
197 | * @param string $name Name of the property to set |
198 | * @param mixed $value New property value |
199 | */ |
200 | public function __set( $name, $value ) { |
201 | $this->_set( $name, $value ); |
202 | } |
203 | |
204 | /** |
205 | * This function creates a new object of the real class and replace it in |
206 | * the global variable. |
207 | * This is public, for the convenience of external callers wishing to access |
208 | * properties, e.g. eval.php |
209 | * |
210 | * @param string $name Name of the method called in this object. |
211 | * @param int $level Level to go in the stack trace to get the function |
212 | * who called this function. |
213 | * @return object The unstubbed version of itself |
214 | */ |
215 | // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore |
216 | public function _unstub( $name = '_unstub', $level = 2 ) { |
217 | static $recursionLevel = 0; |
218 | |
219 | if ( !$GLOBALS[$this->global] instanceof self ) { |
220 | return $GLOBALS[$this->global]; // already unstubbed. |
221 | } |
222 | |
223 | if ( get_class( $GLOBALS[$this->global] ) != $this->class ) { |
224 | $caller = wfGetCaller( $level ); |
225 | if ( ++$recursionLevel > 2 ) { |
226 | throw new LogicException( "Unstub loop detected on call of " |
227 | . "\${$this->global}->$name from $caller\n" ); |
228 | } |
229 | wfDebug( "Unstubbing \${$this->global} on call of " |
230 | . "\${$this->global}::$name from $caller" ); |
231 | $GLOBALS[$this->global] = $this->_newObject(); |
232 | --$recursionLevel; |
233 | return $GLOBALS[$this->global]; |
234 | } |
235 | } |
236 | } |
237 | |
238 | /** @deprecated class alias since 1.40 */ |
239 | class_alias( StubObject::class, 'StubObject' ); |