Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:75122 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 56100 invoked from network); 28 Jun 2014 13:36:56 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 28 Jun 2014 13:36:56 -0000 Authentication-Results: pb1.pair.com smtp.mail=sebastian@php.net; spf=unknown; sender-id=unknown Authentication-Results: pb1.pair.com header.from=sebastian@php.net; sender-id=unknown Received-SPF: unknown (pb1.pair.com: domain php.net does not designate 93.190.64.237 as permitted sender) X-PHP-List-Original-Sender: sebastian@php.net X-Host-Fingerprint: 93.190.64.237 mail-1.de-punkt.de Received: from [93.190.64.237] ([93.190.64.237:55412] helo=mail-1.de-punkt.de) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id BD/60-52706-5F4CEA35 for ; Sat, 28 Jun 2014 09:36:55 -0400 Received: from localhost (localhost [127.0.0.1]) by mail-1.de-punkt.de (Postfix) with ESMTP id B11373A340 for ; Sat, 28 Jun 2014 15:36:49 +0200 (CEST) X-Virus-Scanned: Debian amavisd-new at mail-1.de-punkt.de Received: from mail-1.de-punkt.de ([127.0.0.1]) by localhost (mail-1.de-punkt.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id HjYVj3rJzrmB for ; Sat, 28 Jun 2014 15:36:49 +0200 (CEST) Received: from localhost.localdomain (GK-84-46-96-210.routing.wtnet.de [84.46.96.210]) (Authenticated sender: php@sebastian-bergmann.de) by mail-1.de-punkt.de (Postfix) with ESMTPSA id 559DB3A33D for ; Sat, 28 Jun 2014 15:36:49 +0200 (CEST) Message-ID: <53AEC4F0.8040307@php.net> Date: Sat, 28 Jun 2014 15:36:48 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Thunderbird/24.6.0 MIME-Version: 1.0 To: internals@lists.php.net Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit Subject: Revisiting Anonymous Classes From: sebastian@php.net (Sebastian Bergmann) In a unit test, test doubles (dummies, fakes, stubs, spys, and mocks) are used to simulate the behavior of complex, real objects when a real object is difficult or impossible to incorporate into a unit test. A test double can be used anywhere in the program where the program expects an object of the original class. Traditionally, frameworks and libraries are used to automatically generate test double objects based on the original objects using reflection. This idea stems from a time when IDEs were not powerful enough to do this generation for us. Quoting Robert C. Martin [1]: "Stubs and spies are very easy to write. My IDE makes it trivial. I just point at the interface and tell the IDE to implement it. Voila! It gives me a dummy. Then I just make a simple modification and turn it into a stub or a spy. [...] I don't like the strange syntax of mocking tools, and the complications they add to my setups. I find writing my own test doubles to be simpler in most cases." The more I think about this (especially in the aftermath of the unserialize() issue), the more it appeals to me to have "real code", generated in parts by my IDE, for my test doubles. The question remains, though, where to put this code. Obviously it belongs with the test suite. But how to name the test double classes? Unique names are required to be able to have multiple (differently configured) test doubles for the same original class. Mocking tools (such as PHPUnit_MockObject, for instance) use random names for their test double classes. Anonymous classes could be a solution for this problem. I was surprised to find [2] (and even more so because I voted "No" on it ... and had no recollection of reading the RFC or voting on it). Most likely I did not see a use case for anonymous classes back then. I would like to propose the following syntax for declaring an anonymous class that does not implement an interface or extend a class: $o = new { public function method() { // ... } // ... }; An anonymous class can extend one class or implement one interface. Unlike non-anonymous classes, an anonymous class cannot do both. In other words, it cannot both extend a class and implement an interface, nor can it implement more than one interface. The syntax to have an anonymous class implement an interface or extend a class is the same: interface AnInterface { public function method(); } $o = new AnInterface { public function method() { // ... } } class AClass { public function method() { // ... } } $o = new AClass { // ... } The ReflectionClass and ReflectionObject classes need to be adapted for anonymous classes. Only new syntax elements would be added to the PHP language. Existing elements of the PHP language syntax would not be changed. No new reserved words would be added to the PHP language. There should not be an impact on backwards compatibility. In case there is enough positive feedback to the syntax proposed in this email I would gladly write a formal RFC. I could also help with writing tests and documentation. Unfortunately I cannot implement the feature myself. -- [1] http://blog.8thlight.com/uncle-bob/2014/05/14/TheLittleMocker.html [2] https://wiki.php.net/rfc/anonymous_classes