MediaWiki master
DeprecationHelper.php
Go to the documentation of this file.
1<?php
60trait DeprecationHelper {
61
71 protected static $deprecatedPublicProperties = [];
72
78 private $dynamicPropertiesAccessDeprecated = false;
79
94 protected function deprecatePublicProperty(
95 $property,
96 $version,
97 $class = null,
98 $component = null
99 ) {
100 if ( isset( self::$deprecatedPublicProperties[$property] ) ) {
101 return;
102 }
103 self::$deprecatedPublicProperties[$property] = [
104 $version,
105 $class ?: __CLASS__,
106 $component,
107 null, null
108 ];
109 }
110
129 string $property,
130 string $version,
131 $getter,
132 $setter = null,
133 $class = null,
134 $component = null
135 ) {
136 if ( isset( self::$deprecatedPublicProperties[$property] ) ) {
137 return;
138 }
139 self::$deprecatedPublicProperties[$property] = [
140 $version,
141 $class ?: __CLASS__,
142 null,
143 $getter,
144 $setter,
145 $component
146 ];
147 }
148
158 string $version,
159 string $class = null,
160 string $component = null
161 ) {
162 $this->dynamicPropertiesAccessDeprecated = [ $version, $class ?: __CLASS__, $component ];
163 }
164
165 public function __isset( $name ) {
166 // Overriding magic __isset is required not only for isset() and empty(),
167 // but to correctly support null coalescing for dynamic properties,
168 // e.g. $foo->bar ?? 'default'
169 if ( isset( self::$deprecatedPublicProperties[$name] ) ) {
170 [ $version, $class, $component, $getter ] = self::$deprecatedPublicProperties[$name];
171 $qualifiedName = $class . '::$' . $name;
172 wfDeprecated( $qualifiedName, $version, $component, 2 );
173 if ( $getter ) {
174 return $this->deprecationHelperCallGetter( $getter );
175 }
176 return true;
177 }
178
179 $ownerClass = $this->deprecationHelperGetPropertyOwner( $name );
180 if ( $ownerClass ) {
181 // Someone tried to access a normal non-public property. Try to behave like PHP would.
182 return false;
183 } else {
184 if ( $this->dynamicPropertiesAccessDeprecated ) {
185 [ $version, $class, $component ] = $this->dynamicPropertiesAccessDeprecated;
186 $qualifiedName = $class . '::$' . $name;
187 wfDeprecated( $qualifiedName, $version, $component, 2 );
188 }
189 return false;
190 }
191 }
192
193 public function __get( $name ) {
194 if ( isset( self::$deprecatedPublicProperties[$name] ) ) {
195 [ $version, $class, $component, $getter ] = self::$deprecatedPublicProperties[$name];
196 $qualifiedName = $class . '::$' . $name;
197 wfDeprecated( $qualifiedName, $version, $component, 2 );
198 if ( $getter ) {
199 return $this->deprecationHelperCallGetter( $getter );
200 }
201 return $this->$name;
202 }
203
204 $ownerClass = $this->deprecationHelperGetPropertyOwner( $name );
205 $qualifiedName = ( $ownerClass ?: get_class( $this ) ) . '::$' . $name;
206 if ( $ownerClass ) {
207 // Someone tried to access a normal non-public property. Try to behave like PHP would.
208 trigger_error( "Cannot access non-public property $qualifiedName", E_USER_ERROR );
209 } elseif ( property_exists( $this, $name ) ) {
210 // Normally __get method will not be even called if the property exists,
211 // but in tests if we mock an object that uses DeprecationHelper,
212 // __get and __set magic methods will be mocked as well, and called
213 // regardless of the property existence. Support that use-case.
214 return $this->$name;
215 } else {
216 // Non-existing property. Try to behave like PHP would.
217 trigger_error( "Undefined property: $qualifiedName", E_USER_NOTICE );
218 }
219 return null;
220 }
221
222 public function __set( $name, $value ) {
223 if ( isset( self::$deprecatedPublicProperties[$name] ) ) {
224 [ $version, $class, $component, , $setter ] = self::$deprecatedPublicProperties[$name];
225 $qualifiedName = $class . '::$' . $name;
226 wfDeprecated( $qualifiedName, $version, $component, 2 );
227 if ( $setter ) {
228 $this->deprecationHelperCallSetter( $setter, $value );
229 } elseif ( property_exists( $this, $name ) ) {
230 $this->$name = $value;
231 } else {
232 trigger_error( "Cannot access non-public property $qualifiedName", E_USER_ERROR );
233 }
234 return;
235 }
236
237 $ownerClass = $this->deprecationHelperGetPropertyOwner( $name );
238 $qualifiedName = ( $ownerClass ?: get_class( $this ) ) . '::$' . $name;
239 if ( $ownerClass ) {
240 // Someone tried to access a normal non-public property. Try to behave like PHP would.
241 trigger_error( "Cannot access non-public property $qualifiedName", E_USER_ERROR );
242 } else {
243 if ( $this->dynamicPropertiesAccessDeprecated ) {
244 [ $version, $class, $component ] = $this->dynamicPropertiesAccessDeprecated;
245 $qualifiedName = $class . '::$' . $name;
246 wfDeprecated( $qualifiedName, $version, $component, 2 );
247 }
248 // Non-existing property. Try to behave like PHP would.
249 $this->$name = $value;
250 }
251 }
252
260 private function deprecationHelperGetPropertyOwner( $property ) {
261 // Returning false is a non-error path and should avoid slow checks like reflection.
262 // Use array cast hack instead.
263 $obfuscatedProps = array_keys( (array)$this );
264 $obfuscatedPropTail = "\0$property";
265 foreach ( $obfuscatedProps as $obfuscatedProp ) {
266 // private props are in the form \0<classname>\0<propname>
267 if ( strpos( $obfuscatedProp, $obfuscatedPropTail, 1 ) !== false ) {
268 $classname = substr( $obfuscatedProp, 1, -strlen( $obfuscatedPropTail ) );
269 if ( $classname === '*' ) {
270 // protected property; we didn't get the name, but we are on an error path
271 // now so it's fine to use reflection
272 return ( new ReflectionProperty( $this, $property ) )->getDeclaringClass()->getName();
273 }
274 return $classname;
275 }
276 }
277 return false;
278 }
279
280 private function deprecationHelperCallGetter( $getter ) {
281 if ( is_string( $getter ) ) {
282 $getter = [ $this, $getter ];
283 } elseif ( ( new ReflectionFunction( $getter ) )->getClosureThis() !== null ) {
284 $getter = $getter->bindTo( $this );
285 }
286 return $getter();
287 }
288
289 private function deprecationHelperCallSetter( $setter, $value ) {
290 if ( is_string( $setter ) ) {
291 $setter = [ $this, $setter ];
292 } elseif ( ( new ReflectionFunction( $setter ) )->getClosureThis() !== null ) {
293 $setter = $setter->bindTo( $this );
294 }
295 $setter( $value );
296 }
297}
deprecatePublicPropertyFallback(string $property, string $version, $getter, $setter=null, $class=null, $component=null)
Mark a removed public property as deprecated and provide fallback getter and setter callables.
deprecatePublicProperty( $property, $version, $class=null, $component=null)
Mark a property as deprecated.
__set( $name, $value)
__get( $name)
deprecateDynamicPropertiesAccess(string $version, string $class=null, string $component=null)
Emit deprecation warnings when dynamic and unknown properties are accessed.
__isset( $name)
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.