Last month, Ahmad Farouk asked a question on StackOverflow about siblings
accessing protected methods http://stackoverflow.com/q/9425770/90527 (
http://stackoverflow.com/q/9425770/90527) in PHP (I've been using 5.3.8 on
OS X; others haven't mentioned which versions they've tested). He was
surprised at the current behavior, where whether a constructor was
abstract, or concrete in an abstract parent class, or in a concrete parent
class affected whether or not a child class could be instantiated in
sibling classes. All of us involved in the Q&A were wondering what the
appropriate behavior should be.
The documentation for "protected http://php.net/protected" states in the
first paragraph on visibility:
Members declared protected can be accessed only within the class itself
and by inherited and parent classes.
Bug 37632 https://bugs.php.net/bug.php?id=37632 (protected methods not
accessible by siblings) suggests that protected methods should be
accessible by all relatives, not just ancestors and descendants. The actual
behavior is different from either of these (and apparently inconsistent).
Here's a breakdown of the current behavior, as far as I can discern:
-
Protected properties in a child class that hide a parent's property can't
be accessed in siblings (which I don't find surprising, but may be
erroneous). -
Protected constructors declared abstract in a parent can be accessed in
sibling classes.abstract class BaseClass {
abstract protected function __construct();
}
class MommasBoy extends BaseClass {
protected function __construct(){
echo METHOD, "\n";
}
}
class LatchkeyKid extends BaseClass {
public function __construct() {
echo 'In ', CLASS, ":\n";
$kid = new MommasBoy();
}
}
$obj = new LatchkeyKid();Result:
In LatchkeyKid:
MommasBoy::__construct
-
Concrete protected constructors can't be accessed in sibling classes,
whether the parent is abstract or concrete. Remove the "abstract" from
BaseClass::__construct and add an empty body in the above code, and the
result is a fatal error: "Call to protected MommasBoy::__construct() from
context 'LatchkeyKid'". Further, remove "abstract" from BaseClass, and this
same fatal error results. -
Protected __clone and __destruct are not accessible in siblings, whether
or not they're abstract.abstract class BaseClass {
abstract protected function __clone();
}class MommasBoy extends BaseClass {
protected function __clone() {
echo METHOD, "\n";
}
}class LatchkeyKid extends BaseClass {
public function __construct() {
echo 'In ', CLASS, ":\n";
$kid = new MommasBoy();
$kid = clone $kid;
}
public function __clone() {}
}$obj = new LatchkeyKid();
Result:
In LatchkeyKid:
Fatal error: Call to protected MommasBoy::__clone() from context
'LatchkeyKid'
Making __clone concrete in the above code has no affect on the behavior;
the same error is raised.
-
Non-magic methods are accessible in relatives, whether they're abstract
or concrete in a parent.abstract class BaseClass {
abstract protected function abstract_protected();
protected function concrete() {}
}class MommasBoy extends BaseClass {
/* accessible in relatives */
protected function abstract_protected() {
return METHOD;
}
protected function concrete() {
return METHOD;
}
}class LatchkeyKid extends BaseClass {
function abstract_protected() {}
public function __construct() {
$kid = new MommasBoy();
echo $kid->abstract_protected(), "\n", $kid->concrete(), "\n";
}
}$obj = new LatchkeyKid();
Result:
In LatchkeyKid:
MommasBoy::abstract_protected
MommasBoy::concrete
- Most magic methods must be public, so protected access is moot. If you
declare magic methods (other than __construct, __destruct and __clone) as
protected and ignore any warnings that the method must be public, they
appear to be accessible in relatives, as with non-magic methods.
The behavior of protected __construct is the odd one out. Except for the
abstract protected case, it behaves as __clone and __destruct, whereas I
would have expected __construct to always behave as __clone and
__destruct. The behavior of __clone and __destruct is itself somewhat
surprising, as it differs from other methods. It could be considered to be
a bug along the lines of bug 37632 https://bugs.php.net/bug.php?id=37632,
but only affecting these three magic methods. For a reason why this could
be buggy behavior, consider the following code:
abstract class BaseClass {
abstract protected function __clone();
}
class MommasBoy extends BaseClass {
protected function __clone() {
echo __METHOD__, "\n";
}
}
class LatchkeyKid extends BaseClass {
public function __construct() {
echo 'In ', __CLASS__, ":\n";
echo 'Duplicating ', __CLASS__, ': ';
$this->dup($this); // succeeds
echo "success\n";
$darling = new MommasBoy();
echo 'Duplicating MommasBoy: ';
$darling = $this->dup($darling); // fatal error
echo "success\n";
}
public function __clone() {}
public function dup(BaseClass $kid) {
return clone $kid;
}
}
$obj = new LatchkeyKid();
LatchkeyKid::dup takes a BaseClass, which declares __clone as protected.
Since LatchkeyKid descends from BaseClass, it should be able to access all
protected methods, so the expectation would be that dup() shouldn't
generate the error that it does.
Access for __clone is enforced specially in zend_vm_def.h, specifically in
the ZEND_CLONEhttp://git.php.net/?p=php-src.git;a=blob;f=Zend/zend_vm_def.h;h=8cceb19d83affc3aeee221fecb4026dfc189dff3;hb=HEAD#l3391
opcode
handler, which is why it behaves differently from normal methods. In the
time that I looked, I couldn't find where the access behavior for
__construct and __destruct was controlled in the source code.
In summary: should abstract protected constructors be inaccessible by
siblings, as is true of __clone and __destruct? Should __construct, __clone
and __destruct always be accessible in relatives, as is true of other
methods? Depending on the answers, there could be a documentation issue, or
bugs.
Hi!
handler, which is why it behaves differently from normal methods. In the
time that I looked, I couldn't find where the access behavior for
__construct and __destruct was controlled in the source code.
Access for functions is defined by zend_check_protected() and in
zend_std_get_method(), this code:
if (UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fbc),
EG(scope)))) {
In summary: should abstract protected constructors be inaccessible by
siblings, as is true of __clone and __destruct? Should __construct, __clone
and __destruct always be accessible in relatives, as is true of other
methods? Depending on the answers, there could be a documentation issue, or
bugs.
In general, function defined in base class should be accessible to all
siblings of that class. The rationale behind it is that if you define
function (even abstract) in your base class, you saying it will be
available to any instance (including extended ones) of this class. So
any descendant of this class can use it.
Now, I'm not sure why non-abstract ctors don't work this way - may be an
artifact of ctor being special function unlike all others. Needs to be
checked further in the code, I'll try to do it a bit later.
As for __clone, it may be a bug seeing that the same code in
zend_std_get_method() uses zend_get_function_root_class() but
zend_vm_def code does not. I don't see much reason why it is not there.
Same story with __destruct which is zend_objects_destroy_object() -
looks like zend_get_function_root_class() is missing there too. I'll
double check but from cursory look I see no reason why it's not there.
--
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
Hi!
In summary: should abstract protected constructors be inaccessible by
siblings, as is true of __clone and __destruct? Should __construct, __clone
and __destruct always be accessible in relatives, as is true of other
methods? Depending on the answers, there could be a documentation issue, or
bugs.
OK, I checked why ctor behaves differently, and it's because parent ctor
is considered to be prototype for child ctor (with signature
enforcement, etc.) only if it's declared abstract or brought from the
interface. So, by declaring ctor as abstract or making it part of the
interface, you make it part of the contract and thus accessible to all
hierarchy. If you do not do that, ctors are completely unrelated to each
other (this is different for all other non-static methods) and thus
having parent ctor doesn't say anything about child ctor, so parent
ctor's visibility does not carry over. So for ctor is not a bug.
I still think it's most probably a bug for __clone and __destruct.
--
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
Hi!
In summary: should abstract protected constructors be inaccessible by
siblings, as is true of __clone and __destruct? Should __construct, __clone
and __destruct always be accessible in relatives, as is true of other
methods? Depending on the answers, there could be a documentation issue, or
bugs.
I've submitted bug #61782 (https://bugs.php.net/bug.php?id=61782) to
track the issue with __clone and __destruct.
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227