Friday 2 October 2015

PHP 7: anonymous classes

G'day:
I thought it was high time I wrote an article with some code in it. Also one of my (now former ) colleagues - Cisco - asked me to write about this, so here I am.

Another new feature of PHP 7 (see the rest of my bumpf on PHP 7) is the ability to create anonymous classes. An anonymous class is to a named class what a closure (or anonymous function, or  function expression) is to a named function. Well it kinda is. Only kinda cos I think they've ballsed-up their implementation.

Here's an example of an anonymous class in action:

// inline.php

$o = new class(17) {
    private $x;

    function __construct($x){
        $this->x = $x;
    }

    function multiply($y){
        return $this->x * $y;
    }
};
$result = $o->multiply(19);

echo $result;

Here I use an anonymous class to create an object which is initialised with a property $x with a value of 17, and I then call a method to multiply that by 19. The output is predictable:

>php inline.php
323
>

This is all well and good, but an anonymous class should create a class, not an object. Obviously here it's creating the class inline, and then creating a new object out of it straight away. But what if I want to use this class again? I should be able to create just the class as a variable:

// classObject.php

$c = class {
    private $x;

    function __construct($x){
        $this->x = $x;
    }

    function multiply($y){
        return $this->x * $y;
    }
};

$o = new $c(17);
$result = $o->multiply(19);

echo $result;

This should work. Here I'm declaring the class via the anonymous class expression, and then calling new on that class. However this just errors:

>php classObject.php
PHP Parse error:  syntax error, unexpected 'class' (T_CLASS) in classObject.php on line 4

Parse error: syntax error, unexpected 'class' (T_CLASS) in classObject.php on line 4

>

I have monkeyed as much as I can with the syntax, but I cannot get an anonymous class expression to actually return a class.

Let's think about this for a second, and here's an equivalent example with a function expression to contrast:

// functionExpressionType.php

function add($x, $y){
    return $x + $y;
}
$addFunctionName = 'add';

printf('is_callable(add): %d%s', is_callable($addFunctionName), PHP_EOL);
$multiply = function($x, $y){
    return $x * $y;
};
printf('is_callable($multiply): %d%s', is_callable($multiply), PHP_EOL);

Here I look at both a function declared via a function statement, and another declared via a function expression. In both cases, they return a callable (ie: a function):

>php functionExpressionType.php
is_callable(add): 1
is_callable($multiply): 1

>



So - to be clear - a function expression returns a function. Not the result of calling the function. Now the function-expression equivalent of how class expressions "work" can be achieved in PHP 7:

// iife.php
$quotient = (function($x, $y){
     return $x / $y;
})(19,17);
echo $quotient;

But there's syntactic sugar to use the function in a truly anonymous, disposable fashion. The equivalent of this seems to be the only way of using anonymous classes in PHP.

BTW, one can do the anonymous class version of an IIFE too:

// inlineExpression.php

echo (new class(17) {
    private $x;

    function __construct($x){
        $this->x = $x;
    }

    function multiply($y){
        return $this->x * $y;
    }
})->multiply(19);

>php inlineExpression.php

323
>

Although why one would want to do that is beyond me.

Right, so why would one want to use these? Well... I dunno if one would, TBH, given the current implementation. If they worked properly, then one usage one can put them to is to have inner classes, which are prohibited if one were to try to use a class statement inside another class statement. This code does not work:

// nestedViaStatement.php

class Person {

    private $name;

    class Name {
        private $firstName;
        private $lastName;

        function __construct($firstName, $lastName){
            $this->firstName = $firstName;
            $this->lastName = $lastName;
        }

        function getFullName(){
            return sprintf('%s %s', $this->firstName, $this->lastName);
        }
    }

    function __construct($firstName, $lastName){
        $this->name = new Name($firstName, $lastName);
    }

    function getName(){
        return $this->name->getFullName();
    }

}

$physicist = new Person("Ernest", "Rutherford");

$fullName = $physicist->getName();

echo $fullName;


>php7 nestedViaStatement.php
PHP Parse error:  syntax error, unexpected 'class' (T_CLASS), expecting function
 (T_FUNCTION) in nestedViaStatement.php on line 8

Parse error: syntax error, unexpected 'class' (T_CLASS), expecting function (T_FUNCTION) in nestedViaStatement.php on line 8

>

Here I have a completely legit use case wherein I have a Person class, and it also describes behaviour for the person's name property, by defining an inner Name class. Name has no purpose outside the context of Person, hence it being an inner class. But PHP does not support this . If the class expression implementation had been done correctly, one could do this:

// nestedViaExpressionAlternative.php

class Person {

    private $Name = new class($firstName, $lastName){
        private $firstName;
        private $lastName;

        function __construct($firstName, $lastName){
            $this->firstName = $firstName;
            $this->lastName = $lastName;
        }

        function getFullName(){
            return sprintf('%s %s', $this->firstName, $this->lastName);
        }
    };
    private $name;

    function __construct($firstName, $lastName){
        $this->name = new $this->Name($firstName, $lastName);
    }

    function getName(){
        return $this->name->getFullName();
    }

}

We could use a class expression to create the $name class. However - as I have pointed out - this doesn't work either. Instead I need to do this:

// nestedViaExpressionActual.php

class Person {

    private $name;

    function __construct($firstName, $lastName){
        $this->name = new class($firstName, $lastName){
            private $firstName;
            private $lastName;

            function __construct($firstName, $lastName){
                $this->firstName = $firstName;
                $this->lastName = $lastName;
            }

            function getFullName(){
                return sprintf('%s %s', $this->firstName, $this->lastName);
            }
        };
    }

    function getName(){
        return $this->name->getFullName();
    }

}

>php7 nestedViaExpressionActual.php
Ernest Rutherford
>

It could be worse, but it it's a bit clunk. Also, it completely invalidates the notion of code reuse: each "anonymous class" can only be used to create one object. Let's say I have a Contact class, and within that I want a PhoneNumber inner class (maybe it has a formatForLocal(), formatForInternational() methods or something)... I can do this easily with an anonymous class in PHP provided I only have one phone number. if I wanted to use the same inner class for a home number (do people still have those?), a work number and a mobile number... anonymous classes just won't help you.

I realise anonymous classes weren't specifically intended for this purpose, but I really struggle to see what they were intended for.

I wonder if I should suggest to the PHP bods that they allow a class expression to actually be assignable. It looks to me like a code parsing issue, rather than an innate shortfall of the implementation.

How would you use these things, btw? Any idea?

--
Adam