In Search of an Anonymous Class Factory

One of the more interesting new features introduced to PHP with the arrival of version 7 is Anonymous Classes. Anonymous Functions (or Closures) have been a part of the language since version 5.3.0, and are something that I find incredibly useful; but (aside from a few very specific circumstances) I couldn’t see any real everyday benefits for coders (other than perhaps a few library developers) in Anonymous Classes.

Then back in January (as I was waiting for my flight to the continent for PHPBenelux) I was intrigued by a request to find a way of dynamically applying Traits to a class at run-time. With time on my hands as I was sitting in the airport, I considered the problem; and my first thought was to build an Anonymous class, extending the requested class (so that it would still contain all the base properties and functionality, and could be type-hinted, but also applying the requested Trait or set of Traits.

With my curiosity piqued, I set up a base class and a couple of Traits, and started looking at how to implement my Anonymous Class solution.

class baseClass {
    protected $a;
    protected $b;

    public function __construct($a, $b = null) {
        $this->a = $a;
        $this->b = $b;
    }

    public function sayWhat() {
        echo $this->a, $this->b, PHP_EOL;
    }
 }

OK, a pretty simplistic class, but this was purely for the purpose of a proof of concept, so it didn’t need to do anything clever. The two Traits that I created were every bit as simplistic.

trait SayAB {
    private $separator = ' ';

    public function sayAB() {
        echo $this->a, $this->separator, $this->b, PHP_EOL;
    }
}

and

trait SayBA {
    private $separator = ' ';

    public function sayBA() {
        echo $this->b, $this->separator, $this->a, PHP_EOL;
    }
}

I could see some potential in this approach. Image a series of models as classes, with Traits such as SoftDelete or Audit, that could be soft-configured to easily enable or disable those features. Simply being able to call a factory that would extend the relevant model class and apply those soft-configured Traits – without needing to change the model code itself – could be a very useful to keep the core code clean.


 

After a few abortive attempts at writing a little script to create an anonymous class that extended baseClass and applied the SayAB and SayBA Traits, and some discussions with the PHP Internals folks who frequent the StackOverflow PHP chat room; I discovered that I’d need to build the definition as a string, and then eval() it.

$className = 'baseClass';
$trait1 = 'SayAB';
$trait2 = 'SayBA';

$args = [
    'Hello',
    'World'
];

$definition = "new class(...\$args) extends $className {
    use $trait1;
    use $trait2;
}";

$anonymous = eval("return $definition;");

$instance1 = new $anonymous($args);
$instance1->sayWhat();
$instance1->sayAB();
$instance1->sayBA();

$instance2 = new $anonymous('Mark Baker');
$instance2->sayWhat();
$instance2->sayAB();
$instance2->sayBA();

Using eval() felt somehow “dirty”; but I was controlling the values used to build the code string that was being evalled, so there wasn’t any risk of external injection, and while any errors in my anonymous class definition would be harder to debug with eval’s limited error reporting, that was a compromise that I could live with.

What irritated me more than the use of eval() was the fact that I had to define the constructor arguments before actually evalling the class definition… or at least I had to define a “stub” value for each mandatory argument, even if it wasn’t the values that would be used on instantiation of the anonymous class. That seemed like a premature and unnecessary overhead, and a breach of the DRY principle. The argument values used on instantiation of the Anonymous Class (i.e. when calling $instance = new $anonymous($args); or $instance = new $anonymous('Hello', 'World');) are the arguments that the class constructor actually accepts.

However, it did at least work: it created the Anonymous Class I’d expected; and allowed me to call the methods defined in base class itself, and in the traits. So I refactored my simple procedural code as an Anonymous Class Factory.

class AnonymousClassFactory {
    private $className;
    private $constructorArgs;
    private $traits;

    public function __construct($className, ...$args) {
        $this->className = $className;
        $this->constructorArgs = $args;
    }

    public function withConstructorArguments(...$args) {
        $this->constructorArgs = $args;
    }

    public function withTraits(...$traits) {
        $this->traits = $traits;

        return $this;
    }

    public function create() {
        $definition = "new class(...\$this->constructorArgs) extends $this->className {" . PHP_EOL;
        foreach($this->traits as $trait) {
            $definition .= "use $trait;" . PHP_EOL;
        }
        $definition .= '}';
        return eval("return $definition;");
    }
}

which I could then call using:

$anonymous = (new AnonymousClassFactory('baseClass'))
    ->withConstructorArguments('hello', 'world')
    ->withTraits('SayAB', 'SayBA')
    ->create();

$anonymous->sayWhat();
$anonymous->sayAB();
$anonymous->sayBA();

In this way, the actual instantiation is against the value returned from the create() method of the AnonymousClassFactory.

This felt somewhat better, because I was at least hiding the issue with the constructor arguments by creating inside the factory, and instantiating using the values that had been passed to the factory. It was an issue that could probably have been solved more cleanly using Reflection if I’d wanted to continue work on the class. But this was only a proof-of-concept to satisfy my own curiosity anyway, not something I’d ever be inclined to use in a production environment, so I shelved any more work on my Anonymous Class Factory, and that should have been the end of the story…

… except that it wasn’t.


 

I was re-reading some of the PHP Documentation last month when I realised that each Anonymous Class is inextricably linked to the (one and only) instance of that class. You can’t create a new Anonymous Class without also instantiating that class at the same time. You can’t save the class definition, and re-use it to create several instances of your Anonymous Class; not in the way that you can with Anonymous Functions.

If I create an Anonymous Function, I can assign that function to a variable, and re-use it (with different arguments) elsewhere in my code. So why can’t I assign an Anonymous Class definition to a variable, so that I can create new instances elsewhere in my code, with different constructor arguments?

