rfc:anonymous_classes
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
rfc:anonymous_classes [2015/02/25 22:20] – philstu | rfc: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.4 | + | * Version: 0.6 |
* Date: 2013-09-22 | * Date: 2013-09-22 | ||
* Author: Joe Watkins < | * Author: Joe Watkins < | ||
- | * Status: | + | * Status: |
* First Published at: http:// | * First Published at: http:// | ||
Line 27: | Line 27: | ||
}); | }); | ||
</ | </ | ||
+ | |||
+ | ===== Syntax and Examples ===== | ||
+ | |||
+ | new class (arguments) {definition} | ||
+ | |||
+ | Note: in a previous version of this RFC, the arguments were after the definition, this has been changed to reflect the feedback during the last discussion. | ||
+ | |||
+ | <code php> | ||
+ | <?php | ||
+ | /* implementing an anonymous console object from your framework maybe */ | ||
+ | (new class extends ConsoleProgram { | ||
+ | public function main() { | ||
+ | /* ... */ | ||
+ | } | ||
+ | })-> | ||
+ | |||
+ | /* return an anonymous implementation of a Page for your MVC framework */ | ||
+ | return new class($controller) implements Page { | ||
+ | public function __construct($controller) { | ||
+ | /* ... */ | ||
+ | } | ||
+ | /* ... */ | ||
+ | }; | ||
+ | |||
+ | /* vs */ | ||
+ | class MyPage implements Page { | ||
+ | public function __construct($controller) { | ||
+ | /* ... */ | ||
+ | } | ||
+ | /* ... */ | ||
+ | } | ||
+ | return new MyPage($controller); | ||
+ | |||
+ | /* return an anonymous extension of the DirectoryIterator class */ | ||
+ | return new class($path) extends DirectoryIterator { | ||
+ | /* ... */ | ||
+ | }; | ||
+ | |||
+ | /* vs */ | ||
+ | class MyDirectoryIterator { | ||
+ | /* .. */ | ||
+ | } | ||
+ | return new MyDirectoryIterator($path); | ||
+ | |||
+ | /* return an anon class from within another class (introduces the first kind of nested class in PHP) */ | ||
+ | class MyObject extends MyStuff { | ||
+ | public function getInterface() { | ||
+ | return new class implements MyInterface { | ||
+ | /* ... */ | ||
+ | }; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | /* return a private object implementing an interface */ | ||
+ | class MyObject extends MyStuff { | ||
+ | /* suitable ctor */ | ||
+ | | ||
+ | private function getInterface() { | ||
+ | return new class(/* suitable ctor args */) extends MyObject implements MyInterface { | ||
+ | /* ... */ | ||
+ | }; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Note: the ability to declare and use a constructor in an anonymous class is necessary where control over construction must be exercised. | ||
+ | |||
+ | ===== Inheritance/ | ||
+ | |||
+ | Extending classes works just as you'd expect. | ||
+ | |||
+ | <code php> | ||
+ | <?php | ||
+ | |||
+ | class Foo {} | ||
+ | |||
+ | $child = new class extends Foo {}; | ||
+ | |||
+ | var_dump($child instanceof Foo); // true | ||
+ | </ | ||
+ | |||
+ | Traits work identically as in named class definitions too. | ||
+ | |||
+ | <code php> | ||
+ | <?php | ||
+ | |||
+ | trait Foo { | ||
+ | public function someMethod() { | ||
+ | return " | ||
+ | } | ||
+ | } | ||
+ | |||
+ | $anonClass = new class { | ||
+ | use Foo; | ||
+ | }; | ||
+ | |||
+ | var_dump($anonClass-> | ||
+ | </ | ||
+ | |||
+ | ===== Reflection ===== | ||
+ | |||
+ | The only change to reflection is to add ReflectionClass:: | ||
+ | |||
+ | ===== Serialization ===== | ||
+ | |||
+ | Serialization is not supported, and 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> | ||
+ | function my_factory_function(){ | ||
+ | return new class{}; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | get_class(my_factory_function()) would return " | ||
+ | |||
+ | <code php> | ||
+ | class mine {} | ||
+ | |||
+ | new class extends mine {}; | ||
+ | </ | ||
+ | |||
+ | This class name will be " | ||
+ | |||
+ | 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 = []; | ||
+ | |||
+ | for ($i = 0; $i < 2; $i++) { | ||
+ | $identicalAnonClasses[$i] = new class(99) { | ||
+ | public $i; | ||
+ | public function __construct($i) { | ||
+ | $this->i = $i; | ||
+ | } | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | var_dump($identicalAnonClasses[0] == $identicalAnonClasses[1]); | ||
+ | |||
+ | $identicalAnonClasses[2] = new class(99) { | ||
+ | public $i; | ||
+ | public function __construct($i) { | ||
+ | $this->i = $i; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | var_dump($identicalAnonClasses[0] == $identicalAnonClasses[2]); | ||
+ | </ | ||
+ | |||
+ | Both classes where identical in every way, other than their generated name. | ||
===== Use Cases ===== | ===== Use Cases ===== | ||
- | Following the general advice, the area of code testing | + | Code testing |
+ | |||
+ | A few quick points: | ||
+ | |||
+ | * 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:// | ||
+ | |||
+ | <code php> | ||
+ | // PHP 5.x | ||
+ | class MyLogger { | ||
+ | public function log($msg) { | ||
+ | print_r($msg . " | ||
+ | } | ||
+ | } | ||
+ | |||
+ | $pusher-> | ||
+ | |||
+ | // New Hotness | ||
+ | $pusher-> | ||
+ | public function log($msg) { | ||
+ | print_r($msg . " | ||
+ | } | ||
+ | }); | ||
+ | </ | ||
+ | |||
+ | 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 reused, that 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> | ||
+ | $subject-> | ||
+ | function update(SplSubject $s) { | ||
+ | printf(" | ||
+ | } | ||
+ | }); | ||
+ | </ | ||
Here is one example, which covers converting PSR-7 middleware to Laravel 5-style middleware. | Here is one example, which covers converting PSR-7 middleware to Laravel 5-style middleware. | ||
Line 50: | Line 243: | ||
</ | </ | ||
- | 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; | + | 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> | ||
Line 70: | Line 263: | ||
} | } | ||
} | } | ||
- | ?> | ||
</ | </ | ||
Line 103: | Line 295: | ||
How you choose to do it for any specific application, | How you choose to do it for any specific application, | ||
- | Various use cases have been suggested on the mailing list: http:// | + | Various use cases have been suggested on the mailing list: http:// |
- | + | ||
- | ===== 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 " | The use case is one-time usage of an " | ||
probably pass callbacks into a " | probably pass callbacks into a " | ||
Line 123: | Line 313: | ||
/* do something */ | /* do something */ | ||
} | } | ||
- | }); | + | }; |
</ | </ | ||
Line 129: | Line 319: | ||
would need several callbacks passed to the constructor. | would need several callbacks passed to the constructor. | ||
Also ' | Also ' | ||
- | |||
- | ---- | ||
- | 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-> | ||
- | function update(SplSubject $s) { | ||
- | printf(" | ||
- | } | ||
- | }); | ||
- | </ | ||
- | |||
- | ---- | ||
- | |||
- | 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 ... | ||
- | } | ||
- | } | ||
- | </ | ||
- | 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 ... | ||
- | } | ||
- | } ?> | ||
- | </ | ||
- | 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' | ||
- | expressive? | ||
- | |||
- | Take for example an API where you'd typically wrap a method call in | ||
- | try/catch blocks to handle the various " | ||
- | 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 | Overriding a specific method in a class is one handy use. Instead of creating | ||
Line 234: | Line 349: | ||
$process = new \My\Namespace\Process\Process; | $process = new \My\Namespace\Process\Process; | ||
</ | </ | ||
- | |||
- | ---- | ||
- | One additional use case I can think of is a composition where it is not | ||
- | exposed to the outside of the class. An anonymous class extending a small | ||
- | interface or extending an abstract class with only a few methods seems to be | ||
- | the suitable candidate here IMO. If PHP would support inner classes I would | ||
- | probably prefer a private inner class when the anonymous class starts to | ||
- | hold to much logic. | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
Line 261: | Line 368: | ||
===== Open Issues ===== | ===== Open Issues ===== | ||
- | The question of whether or not to disable serialization for anonymous objects. | + | None |
===== Future Scope ===== | ===== Future Scope ===== | ||
Line 267: | Line 374: | ||
The changes made by this patch mean named nested classes are easier to implement (by a tiny bit). | The changes made by this patch mean named nested classes are easier to implement (by a tiny bit). | ||
- | ===== Proposed Voting Choices | + | ===== References |
- | Straight forward, we should have this, we should not have this. | + | PHP 7 Discussion: http://marc.info/? |
- | ===== Syntax and Examples | + | ===== Proposed Voting Choices |
- | new class (arguments) {definition} | + | The voting choices are yes (in favor for accepting this RFC for PHP 7) or no (against it). |
- | Note: in a previous version of this RFC, the arguments were after the definition, this has been changed to reflect the feedback during the last discussion. | + | ===== Vote ===== |
- | <code php> | + | Vote starts on March 13th, and will end two weeks later, on March 27th. |
- | <?php | + | |
- | /* implementing an anonymous console object from your framework maybe */ | + | |
- | (new class extends ConsoleProgram { | + | |
- | public function main() { | + | |
- | / | + | |
- | } | + | |
- | })-> | + | |
- | /* return an anonymous implementation of a Page for your MVC framework */ | + | This RFC requires |
- | return new class($controller) implements Page { | + | |
- | public function __construct($controller) { | + | |
- | /* ... */ | + | |
- | } | + | |
- | /* ... */ | + | |
- | }; | + | |
- | /* vs */ | + | <doodle title=" |
- | class MyPage implements Page { | + | * Yes |
- | public function __construct($controller) { | + | |
- | /* ... */ | + | </doodle> |
- | } | + | |
- | | + | |
- | } | + | |
- | return new MyPage($controller); | + | |
- | /* return an anonymous extension of the DirectoryIterator class */ | + | ===== Changelog ===== |
- | return new class($path) extends DirectoryIterator { | + | |
- | /* ... */ | + | |
- | }; | + | |
- | /* vs */ | + | |
- | class MyDirectoryIterator { | + | * v0.5.2 Updated name examples |
- | /* .. */ | + | * v0.5.1 Example improvements |
- | } | + | * v0.5: Added traits example |
- | return new MyDirectoryIterator($path); | + | * v0.4: Comparisons example |
- | + | | |
- | /* return an anon class from within another class (introduces the first kind of nested class in PHP) */ | + | * v0.2: Brought back for discussion |
- | class MyObject extends MyStuff { | + | * v0.1: Initial draft |
- | public function getInterface() { | + | |
- | return new class implements MyInterface { | + | |
- | /* ... */ | + | |
- | }; | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | + | ||
- | /* return a private object implementing an interface */ | + | |
- | class MyObject extends MyStuff { | + | |
- | /* suitable ctor */ | + | |
- | + | ||
- | private function getInterface() { | + | |
- | return new class(/* suitable ctor args */) extends MyObject implements MyInterface { | + | |
- | /* ... */ | + | |
- | }; | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | ?> | + | |
- | </ | + | |
- | + | ||
- | Note: the ability to declare | + | |
- | + | ||
- | ===== Code Paths ===== | + | |
- | + | ||
- | Code such as: | + | |
- | + | ||
- | <code php> | + | |
- | while ($i++< | + | |
- | class myNamedClass { | + | |
- | /* ... */ | + | |
- | } | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | will fail to execute, however code such as: | + | |
- | + | ||
- | <code php> | + | |
- | while ($i++< | + | |
- | | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | will work as expected: the definition will be re-used, creating a new object. | + | |
- | + | ||
- | ===== Internal Class Naming ===== | + | |
- | + | ||
- | The internal name of an anonymous class is generated based on the scope which created it, such that: | + | |
- | + | ||
- | <code php> | + | |
- | function my_factory_function(){ | + | |
- | return new class{}; | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | get_class(my_factory_function()) would return '' | + | |
- | + | ||
- | And in the following example: | + | |
- | + | ||
- | <code php> | + | |
- | class Other { | + | |
- | public function getMine() { | + | |
- | return new class{}; | + | |
- | } | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | get_class((new Other())-> | + | |
- | + | ||
- | Finally: | + | |
- | + | ||
- | <code php> | + | |
- | var_dump(get_class(new class{})); | + | |
- | </ | + | |
- | + | ||
- | in the global scope would return '' | + | |
- | + | ||
- | ===== Comparisons ===== | + | |
- | + | ||
- | 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. | + | |
- | + | ||
- | < | + | |
- | <?php | + | |
- | + | ||
- | $identicalAnonClasses = []; | + | |
- | + | ||
- | for ($i = 0; $i < 2; $i++) { | + | |
- | $identicalAnonClasses[$i] = new class(99) { | + | |
- | public $i; | + | |
- | public function __construct($i) { | + | |
- | $this->i = $i; | + | |
- | } | + | |
- | }; | + | |
- | } | + | |
- | + | ||
- | var_dump($identicalAnonClasses[0] == $identicalAnonClasses[1]); | + | |
- | + | ||
- | $identicalAnonClasses[2] = new class(99) { | + | |
- | public $i; | + | |
- | public function __construct($i) { | + | |
- | $this->i = $i; | + | |
- | } | + | |
- | }; | + | |
- | + | ||
- | var_dump($identicalAnonClasses[0] == $identicalAnonClasses[2]); | + | |
- | </ | + | |
- | + | ||
- | Both classes where identical in every way, other than their generated name. | + | |
- | + | ||
- | ===== Inheritance ===== | + | |
- | + | ||
- | Some have queried how inheritance might work and suggested the utilization of the " | + | |
- | + | ||
- | This doesn' | + | |
- | + | ||
- | It seems to me that use($my, $arguments) is not actually different from passing those arguments to a constructor, | + | |
- | + | ||
- | Other options have been toyed with, such as a super:: keyword, but dropped | + | |
- | + | ||
- | Bottom line: should you need the functionality of an anonymous class to be tightly coupled to another object, then it's reasonable that you should utilize normal inheritance and passing by reference in order to achieve that coupling. | + | |
- | + | ||
- | ===== Reflection ===== | + | |
- | + | ||
- | The only change to reflection is to add ReflectionClass:: | + | |
===== Implementation ===== | ===== Implementation ===== | ||
https:// | https:// | ||
- | |||
- | ===== References ===== | ||
- | |||
- | PHP 7 Discussion: http:// | ||
- | |||
- | ===== Changelog ===== | ||
- | |||
- | * v0.3: ReflectionClass:: | ||
- | * v0.2: Brought back for discussion | ||
- | * v0.1: Initial drafts |
rfc/anonymous_classes.1424902838.txt.gz · Last modified: 2017/09/22 13:28 (external edit)