Code has been edited to better fit on each slide. Actual Illuminate Cotainer code is PSR-2 compliant (or close to it), has comments, and doesn't include scalar parameter types.
Specifically, this comparison
$container = new Illuminate\Container\Container(); // if everything can't be autowired, bind here... \Slim\Factory\AppFactory::setContainer($container); // ...or here $app = AppFactory::create();
// ...or here $app->run();
class Auth { protected $rs; public function __construct(RaffleService $rs) { $this->rs = $rs; } // snip } $container->get(Auth::class); // instance of Auth
$container->get(Auth::class); // different instance
public function get($id) { try { return $this->resolve($id); } catch (Exception $e) { if ($this->has($id)) { throw $e; } throw new EntryNotFoundException($id); } }
public function has($id) { return $this->bound($id); } public function bound($abstract) { return isset($this->bindings[$abstract]) || isset($this->instances[$abstract]) || $this->isAlias($abstract); }
Prior to version 5.7, the get() method would fail if a dependency was autowired rather than declared explicitly. Laravel PR #25870 fixed this.
// in service config $container->singleton(Auth::class); // or you could use $container->bind(Auth::class, null, $shared = true);
// Use Laravel? You're probably used to...
$container->make(Auth::class); // ...or we can use ArrayAccess $container[Auth::class]; // same instance as above
Yes, but it's identical to pulling a dependency via ArrayAccess.
public function offsetGet($key) { return $this->make($key); } public function make($abstract, array $parameters = []) { return $this->resolve($abstract, $parameters); }
public function singleton($abstract, $concrete = null) { $this->bind($abstract, $concrete, true); } // available as of 6.x public function singletonIf($abstract, $concrete = null) { if (! $this->bound($abstract)) { $this->singleton($abstract, $concrete); } }
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
if (is_null($concrete)) $concrete = $abstract;
if (! $concrete instanceof Closure)
$concrete = $this->getClosure($abstract, $concrete);
$this->bindings[$abstract] =
compact('concrete', 'shared');
if ($this->resolved($abstract)) $this->rebound($abstract);
}
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
if (is_null($concrete)) $concrete = $abstract;
if (! $concrete instanceof Closure)
$concrete = $this->getClosure($abstract, $concrete);
$this->bindings[$abstract] =
compact('concrete', 'shared');
if ($this->resolved($abstract)) $this->rebound($abstract);
}
protected function dropStaleInstances($abstract)
{
unset(
$this->instances[$abstract],
$this->aliases[$abstract]
);
}
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
if (is_null($concrete)) $concrete = $abstract;
if (! $concrete instanceof Closure)
$concrete = $this->getClosure($abstract, $concrete);
$this->bindings[$abstract] =
compact('concrete', 'shared');
if ($this->resolved($abstract)) $this->rebound($abstract);
}
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
if (is_null($concrete)) $concrete = $abstract;
if (! $concrete instanceof Closure)
$concrete = $this->getClosure($abstract, $concrete);
$this->bindings[$abstract] =
compact('concrete', 'shared');
if ($this->resolved($abstract)) $this->rebound($abstract);
}
protected function getClosure($abstract, $concrete) { return function ($container, $parameters = []) use ($abstract, $concrete) { if ($abstract == $concrete) { return $container->build($concrete); } // switched from using make() in 5.8 return $container->resolve( $concrete, $parameters, $raiseEvents = false ); }; }
function (Container $c, $params = []) {
return new MyService(
'foo',
'bar',
$c->get(SomeDependency::class)
);
}
$container->bind( ExtendedPdo::class, function () use ($env) { return new \Aura\Sql\ExtendedPdo( /* DSN here */, $env['DB_USER'], $env['DB_PASSWORD'] ); } );
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
if (is_null($concrete)) $concrete = $abstract;
if (! $concrete instanceof Closure)
$concrete = $this->getClosure($abstract, $concrete);
$this->bindings[$abstract] =
compact('concrete', 'shared');
if ($this->resolved($abstract)) $this->rebound($abstract);
}
// WARNING: not valid PHP code
protected $bindings = [
string => [
concrete => Closure(Container, array): mixed,
shared => boolean
], ...
];
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
if (is_null($concrete)) $concrete = $abstract;
if (! $concrete instanceof Closure)
$concrete = $this->getClosure($abstract, $concrete);
$this->bindings[$abstract] =
compact('concrete', 'shared');
if ($this->resolved($abstract)) $this->rebound($abstract);
}
public function resolved(string $abstract) { if ($this->isAlias($abstract)) { $abstract = $this->getAlias($abstract); } return isset($this->resolved[$abstract]) || isset($this->instances[$abstract]); }
protected function rebound(string $abstract) { $instance = $this->make($abstract); foreach ( $this->getReboundCallbacks($abstract) as $callback ) { call_user_func($callback, $this, $instance); } }
protected function getReboundCallbacks($abstract) {
// set via rebinding($abstract, Closure)
return $this->reboundCallbacks[$abstract] ?? []; }
Yes, and it has been around forever.
public function bindIf( $abstract, $concrete = null, $shared = false ) { if (! $this->bound($abstract)) { $this->bind($abstract, $concrete, $shared); } }
$app->post('/{id}', function ($req, $res, $args) { $id = $args['id']; // snip if (!$this->auth->isAuthorized($req, $id)) return $res->withRedirect('/'); // snip }
Slim binds the container to $this in route closures, and...
public function __get($key) { return $this[$key]; }
$container->alias(Auth::class, 'auth');
public function alias($abstract, $alias)
{
// check moved from getAlias() in 5.8
if ($alias === $abstract)
throw new LogicException(
"[{$abstract}] is aliased to itself."
);
// forward lookup
$this->aliases[$alias] = $abstract;
// reverse lookup
$this->abstractAliases[$abstract][] = $alias;
}
public function isAlias($name) { return isset($this->aliases[$name]); } public function getAlias($abstract) { if (! isset($this->aliases[$abstract])) { return $abstract; } return $this->getAlias($this->aliases[$abstract]); }
protected function resolve(
string $abstract, array $parameters = [], bool $raiseEvents = true ) { $abstract = $this->getAlias($abstract); // do things with the un-aliased abstract identifier }
getAlias() is used in 10 other places in the Container class, including in getAlias()
$container->instance(MyClass::class, $preBuiltInstance); public function instance($abstract, $instance) { $this->removeAbstractAlias($abstract); $isBound = $this->bound($abstract); unset($this->aliases[$abstract]); $this->instances[$abstract] = $instance; if ($isBound) $this->rebound($abstract); return $instance; }
public static function getInstance() { if (is_null(static::$instance)) { static::$instance = new static; } return static::$instance; } public static function setInstance( ContainerContract $container = null ) { return static::$instance = $container; }
class RaffleService {
// properties for dependencies
public function __construct(
ExtendedPdoInterface $db, // interface
SMS $sms, // interface
$phone_number // string
) {
// set properties
}
// snip
}
$container->bind( ExtendedPdoInterface::class, ExtendedPdo::class );
$container[SMS::class] = function() use ($env) { if (isset($env['TWILIO_SID'])) return new TwilioSMS(/* env params */); if (isset($env['NEXMO_KEY'])) return new NexmoSMS(/* env params */); if (isset($env['DUMMY_SMS_WAIT_MS'])) return new DummySMS($env['DUMMY_SMS_WAIT_MS']); throw new InvalidArgumentException(/* some message */); };
public function offsetSet($key, $value) { $this->bind( $key, $value instanceof Closure ? $value : function () use ($value) { return $value; } ); }
$container ->when(RaffleService::class) // can be an array as of 5.7 ->needs('$phone_number') ->give($env['PHONE_NUMBER']); // can be a Closure // or, for a simple cases like this... $container->addContextualBinding( RaffleService::class, '$phone_number', $env['PHONE_NUMBER'] );
public function when($concrete) { $aliases = []; foreach (Util::arrayWrap($concrete) as $c) { $aliases[] = $this->getAlias($c); } return new ContextualBindingBuilder($this, $aliases); }
// inside ContextualBindingBuilder
public function needs($abstract) {
$this->needs = $abstract;
return $this;
}
public function give($implementation) {
foreach (Util::arrayWrap($this->concrete) as $concrete)
$this->container->addContextualBinding(
$concrete,
$this->needs,
$implementation
);
}
// back inside Container
public function addContextualBinding(
$concrete,
$abstract,
$implementation
) {
$this->contextual[$concrete][$this->getAlias($abstract)]
= $implementation;
}
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$abstract = $this->getAlias($abstract);
$needsContextualBuild = !empty($parameters)
|| !is_null($this->getContextualConcrete($abstract));
if (isset($this->instances[$abstract])
&& !$needsContextualBuild)
return $this->instances[$abstract];
// snip
}
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$abstract = $this->getAlias($abstract);
$needsContextualBuild = !empty($parameters)
|| !is_null($this->getContextualConcrete($abstract));
if (isset($this->instances[$abstract])
&& !$needsContextualBuild)
return $this->instances[$abstract];
// snip
}
protected function getContextualConcrete($abstract)
{
// e.g. RaffleService::class
if (! is_null($binding = $this->findInContextualBindings($abstract)))
return $binding;
if (empty($this->abstractAliases[$abstract]))
return;
// e.g. "raffleService"
foreach ($this->abstractAliases[$abstract] as $alias)
if (! is_null($binding = $this->findInContextualBindings($alias)))
return $binding;
}
protected function findInContextualBindings($abstract) { return $this->contextual[end($this->buildStack)][$abstract] ?? null; }
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$abstract = $this->getAlias($abstract);
$needsContextualBuild = !empty($parameters)
|| !is_null($this->getContextualConcrete($abstract));
if (isset($this->instances[$abstract])
&& !$needsContextualBuild)
return $this->instances[$abstract];
// snip
}
protected function resolve(/* snip */) { // snip $this->with[] = $parameters; $concrete = $this->getConcrete($abstract); // snip }
protected function resolve(/* snip */) { // snip $this->with[] = $parameters; $concrete = $this->getConcrete($abstract); // snip }
protected function getConcrete(string $abstract)
{
if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
return $concrete;
}
// if we have an explicit binding, use it
if (isset($this->bindings[$abstract])) {
return $this->bindings[$abstract]['concrete'];
}
// otherwise, let autowiring do its job
return $abstract;
}
protected function resolve(/* snip */) { // $concrete was just set to $this->getConcrete($abstract) if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else {
$object = $this->make($concrete); } // snip }
protected function isBuildable($concrete, $abstract) { return $concrete === $abstract || $concrete instanceof Closure; }
protected function resolve(/* snip */) { // snip if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { // non-Closure contextual concrete $object = $this->make($concrete); } // snip }
public function build($concrete)
{
if ($concrete instanceof Closure)
return $concrete($this, $this->getLastParameterOverride());
try {
$reflector = new ReflectionClass($concrete);
} catch (ReflectionException $e) { // try-catch as of 6.x
throw new BindingResolutionException(
"Target class [$concrete] does not exist.", 0, $e
);
}
if (! $reflector->isInstantiable()) // abstract or interface
return $this->notInstantiable($concrete);
// snip
}
public function build($concrete)
{
// snip
$this->buildStack[] = $concrete;
$constructor = $reflector->getConstructor();
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
// snip
}
public function build($concrete)
{
// snip
$this->buildStack[] = $concrete;
$constructor = $reflector->getConstructor();
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
// snip
}
public function build($concrete) { // snip $dependencies = $constructor->getParameters(); try { $instances = $this->resolveDependencies($dependencies); } catch (BindingResolutionException $e) { array_pop($this->buildStack); throw $e; } array_pop($this->buildStack); return $reflector->newInstanceArgs($instances); }
public function build($concrete) { // snip $dependencies = $constructor->getParameters(); try { $instances = $this->resolveDependencies($dependencies); } catch (BindingResolutionException $e) { array_pop($this->buildStack); throw $e; } array_pop($this->buildStack); return $reflector->newInstanceArgs($instances); }
protected function resolveDependencies(array $dependencies) { $results = []; foreach ($dependencies as $dependency) { if ($this->hasParameterOverride($dependency)) { $results[] = $this->getParameterOverride($dependency); continue; } $results[] = is_null($dependency->getClass()) ? $this->resolvePrimitive($dependency) : $this->resolveClass($dependency); } return $results; }
protected function hasParameterOverride($dependency) { return array_key_exists( $dependency->name, $this->getLastParameterOverride() ); }
protected function getLastParameterOverride() { return count($this->with) ? end($this->with) : []; }
protected function getParameterOverride($dependency) { return $this->getLastParameterOverride()[$dependency->name]; }
protected function resolveDependencies(array $dependencies) { $results = []; foreach ($dependencies as $dependency) { if ($this->hasParameterOverride($dependency)) { $results[] = $this->getParameterOverride($dependency); continue; } $results[] = is_null($dependency->getClass()) ? $this->resolvePrimitive($dependency) : $this->resolveClass($dependency); } return $results; }
protected function resolvePrimitive(ReflectionParameter $parameter)
{
if (! is_null(
$concrete = $this->getContextualConcrete('$'.$parameter->name)
)) {
return $concrete instanceof Closure ? $concrete($this) : $concrete;
}
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
}
$this->unresolvablePrimitive($parameter);
}
protected function resolvePrimitive(ReflectionParameter $parameter)
{
if (! is_null(
$concrete = $this->getContextualConcrete('$'.$parameter->name)
)) {
return $concrete instanceof Closure ? $concrete($this) : $concrete;
}
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
}
$this->unresolvablePrimitive($parameter);
}
protected function resolvePrimitive(ReflectionParameter $parameter)
{
if (! is_null(
$concrete = $this->getContextualConcrete('$'.$parameter->name)
)) {
return $concrete instanceof Closure ? $concrete($this) : $concrete;
}
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
}
$this->unresolvablePrimitive($parameter);
}
protected function resolveClass(ReflectionParameter $parameter) { try { return $this->make($parameter->getClass()->name); } catch (BindingResolutionException $e) { if ($parameter->isOptional()) { return $parameter->getDefaultValue(); } throw $e; } }
protected function resolveClass(ReflectionParameter $parameter) { try { return $this->make($parameter->getClass()->name); } catch (BindingResolutionException $e) { if ($parameter->isOptional()) { return $parameter->getDefaultValue(); } throw $e; } }
protected function resolve(/* snip */)
{
// $object = $this->build($concrete) or $this->make($concrete)
foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } if ($this->isShared($abstract) && ! $needsContextualBuild) { $this->instances[$abstract] = $object; } // snip
}
protected function resolve(/* snip */)
{
// $object = $this->build($concrete) or $this->make($concrete)
foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } if ($this->isShared($abstract) && ! $needsContextualBuild) { $this->instances[$abstract] = $object; } // snip
}
protected function getExtenders($abstract) { $abstract = $this->getAlias($abstract); return $this->extenders[$abstract] ?? []; }
See Extender docs for how to use, and this code block for the extend() implementation
protected function resolve(/* snip */)
{
// $object = $this->build($concrete) or $this->make($concrete)
foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } if ($this->isShared($abstract) && ! $needsContextualBuild) { $this->instances[$abstract] = $object; }
// snip
}
protected function resolve(/* snip */) { // snip; object has been extended and persisted to instances if needed if ($raiseEvents) { $this->fireResolvingCallbacks($abstract, $object); } $this->resolved[$abstract] = true; array_pop($this->with); return $object; }
protected function fireResolvingCallbacks($abstract, $object) {
// set by resolving(Closure)
$this->fireCallbackArray($object, $this->globalResolvingCallbacks);
$this->fireCallbackArray( // set by resolving($abstract, Closure)
$object,
$this->getCallbacksForType(
$abstract,
$object,
$this->resolvingCallbacks
)
);
// set by afterResolving(Closure) && afterResolving($abstract, Closure)
$this->fireAfterResolvingCallbacks($abstract, $object);
}
protected function resolve(/* snip */) { // snip; object has been extended and persisted to instances if needed if ($raiseEvents) { $this->fireResolvingCallbacks($abstract, $object); } $this->resolved[$abstract] = true; array_pop($this->with); return $object; }
You can also curry closures with wrap($closure, $params = [])