Moreover, when you create a class in PHP, the instance properties for each individual instance are maintained independently; but the class method code only exists once in memory, no matter how many instances you create. An Anonymous Class is similar, the method code definition exists once in memory, at the point/line where it is defined in the code; But because my factory was using an eval() to actually define the class, that link was to the evaluated code, and as each block of evaluated code is a separate block in memory, every individual instance had its own individual code definition in memory as well, even when the code definitions were identical. Typically, the memory used by an evalled block will be cleared up by PHP’s garbage collection when it is no longer in scope, but I’m guessing that because it now had a reference to my Anonymous Class instance, it couldn’t be freed up again. That meant that factory was using a lot of memory if it was used to create a lot of instances.


 

Back to the drawing board, and I modified my factory to cache an instance of each different class that it created, and return a clone of that class, so each unique Anonymous Class was only created once, and then cloned whenever a new instance was requested (including the initial request, to ensure that the “master instance” wouldn’t be affected by any subsequent changes to the returned instance.

class AnonymousClassFactory {
    private $className;
    private $constructorArgs;
    private $traits = [];

    private static $instances = [];

    public function __construct($className, ...$args) {
        $this->className = $className;
        $this->constructorArgs = $args;
    }

    public function withConstructorArguments(...$args) {
        $this->constructorArgs = $args;

        return $this;
    }

    public function withTraits(...$traits) {
        sort($traits);
        $this->traits = $traits;

        return $this;
    }

    private function buildDefinition() {
        $definition = "new class(...\$this->constructorArgs) extends $this->className {" . PHP_EOL;
        foreach($this->traits as $trait) {
            $definition .= "use $trait;" . PHP_EOL;
        }
        $definition .= "public function __reconstruct(...\$args) {" . PHP_EOL;
        $definition .= " parent::__construct(...\$args);" . PHP_EOL;
        $definition .= '}';
        $definition .= '}';
        return eval("return $definition;");
    }

    public function create() {
        $hash = md5($this->className . implode(',', $this->traits));
        if (!isset(self::$instances[$hash])) {
            self::$instances[$hash] = $this->buildDefinition();
        }
        $instance = clone self::$instances[$hash];
        $instance->__reconstruct(...$this->constructorArgs);
        return $instance;
    }
}

Because the clone has already been instantiated once when it was first created, I inject the constructor arguments for this new instance into the clone to re-initialise it by calling the __reconstruct() method, which reruns the constructor with the new arguments.

And because every returned instance is a clone of the original, it doesn’t matter what might have been changed in a previous instance, because each clone is independent of the “master copy” in cache.

Not only does this new version of the factory use a lot less memory than the original when creating multiple instances of the same Anonymous Class, it’s also a lot faster as well. Probably some of that is down to the reduced memory overhead (less memory allocation/deallocation, which is always surprising costly), but also because it only needs to execute eval() once for each unique class definition, which also eliminates a costly performance overhead.


 

I’m certain that this won’t be the final chapter in the saga of my Anonymous Class Factory. I’ll certainly post some speed and memory usage figures here to show the differences, and I’ll surely revisit the code at some point and take a fresh look at how I might improve it.

In the meanwhile, I’m still not totally convinced of the use cases for Anonymous Classes, or perhaps not with the way they can be used as currently implemented. Yet they’re still an interesting new addition to PHP, and worth exploring if only to better understand how they work and how they might be used.

 

Further Reading:

 

Addendum:

I’ve now posted performance (speed and memory) results of the factory methods here.

Advertisements
This entry was posted in PHP and tagged , , , . Bookmark the permalink.

10 Responses to In Search of an Anonymous Class Factory

  1. axiac says:

    Very interesting idea.

    Since PHP 5.3 it is possible to create a new object by passing an existing instance to the “new” operator. It is a (deeply hidden but) documented feature; see http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.new, example #5.

    In fact, this is how your initial code creates $instance1 and $instance2.

    By using “new” instead of “clone” with “self::$instances[$hash]” there is no need for “__reconstruct()” any more. I bet you also don’t like the need for the “__reconstruct()” method 🙂

    I am curious how this change impacts the benchmarks. It should slightly improve the speed; I cannot tell about the memory usage.

    Like

    • Mark Baker says:

      Thanks for pointing out this variation; and it certainly feels better not to have the __reconstruct() method

      An initial check proves that it certainly works, and uses as much memory as methods #1 and method #2, while timing of a single run gives me fractionally slower speeds than method #4, but I’ll need to run more controlled speed tests to verify that… I’ll do a series of runs, and add the details to this posting

      Like

    • Mark Baker says:

      I’ve run additional speed tests now using `return new self::$instances[$hash](…$this->constructorArgs);` and it is indeed faster than method #4, with identical memory usage to methods #1 and #2.

      Out of interest, I also tried using a Reflection approach (`return (new ReflectionClass(self::$instances[$hash]))->newInstanceArgs($this->constructorArgs);`) … that is slower than method #4, and with identical memory usage to methods #1 and #2.

      I’ll update this posting with details in due course

      Like

    • Mark Baker says:

      Updated results posted – I’ve updated the results with memory and speed figures for this modification, as well as for a Reflection approach

      Like

  2. Pingback: Anonymous classes benchmarked - murze.be

  3. Pingback: Mark Baker: Anonymous Class Factory – The Results are in – PHP Boutique

  4. Pingback: Mark Baker: Anonymous Class Factory – The Results are in – SourceCode

  5. Pingback: PHP Annotated Monthly – May 2016 | PhpStorm Blog

  6. Pingback: Mark Baker: In Search of an Anonymous Class Factory – SourceCode

  7. Pingback: In Search of an Anonymous Class Factory - murze.be

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s