rfc:anonymous_classes

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:anonymous_classes [2015/03/04 19:08] philsturfc:anonymous_classes [2017/09/22 13:28] (current) – external edit 127.0.0.1
Line 1: Line 1:
  
 ====== PHP RFC: Anonymous Classes ====== ====== PHP RFC: Anonymous Classes ======
-  * Version: 0.5+  * Version: 0.6
   * Date: 2013-09-22   * Date: 2013-09-22
   * Author: Joe Watkins <krakjoe@php.net>, Phil Sturgeon <philstu@php.net>   * Author: Joe Watkins <krakjoe@php.net>, Phil Sturgeon <philstu@php.net>
-  * Status: Discussion (Previously Declined for 5.x)+  * Status: Implemented (in PHP 7.0)
   * First Published at: http://wiki.php.net/rfc/anonymous_classes   * First Published at: http://wiki.php.net/rfc/anonymous_classes
  
Line 27: Line 27:
 }); });
 </code> </code>
- 
-===== Use Cases ===== 
- 
-Following the general advice, the area of code testing appears to present the most significant number of use cases, however, where anonymous classes are a part of a language they do find their way into many areas of development, not just testing. Whether it is technically correct to use an anonymous class depends almost entirely on an individual application, or even object depending on perspective. 
- 
-Here is one example, which covers converting PSR-7 middleware to Laravel 5-style middleware. 
- 
-<code php> 
-<?php 
-$conduit->pipe(new class implements MiddlewareInterface { 
-    public function __invoke($request, $response, $next) 
-    { 
-        $laravelRequest = mungePsr7ToLaravelRequest($request); 
-        $laravelNext    = function ($request) use ($next, $response) { 
-            return $next(mungeLaravelToPsr7Request($request), $response) 
-        }; 
-        $laravelMiddleware = new SomeLaravelMiddleware(); 
-        $response = $laravelMiddleware->handle($laravelRequest, $laravelNext); 
-        return mungeLaravelToPsr2Response($response); 
-    } 
-}); 
-</code> 
- 
-Anonymous classes do present the opportunity to create the first kind of nested class in PHP, you might nest for slightly different reasons to creating an anonymous class, so that deserves some discussion;  
- 
-<code php> 
-<?php 
-class Outside { 
-    protected $data; 
-     
-    public function __construct($data) { 
-        $this->data = $data; 
-    } 
-     
-    public function getArrayAccess() { 
-        return new class($this->data) extends Outside implements ArrayAccess { 
-            public function offsetGet($offset) { return $this->data[$offset]; } 
-            public function offsetSet($offset, $data) { return ($this->data[$offset] = $data); } 
-            public function offsetUnset($offset) { unset($this->data[$offset]); } 
-            public function offsetExists($offset) { return isset($this->data[$offset]); } 
-        }; 
-    } 
-} 
-?> 
-</code> 
- 
-Note: Outer is extended not for access to $this->data - that could just be passed into a constructor; extending Outer allows the nested class implementing ArrayAccess permission to execute protected methods, declared in the Outer class, on the same $this->data, and if by reference, as if they are the Outer class. 
- 
-In the simple example above Outer::getArrayAccess takes advantage of anonymous classes to declare and create an ArrayAccess interface object for Outer. 
- 
-By making getArrayAccess private the anonymous class it creates can be said to be a private class.  
- 
-This increases the possibilities for grouping of your objects functionality, can lead to more readable, some might say more maintainable code. 
- 
-The alternative to the above is the following: 
- 
-<code php> 
-class Outer implements ArrayAccess { 
-    public $data; 
-     
-    public function __construct($data) { 
-        $this->data; 
-    } 
-     
-    public function offsetGet($offset) { return $this->data[$offset]; } 
-    public function offsetSet($offset, $data) { return ($this->data[$offset] = $data); } 
-    public function offsetUnset($offset) { unset($this->data[$offset]); } 
-    public function offsetExists($offset) { return isset($this->data[$offset]); } 
-     
-} 
-</code> 
- 
-Pass-by-reference is not used in the examples above, so behaviour with regard to $this->data should be implicit. 
- 
-How you choose to do it for any specific application, whether getArrayAccess is private or not, whether to pass by reference or not, depends on the application. 
- 
-Various use cases have been suggested on the mailing list: http://php.markmail.org/message/sxqeqgc3fvs3nlpa?q=anonymous+classes+php 
- 
-===== Use Cases from the Community ===== 
-Below are some excerpts of the discussion on internals mailing list including some use cases that the community see: 
- 
----- 
-The use case is one-time usage of an "implementation", where you currently 
-probably pass callbacks into a "Callback*"-class like 
-<code php> 
-    $x = new Callback(function() { 
-        /* do something */ 
-    }); 
- 
-    /* vs */ 
- 
-    $x = new class extends Callback { 
-        public function doSometing() 
-        { 
-            /* do something */ 
-        } 
-    }; 
-</code> 
- 
-Imagine you have several abstract methods in one interface/class, which 
-would need several callbacks passed to the constructor. 
-Also '$this' is mapped to the right objects. 
- 
----- 
-It also avoids usage of these classes outside the scope where they are 
-defined... 
- 
----- 
-It is a widely used pattern in object oriented programming ... where you code against interfaces : 
-<code php> 
-$subject->attach(new class implements SplObserver { 
-  function update(SplSubject $s) { 
-    printf("Got update from: %s\n" $subject); 
-  } 
-}); 
-</code> 
- 
----- 
- 
-It would also definitely be very useful for mocking in tests. We could create  
-on-the-fly implementations for interfaces, avoiding using complex  
-mocking API (PHPUnit or others). 
- 
----- 
-There are many use cases where anonymous classes are useful, even in the 
-presence of lambdas. I use them quite often when dealing with graphical 
-interfaces and templates. Here is an example: 
- 
-<code php> 
-abstract class MyFancyHtmlListView extends UI { 
-  protected function IsHeaderVisible(){ return true; } 
-  protected function GetListItemMenu(){ return null; } 
-  protected function OnItemClick( $item ){ } 
-  protected abstract function RenderListItem( $item ); 
-  public function Render(){ 
-    // echo ... 
-  } 
-} 
-</code> 
-With anonymous classes we could do something like this: 
- 
-<code php> 
-<?= new class extends HTMLView { 
-  protected function IsHeaderVisible(){ 
-    return false; 
-  } 
-  protected function RenderListItem( $item ){ 
-    // echo ... 
-  } 
-} ?> 
-</code> 
-The biggest advantage is that a missing RenderListItem could be statically 
-verified. 
- 
-It is just a pattern that follows a different way of thinking: Instead of 
-having a list of parameters (including lambdas), we have standard methods 
-that take advantage of all the nice properties of OOP such as abstraction, 
-inheritance and polymorphism. 
- 
----- 
- 
-Playing devil's advocate here, could this feature make the language more 
-expressive? 
- 
-Take for example an API where you'd typically wrap a method call in 
-try/catch blocks to handle the various "outcomes" e.g. a user login, you'd 
-maybe have a UserDisabled exception, a UserAlreadyLoggedIn exception, a 
-UserPasswordIncorrect exception, etc. 
- 
-With the addition of this syntactic sugar, the method could instead accept 
-an anonymous class with a onDisabled, onLoggedIn, onPasswordIncorrect 
-methods. 
- 
-Perhaps it would also have a performance benefit over cascading through 
-catch blocks? Though someone else would have to confirm that. 
- 
----- 
- 
-Overriding a specific method in a class is one handy use. Instead of creating 
-a new class that extends the original class, you can just use an anonymous 
-class and override the methods that you want. 
- 
-E.G; You can to the following: 
- 
-<code php> 
-use Symfony\Component\Process\Process; 
- 
-$process = new class extends Process { 
-    public function start() { 
-        /* ... */ 
-    } 
-}; 
-</code> 
-instead of the following: 
-<code php> 
-namespace My\Namespace\Process; 
- 
-use Symfony\Component\Process\Process as Base; 
- 
-class Process extends Base { 
-    public function start() { 
-        /* ... */ 
-    } 
-} 
- 
-$process = new \My\Namespace\Process\Process; 
-</code> 
- 
-===== Backward Incompatible Changes ===== 
- 
-New syntax that will fail to parse in previous versions, so no BC breaks. 
- 
-===== Proposed PHP Version(s) ===== 
- 
-7.0 
- 
-===== SAPIs Impacted ===== 
- 
-All 
- 
-===== Impact to Existing Extensions ===== 
- 
-No impact on existing libraries 
- 
-===== Open Issues ===== 
- 
-The question of whether or not to disable serialization for anonymous objects. 
- 
-===== Future Scope ===== 
- 
-The changes made by this patch mean named nested classes are easier to implement (by a tiny bit). 
- 
-===== Proposed Voting Choices ===== 
- 
-Straight forward, we should have this, we should not have this. 
  
 ===== Syntax and Examples ===== ===== Syntax and Examples =====
