In PSR-11, what do ContainerInterface::get() and has() promise, and what does get() throw for an unknown identifier?
answer
- two methods, both take a string id
- has() false means get() must throw
- NotFoundExceptionInterface extends ContainerExceptionInterface
- has() true is not a success guarantee
- do not rely on the same instance
basics
~10 sPSR-11's ContainerInterface has get($id), returning any entry, and has($id), returning a bool. If has() returns false, get() must throw Psr\Container\NotFoundExceptionInterface; other container errors implement ContainerExceptionInterface.
solid answer
~40 s`Psr\Container\ContainerInterface` is read-only: `get($id)` returns the entry for a string identifier, which can be any value, and `has($id)` returns `true` if the container knows the identifier. The contract links them: if `has($id)` is `false`, `get($id)` MUST throw a `NotFoundExceptionInterface`. The reverse is weaker: `has()` returning `true` only means that identifier is known; `get()` can still fail, for instance with a `ContainerExceptionInterface` for a broken definition, and the meta document notes a `NotFoundExceptionInterface` can surface when one of the entry's own dependencies is missing. Two successive `get()` calls SHOULD return the same value, but users should not rely on it. Identifiers are opaque strings. PSR-11 defines nothing about registering entries; that is each container's own API.
code
php · 19 lines<?php
declare(strict_types=1);
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
function resolveJob(ContainerInterface $container, string $jobId): object
{
if (!$container->has($jobId)) {
throw new UnknownJob("No job registered as {$jobId}"); // bad request
}
try {
return $container->get($jobId);
} catch (NotFoundExceptionInterface $e) {
// has() said yes, so a dependency of the job is missing: misconfiguration
throw new \LogicException("Job {$jobId} is misconfigured", previous: $e);
}
}go deeper
Remember the two methods, get() and has(), and that get() throws a NotFoundExceptionInterface for an identifier the container does not know.
Explain the one-way link between has() and get(), the two exception interfaces, and why a not-found exception after has() returned true signals misconfiguration.
Keep ContainerInterface in infrastructure code only, and design error handling that distinguishes unknown identifiers from broken container configuration.
Decide how far a codebase depends on a specific container's API versus PSR-11, weighing portability against features like autowiring and compilation.
## What PSR-11 standardises **PSR-11** defines how code **reads** from a dependency injection container. It ships as the `psr/container` package, and its whole API is one interface with two methods, plus two exception interfaces, all in the `Psr\Container` namespace. It deliberately says nothing about how entries are registered, configured, scoped or built: those are each container's own API. ## The two methods | Method | Parameter | Returns | Rule | |---|---|---|---| | `get($id)` | an entry identifier, which MUST be a string | anything (`mixed`) | throws `NotFoundExceptionInterface` if the identifier is not known | | `has($id)` | an entry identifier, which MUST be a string | `bool` | `true` if the identifier is known, `false` otherwise | An **entry identifier** is any PHP-legal string of at least one character. It is **opaque**: callers should not assume its structure means anything. In practice many containers use class or interface names such as `LoggerInterface::class`, but PSR-11 does not require it. ## How get() and has() relate 1. If `has($id)` returns `false`, `get($id)` **must** throw a `Psr\Container\NotFoundExceptionInterface`. 2. If `has($id)` returns `true`, the interface only promises that no entry is missing for **this** identifier. `get()` can still throw: a flawed definition or a cyclic dependency raises some `ContainerExceptionInterface`, and an exception thrown while instantiating the entry may propagate unwrapped. 3. The PSR-11 meta document adds that `get()` can even throw a `NotFoundExceptionInterface` after `has()` returned `true`, when one of the entry's own dependencies is missing. That is how you tell a bad request from a misconfigured container: check `has()` first, and a not-found exception after a `true` means misconfiguration. ## Same value on every call? Two successive calls to `get()` with the same identifier **should** return the same value. But depending on the implementation or its configuration, different values may be returned, so users **should not** rely on getting the same instance. Whether an entry is shared or built fresh each time is container configuration, outside PSR-11. ## The exception interfaces - `Psr\Container\ContainerExceptionInterface` is the base. Exceptions thrown directly by the container **should** implement it. - `Psr\Container\NotFoundExceptionInterface` extends it and **must** be thrown by `get()` for an unknown identifier. Catching `NotFoundExceptionInterface` catches only not-found failures, while catching `ContainerExceptionInterface` catches both kinds. ## Package details - `psr/container` 1.1 added parameter types, and 2.0 added return types, but only to `has()`, so `get()` stays declared without a return type because an entry can be anything. - A container package declares that it provides `psr/container-implementation`, a virtual package, and a project needing some implementation can require that instead of naming one. ## What PSR-11 does not cover - Registration: no `set()`, `bind()` or `register()` method exists in the interface. - Lifetimes: shared versus per-call instances. - Autowiring, configuration files, compilation. - Extra `get()` parameters: some containers accept optional extra arguments, which is legal PHP, but code written against PSR-11 must not use them. ## Where PSR-11 code appears The interface is mostly consumed by infrastructure: routers resolving a controller name, middleware pipelines resolving a middleware name, factories. Application classes should receive their collaborators directly, a point PSR-11 itself makes in its recommended-usage section; using the container as a lookup service inside ordinary classes is the service-locator anti-pattern. ## Identifiers in practice Because identifiers are opaque strings, conventions come from the container and the application, not from PSR-11: - **class or interface names** via `::class`, such as `ClockInterface::class`, which static analysers and IDEs can follow; - **named entries** such as `'config.scheduler.batch_size'` for parameters, since `get()` may return scalars and arrays as well as objects; - **aliases**, which let two packages with different naming habits refer to the same entry. Whichever convention is used, code that calls `get()` with a computed identifier should call `has()` first, or be ready for `NotFoundExceptionInterface`, since a computed name can always be wrong.
- Why does psr/container 2.0 add a return type to has() but not to get()?`has()` always returns a boolean, so `bool` can be declared. `get()` can return any value, an object, an array, a string, a number, so no narrower type than `mixed` fits, and PSR-11 left it undeclared. Static analysers often add generics or stubs so `get(Foo::class)` is understood as returning `Foo`.
- How do you register a service using PSR-11?You cannot: PSR-11 only standardises reading. `ContainerInterface` has `get()` and `has()` and nothing else. Registration, lifetimes and autowiring are each container's own API, configured by the application, while libraries that only need to fetch entries type against `ContainerInterface`.
saying these in an interview costs you the question
- Believes has() returning true guarantees get() will succeed.
- Says get() returns null for an unknown identifier.
- Expects PSR-11 to define set() or bind() for registering services.
- Relies on get() always returning the same shared instance.
- Parses meaning out of entry identifiers as if they were structured.