-
Notifications
You must be signed in to change notification settings - Fork 0
Resolution And Caching
This page describes exactly how the container turns an identifier into a value,
how has() decides what exists, and why every entry behaves as a shared
(singleton) instance. If you have read Autowiring and
Binding & Factories, this is the mental model that ties
them together.
get($id) resolves in this strict order:
-
Cache hit — if
$idwas resolved before, return the cached value. -
Registered definition — if
$idwas registered withset(), build it (invoke the closure, autowire the class name, or return the stored value), cache the result, and return it. -
Autowirable class — if
$idis an existing class name (class_exists), autowire it, cache the instance, and return it. -
Not found — otherwise throw
NotFoundException.
use InitPHP\Container\Container;
$container = new Container();
$container->set('greeting', 'hello');
$container->get('greeting'); // step 2 → 'hello'
$container->get(DateTime::class); // step 3 → autowired DateTime
$container->get('nope'); // step 4 → NotFoundExceptionA registered definition always wins over autowiring. If you set() a class name
to an alternative implementation, that binding takes precedence over building the
requested class directly.
Step 2 above hands the stored definition to an internal builder that picks a branch based on its type:
| Definition type | What happens |
|---|---|
Closure |
Invoked as $definition($container); the return value is the entry. |
string that is an existing class |
Autowired via reflection. |
| anything else (object, scalar, array, non-class string) | Returned unchanged. |
This is why a closure is the tool of choice for entries that need runtime configuration, and why storing a ready-made object simply hands it back later.
public function has(string $id): bool;has($id) returns true when get($id) would not throw a
NotFoundException — that is, when $id is:
- already resolved (cache hit), or
- a registered definition, or
- an existing class name (
class_exists, with autoloading).
$container->set('app.name', 'InitPHP');
$container->has('app.name'); // true (registered)
$container->has(DateTime::class); // true (autowirable class)
$container->has('missing'); // false
has()is not a guarantee of success. A class can exist and still fail to build — for example if its constructor needs a scalar.has()only promises thatget()will not raiseNotFoundException; it may still raise another container exception. See Limitations → has() and autowiring.
Note also that an unbound interface is reported as false by has(),
because class_exists() does not report interfaces. Bind it first (see
Binding & Factories).
Every value the container resolves — whether from a definition or from autowiring — is stored in an internal cache and reused. The practical consequence is that the container hands out shared instances:
$a = $container->get(Engine::class);
$b = $container->get(Engine::class);
$a === $b; // trueThe same applies to closure factories: the closure runs once, and its result is cached.
$count = 0;
$container->set('svc', function () use (&$count) {
$count++;
return new stdClass();
});
$container->get('svc');
$container->get('svc');
$count; // 1 — the factory ran only onceRe-registering an identifier with set() clears its cached instance, so the
next get() rebuilds it:
$container->set('mode', 'production');
$container->get('mode'); // 'production'
$container->set('mode', 'testing');
$container->get('mode'); // 'testing' — old cached value discardedThere is no method to clear a single entry without re-registering it, and no "flush all" operation. The container is designed to live for the duration of a request or process.
The container has no transient/prototype scope. When you genuinely need a new
object per call, do not route it through get(). Either construct it directly:
$mail = new Mailer($container->get('mailer.transport'));…or expose a factory object whose method returns a new instance, and register that factory in the container:
class MailerFactory
{
public function __construct(private Transport $transport) {}
public function create(): Mailer
{
return new Mailer($this->transport); // fresh each call
}
}
$container->set(MailerFactory::class); // the factory is the shared singleton
$mailer = $container->get(MailerFactory::class)->create(); // mailer is freshWhile building a class, the container records that it is in progress. If
resolving its dependencies leads back to the same class — directly or through a
chain — it raises CircularDependencyException
instead of recursing until the process runs out of memory:
class A { public function __construct(public B $b) {} }
class B { public function __construct(public A $a) {} }
$container->get(A::class); // CircularDependencyExceptionThe in-progress marker is cleared whether the build succeeds or fails, so a later, legitimate resolution of the same class is unaffected.
- Autowiring — how class dependencies are discovered and built.
- Binding & Factories — the registration styles feeding step 2.
- Exceptions — every error this lifecycle can raise.
initphp/container · MIT License · part of the InitPHP family
Source · Issues · Discussions · Packagist · Contributing · Security Policy
Getting Started
Core Usage
Reference
Practical Guides
Migration & Help