MediaWiki REL1_37
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}
deprecatePublicProperty( $property, $version, $class=null, $component=null)
Mark a property as deprecated.
__set( $name, $value)
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.
__get( $name)
deprecateDynamicPropertiesAccess(string $version, string $class=null, string $component=null)
Emit deprecation warnings when dynamic and unknown properties are accessed.
bool array $dynamicPropertiesAccessDeprecated
Whether to emit a deprecation warning when unknown properties are accessed.
__isset( $name)
trait DeprecationHelper
Use this trait in classes which have properties for which public access is deprecated or implementati...
deprecationHelperGetPropertyOwner( $property)
Like property_exists but also check for non-visible private properties and returns which class in the...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.