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