Hello!
we're finding some problems with property overloading (__get() and
__set()). Here is an RFC describing what we'd like to see changed.
Please comment on this.
Introduction:
PHP currently supports property overloading with the magic functions
__get()
and __set(). Those methods are called when the code "echo $object->a" or
"$object->a = 42" are executed and the property "a" is not declared in
the
class. The magic methods are responsibly for:
- checking if the property actually "exists"
- setting the value or returning the value depending on the action
Problems:
1. There is no way to document the 'virtual' properties with any of
the existing
documentation tools (such as phpdoc and doxygen)
2. There is no way how the magic methods know if a specific 'virtual'
property
exists or not as those properties are not declared usually - unless
you
define an array with property names as a class constant (which is not
allowed either).
3. There is no way for the magic methods to return a meaningfull error
when a
property doesn't "exist". Of course it is possible to throw an
error with
"trigger_error" or "throw" in case a property doesn't "exist" in a
specific
class, but the file and line numbers would not match the actually
get/set
action. debug_backtrace() can be used to retrieve the correct file
and line,
but as you have to do this for every class where you want to use
setters and
getters *and* you have to implement your own error message rendering
function this is not really a suitable option either.
Solutions:
- For problem 1. we can introduce a keyword (or use an existing one)
to define
that it is a virtual property ('abstract' or 'virtual' come to
mind). When
declaring it like this it's easy to document, and we can also implement
visibility for those virtual properties. Marcus already has a patch
for
this somewhere, which uses the "abstract" keyword for this purpose:
<?php
class Base
{
/**
* @var int Contains all X
*/
abstract public $x = 1;
/**
* @var int Contains all Y
*/
abstract protected $y = 2;
// abstract private $z = 3; abstract properties cannot be private
}
?>
- In combination to the introduced keyword we need an easy way to
check if a
passed property name is declared in the class as 'virtual'.
Currently (with
the 'abstract' keyword properly implemented) you'd have to do:
<?php
class Base
{
abstract public $x = 1;
function __get($name)
{
try {
$prop = new ReflectionProperty('Base', $name);
if ( !$prop->isAbstract() ) {
/* throw error */
}
} catch ($e) {
/* throw error */
}
}
}
$b = new Base();
echo $b->foo;
?>
This is ofcourse overly complicated. A better workable solution
would be -
without breaking BC (suggestions for syntax here are very welcome):
<?php
class Base
{
abstract public $x = 1;
function __get($name)
{
if (!self::isVirtual($name))) {
/* throw error */
}
}
}
$b = new Base();
echo $b->foo;
?>
- Problem 3 can be solved by:
* allowing optional by-ref parameters to the __set
and __get function, so that their signatures become:
function __set($name, $value [, &$error] )
function __get($name [, &$error] )
If the parameter is not given things behave just like they do now
-> __set
and __get can not throw meaningful errors with the correct
file/line of the
conflicting statement
If the parameter is used in the function's declaration and is set to
"false" when the __set or __get function returns, the engine can
throw an
error on the assignment line with the correct file/line
combination. If
it's set to "true" (or "null") then the engine should not throw
any error.
* Use a different __set and __get function for 'abstract'
properties, then
the engine can already determine if you're doing something wrong
before
executing the __set and __get methods.
regards,
Derick