PSR Standards
PHP has no single organization controlling how frameworks and libraries are built, which historically meant every framework invented its own conventions for the same handful of basic concerns — its own way of naming and locating classes, its own logging interface, its own way of representing an HTTP request. That worked fine inside a single framework's walls, but it meant a logging library written for one framework was simply incompatible with another, and swapping one piece of infrastructure for another often meant rewriting code that had no real reason to be framework-specific in the first place. PSRs — PHP Standards Recommendations — exist to fix exactly that problem, and understanding what they are is useful even before writing framework code directly, because their vocabulary shows up constantly in package documentation and error messages.
PHP-FIG and how a PSR comes to exist
PHP-FIG, the PHP Framework Interop Group, is a group of maintainers from major PHP projects and frameworks who collaborate on shared standards for the specific problems that benefit from everyone agreeing on one answer rather than each maintaining a slightly different one. A PSR is a formal recommendation that comes out of that process — not a language feature enforced by the PHP engine itself, but a specification that any library or framework can choose to implement. Nothing forces a package to follow a PSR, but doing so is what makes that package interoperable with the rest of the PSR-compliant ecosystem, which in practice is most of the actively maintained one.
PSR-1 and PSR-12: basic and extended coding style
PSR-1 lays out a small set of baseline rules almost every PHP project already follows without thinking about it — files should either declare symbols (classes, functions) or cause side effects (like emitting output), but ideally not both; class names use StudlyCaps; method names use camelCase; constants are uppercase with underscores. PSR-12 builds on top of that with a much more detailed style guide covering brace placement, indentation, spacing around control structures, and where use imports go, so that code from different authors and different projects reads consistently.
Before (inconsistent style) | After (PSR-12) |
|---|---|
function calc($a,$b){ if($a>$b) { return $a; }else{ return $b; } } | Four-space indentation, space after commas, brace on its own line for functions, |
class userService{} | class UserService {} |
CONST maxRetries = 3; | const MAX_RETRIES = 3; |
The same function, before and after PSR-12 formatting
<?php
// Before
function calc($a,$b){
if($a>$b) { return $a; }else{ return $b; }
}
// After
function calc($a, $b)
{
if ($a > $b) {
return $a;
} else {
return $b;
}
}None of this changes what the code does — both versions run identically. What PSR-12 buys a team is that any PHP developer can open an unfamiliar PSR-12-compliant file and immediately recognize its shape, instead of spending a moment adjusting to a different indentation and brace convention every time they switch between codebases. Most editors and tools like PHP-CS-Fixer or PHPCS can enforce PSR-12 automatically, so in practice a developer rarely needs to apply these rules by hand.
PSR-4: autoloading
PSR-4 defines how a fully qualified class name maps to a file path on disk — a namespace prefix corresponds to a base directory, and the rest of the namespace, with backslashes replaced by directory separators, gives the path to the file underneath it. The composer-autoload page covers this mapping and Composer's generated autoloader in depth, so it's only worth noting here that PSR-4 is itself a PHP-FIG standard, which is exactly why every Composer package can declare its own autoload.psr-4 section and have it merge seamlessly with every other installed package's mapping — they all agree on the same resolution rule.
PSR-3: a common logger interface
PSR-3 defines Psr\Log\LoggerInterface, a standard shape for a
logger: methods like ->info(), ->warning(), ->error(), and
->critical(), each accepting a message string and an optional
array of context data. The interface itself does no logging — it
just describes the shape any compliant logger must expose.
Code written against the PSR-3 interface, not a concrete logger
<?php
use Psr\Log\LoggerInterface;
class PaymentProcessor
{
public function __construct(private LoggerInterface $logger) {}
public function charge(float $amount): void
{
$this->logger->info('Charging customer', ['amount' => $amount]);
// ... payment logic ...
$this->logger->info('Charge succeeded');
}
}Because PaymentProcessor only depends on LoggerInterface, not on any particular logging library, it can be handed Monolog's PSR-3 logger, a simple file-based logger, or a testing logger that collects messages in memory for assertions — all without changing a single line of PaymentProcessor itself. If PSR-3 didn't exist and every logging library defined its own interface with differently named methods, PaymentProcessor would have to pick one specific library to depend on directly, and swapping loggers later would mean rewriting every call site that logs anything.
PSR-7: HTTP message interfaces
PSR-7 standardizes how an HTTP request and response are represented
as objects — RequestInterface, ResponseInterface,
ServerRequestInterface, and related interfaces for URIs, streams,
and uploaded files. Instead of a framework reading straight from
PHP's global superglobals like $_GET, $_POST, and $_SERVER,
the incoming request becomes an immutable object with methods like
->getMethod(), ->getUri(), and ->getHeader(), and the outgoing
response is built the same way, with methods like ->withStatus()
returning a new response instance rather than mutating one in place.
Illustrative shape of PSR-7 style request handling middleware
<?php
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
function handle(ServerRequestInterface $request): ResponseInterface
{
$path = $request->getUri()->getPath();
$method = $request->getMethod();
// ...build a response based on $method and $path...
}The practical payoff is that middleware — small pieces of code that inspect or modify a request on its way in, or a response on its way out, such as authentication checks or CORS headers — can be written against the PSR-7 interfaces and then reused across any framework or HTTP library that also speaks PSR-7, instead of being tied to one framework's specific request and response classes.
PSR-11: the container interface
PSR-11 standardizes the shape of a dependency injection container:
two methods, ->get($id) to retrieve an entry, and ->has($id) to
check whether one exists, both defined on Psr\Container\ContainerInterface.
A dependency injection container is an object responsible for
building other objects and their dependencies, so that application
code can ask for "the mailer" or "the logger" without knowing how
those objects are constructed or wired together.
Code that only depends on the PSR-11 interface
<?php
use Psr\Container\ContainerInterface;
function bootstrap(ContainerInterface $container): void
{
if ($container->has('logger')) {
$logger = $container->get('logger');
$logger->info('Application booted');
}
}Because bootstrap() only depends on ContainerInterface, the same function works whether the actual container underneath is one library's implementation or another's, as long as both implement PSR-11. Before this standard existed, code that needed a container had to be written against one specific container library's particular method names.
Why interoperability is the whole point
Every one of these standards solves the same underlying problem in a different domain: a library author writes code against an interface rather than a specific implementation, so that anyone using that library can plug in whichever compliant implementation fits their project. A logging package written against Psr\Log\LoggerInterface works with Monolog, or with any other PSR-3-compatible logger, with zero modification to the package itself — the package never even needs to know Monolog exists. If every library instead defined and required its own bespoke logger interface, using two libraries together that both wanted to log something would mean either maintaining two separate loggers, or writing adapter code to bridge one interface to the other, for every such pairing. PSRs turn what would otherwise be combinatorial glue code into a single shared contract that any compliant piece of software already speaks.
PHP-FIG is a group of framework and library maintainers who publish PSRs to standardize solutions to common cross-project problems.
PSR-1 and PSR-12 standardize coding style — naming conventions, brace placement, indentation — so code reads consistently across projects.
PSR-4 standardizes how a namespaced class name maps to a file path, which is what makes Composer's autoloader work across every installed package.
PSR-3's
LoggerInterfacelets any PSR-3-compatible logging library be swapped in without changing the code that logs against it.PSR-7 represents HTTP requests and responses as objects, enabling middleware to be reused across different frameworks and HTTP libraries.
PSR-11's
ContainerInterfacestandardizesget()andhas()so dependency injection containers are interchangeable.A PSR is an interface or convention, not a shipped implementation — actual functionality still comes from a concrete library that implements it.