skip to content

Container, Event & Clock PSRs

PSR-11 standardises reading a container with get and has, PSR-14 dispatches events through listener providers, and PSR-20 injects the current time. Interviewers probe service-locator misuse.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In PSR-11, what do ContainerInterface::get() and has() promise, and what does get() throw for an unknown identifier?

level: juniorimportance: must knowfreq 45%

answer

  1. two methods, both take a string id
  2. has() false means get() must throw
  3. NotFoundExceptionInterface extends ContainerExceptionInterface
  4. has() true is not a success guarantee
  5. do not rely on the same instance

basics

~10 s

PSR-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
<?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

for a junior

Remember the two methods, get() and has(), and that get() throws a NotFoundExceptionInterface for an identifier the container does not know.

for a middle

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.

for a senior

Keep ContainerInterface in infrastructure code only, and design error handling that distinguishes unknown identifiers from broken container configuration.

for a principal

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.
open as a page

Why does PSR-11 advise against injecting ContainerInterface into a class so it can fetch its own dependencies, and when is injecting the container legitimate?

level: seniorimportance: must knowfreq 50%

basics

~20 s

Injecting ContainerInterface so a class pulls its own dependencies makes the container a service locator: dependencies are hidden, tests need a container, entry names are hard-coded. It is legitimate when a class computes which entry it needs, like a router.

open as a page

In PSR-14, how do EventDispatcherInterface and ListenerProviderInterface split the work of delivering an event to its listeners?

level: middleimportance: should knowfreq 30%

basics

~20 s

In PSR-14 a ListenerProvider decides which listeners apply to an event and in what order, but must not call them. The EventDispatcher asks the provider, calls each listener synchronously in that order, and returns the same event object.

open as a page

In PHP, why inject a PSR-20 ClockInterface instead of calling time() or new DateTimeImmutable(), and how does that make a scheduler testable?

level: middleimportance: should knowfreq 38%

basics

~20 s

time() and new DateTimeImmutable() read the real clock, so tests cannot control the current time. PSR-20's ClockInterface::now() returns a DateTimeImmutable from an injected clock: production passes a system clock, tests pass a frozen or adjustable one.

open as a page

In PSR-14, how does a StoppableEventInterface event stop further listeners, and what must a dispatcher do when a listener throws?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

A PSR-14 dispatcher checks isPropagationStopped() before each listener and returns the event once it is true. A listener's throwable stops the remaining listeners and must reach the emitter; the dispatcher may only log and rethrow it.

open as a page