Skip to content

Instantly share code, notes, and snippets.

@OO00O0O
Last active January 28, 2021 09:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OO00O0O/9e010673f91e80073edc40de57a62e2d to your computer and use it in GitHub Desktop.
Save OO00O0O/9e010673f91e80073edc40de57a62e2d to your computer and use it in GitHub Desktop.
PHP single function dependency injection
<?php declare(strict_types=1);
if (PHP_VERSION_ID < 80000) { die('only php 8'); }
/**
* Single function dependency injection POC
*/
interface Inter {}
class A { public function __construct(private int $id) {} }
class E { public function __construct(private int $id) {} }
class B { public function __construct(private A|E $a) {} }
class C { public function __construct(private A $a, public B $b, string $name = '') {} }
class D implements Inter { public function __construct(private int $id, public C $c, public B $b) {} }
$map['id'] = 8;
assert(di(A::class, $map) instanceof A, 'should be A');
$map = ['id' => 10];
assert(di(B::class, $map) instanceof B, 'should be B');
$map[A::class] = new A(1000);
$map['name'] = 'Some';
assert(di(C::class, $map) instanceof C, 'should be C');
di(Inter::class, $map);
print_r($map);
/**
* Single function dependency injection
* Supports classes, interfaces(first class that implements)
* and instance map that works as parameters too
* @link https://gist.github.com/ernestas-s/9e010673f91e80073edc40de57a62e2d
* @param class-string $class
* @param array<string,mixed> $map
* @param callable|null $check
* @param callable|null $inject
* @return object<class-string>
* @throws ReflectionException
*/
function di(
string $class,
array &$map = [],
?callable $check = null,
?callable $inject = null,
): object
{
if (isset($map[$class])) {
return $map[$class];
}
$r = new \ReflectionClass($class);
if ($r->isInterface()) {
foreach (get_declared_classes() as $existingClass) {
if (in_array($r->name, class_implements($existingClass), true)) {
return $map[$r->name] = di($existingClass, $map);
}
}
throw new \RuntimeException("Nothing implements {$r->name} interface");
}
if (!$r->isInstantiable()) {
throw new \RuntimeException("{$r->name} can not have instance");
}
return $map[$class] = $r->newInstanceArgs($r->getConstructor()
? array_map(static function (\ReflectionParameter|\ReflectionProperty $p) use (&$map, $check, $inject) {
$type = $p->hasType() ? (string)$p->getType() : null;
return match (true) {
is_callable($check) && $check($p) => is_callable($inject) ? $inject($p) : null,
isset($map[$p->name]) => $map[$p->name],
$type && (class_exists($type) || interface_exists($type)) => di($type, $map),
$type && str_contains($type, '|') => di(array_filter(explode('|', $type),
static fn(string $c) => class_exists($c) || interface_exists($c))[0]
?? ($p->isDefaultValueAvailable() ? $p->getDefaultValue() : null)
?? ($p->allowsNull() ? null : throw new \RuntimeException("Undefined \${$p->name}")),
$map
),
$p->isDefaultValueAvailable() => $p->getDefaultValue(),
$p->allowsNull() => null,
default => throw new \RuntimeException("Undefined \${$p->name}"),
};
}, $r->getConstructor()->getParameters())
: []
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment