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