135 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			135 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * Trait for issuing warnings on deprecated access.
 | |
|  *
 | |
|  * Adapted from https://github.com/wikimedia/mediawiki/blob/4aedefdbfd193f323097354bf581de1c93f02715/includes/debug/DeprecationHelper.php
 | |
|  *
 | |
|  */
 | |
| 
 | |
| 
 | |
| namespace dokuwiki\Debug;
 | |
| 
 | |
| /**
 | |
|  * Use this trait in classes which have properties for which public access
 | |
|  * is deprecated. Set the list of properties in $deprecatedPublicProperties
 | |
|  * and make the properties non-public. The trait will preserve public access
 | |
|  * but issue deprecation warnings when it is needed.
 | |
|  *
 | |
|  * Example usage:
 | |
|  *     class Foo {
 | |
|  *         use DeprecationHelper;
 | |
|  *         protected $bar;
 | |
|  *         public function __construct() {
 | |
|  *             $this->deprecatePublicProperty( 'bar', '1.21', __CLASS__ );
 | |
|  *         }
 | |
|  *     }
 | |
|  *
 | |
|  *     $foo = new Foo;
 | |
|  *     $foo->bar; // works but logs a warning
 | |
|  *
 | |
|  * Cannot be used with classes that have their own __get/__set methods.
 | |
|  *
 | |
|  */
 | |
| trait PropertyDeprecationHelper
 | |
| {
 | |
| 
 | |
|     /**
 | |
|      * List of deprecated properties, in <property name> => <class> format
 | |
|      * where <class> is the the name of the class defining the property
 | |
|      *
 | |
|      * E.g. [ '_event' => '\dokuwiki\Cache\Cache' ]
 | |
|      * @var string[]
 | |
|      */
 | |
|     protected $deprecatedPublicProperties = [];
 | |
| 
 | |
|     /**
 | |
|      * Mark a property as deprecated. Only use this for properties that used to be public and only
 | |
|      *   call it in the constructor.
 | |
|      *
 | |
|      * @param string $property The name of the property.
 | |
|      * @param null $class name of the class defining the property
 | |
|      * @see DebugHelper::dbgDeprecatedProperty
 | |
|      */
 | |
|     protected function deprecatePublicProperty(
 | |
|         $property,
 | |
|         $class = null
 | |
|     ) {
 | |
|         $this->deprecatedPublicProperties[$property] = $class ?: get_class();
 | |
|     }
 | |
| 
 | |
|     public function __get($name)
 | |
|     {
 | |
|         if (isset($this->deprecatedPublicProperties[$name])) {
 | |
|             $class = $this->deprecatedPublicProperties[$name];
 | |
|             DebugHelper::dbgDeprecatedProperty($class, $name);
 | |
|             return $this->$name;
 | |
|         }
 | |
| 
 | |
|         $qualifiedName = get_class() . '::$' . $name;
 | |
|         if ($this->deprecationHelperGetPropertyOwner($name)) {
 | |
|             // Someone tried to access a normal non-public property. Try to behave like PHP would.
 | |
|             trigger_error("Cannot access non-public property $qualifiedName", E_USER_ERROR);
 | |
|         } else {
 | |
|             // Non-existing property. Try to behave like PHP would.
 | |
|             trigger_error("Undefined property: $qualifiedName", E_USER_NOTICE);
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     public function __set($name, $value)
 | |
|     {
 | |
|         if (isset($this->deprecatedPublicProperties[$name])) {
 | |
|             $class = $this->deprecatedPublicProperties[$name];
 | |
|             DebugHelper::dbgDeprecatedProperty($class, $name);
 | |
|             $this->$name = $value;
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         $qualifiedName = get_class() . '::$' . $name;
 | |
|         if ($this->deprecationHelperGetPropertyOwner($name)) {
 | |
|             // Someone tried to access a normal non-public property. Try to behave like PHP would.
 | |
|             trigger_error("Cannot access non-public property $qualifiedName", E_USER_ERROR);
 | |
|         } else {
 | |
|             // Non-existing property. Try to behave like PHP would.
 | |
|             $this->$name = $value;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Like property_exists but also check for non-visible private properties and returns which
 | |
|      * class in the inheritance chain declared the property.
 | |
|      * @param string $property
 | |
|      * @return string|bool Best guess for the class in which the property is defined.
 | |
|      */
 | |
|     private function deprecationHelperGetPropertyOwner($property)
 | |
|     {
 | |
|         // Easy branch: check for protected property / private property of the current class.
 | |
|         if (property_exists($this, $property)) {
 | |
|             // The class name is not necessarily correct here but getting the correct class
 | |
|             // name would be expensive, this will work most of the time and getting it
 | |
|             // wrong is not a big deal.
 | |
|             return __CLASS__;
 | |
|         }
 | |
|         // property_exists() returns false when the property does exist but is private (and not
 | |
|         // defined by the current class, for some value of "current" that differs slightly
 | |
|         // between engines).
 | |
|         // Since PHP triggers an error on public access of non-public properties but happily
 | |
|         // allows public access to undefined properties, we need to detect this case as well.
 | |
|         // Reflection is slow so use array cast hack to check for that:
 | |
|         $obfuscatedProps = array_keys((array)$this);
 | |
|         $obfuscatedPropTail = "\0$property";
 | |
|         foreach ($obfuscatedProps as $obfuscatedProp) {
 | |
|             // private props are in the form \0<classname>\0<propname>
 | |
|             if (strpos($obfuscatedProp, $obfuscatedPropTail, 1) !== false) {
 | |
|                 $classname = substr($obfuscatedProp, 1, -strlen($obfuscatedPropTail));
 | |
|                 if ($classname === '*') {
 | |
|                     // sanity; this shouldn't be possible as protected properties were handled earlier
 | |
|                     $classname = __CLASS__;
 | |
|                 }
 | |
|                 return $classname;
 | |
|             }
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
| }
 |