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