Note: the following has been tested in PHP 5.3.8. Other versions may vary.
Since there is no formal specification for PHP, there is no way to answer this in terms of what is supposed to happen. The closest we can get is the protected statement from the PHP manual:
Declared protected members can only be accessed within the class itself and by inherited and parent classes.
Although a member can be overridden in ChildClass
(preserving the "protected" specifier), it was originally declared in BaseClass
, so it remains visible to descendants of BaseClass
.
In direct contrast to this interpretation, compare the behavior for the protected property:
<?php abstract class BaseClass { protected $_foo = 'foo'; abstract protected function __construct(); } class MommasBoy extends BaseClass { protected $_foo = 'foobar'; protected function __construct(){ echo __METHOD__, "\n"; } } class LatchkeyKid extends BaseClass { public function __construct() { echo 'In ', __CLASS__, ":\n"; $kid = new MommasBoy(); echo $kid->_foo, "\n"; } } $obj = new LatchkeyKid();
Conclusion:
In LatchkeyKid:
MommasBoy :: __ construct
Fatal error: Cannot access protected property MommasBoy :: $ _ foo in - on line 18
Changing an abstract __construct
to a specific function with an empty implementation gives the desired behavior.
abstract class BaseClass { protected function __construct() {} }
However, non-magical methods are visible to relatives, regardless of whether they are abstract (most magic methods should be publicly available).
<?php abstract class BaseClass { abstract protected function abstract_protected(); protected function concrete() {} } class MommasBoy extends BaseClass { protected function abstract_protected() { return __METHOD__; } protected function concrete() { return __METHOD__; } } class LatchkeyKid extends BaseClass { function abstract_protected() {} public function __construct() { echo 'In ', __CLASS__, ":\n"; $kid = new MommasBoy(); echo $kid->abstract_protected(), "\n", $kid->concrete(), "\n"; } } $obj = new LatchkeyKid();
Conclusion:
In LatchkeyKid:
MommasBoy :: abstract_protected
MommasBoy :: concrete
If you ignore warnings and declare magic methods (other than __construct
, __destruct
and __clone
) as protected
, they appear to be available to relatives, as with non-magic methods.
Protected __clone
and __destruct
not available to relatives, regardless of whether they are abstract. This makes me think that the behavior of abstract __construct
is a mistake.
<?php 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();
Conclusion:
In LatchkeyKid:
Fatal error: Call to protected MommasBoy :: __ clone () from context 'LatchkeyKid' in - on line 16
Access to __clone
is done in zend_vm_def.h (in particular, the ZEND_CLONE
opcode handler). This is in addition to method access checks, so there may be other behavior. However, I do not see a special appeal for access to __destruct
, so there is clearly more.
Stas Malyshev (hi, Stas!), One of the PHP developers, looked over __construct
, __clone
and __destruct
and said the following:
In general, a function defined in a base class should be available to all [descendants] of this class. The rationale for this is that if you define (even abstractly) in your base class, you say that it will be available for any instance (including extended) of this class. So any descendant of this class can use it.
[...] I checked why ctor behaves differently, and this is because the parent ctor is considered the prototype for the ctor child (signed with enforce, etc.) only if it declared an abstract or an interface. So, by declaring ctor abstract or by making it part of the interface, you make it part of the contract and, therefore, accessible to all hierarchies. If you do not, ctors is completely unrelated to each other (this is different for all other non-static methods) and thus, if the parent ctor does not say anything about the ctor child, therefore the parent ctor visibility is not portable. So for ctor is not a mistake. [Note: this is similar to J. Bruni's answer.]
I still think this is most likely an error for __clone and __destruct.
[...]
I posted bug # 61782 to track issues with __clone and __destruct.