Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:59357 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 80468 invoked from network); 5 Apr 2012 23:19:36 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 5 Apr 2012 23:19:36 -0000 Authentication-Results: pb1.pair.com header.from=ww.galen@gmail.com; sender-id=pass Authentication-Results: pb1.pair.com smtp.mail=ww.galen@gmail.com; spf=pass; sender-id=pass Received-SPF: pass (pb1.pair.com: domain gmail.com designates 209.85.210.170 as permitted sender) X-PHP-List-Original-Sender: ww.galen@gmail.com X-Host-Fingerprint: 209.85.210.170 mail-iy0-f170.google.com Received: from [209.85.210.170] ([209.85.210.170:39811] helo=mail-iy0-f170.google.com) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id 9A/48-30259-7882E7F4 for ; Thu, 05 Apr 2012 19:19:36 -0400 Received: by iaeh11 with SMTP id h11so2914746iae.29 for ; Thu, 05 Apr 2012 16:19:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:from:date:message-id:subject:to:content-type; bh=alFPQP6dBPfFyyDtPOstTHAV02jamnK01QdwMVwex2g=; b=g5EmvuzISKelg+icRr8wQt8DzT8SXnoEIntpjMK6rQL6fJjKhZ8qsU6GEDxosEXWp6 5xwOT+7iczEaVabXpXrfH3m0Y6GLuq7AyRfyav3an3/zls4C25dmZr8wKMd3u5Tmy+Wj 00qI9yVUXVtbkBKD2Ibe3z409TTKXZCqboJ2xf4eSNljQRM6SKhfAT2gQA6pJavdlqHE G5UgoQk62FcvWNGa0CiuAs7nPYSgfbImlmX4AiU0izY0RZ4OorzDi9by351B7qNh8xhh cSZXgpjKXPAUbH8fcYqNyiy1cwsUQN+rZLZ+CczgbgpBtJJdaQoGQ9r7IjI3yoxOJG1N ArkA== Received: by 10.42.58.76 with SMTP id g12mr467468ich.32.1333667972119; Thu, 05 Apr 2012 16:19:32 -0700 (PDT) MIME-Version: 1.0 Received: by 10.231.164.132 with HTTP; Thu, 5 Apr 2012 16:18:51 -0700 (PDT) Date: Thu, 5 Apr 2012 16:18:51 -0700 Message-ID: To: internals@lists.php.net Content-Type: multipart/alternative; boundary=20cf30334a05b9fbc004bcf6c505 Subject: Access by siblings of (abstract) protected methods From: ww.galen@gmail.com (Galen Wright-Watson) --20cf30334a05b9fbc004bcf6c505 Content-Type: text/plain; charset=ISO-8859-1 Last month, Ahmad Farouk asked a question on StackOverflow about siblings accessing protected methods ( 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 " 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 (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 , 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_CLONE 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. --20cf30334a05b9fbc004bcf6c505--