PHP currently supports property overloading provided by magic methods __get() and __set(). Those methods are called when accessed property is not declared in the class. Actually, this overloading is a bit frustrating.

This is my solution how to simulate properties with accessor methods in a Delphi way.

So…

// define new "like-keyword"
// value is short and unusual string
define('property',   "\0\0");

Maybe _property is a better choice. It doesn't matter now.

And the Initializator:

function _property($obj)
{
	// use cache (analyze object only once)
	static $cache;
	$props = & $cache[ get_class($obj) ];

	if ($props === null) {
	// build list of properties
	// 1) get_object_vars prevents Object Iteration
	// 2) outside of object, it returns only public members
	$props = array_flip(array_keys(get_object_vars($obj), property, true));
	}

	foreach ($props as $name => $foo) {
	unset($obj->$name); // unset property to pass control to __set() and __get()
	}

	return $props;
}

See documented source code of class:

class Rectangle
{
	// list of properties
	private $_props;

	// shadow properties
	private $_width;
	private $_height;


	/**
	 * @var int  phpDoc for virtual property!
	 * getter: $_width  setter: setWidth
	 */
	public $width  = property;
	public $height = property;
	public $area   = property;


	function __construct()
	{
		// property initialization
		$this->_props = _property($this);
	}

	/**
	 * Universal setter.
	 *
	 * If property 'abc' is declared,
	 * there must be a setAbc() setter,
	 * otherwise the property is read-only
	 */
	final function __set($name, $value)
	{
		if (!isset($this->_props[$name])) {
			throw new Exception("Undefined property '$name'.");
		} elseif (method_exists($this, 'set'.$name)) {
			$this->{'set'.$name}($value);
		} else {
			throw new Exception("Property '$name' is read-only.");
		}
	}

	/**
	 * Universal getter.
	 *
	 * If property 'abc' is declared,
	 * there must be a getAbc() getter,
	 * otherwise it uses the variable $_abc
	 */
	final function &__get($name)
	{
		if (!isset($this->_props[$name])) {
			throw new Exception("Undefined property '$name'.");
		} elseif (method_exists($this, 'get'.$name)) {
			return $this->{'get'.$name}();
		} else {
			return $this->{'_'.$name};
		}
	}


	// getters and setters:

	protected function setWidth($value)
	{
		$this->_width = abs($value);
	}

	protected function setHeight($value)
	{
		$this->_height = abs($value);
	}

	protected function getArea()
	{
		return $this->_height * $this->_width;
	}

}


// usage:

$rect = new Rectangle();
$rect->width = 10;
$rect->height = 5;

echo $rect->area;
// prints 50

// try receive private member
echo $rect->props;
// throw exception

Pros:

  • universal __set & __get, no overriden is needed
  • magic methods know if specific property exists
  • so this mechanism cannot be misused to get private members
  • properties are case-sensitive, like other variables
  • these __set & __get works very fast (no switches or ifs)
  • virtual properties may be documented with any of the existing tools (phpDoc, doxygen)
  • IDE autocomplete based on phpDoc will work
  • easily adaptable for future PHP6 (I hope)

Cons:

  • overloading is still not safe (see bug #36484, fixed in PHP 5.2.1)

Since PHP 5.1.0 it is also possible to overload the isset() and unset():

	final private function __isset($name)
	{
		return isset($this->_props[$name]);
	}

	final private function __unset($name)
	{
		unset($this->_props[$name]);
	}

Some links: