skip to content

PHP-FIG Standards

The accepted PSRs and PER Coding Style: style rules, HTTP message interfaces, logger and cache contracts, container, events and clock. Interviewers probe which PSR solves which interop problem.

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

explore

questions

22

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

In PSR-3, what are the eight log levels in Psr\Log\LogLevel, and how do you choose between them?

level: juniorimportance: must knowfreq 55%

basics

~20 s

PSR-3 defines eight RFC 5424 levels: emergency, alert, critical, error, warning, notice, info and debug, each with its own LoggerInterface method. Choose by urgency: from system unusable, through failures and oddities, down to routine events and diagnostics.

open as a page

In PHP, what are the PSR-1 and PSR-12 coding standards, and what kinds of rules does each one set?

level: juniorimportance: must knowfreq 55%

basics

~20 s

PSR-1 is PHP-FIG's basic coding standard: tags, UTF-8, naming, one class per autoloadable file, and no mixing of declarations with side effects. PSR-12 extends it with formatting: indentation, line endings, braces, spacing and file-header order, replacing PSR-2.

open as a page

In PSR-15, what do MiddlewareInterface::process() and RequestHandlerInterface::handle() declare, and how does a rate-limiting middleware reject or delegate a request?

level: middleimportance: must knowfreq 50%

basics

~10 s

PSR-15 declares handle(ServerRequestInterface $request): ResponseInterface and process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface. A rate limiter returns its own 429 response to reject, or calls $handler->handle($request) and returns that response.

open as a page

In PSR-7, why does calling $response->withHeader('X-Trace-Id', $id) without assigning the result leave the response unchanged?

level: middleimportance: must knowfreq 62%

basics

~10 s

PSR-7 messages are immutable: withHeader() returns an instance carrying the change and must leave the original untouched. Discarding the return value discards the change; write $response = $response->withHeader(...) or return the new instance.

open as a page

What is the difference between PSR-6 CacheItemPoolInterface and PSR-16 CacheInterface, and which should a reusable PHP library depend on?

level: middleimportance: must knowfreq 48%

basics

~20 s

PSR-6 is a pool of item objects: getItem(), isHit(), set(), save(), with deferred saves. PSR-16 is a simple key/value API: get(), set(), delete() with a TTL. A library needing only get/set usually depends on PSR-16.

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 PHP, what is PSR-7, and which interfaces does it define to represent HTTP requests, responses and their parts?

level: juniorimportance: should knowfreq 42%

basics

~10 s

PSR-7 is the PHP-FIG standard for HTTP messages: interfaces in Psr\Http\Message for requests, server requests, responses, bodies (StreamInterface), URIs and uploaded files. Code typed against them works with any conforming implementation.

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-7, what are ServerRequestInterface attributes for, and how does a middleware hand a derived value to the next handler?

level: middleimportance: should knowfreq 40%

basics

~10 s

ServerRequestInterface attributes carry values derived from the request, such as route matches or an authenticated client id. A middleware calls $request->withAttribute('name', $value) and passes that new request on; later code reads getAttribute('name', $default).

open as a page

In PSR-7, why can a second call to $request->getBody()->getContents() return an empty string?

level: middleimportance: should knowfreq 32%

basics

~20 s

StreamInterface wraps a PHP stream with a cursor. getContents() returns only the remaining bytes, so after one full read the cursor is at the end and the next call returns ''. Call rewind() first or cast with (string).

open as a page

In PSR-3, why should a log message use {placeholder} tokens filled from the context array, and where does a caught exception belong?

level: middleimportance: should knowfreq 45%

basics

~20 s

PSR-3 intends the message to be a static string with {name} placeholders and all variable data in the context array, so the logger can escape it per output format. A caught exception goes in the context under the 'exception' key.

open as a page

In PSR-6, what do CacheItemPoolInterface::saveDeferred() and commit() do, and what may a pool do with deferred items before commit()?

level: middleimportance: should knowfreq 25%

basics

~20 s

saveDeferred() queues an item for later persistence and commit() persists every queued item, so a pool can batch writes. A pool may persist deferred items earlier, must not lose them, and must return them from getItem() before commit.

open as a page

In PHP-FIG terms, why are PSR-2 and PSR-0 deprecated, and how does PER Coding Style differ from PSR-12?

level: middleimportance: should knowfreq 30%

basics

~20 s

PSR-0 was deprecated in 2014 in favour of PSR-4 autoloading, and PSR-2 in 2019 in favour of PSR-12. Accepted PSRs are frozen, so style work continues in PER Coding Style, a versioned, evolving recommendation built on PSR-12.

open as a page

Under PSR-12, in what order must a PHP file's header blocks appear, and how must declare(strict_types=1) and use imports be written?

level: middleimportance: should knowfreq 28%

basics

~20 s

PSR-12 orders the header as: opening tag, file docblock, declare statements, namespace, class imports, function imports, constant imports, then code, each block separated by one blank line. The declare is written exactly declare(strict_types=1), and imports never start with a backslash.

open as a page

In PHP's PSR-1, what does 'declare symbols or cause side effects, but not both' mean, and why does it matter?

level: middleimportance: should knowfreq 32%

basics

~20 s

Under PSR-1, a file that declares classes, functions or constants should do nothing else when included: no output, ini changes, includes or connections. Side effects belong in entry-point files, so autoloading a class never triggers hidden behaviour.

open as a page

When writing a framework-agnostic PHP package that calls a remote API, how do PSR-17 factories and PSR-18's ClientInterface keep it independent of any HTTP implementation, and what does sendRequest() throw?

level: seniorimportance: should knowfreq 28%

basics

~20 s

The package type-hints PSR-17 factories to build requests and PSR-18's ClientInterface to send them; the application injects concrete implementations. sendRequest() returns 4xx and 5xx responses normally and throws ClientExceptionInterface only when it cannot send or parse.

open as a page

When writing a framework-agnostic PHP library that should log and cache, how do you depend on PSR-3 and PSR-16 without forcing either on the application?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Require only psr/log and psr/simple-cache, accept LoggerInterface and CacheInterface in the constructor, default the logger to NullLogger and make the cache optional. The application injects its own implementations; the library never names a vendor.

open as a page

You are starting a new open-source PHP package in 2026; which coding style standard do you adopt, and how do you make contributors follow it?

level: seniorimportance: should knowfreq 24%

basics

~20 s

Adopt PER Coding Style at a pinned version, which builds on PSR-12 and PSR-1, state it in the contributing guide, and enforce it with committed formatter configuration plus a CI check, so style never becomes a review discussion.

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

Under PSR-6 and PSR-16, which cache key characters and lengths must every implementation accept, and which characters are reserved?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

Both PSRs require implementations to accept keys of A-Z, a-z, 0-9, underscore and period, up to 64 characters in UTF-8. The characters {}()/@: are reserved and must not be supported; an illegal key throws the standard's InvalidArgumentException.

open as a page