summaryrefslogtreecommitdiff
path: root/platform/www/inc/Debug/PropertyDeprecationHelper.php
diff options
context:
space:
mode:
Diffstat (limited to 'platform/www/inc/Debug/PropertyDeprecationHelper.php')
-rw-r--r--platform/www/inc/Debug/PropertyDeprecationHelper.php134
1 files changed, 134 insertions, 0 deletions
diff --git a/platform/www/inc/Debug/PropertyDeprecationHelper.php b/platform/www/inc/Debug/PropertyDeprecationHelper.php
new file mode 100644
index 0000000..6289d5b
--- /dev/null
+++ b/platform/www/inc/Debug/PropertyDeprecationHelper.php
@@ -0,0 +1,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;
+ }
+}