PHP.GT

Lazy loaders

Lazy loaders allow the container to construct a service only when it is first requested.

This is useful when a service is expensive to create, depends on other services, or should only exist in some requests.

Register a loader callback

Use setLoader() to associate a class name with a callback:

$container->setLoader(Greeter::class, function():Greeter {
	return new Greeter();
});

On the first get(Greeter::class), the callback is invoked and the returned object is stored in the container.

Subsequent calls return the same instance, not a fresh object each time.

This means that in applicaiton code, if an object isn’t used, it isn’t constructed - and if it’s used more than once, it’s only ever constructed once.

Nullable loaders

Loaders may return null if the return type allows it:

$container->setLoader(Greeter::class, function():?Greeter {
	return null;
});

After the first lookup, null is cached for that type. This can be useful when a service is genuinely optional. For example, a User object could be nullable in application code if an area of the application allows non-authenticated use. Requesting ?User can be used as an indicator to whether the user has authenticated yet.

Loader callbacks can use injected dependencies

Loader callbacks are invoked through the library’s Injector, so the callback itself can declare service dependencies:

$container->setLoader(UriGreeter::class, function(GreetingInterface $greeter):UriGreeter {
	return new UriGreeter($greeter);
});

As long as GreetingInterface can be resolved by the container, the loader can depend on it.

This means loaders can be chained together naturally.

Register loader methods from an object

If you prefer not to register each loader manually, use addLoaderClass():

$container->addLoaderClass(new class {
	public function loadGreeter():GreetingInterface {
		return new Greeter();
	}

	public function loadUriGreeter(GreetingInterface $greeter):UriGreeter {
		return new UriGreeter($greeter);
	}
});

The container scans every public method on the object. Any method with a declared return type becomes a loader for that type.

Methods without a return type are ignored.

Return types define the service key

With addLoaderClass(), the method name does not matter. The return type is what determines which service the method loads.

Because of that, this is valid:

$container->addLoaderClass(new class {
	public function doTheGreetThing():GreetingInterface {
		return new Greeter();
	}
});

The service is still resolved via GreetingInterface::class.

Base classes and interfaces from loaders

When a loader is added for a concrete class, its parent classes and implemented interfaces are also mapped to that same loader.

That means a loader for Greeter can also satisfy GreetingInterface if Greeter implements it.


To see how services are passed into functions and methods, continue to the Injector class section.