[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/Debug/ -> PropertyDeprecationHelper.php (source)

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