Line 326: Line 91:
     }     }
 } }
- 
-?> 
 </code> </code>
  
Line 333: Line 96:
  
 ===== Inheritance/Traits ===== ===== Inheritance/Traits =====
- 
-Extending classes works just as you'd expect. 
- 
-<code php> 
-<?php 
- 
-class Foo {} 
- 
-$child = new class extends Foo {}; 
- 
-var_dump($child instanceof Foo); 
-</code> 
  
 Extending classes works just as you'd expect. Extending classes works just as you'd expect.
Line 376: Line 127:
 </code> </code>
  
-===== Comparisons =====+===== Reflection =====
  
-Multiple anonymous classes created in the same position (say, a loopcan be compared with `==`but those created elsewhere will not match as they will have a different name.+The only change to reflection is to add ReflectionClass::isAnonymous()
 + 
 +===== Serialization ===== 
 + 
 +Serialization is not supportedand will error just as anonymous functions do. 
 + 
 +===== Internal Class Naming ===== 
 + 
 +The internal name of an anonymous class is generated with a unique reference based on its address.
  
 <code php> <code php>
-<?php+function my_factory_function(){ 
 +    return new class{}; 
 +
 +</code>
  
 +get_class(my_factory_function()) would return "class@0x7fa77f271bd0" even if called multiple times, as it is the same definition. The word "class" is used by default, but if the anonymous class extends a named class it will use that:
 +
 +<code php>
 +class mine {}
 +
 +new class extends mine {};
 +</code>
 +
 +This class name will be "mine@0x7fc4be471000".
 +
 +Multiple anonymous classes created in the same position (say, a loop) can be compared with `==`, but those created elsewhere will not match as they will have a different name.
 +
 +<code php>
 $identicalAnonClasses = []; $identicalAnonClasses = [];
  
Line 408: Line 183:
 Both classes where identical in every way, other than their generated name. Both classes where identical in every way, other than their generated name.
  
-===== Reflection =====+===== Use Cases =====
  
-The only change to reflection is to add ReflectionClass::isAnonymous().+Code testing presents the most significant number of use cases, however, where anonymous classes are a part of a language they do find their way into many use cases, not just testing. Whether it is technically correct to use an anonymous class depends almost entirely on an individual application, or even object depending on perspective.
  
-===== Code Paths =====+A few quick points:
  
-Code such as:+  * Mocking tests becomes easy as pie. Create on-the-fly implementations for interfaces, avoiding using complex mocking APIs. 
 +  * Keep usage of these classes outside the scope they are defined in 
 +  * Avoid hitting the autoloader for trivial implementations 
 + 
 +Tweaking existing classes which only change a single thing can make this very easy. Taking an example from the [[https://github.com/pusher/pusher-http-php#debugging--logging|Pusher PHP library]]:
  
 <code php> <code php>
-while ($i++<10) { +// PHP 5.x 
-    class myNamedClass { +class MyLogger { 
-        /* ... */ +  public function log($msg) { 
-    }+    print_r($msg "\n"); 
 +  }
 } }
 +
 +$pusher->setLogger( new MyLogger() );
 +
 +// New Hotness
 +$pusher->setLogger(new class {
 +  public function log($msg) {
 +    print_r($msg . "\n");
 +  }
 +});
 </code> </code>
  
-will fail to executehowever code such as:+This saved us making a new file, or placing the class definition at the top of the file or somewhere a long way from its usage. For big complex actions, or anything that needs to be reusedthat would of course be better off as a named class, but in this case it's nice and handy to not bother. 
 + 
 +If you need to implement a very light interface to create a simple dependency:
  
 <code php> <code php>
-while ($i++<10) { +$subject->attach(new class implements SplObserver { 
-  new class {}; +  function update(SplSubject $s) { 
-}+    printf("Got update from: %s\n", $subject); 
 +  } 
 +});
 </code> </code>
  
-will work as expected: the definition will be re-usedcreating a new object.+Here is one examplewhich covers converting PSR-7 middleware to Laravel 5-style middleware.
  
-===== Internal Class Naming =====+<code php> 
 +<?php 
 +$conduit->pipe(new class implements MiddlewareInterface { 
 +    public function __invoke($request, $response, $next) 
 +    { 
 +        $laravelRequest mungePsr7ToLaravelRequest($request); 
 +        $laravelNext    function ($request) use ($next, $response) { 
 +            return $next(mungeLaravelToPsr7Request($request), $response) 
 +        }; 
 +        $laravelMiddleware new SomeLaravelMiddleware(); 
 +        $response $laravelMiddleware->handle($laravelRequest, $laravelNext); 
 +        return mungeLaravelToPsr2Response($response); 
 +    } 
 +}); 
 +</code>
  
-The internal name of an anonymous class is generated based on the scope which created itsuch that:+Anonymous classes do present the opportunity to create the first kind of nested class in PHP. You might nest for slightly different reasons to creating an anonymous class, so that deserves some discussion; 
  
 <code php> <code php>
-function my_factory_function(){ +<?php 
-    return new class{};+class Outside { 
 +    protected $data; 
 +     
 +    public function __construct($data) { 
 +        $this->data = $data; 
 +    
 +     
 +    public function getArrayAccess() { 
 +        return new class($this->data) extends Outside implements ArrayAccess { 
 +            public function offsetGet($offset) { return $this->data[$offset]; } 
 +            public function offsetSet($offset, $data) { return ($this->data[$offset] = $data)
 +            public function offsetUnset($offset) { unset($this->data[$offset]);
 +            public function offsetExists($offset) { return isset($this->data[$offset]);
 +        }; 
 +    }
 } }
 </code> </code>
  
-get_class(my_factory_function()) would return ''my_factory_function$$1''+Note: Outer is extended not for access to $this->data - that could just be passed into a constructor; extending Outer allows the nested class implementing ArrayAccess permission to execute protected methods, declared in the Outer class, on the same $this->data, and if by reference, as if they are the Outer class.
  
-And in the following example:+In the simple example above Outer::getArrayAccess takes advantage of anonymous classes to declare and create an ArrayAccess interface object for Outer. 
 + 
 +By making getArrayAccess private the anonymous class it creates can be said to be a private class.  
 + 
 +This increases the possibilities for grouping of your objects functionality, can lead to more readable, some might say more maintainable code. 
 + 
 +The alternative to the above is the following:
  
 <code php> <code php>
-class Other +class Outer implements ArrayAccess { 
-    public function getMine() { +    public $data; 
-        return new class{};+     
 +    public function __construct($data) { 
 +        $this->data;
     }     }
 +    
 +    public function offsetGet($offset) { return $this->data[$offset]; }
 +    public function offsetSet($offset, $data) { return ($this->data[$offset] = $data); }
 +    public function offsetUnset($offset) { unset($this->data[$offset]); }
 +    public function offsetExists($offset) { return isset($this->data[$offset]); }
 +    
 } }
 </code> </code>
  
-get_class((new Other())->getMine()) would return ''Other$$1''+Pass-by-reference is not used in the examples above, so behaviour with regard to $this->data should be implicit.
  
-Finally:+How you choose to do it for any specific application, whether getArrayAccess is private or not, whether to pass by reference or not, depends on the application.
  
 +Various use cases have been suggested on the mailing list: http://php.markmail.org/message/sxqeqgc3fvs3nlpa?q=anonymous+classes+php, and here are a few.
 +
 +----
 +
 +The use case is one-time usage of an "implementation", where you currently
 +probably pass callbacks into a "Callback*"-class like
 <code php> <code php>
-var_dump(get_class(new class{}));+    $x = new Callback(function() { 
 +        /* do something */ 
 +    }); 
 + 
 +    /* vs */ 
 + 
 +    $x = new class extends Callback { 
 +        public function doSometing() 
 +        { 
 +            /* do something */ 
 +        } 
 +    };
 </code> </code>
  
-in the global scope would return ''Class$$1'on the first execution.+Imagine you have several abstract methods in one interface/class, which 
 +would need several callbacks passed to the constructor. 
 +Also '$thisis mapped to the right objects.
  
-===== Implementation =====+Overriding a specific method in a class is one handy use. Instead of creating 
 +a new class that extends the original class, you can just use an anonymous 
 +class and override the methods that you want.
  
-https://github.com/php/php-src/pull/1118+E.G; You can to the following: 
 + 
 +<code php> 
 +use Symfony\Component\Process\Process; 
 + 
 +$process = new class extends Process { 
 +    public function start() { 
 +        /... */ 
 +    } 
 +}; 
 +</code> 
 +instead of the following: 
 +<code php
 +namespace My\Namespace\Process; 
 + 
 +use Symfony\Component\Process\Process as Base; 
 + 
 +class Process extends Base { 
 +    public function start() { 
 +        /* ... */ 
 +    } 
 +
 + 
 +$process = new \My\Namespace\Process\Process; 
 +</code> 
 + 
 +===== Backward Incompatible Changes ===== 
 + 
 +New syntax that will fail to parse in previous versions, so no BC breaks. 
 + 
 +===== Proposed PHP Version(s) ===== 
 + 
 +7.0 
 + 
 +===== SAPIs Impacted ===== 
 + 
 +All 
 + 
 +===== Impact to Existing Extensions ===== 
 + 
 +No impact on existing libraries 
 + 
 +===== Open Issues ===== 
 + 
 +None 
 + 
 +===== Future Scope ===== 
 + 
 +The changes made by this patch mean named nested classes are easier to implement (by a tiny bit).
  
 ===== References ===== ===== References =====
  
 PHP 7 Discussion: http://marc.info/?l=php-internals&m=142478600309800&w=2 PHP 7 Discussion: http://marc.info/?l=php-internals&m=142478600309800&w=2
 +
 +===== Proposed Voting Choices =====
 +
 +The voting choices are yes (in favor for accepting this RFC for PHP 7) or no (against it).
 +
 +===== Vote =====
 +
 +Vote starts on March 13th, and will end two weeks later, on March 27th.
 +
 +This RFC requires a 2/3 majority.
 +
 +<doodle title="Anonymous Classes" auth="philstu" voteType="single" closed="true">
 +   * Yes
 +   * No
 +</doodle>
  
  ===== Changelog =====  ===== Changelog =====
  
 +  * v0.6 Serialization not supported and names added
 +  * v0.5.2 Updated name examples
 +  * v0.5.1 Example improvements
   * v0.5: Added traits example and tests   * v0.5: Added traits example and tests
   * v0.4: Comparisons example   * v0.4: Comparisons example
   * v0.3: ReflectionClass::isAnonymous() and simple example   * v0.3: ReflectionClass::isAnonymous() and simple example
   * v0.2: Brought back for discussion   * v0.2: Brought back for discussion
-  * v0.1: Initial drafts+  * v0.1: Initial draft 
 + 
 +===== Implementation ===== 
 + 
 +https://github.com/php/php-src/pull/1118
rfc/anonymous_classes.1425496081.txt.gz · Last modified: 2017/09/22 13:28 (external edit)