1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
<?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;
}
}
|