4. PHP7 Anonymous Classes
• What is an Anonymous Class?
• A class that is defined “inline”, within user code, at runtime
• Sometimes called “inline” or “nested” classes
• Don’t have the scope of the class where they are created
• Not assigned a class name
6. PHP7 Anonymous Classes
• How do I use an Anonymous Class?
• No name given to the class (internally assigned by PHP)
• Closing semi-colon (;)
• Constructor arguments passed in class definition
• All objects created by the same anonymous class declaration are instances of
that class (the internal class reference is identical).
$objectInstance = new class( 'arguments'... ) {
// defined properties and methods
};
7. PHP7 Anonymous Classes
function locationFormatBuilder (array $location = []) {
return new class (...$location) {
const POINT_MASK = '% 3d° % 2d' %5.2f" %s';
public function __construct($latitude, $longitude) {
$this->latitude = $latitude;
$this->longitude = $longitude;
}
protected function point($point, $orientation) {
…
return vsprintf(self::POINT_MASK, [$degrees, $minutes, $seconds, $direction]);
}
public function __toString() {
return vsprintf(
'%s, %s',
[$this->point($this->latitude, ['N','S']), $this->point($this->longitude, ['E','W'])]
);
}
};
}
8. PHP7 Anonymous Classes
$locations = [
[ 51.5074, -0.1278 ], // London
[ 40.7128, -74.0059 ], // New York
[ -22.9068, -43.1729 ], // Rio de Janeiro
[ -33.9249, 18.4241 ], // Cape Town
[ 19.0760, 72.8777 ], // Mumbai
[ 35.6895, 139.6917 ], // Tokyo
[ -33.8688, 151.2093 ], // Sydney
[ 31.7683, 35.2137 ], // Jerusalem
];
foreach ($locations as $location) {
echo locationFormatBuilder($location), PHP_EOL;
}
9. 51° 30' 26.00" N, 0° 7' 40.00" W
40° 42' 46.00" N, 74° 0' 21.00" W
22° 54' 24.00" S, 43° 10' 22.00" W
33° 55' 29.00" S, 18° 25' 26.00" E
19° 4' 33.00" N, 72° 52' 39.00" E
35° 41' 22.00" N, 139° 41' 30.00" E
33° 52' 7.00" S, 151° 12' 33.00" E
31° 46' 5.00" N, 35° 12' 49.00" E
PHP7 Anonymous Classes
10. PHP7 Anonymous Classes
• How do I use an Anonymous Class?
• Can “implement” and “extend”
• Can use Traits
$objectInstance = new class( 'arguments'... ) {
// defined properties and methods
};
11. PHP7 Anonymous Classes
• Anonymous Class Factories
• Builds an Anonymous Class that extends the “UserModel” class with optional
Traits
• Uses “eval()”
$anonymousUserInstance = (new AnonymousClassFactory('UserModel'))
->withConstructorArguments($databaseConnection, $loggingEnabled)
->withTraits('softDelete', 'auditable')
->create();
15. PHP7 Anonymous Classes
• Anonymous Class Factories
• In Search of an Anonymous Class Factory
https://markbakeruk.net/2016/05/03/in-search-of-an-anonymous-class-factory/
• Anonymous Class Factory – The Results are in
https://markbakeruk.net/2016/05/12/anonymous-class-factory-the-results-are-in/
$anonymousUserInstance = (new AnonymousClassFactory('UserModel'))
->withConstructorArguments($databaseConnection, $loggingEnabled)
->withTraits('softDelete', 'auditable')
->create();
16. PHP7 Anonymous Classes
class UserController {
protected $user;
public function __construct(UserModel $user) {
$this->user = $user;
}
}
$controller = new UserController($anonymousUserInstance);
17. PHP7 Anonymous Classes
• What is an Anonymous Class?
• How do I use an Anonymous Class?
• What Limitations are there to Anonymous Classes?
18. PHP7 Anonymous Classes
• What Limitations are there to Anonymous Classes?
• Definition and Instantiation in a Single Step
• Cannot be Serialized
• Effectively Final
• No OpCache Optimisations
• No Documentation (DocBlocks)
• No IDE Hinting
19. PHP7 Anonymous Classes
• What is an Anonymous Class?
• How do I use an Anonymous Class?
• What Limitations are there to Anonymous Classes?
• Why would I use Anonymous Classes?
20. PHP7 Anonymous Classes
• Why would I use Anonymous Classes?
• One-off Objects
• Private Classes
• Test Mocking
• On-demand Typed Structures (instead of generic arrays or StdClass)
• Ad-hoc adapters in a loosely coupled architecture
• To bypass Autoloading
21. PHP7 Anonymous Classes
• Why would I use Anonymous Classes? – Private Classes
class deathwatch {
public static function monitor($object, Closure $callback) {
$keyName = 'monitor' . spl_object_hash($object);
$monitor = new class($callback) {
protected $callback;
public function __construct($callback) {
$this->callback = $callback;
}
public function __destruct() {
$callback = $this->callback;
$callback();
}
};
$object->$keyName = $monitor;
}
}
22. PHP7 Anonymous Classes
• Why would I use Anonymous Classes? – Private Classes
class character {
protected $name;
public function __construct($name) {
$this->name = $name;
}
}
$characters = [
new character('Ned Stark'),
new character('Olenna Tyrell'),
new character('Tywin Lannister'),
new character('Myrcella Baratheon'),
];
23. PHP7 Anonymous Classes
• Why would I use Anonymous Classes? – Private Classes
deathwatch::monitor($characters[0], function() {
echo 'Oh noes', PHP_EOL;
});
deathwatch::monitor($characters[1], function() {
echo 'Oh yaes', PHP_EOL;
});
deathwatch::monitor($characters[2], function() {
echo 'Oh no, not again!', PHP_EOL;
});
unset($characters[0], $characters[1]);
echo PHP_EOL, '---===---', PHP_EOL, PHP_EOL;
24. PHP7 Anonymous Classes
• Why would I use Anonymous Classes? – Private Classes
Oh noes
Oh yaes
---===---
Oh no, not again!
25. PHP7 Anonymous Classes
• Why would I use Anonymous Classes? – Test Mocking
• https://mfyu.co.uk/post/anonymous-classes-in-php-7-are-fantastic-for-tests
26. PHP7 Anonymous Classes
• Inner- or Nested-Classes
class SpyMaster {
private $targetObject;
protected $objectMission;
public function __construct($targetObject) {
$this->targetObject = $targetObject;
$this->objectMission = new SpyObjectMission($targetObject);
}
protected function getHandler() {
…
}
public function infiltrate() {
return $this->getHandler();
}
}
27. PHP7 Anonymous Classes
• Inner- or Nested-Classes
class SpyObjectMission {
public $methods = [];
public $invoker;
public function __construct($targetObject) {
$reflector = new ReflectionObject($targetObject);
$this->invoker = $this->getInvoker($reflector, $targetObject);
}
protected function getInvoker(ReflectionObject $reflector, $targetObject) {
$staticMethods = array_column($reflector->getMethods(ReflectionMethod::IS_STATIC), 'name');
$this->methods = array_diff(
array_column($reflector->getMethods(), 'name'), $staticMethods)
);
$invoker = function($methodName, ...$args) {
return self::$methodName(...$args);
};
return $invoker->bindTo($targetObject, get_class($targetObject));
}
}
28. PHP7 Anonymous Classes
• Inner- or Nested-Classes
protected function getHandler() {
return new class ($this->objectMission) {
private $objectMission;
public function __construct(SpyObjectMission $objectMission) {
$this->objectMission = $objectMission;
}
public function __call($methodName, $args) {
if (in_array($methodName, $this->objectMission->methods)) {
$invoker = $this->objectMission->invoker;
return $invoker($methodName, ...$args);
}
throw new Exception("Object Method {$methodName} does not exist");
}
};
}
29. PHP7 Anonymous Classes
• Inner- or Nested-Classes
$test = new classThatWeWantToTest(1, 2, 3);
$spy = (new SpyMaster($test))
->infiltrate();
echo 'SPY FOR TEST #1', PHP_EOL;
echo $spy->privateMethodThatWeWantToTest(), PHP_EOL;
30. PHP7 Anonymous Classes
• Inner- or Nested-Classes
• Closures, Anonymous Classes and an alternative approach to Test Mocking
(Part 2)
• https://markbakeruk.net/2017/07/30/closures-anonymous-classes-and-an-alternative-
approach-to-test-mocking-part-2/
• Closures, Anonymous Classes and an alternative approach to Test Mocking
(Part 4)
• https://markbakeruk.net/2018/01/23/closures-anonymous-classes-and-an-alternative-
approach-to-test-mocking-part-4/