/
Disallow usage of undefined class properties [5.2.1]

Disallow usage of undefined class properties [5.2.1]

Currently it's possible to use class property that isn't even declared. This of course will produce no IDE auto-complete, but will work because PHP will declare such property dynamically.

It's considered bad practice because class declaration won't be exposing all it's properties. The solution from Phabricator is placing __set and __get methods in base class to ensure that in such cases an exception will the thrown. Example class: https://github.com/phacility/libphutil/blob/master/src/object/Phobject.php

<?php

/**
 * Base class for libphutil objects. Enforces stricter object semantics than
 * PHP.
 *
 * When a program attempts to write to an undeclared object property, PHP
 * creates the property. However, in libphutil this is always an error (for
 * example, a misspelled property name).  Instead of permitting the write,
 * subclasses will throw when an undeclared property is written.
 *
 * When a program attempts to iterate an object (for example, with `foreach`),
 * PHP iterates its public members. However, in libphutil this is always an
 * error (for example, iterating over the wrong variable). Instead of
 * permitting the iteration, subclasses will throw when an object is iterated.
 *
 * (Legitimately iterable subclasses can provide a working implementation of
 * Iterator instead.)
 */
abstract class Phobject implements Iterator {

  public function __get($name) {
    throw new DomainException(
      pht(
        'Attempt to read from undeclared property %s.',
        get_class($this).'::'.$name));
    }

  public function __set($name, $value) {
    throw new DomainException(
      pht(
        'Attempt to write to undeclared property %s.',
        get_class($this).'::'.$name));
  }

  public function current() {
    $this->throwOnAttemptedIteration();
  }

  public function key() {
    $this->throwOnAttemptedIteration();
  }

  public function next() {
    $this->throwOnAttemptedIteration();
  }

  public function rewind() {
    $this->throwOnAttemptedIteration();
  }

  public function valid() {
    $this->throwOnAttemptedIteration();
  }

  private function throwOnAttemptedIteration() {
    throw new DomainException(
      pht(
        'Attempting to iterate an object (of class %s) which is not iterable.',
        get_class($this)));
  }

}

Related Tasks