Subversion Repositories PHPX

Rev

Rev 60 | Blame | Compare with Previous | Last modification | View Log | RSS feed

1
<?php

namespace PointedEars\PHPX;

/**
 * Base class providing generic wrappers for reading from and
 * and writing to inaccessible properties.
 *
 * For each such property there must exist a public dynamically
 * bound method (the PHP default) in the inheriting class whose
 * name is prefixed with 'get' (a <i>getter</i>, for read access)
 * and/or with 'set' (a <i>setter</i>, for write access) followed
 * by the property name.  (It is recommended to write the first
 * letter of the property name in the method name in uppercase.
 * For runtime-efficiency, underscores in the property name are
 * <em>not</em> converted to camel-case ["property_name" requires
 * "getProperty_name", <em>not</em> "getPropertyName"].)
 *
 * The getter or setter would then access the non-public property
 * whose name is the name of the accessed property prefixed with
 * underscore ('_'), called the <em>underscore property</em>.
 * It can make sure that the value of the underscore property is
 * of a specific type or within a specific range, i. e. perform
 * automatic type conversion, normalize values that are out of
 * range when the property is read, and reject/ignore attempts
 * to set unsuitable property values.  This is particularly useful
 * with instances of model classes; see {@link AbstractModel} and
 * {@link Model}.
 *
 * Properties that do not have a getter are not available
 * from unprivileged context, and an exception is thrown
 * when attempting to read from them there.
 *
 * Properties that do not have a setter are effectively
 * <em>read-only</em> from unprivileged context, and
 * an exception is thrown when attempting to write to them there.
 *
 * @author Thomas 'PointedEars' Lahn
 */

abstract class Base
{
  /**
   * Determines if a strict model is enforced.
   *
   * If <code>true</code>, all publicly accessible properties
   * must have a getter if readable, and a setter if writable.
   * Otherwise, accesses to non-existing public properties will be
   * forwarded to the correspnding underline property if no getter
   * or setter has been defined for it.  The default is
   * <code>false</code> (non-strict) as that speeds up property
   * accesses and eases implementation considerably.
   *
   * @var bool
   */

  protected static $_strict = false;

  /**
   * Retrieves a property value.
   *
   * Automagically called when attempting to read data
   * from inaccessible properties.
   *
   * @param string $name
   *   Property name
   * @throws InvalidArgumentException
   *   if the underscore property for the property
   *   named <code><var>$name</var></code> does not exist
   *   or has no getter
   * @return mixed
   *   Return value of the property-specific getter
   */

  public function __get ($name)
  {
    $getter = 'get' . ucfirst($name);
    if (method_exists($this, $getter))
    {
      return $this->$getter();
    }

    if (property_exists($this, "_$name"))
    {
      $class = get_class($this);
      if ($class::$_strict)
      {
        throw new \InvalidArgumentException("Strict model: Property '{$name}' has no getter");
      }

      return $this->{"_$name"};
    }

    throw new \InvalidArgumentException("No such property: '{$name}'");
  }

  /**
   * Sets a property value.
   *
   * Automagically called when attempting to write data
   * to inaccessible properties.
   *
   * @param string $name
   *   Property name
   * @param mixed $value
   *   Property value
   * @throws InvalidArgumentException
   *   if the protected underscore property for the property
   *   named <code><var>$name</var></code> does not exist
   *   or has no setter (is read-only)
   * @return mixed
   *   Return value of the property-specific setter
   */

  public function __set ($name, $value)
  {
    $setter = 'set' . ucfirst($name);
    if (method_exists($this, $setter))
    {
      return $this->$setter($value);
    }

    if (property_exists($this, "_$name"))
    {
      $class = get_class($this);
      if ($class::$_strict)
      {
        throw new \InvalidArgumentException("Strict model: Property '{$name}' has no setter");
      }

      $this->{"_$name"} = $value;
      return $value;
    }

    throw new \InvalidArgumentException("No such property: '{$name}'");
  }
}