rfc:anonymous_classes

PHP RFC: Anonymous Classes

Introduction

For some time PHP has featured anonymous function support in the shape of Closures; this patch introduces the same kind of functionality for objects of an anonymous class.

The ability to create objects of an anonymous class is an established and well used part of Object Orientated programming in other languages.

An anonymous class might be used over a named class:

  • when the class does not need to be documented
  • when the class is used only once during execution

The scope for use is so wide, it is hard to suggest specific use cases; the general use case is throw away implementation of interfaces and abstracts that meet the criteria above.

An anonymous class is a class without a (programmer declared) name, the functionality of the object is no different from that of an object of a named class.

Note: this patch circumvents the normal (advised) limitation of anonymous classes, of single use cases, so this criteria only loosely applies; anonymous classes in PHP lead to more than just anonymous classes.

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.

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;

<?php
class Outside {
    protected $data;
 
    public function __construct($data) {
        $this->data = $data;
    }
 
    public function getArrayAccess() {
        return new class 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]); }
        } ($this->data);
    }
}
?>

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:

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]); }
 
    public function getArrayAccess() {
        return new Outer($this->data);
    }
}

Note: getArrayAccess not strictly necessary, perhaps …

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

    $x = new CallbackFoo(function() {
        /* do something */
    });
 
    /* vs */
 
    $x = new Foo () {
        public function doSometing()
        {
            /* do something */
        }
    });

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 :

$subject->attach(new class implements SplObserver {
  function update(SplSubject $s) {
    printf("Got update from: %s\n" $subject);
  }
);

I use anonymous classes very frequently in Java and in C#, and I would say that they are quite useful …


Adding to the other use-cases already explained: 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:

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:

<?= new MyFancyHtmlListView(){
  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'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.


I think the idea of anonymous classes is very useful.

a use case that I recently encountered, is to override a specific method in a class.

So 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:

use Symfony\Component\Process\Process;
 
$process = new class extends Process {
    public function start() {
        /* ... */
    }
};

instead of the following:

namespace My\Namespace\Process;
 
use Symfony\Component\Process\Process as Base;
 
class Process extends Base {
    public function start() {
        /* ... */
    }
}
 
$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

New syntax that will fail to parse in previous versions, so no BC breaks.

Proposed PHP Version(s)

5.6

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

This patch opens the door for, or appears to go hand in hand with, (named) nested classes

 https://github.com/krakjoe/php-src/compare/anon_class_objects...nesting

The patch above includes access protection on inner classes, but it not a complete implementation …

Proposed Voting Choices

Straight forward, we should have this, we should not have this.

Syntax and Examples

new class {definition} (arguments)

The above syntax is used to avoid conflicts with existing rules in the grammar file.

Note: some have suggested that we should use new class(arguments) {definition}, this makes for a slightly more complex parser, additionally, it doesn't appear to make good sense to pass arguments to a constructor that may not have been declared, making this the simpler, more logical choice.

<?php
/* implementing an anonymous console object from your framework maybe */
(new class extends ConsoleProgram {
    public function main() {
       /* ... */
    }
})->bootstrap();
 
/* return an anonymous implementation of a Page for your MVC framework */
return new class implements Page {
    public function __construct($controller) {
        /* ... */
    }
    /* ... */
} ($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 extends DirectoryIterator {
   /* ... */
} ($path);
 
/* 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 extends MyObject implements MyInterface {
            /* ... */
        } (/* suitable ctor args */);
    }
}
 
?>

Note: the ability to declare and use a constructor in an anonymous class is necessary where control over construction must be exercised.

Code Paths

Code such as:

while ($i++<10) {
    class myNamedClass {
        /* ... */
    }
}

will fail to execute, however code such as:

while ($i++<10) {
  new class {};
}

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:

function my_factory_function(){
    return new class{};
}

get_class(my_factory_function()) would return my_factory_function$$1

And in the following example:

class Other {
    public function getMine() {
        return new class{};
    }
}

get_class1)→getMine()) would return Other$$1

Finally:

var_dump(get_class((new class{})));

in the global scope would return Class$$1 on the first execution.

Inheritance

Some have queried how inheritance might work and suggested the utilization of the “use” statement.

This doesn't appear to be necessary; anonymous classes have constructors and support inheritance like any other class as the examples show.

It seems to me that use($my, $arguments) is not actually different from passing those arguments to a constructor, they are for all intents and purposes the same thing.

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.

Implementation

Rejected Features

N/A

Voting

Voting opened Monday 7th October 2013, closing Monday 14th October 2013.

Merge anon classes to master?
Real name Yes No
aharvey (aharvey)  
arpad (arpad)  
auroraeosrose (auroraeosrose)  
crodas (crodas)  
danielc (danielc)  
datibbaw (datibbaw)  
davey (davey)  
derick (derick)  
dm (dm)  
dragoonis (dragoonis)  
dsp (dsp)  
fa (fa)  
frozenfire (frozenfire)  
googleguy (googleguy)  
guilhermeblanco (guilhermeblanco)  
iliaa (iliaa)  
indeyets (indeyets)  
kalle (kalle)  
levim (levim)  
lstrojny (lstrojny)  
mike (mike)  
patrickallaert (patrickallaert)  
peehaa (peehaa)  
pollita (pollita)  
ralphschindler (ralphschindler)  
reeze (reeze)  
salathe (salathe)  
sebastian (sebastian)  
stas (stas)  
thorstenr (thorstenr)  
tyrael (tyrael)  
zeev (zeev)  
Final result: 9 23
This poll has been closed.

Note: voting choices were changed on the Morning of 7th October 2013, and the vote restarted with clear options.

1) new Other(
rfc/anonymous_classes.txt · Last modified: 2013/10/14 09:04 by krakjoe