Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:84766 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 26064 invoked from network); 14 Mar 2015 10:33:36 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 14 Mar 2015 10:33:36 -0000 Authentication-Results: pb1.pair.com smtp.mail=nikita.ppv@gmail.com; spf=pass; sender-id=pass Authentication-Results: pb1.pair.com header.from=nikita.ppv@gmail.com; sender-id=pass Received-SPF: pass (pb1.pair.com: domain gmail.com designates 209.85.212.169 as permitted sender) X-PHP-List-Original-Sender: nikita.ppv@gmail.com X-Host-Fingerprint: 209.85.212.169 mail-wi0-f169.google.com Received: from [209.85.212.169] ([209.85.212.169:34028] helo=mail-wi0-f169.google.com) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id 1D/C0-19236-D7E04055 for ; Sat, 14 Mar 2015 05:33:35 -0500 Received: by wibg7 with SMTP id g7so4278138wib.1 for ; Sat, 14 Mar 2015 03:33:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:date:message-id:subject:from:to :cc:content-type; bh=oX/BEG31ZsWBuv4auj6oLVmIDg3R8KF+0HrqIp8+1Ug=; b=d1J5uk1jV7HiT6NLJgCCOAjJMkBauXg2T1D3aSbIkuNTGpihQ9yEW7Xd70aDjEfCnk jGjladlZFdk1Ei14rd5UgxQ3CMu3cN1UHOtnrfuMxrgx3pXEN6apCjDuWmsg+95bxMEl 6mpyKc3V+nCtxeBOt/wBOhj+FfTiHETdOpQ2ovnxJgaqeGT4UT2M19QX5CBvKdjvS9F8 Whbjs0AqPh8RFzNWNiLXKmMk2GUAXHzg6xoMqU0TFQik/s8yfuyp0sDw006WDn388G9D h35veMlBIkTUxKMRxSdN/ZGYGO7puLWf3SUj7cWQUOx4n61wbq3t8+3gouT34MvcaZWE TD0w== MIME-Version: 1.0 X-Received: by 10.194.110.38 with SMTP id hx6mr103033234wjb.128.1426329210515; Sat, 14 Mar 2015 03:33:30 -0700 (PDT) Received: by 10.27.10.193 with HTTP; Sat, 14 Mar 2015 03:33:30 -0700 (PDT) In-Reply-To: References: Date: Sat, 14 Mar 2015 11:33:30 +0100 Message-ID: To: Philip Sturgeon Cc: "internals@lists.php.net" Content-Type: multipart/alternative; boundary=089e010d89f2ecf72a05113d2571 Subject: Re: [PHP-DEV] [VOTE] [RFC] Anonymous Classes From: nikita.ppv@gmail.com (Nikita Popov) --089e010d89f2ecf72a05113d2571 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable On Fri, Mar 13, 2015 at 8:33 PM, Philip Sturgeon wrote: > A two week discussion period has been held and there are no outstanding > issues. > > Serialization has been disabled, and generated names have been > explained better in the newest version of the RFC > > https://wiki.php.net/rfc/anonymous_classes > > The implementation needs to be updated with changes from master, but > that can be done at a later point and should not be used as a reason > to vote against. > I'm not yet sure which way to vote on this RFC. I don't think there's anything principally wrong with anonymous classes, but the current RFC seems incomplete to me with regard to scoping (and as future changes in this area are not necessarily backwards-compatible, I'd rather solve this now than later). Lets start off with an example from the RFC: class Outside { protected $data; public function __construct($data) { $this->data =3D $data; } public function getArrayAccess() { return new class($this->data) extends Outside implements ArrayAcces= s { public function offsetGet($offset) { return $this->data[$offset= ]; } public function offsetSet($offset, $data) { return ($this->data[$offset] =3D $data); } public function offsetUnset($offset) { unset ($this->data[$offset]); } public function offsetExists($offset) { return isset ($this->data[$offset]); } }; }} So ... WTF. It probably took me 10 minutes yesterday to finally understand the evil, evil things this code is doing and why it is doing them. This is what happens: The new anon class extends Outside (the wrapping class) and as such also inherits the parent constructor. The new class($this->data) bit passes $this->data to the constructor (the one inherited from Outside), as such $this->data will be assigned in the inner class as well. Because the anonymous class extended from Outside it is allowed to access the protected $data member. Why is this (imho very weird and unintuitive) approach used? Quoting from the RFC: "[E]xtending Outer [sic] allows the nested class implementing ArrayAccess permission to execute protected methods, declared in the Outer [sic] class, on the same $this=E2=86=92data". So the reason behind this is that anonymous classes as implemented by this RFC - and much unlike the anonymous class implementations you will find in other languages like Java or D - are considered to be totally unrelated to the wrapping class and have only "public" access to its scope. I don't think this is good. The solution ("extends Outside") to work around this that is presented in the RFC has a number of problems: * As PHP does not support multiple inheritance, extending Outside for scope access means no other class may be extended. * Extending the wrapping doesn't just give you access to restricted methods/properties of the wrapping class - it will also import everything into the inner class. This means that the anonymous class will have a bunch of additional methods and properties (potentially public ones) which have nothing to do with whatever the class is actually for. * The approach used to pass the data into the inner class by using the constructor of the wrapping class assumes that the constructor a) only performs simple assignments and b) accepts the entire state of the wrapping class as parameters - if this is not the case you will likely not be able to duplicate the state of the wrapping class in the inner anonymous class. To solve this without such hacks, two things are necessary: a) Assuming that the anonymous class somehow got hold of an instance of the wrapping class (i.e. it has an object $obj instanceof Outside), it should be able to access private and protected properties and methods of that object. This is consistent with the general rule that any code within a class body can access it's privates, unless explicitly rebound. For example, the following code should work: class Outside { private $private; public function getAnon() { return new class($this) { private $outside; public function __construct($outside) { $this->outside =3D $outside; } public function getPrivateOfOutside() { return $this->outside->private; } }; } } b) Providing some easy way to access the instance of the wrapping class. The previous example passed it in as a constructor, which is somewhat verbose. A possible syntax for accessing the wrapping instance would be Outside::$this (to borrow Java syntax): class Outside { private $private; public function getAnon() { return new class { public function getPrivateOfOutside() { return Outside::$this->private; } }; } } Point a) is the more fundamental issue, which I think should be addressed from the start. It's also not backwards compatible to introduce it as a later point. Point b) is pretty much just syntax sugar once a) stands. (The example of a) can be further simplified by using a trait that automatically implements that constructor and property, to avoid the boilerplate.) Nikita --089e010d89f2ecf72a05113d2571--