<?php
class InstantiationCounter {
public static $counts = [];
public static function reset() {
self::$counts = [
'Database' => 0,
'Logger' => 0,
'A' => 0,
'B' => 0,
'C' => 0,
'D' => 0,
];
}
}
class Database {
public function __construct() {
InstantiationCounter::$counts['Database']++;
}
}
class Logger {
public function __construct() {
InstantiationCounter::$counts['Logger']++;
}
}
class A {
public $database;
public function __construct(Database $database) {
InstantiationCounter::$counts['A']++;
$this->database = $database;
}
}
class B {
public $database;
public $logger;
public function __construct(Database $database, Logger $logger) {
InstantiationCounter::$counts['B']++;
$this->database = $database;
$this->logger = $logger;
}
}
class C {
public $a;
public $b;
public function __construct(A $a, B $b) {
InstantiationCounter::$counts['C']++;
$this->a = $a;
$this->b = $b;
}
}
class D {
public $a;
public $b;
public $c;
public function __construct(A $a, B $b, C $c) {
InstantiationCounter::$counts['D']++;
$this->a = $a;
$this->b = $b;
$this->c = $c;
}
}
class Container {
protected $classIsInstantiatableCache = [];
protected $isStaticMethodCache = [];
protected $definitions = [];
public function get($className) {
// If there is a manually defined dependency, use it
if (isset($this->definitions[$className])) {
return $this->definitions[$className]();
}
if (!isset($this->classIsInstantiatableCache[$className])) {
$reflector = new ReflectionClass($className);
$this->classIsInstantiatableCache[$className] = $reflector->isInstantiable();
}
if (!$this->classIsInstantiatableCache[$className]) {
throw new Exception("[$className] is not instantiable");
}
$constructor = (new ReflectionClass($className))->getConstructor();
if (is_null($constructor)) {
return new $className;
}
$parameters = $constructor->getParameters();
$dependencies = [];
foreach($parameters as $parameter) {
$type = $parameter->getType();
if (is_null($type) || $type->isBuiltin()) {
throw new Exception("Can't resolve class dependency [$parameter]");
}
$dependency = $type instanceof ReflectionNamedType ? $type->getName() : null;
if (is_null($dependency)) {
throw new Exception("Can't resolve class dependency [$parameter]");
}
if (!isset($this->isStaticMethodCache[$dependency])) {
$this->isStaticMethodCache[$dependency] = (new ReflectionClass($dependency))->getMethod('__construct')->isStatic();
}
if ($this->isStaticMethodCache[$dependency]) {
$dependencies[] = $dependency::getInstance();
} else {
$dependencies[] = $this->get($dependency);
}
}
return (new ReflectionClass($className))->newInstanceArgs($dependencies);
}
public function set($className, callable $factory) {
$this->definitions[$className] = $factory;
}
}
// Benchmark
$iterations = 100000;
$start = $end = null;
InstantiationCounter::reset();
$container = new Container;
// Benchmark: Instantiate $iterations of class D with autowiring
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$instance = $container->get('D');
}
$end = microtime(true);
echo "Time to instantiate $iterations of class D with autowiring: " . ($end - $start) . " seconds\n";
var_export( InstantiationCounter::$counts );
InstantiationCounter::reset();
$container = new Container();
$container->set('Database', function () {
return new Database();
});
$container->set('Logger', function () {
return new Logger();
});
$container->set('A', function () use ($container) {
return new A($container->get('Database'));
});
$container->set('B', function () use ($container) {
return new B($container->get('Database'), $container->get('Logger'));
});
$container->set('C', function () use ($container) {
return new C($container->get('A'), $container->get('B'));
});
$container->set('D', function () use ($container) {
return new D($container->get('A'), $container->get('B'), $container->get('C'));
});
// Benchmark: Instantiate $iterations of class D without autowiring
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$instance = $container->get('D');
#$instance = new D(new A(new Database()), new B(new Database(), new Logger()), new C(new A(new Database()), new B(new Database(), new Logger())));
}
$end = microtime(true);
echo "\nTime to instantiate $iterations of class D without autowiring: " . ($end - $start) . " seconds\n";
var_export( InstantiationCounter::$counts );
?>