[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

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

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