Custom Container
By default the application creates a Solo\Container\Container. You can supply your own as the second constructor argument:
<?php
use Solo\Application\Application;
use Solo\Container\Container;
$container = new Container();
// pre-register services if needed
$app = new Application($config, $container);Without a container argument and without solophp/container installed, the constructor throws RuntimeException.
Requirements
The container must implement Solo\Contracts\Container\WritableContainerInterface: PSR-11 get() and has(), plus set():
public function set(string $id, callable $factory): void;set()registers a factory. The factory receives the container when it runs.- The interface expects shared services: the factory runs on the first
get()of an ID, and later calls return the same instance.Applicationalso works with a container that calls the factory on everyget(), because it hands the router toRoutingMiddlewaredirectly. Applicationcallsset()only for services it has not resolved yet, so containers that refuse to replace a resolved service, like Pimple, work too.- Middleware, controllers and invokable handlers are referenced by class name and resolved with
get().solophp/containercreates any existing class by autowiring its constructor; with a container that does not, register every class you reference.
Registration order
Application registers its own services in the constructor, then runs the providers. So:
- services you register before the constructor can be replaced by providers, and the IDs
Applicationregisters itself, such asConfigorRoutingErrorMiddleware, are always replaced; - to override one of those, or a provider's service, call
set()after the constructor, before the first request:$app->container->set(...).
$app->container is the container the application uses, whether you passed it or it was created.
Adapting a read-only container
A PSR-11 container without set(), such as one compiled by your framework, can be wrapped. The adapter keeps the factories Application and your providers register and passes every other ID on:
<?php
use Psr\Container\ContainerInterface;
use Solo\Contracts\Container\WritableContainerInterface;
final class WritableContainer implements WritableContainerInterface
{
/** @var array<string, callable> */
private array $factories = [];
/** @var array<string, mixed> */
private array $instances = [];
public function __construct(private readonly ContainerInterface $inner)
{
}
public function set(string $id, callable $factory): void
{
$this->factories[$id] = $factory;
unset($this->instances[$id]);
}
public function get(string $id): mixed
{
if (array_key_exists($id, $this->instances)) {
return $this->instances[$id];
}
if (isset($this->factories[$id])) {
return $this->instances[$id] = ($this->factories[$id])($this);
}
return $this->inner->get($id);
}
public function has(string $id): bool
{
return isset($this->factories[$id]) || $this->inner->has($id);
}
}$app = new Application($config, new WritableContainer($frameworkContainer));Services built by the inner container take their dependencies from the inner container only: they do not see the factories registered on the adapter.