1b4b0b31bSMichael Große<?php 2b4b0b31bSMichael Große/** 3b4b0b31bSMichael Große * Trait for issuing warnings on deprecated access. 4b4b0b31bSMichael Große * 5b4b0b31bSMichael Große * Adapted from https://github.com/wikimedia/mediawiki/blob/4aedefdbfd193f323097354bf581de1c93f02715/includes/debug/DeprecationHelper.php 6b4b0b31bSMichael Große * 7b4b0b31bSMichael Große */ 8b4b0b31bSMichael Große 9b4b0b31bSMichael Große 10b4b0b31bSMichael Großenamespace dokuwiki\Debug; 11b4b0b31bSMichael Große 12b4b0b31bSMichael Große/** 13b4b0b31bSMichael Große * Use this trait in classes which have properties for which public access 14b4b0b31bSMichael Große * is deprecated. Set the list of properties in $deprecatedPublicProperties 15b4b0b31bSMichael Große * and make the properties non-public. The trait will preserve public access 16b4b0b31bSMichael Große * but issue deprecation warnings when it is needed. 17b4b0b31bSMichael Große * 18b4b0b31bSMichael Große * Example usage: 19b4b0b31bSMichael Große * class Foo { 20b4b0b31bSMichael Große * use DeprecationHelper; 21b4b0b31bSMichael Große * protected $bar; 22b4b0b31bSMichael Große * public function __construct() { 23b4b0b31bSMichael Große * $this->deprecatePublicProperty( 'bar', '1.21', __CLASS__ ); 24b4b0b31bSMichael Große * } 25b4b0b31bSMichael Große * } 26b4b0b31bSMichael Große * 27b4b0b31bSMichael Große * $foo = new Foo; 28b4b0b31bSMichael Große * $foo->bar; // works but logs a warning 29b4b0b31bSMichael Große * 30b4b0b31bSMichael Große * Cannot be used with classes that have their own __get/__set methods. 31b4b0b31bSMichael Große * 32b4b0b31bSMichael Große */ 33b4b0b31bSMichael Großetrait PropertyDeprecationHelper 34b4b0b31bSMichael Große{ 35b4b0b31bSMichael Große 36b4b0b31bSMichael Große /** 37b4b0b31bSMichael Große * List of deprecated properties, in <property name> => <class> format 38b4b0b31bSMichael Große * where <class> is the the name of the class defining the property 39b4b0b31bSMichael Große * 40b4b0b31bSMichael Große * E.g. [ '_event' => '\dokuwiki\Cache\Cache' ] 41b4b0b31bSMichael Große * @var string[] 42b4b0b31bSMichael Große */ 43b4b0b31bSMichael Große protected $deprecatedPublicProperties = []; 44b4b0b31bSMichael Große 45b4b0b31bSMichael Große /** 46b4b0b31bSMichael Große * Mark a property as deprecated. Only use this for properties that used to be public and only 47b4b0b31bSMichael Große * call it in the constructor. 48b4b0b31bSMichael Große * 49b4b0b31bSMichael Große * @param string $property The name of the property. 50b4b0b31bSMichael Große * @param null $class name of the class defining the property 51*0c5eb5e2SMichael Große * @see DebugHelper::dbgDeprecatedProperty 52b4b0b31bSMichael Große */ 53b4b0b31bSMichael Große protected function deprecatePublicProperty( 54b4b0b31bSMichael Große $property, 55b4b0b31bSMichael Große $class = null 56b4b0b31bSMichael Große ) { 57b4b0b31bSMichael Große $this->deprecatedPublicProperties[$property] = $class ?: get_class(); 58b4b0b31bSMichael Große } 59b4b0b31bSMichael Große 60b4b0b31bSMichael Große public function __get($name) 61b4b0b31bSMichael Große { 62b4b0b31bSMichael Große if (isset($this->deprecatedPublicProperties[$name])) { 63b4b0b31bSMichael Große $class = $this->deprecatedPublicProperties[$name]; 64*0c5eb5e2SMichael Große DebugHelper::dbgDeprecatedProperty($class, $name); 65b4b0b31bSMichael Große return $this->$name; 66b4b0b31bSMichael Große } 67b4b0b31bSMichael Große 68b4b0b31bSMichael Große $qualifiedName = get_class() . '::$' . $name; 69b4b0b31bSMichael Große if ($this->deprecationHelperGetPropertyOwner($name)) { 70b4b0b31bSMichael Große // Someone tried to access a normal non-public property. Try to behave like PHP would. 71b4b0b31bSMichael Große trigger_error("Cannot access non-public property $qualifiedName", E_USER_ERROR); 72b4b0b31bSMichael Große } else { 73b4b0b31bSMichael Große // Non-existing property. Try to behave like PHP would. 74b4b0b31bSMichael Große trigger_error("Undefined property: $qualifiedName", E_USER_NOTICE); 75b4b0b31bSMichael Große } 76b4b0b31bSMichael Große return null; 77b4b0b31bSMichael Große } 78b4b0b31bSMichael Große 79b4b0b31bSMichael Große public function __set($name, $value) 80b4b0b31bSMichael Große { 81b4b0b31bSMichael Große if (isset($this->deprecatedPublicProperties[$name])) { 82b4b0b31bSMichael Große $class = $this->deprecatedPublicProperties[$name]; 83*0c5eb5e2SMichael Große DebugHelper::dbgDeprecatedProperty($class, $name); 84b4b0b31bSMichael Große $this->$name = $value; 85b4b0b31bSMichael Große return; 86b4b0b31bSMichael Große } 87b4b0b31bSMichael Große 88b4b0b31bSMichael Große $qualifiedName = get_class() . '::$' . $name; 89b4b0b31bSMichael Große if ($this->deprecationHelperGetPropertyOwner($name)) { 90b4b0b31bSMichael Große // Someone tried to access a normal non-public property. Try to behave like PHP would. 91b4b0b31bSMichael Große trigger_error("Cannot access non-public property $qualifiedName", E_USER_ERROR); 92b4b0b31bSMichael Große } else { 93b4b0b31bSMichael Große // Non-existing property. Try to behave like PHP would. 94b4b0b31bSMichael Große $this->$name = $value; 95b4b0b31bSMichael Große } 96b4b0b31bSMichael Große } 97b4b0b31bSMichael Große 98b4b0b31bSMichael Große /** 99b4b0b31bSMichael Große * Like property_exists but also check for non-visible private properties and returns which 100b4b0b31bSMichael Große * class in the inheritance chain declared the property. 101b4b0b31bSMichael Große * @param string $property 102b4b0b31bSMichael Große * @return string|bool Best guess for the class in which the property is defined. 103b4b0b31bSMichael Große */ 104b4b0b31bSMichael Große private function deprecationHelperGetPropertyOwner($property) 105b4b0b31bSMichael Große { 106b4b0b31bSMichael Große // Easy branch: check for protected property / private property of the current class. 107b4b0b31bSMichael Große if (property_exists($this, $property)) { 108b4b0b31bSMichael Große // The class name is not necessarily correct here but getting the correct class 109b4b0b31bSMichael Große // name would be expensive, this will work most of the time and getting it 110b4b0b31bSMichael Große // wrong is not a big deal. 111b4b0b31bSMichael Große return __CLASS__; 112b4b0b31bSMichael Große } 113b4b0b31bSMichael Große // property_exists() returns false when the property does exist but is private (and not 114b4b0b31bSMichael Große // defined by the current class, for some value of "current" that differs slightly 115b4b0b31bSMichael Große // between engines). 116b4b0b31bSMichael Große // Since PHP triggers an error on public access of non-public properties but happily 117b4b0b31bSMichael Große // allows public access to undefined properties, we need to detect this case as well. 118b4b0b31bSMichael Große // Reflection is slow so use array cast hack to check for that: 119b4b0b31bSMichael Große $obfuscatedProps = array_keys((array)$this); 120b4b0b31bSMichael Große $obfuscatedPropTail = "\0$property"; 121b4b0b31bSMichael Große foreach ($obfuscatedProps as $obfuscatedProp) { 122b4b0b31bSMichael Große // private props are in the form \0<classname>\0<propname> 123b4b0b31bSMichael Große if (strpos($obfuscatedProp, $obfuscatedPropTail, 1) !== false) { 124b4b0b31bSMichael Große $classname = substr($obfuscatedProp, 1, -strlen($obfuscatedPropTail)); 125b4b0b31bSMichael Große if ($classname === '*') { 126b4b0b31bSMichael Große // sanity; this shouldn't be possible as protected properties were handled earlier 127b4b0b31bSMichael Große $classname = __CLASS__; 128b4b0b31bSMichael Große } 129b4b0b31bSMichael Große return $classname; 130b4b0b31bSMichael Große } 131b4b0b31bSMichael Große } 132b4b0b31bSMichael Große return false; 133b4b0b31bSMichael Große } 134b4b0b31bSMichael Große} 135