PSR standards in 2026: which PHP-FIG specs your project needs
Current status of every PSR, why PER Coding Style replaces PSR-12, and how PSR-4, PSR-3 and PSR-20 make PHP code portable, with a working Composer example.
7 min read

In this article
PSRs are the shared interfaces and conventions published by the PHP Framework Interop Group (PHP-FIG). They are why a logger, cache, HTTP client or clock from one vendor plugs into code written against another. In October 2026 most projects need only a handful: PSR-4 for autoloading, PER Coding Style for formatting, and the interface PSRs (logging, containers, HTTP, cache, events, clock) wherever your code crosses a library boundary.
This guide lists the current status of every PSR as published on php-fig.org, explains which ones to adopt and shows a small project that uses three of them. The example was installed with Composer and run on PHP 8.5.7.
The PSR list and status in October 2026
The PSR index lists numbers 0 to 22. Status matters: deprecated and abandoned PSRs should not be adopted in new code, and drafts can still change.
| PSR | Name | Status | Use it for |
|---|---|---|---|
| 1 | Basic Coding Standard | Accepted | Baseline rules: <?php tags, UTF-8, StudlyCaps classes, camelCase methods |
| 3 | Logger Interface | Accepted | LoggerInterface with eight log levels |
| 4 | Autoloading Standard | Accepted | Mapping namespaces to directories |
| 6 / 16 | Caching Interface / Simple Cache | Accepted | Pool-based cache / simple get-set cache |
| 7 / 17 | HTTP Message Interface / HTTP Factories | Accepted | Immutable request and response objects and their factories |
| 11 | Container Interface | Accepted | get() and has() on dependency injection containers |
| 12 | Extended Coding Style Guide | Accepted | Superseded in practice by PER Coding Style |
| 13 | Hypermedia Links | Accepted | Link objects for HAL-style APIs |
| 14 | Event Dispatcher | Accepted | Framework-agnostic events and listeners |
| 15 | HTTP Handlers | Accepted | Request handlers and middleware |
| 18 | HTTP Client | Accepted | sendRequest() for swappable HTTP clients |
| 20 | Clock | Accepted | ClockInterface::now() for testable time |
| 0 / 2 | Autoloading / Coding Style Guide | Deprecated | Replaced by PSR-4 and PSR-12 |
| 8, 9, 10 | Huggable, Security Advisories, Security Reporting | Abandoned | Do not use |
| 5, 19, 21, 22 | PHPDoc, PHPDoc tags, Internationalization, Application Tracing | Draft | Watch, but do not rely on them yet |
PSR-4: the one every project uses
PSR-4 maps a namespace prefix to a base directory. The specification requires a top-level vendor namespace, sub-namespaces that match subdirectory names, a file name that matches the class name plus .php, and case-sensitive matching. It also says autoloaders must not throw exceptions or raise errors.
You rarely write the autoloader yourself; Composer generates it from composer.json:
{
"name": "acme/billing",
"type": "project",
"require": {
"php": ">=8.4",
"psr/clock": "^1.0",
"psr/log": "^3.0"
},
"autoload": {
"psr-4": {
"Acme\\Billing\\": "src/Billing/"
}
},
"autoload-dev": {
"psr-4": {
"Acme\\Billing\\Tests\\": "tests/"
}
}
}Composer's schema documentation requires namespace prefixes to end in \\ (an escaped backslash in JSON) to avoid conflicts between similar prefixes. With the mapping above, Acme\Billing\InvoiceReminder lives in src/Billing/InvoiceReminder.php. In production, run composer dump-autoload -o (or install with --optimize-autoloader) to turn PSR-4 rules into a class map; in our demo it reported Generated optimized autoload files containing 12 classes.
The most common PSR-4 bug is case. A file named invoiceReminder.php works on macOS, whose default file system is case-insensitive, and then fails on a Linux server. The specification requires the case to match exactly.

Coding style: PSR-12 is replaced by PER Coding Style
PSR-12 is still listed as accepted, but PSRs are frozen once accepted, so it cannot describe syntax added after 2019. PHP-FIG now publishes the PHP Evolving Recommendation (PER) Coding Style, currently version 3.1, which states that it "extends, expands and replaces" PSR-12 and requires PSR-1.
The core rules are familiar: four spaces of indentation and no tabs, a soft line limit of 120 characters with no hard limit, and lowercase keywords and types. Newer versions add rules for modern syntax, including:
- Enums: PascalCase case names and no space before the colon in
enum Suit: string. - Property hooks: brace placement for long hooks and single spaces around
=>for short ones. - Attributes: each attribute block on its own line above the declaration.
- Short closures: no space after
fn, sofn($x) => $x * 2. - The pipe operator:
|>is formatted as a binary operator. - Compound types:
nulllast in unions, and?Tpreferred for a single nullable type.
Do not format by hand. PHP-CS-Fixer ships an @PER-CS rule set that follows the newest PER version it supports, plus pinned sets such as @PER-CS3x0 if you want upgrades to be explicit:
<?php
// .php-cs-fixer.dist.php
$finder = (new PhpCsFixer\Finder())->in([__DIR__ . '/src', __DIR__ . '/tests']);
return (new PhpCsFixer\Config())
->setRules(['@PER-CS' => true])
->setFinder($finder);Interface PSRs: depend on contracts, not vendors
The interface PSRs pay off when a class needs a service it should not care about. This class depends on PSR-20 for time and PSR-3 for logging, and on nothing vendor-specific:
<?php
declare(strict_types=1);
namespace Acme\Billing;
use Psr\Clock\ClockInterface;
use Psr\Log\LoggerInterface;
final readonly class InvoiceReminder
{
public function __construct(
private ClockInterface $clock,
private LoggerInterface $logger,
) {
}
public function isOverdue(\DateTimeImmutable $dueDate): bool
{
$overdue = $this->clock->now() > $dueDate;
if ($overdue) {
$this->logger->info('Invoice overdue since {due}', [
'due' => $dueDate->format('Y-m-d'),
]);
}
return $overdue;
}
}In tests, a frozen clock replaces the system clock with a few lines:
<?php
declare(strict_types=1);
namespace Acme\Billing\Tests;
use Psr\Clock\ClockInterface;
final readonly class FrozenClock implements ClockInterface
{
public function __construct(private \DateTimeImmutable $now)
{
}
public function now(): \DateTimeImmutable
{
return $this->now;
}
}With the clock frozen at 9 October 2026, our run returned true for an invoice due on 1 October and logged INFO: Invoice overdue since 2026-10-01, and returned false for one due on 31 October. No sleeping, no mocking framework and no global state were needed. The installed packages were psr/clock 1.0.0 and psr/log 3.0.2.

The HTTP family: PSR-7, 15, 17 and 18
These four work together. PSR-7 defines immutable request and response messages, PSR-17 defines factories to create them, PSR-15 defines middleware and request handlers, and PSR-18 defines a client that sends a PSR-7 request and returns a PSR-7 response. A middleware written against them runs in any PSR-15 compatible framework:
<?php
declare(strict_types=1);
namespace Acme\Http;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
final readonly class MaintenanceMode implements MiddlewareInterface
{
public function __construct(
private ResponseFactoryInterface $responses,
private bool $enabled,
) {
}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
): ResponseInterface {
if (!$this->enabled) {
return $handler->handle($request);
}
return $this->responses->createResponse(503)
->withHeader('Retry-After', '600');
}
}Notice that the middleware asks for a PSR-17 factory instead of creating a response class from a specific library. That single decision keeps it portable.
Containers, events and caches
- PSR-11 standardises reading from a container with
get()andhas(). Type-hint it in framework glue code, not in domain classes, which should receive their dependencies through constructors. - PSR-14 lets libraries dispatch events without choosing an event system for you.
- PSR-16 is the simpler cache interface and is enough for most applications; PSR-6 adds cache items and deferred saves for advanced cases.
Gotchas
- Adopting a draft. PSR-5 and PSR-19 (PHPDoc) are still drafts after many years. Static analysers define their own PHPDoc conventions in practice.
- Citing PSR-2. It is deprecated. Linters and job descriptions that still name it should move to PER Coding Style.
- Implementing interfaces you do not need. PSRs are for boundaries between packages. Inside your own application, a concrete class is often clearer.
- Forgetting the provide key. Libraries that implement an interface PSR usually declare it in Composer's
providesection (for examplepsr/log-implementation), which lets you require an implementation without naming a vendor.
What to do next
Check that every class in your project follows your PSR-4 mapping by running composer dump-autoload -o and reading the warnings. Add PHP-CS-Fixer with @PER-CS to CI. Then look for direct dependencies on a specific logger, clock or HTTP client in your domain code and replace them with the matching PSR interface. Those three changes give you most of the value the PSRs offer.
Frequently asked questions
Is PSR-12 still the PHP coding standard?
PSR-12 is still listed as accepted, but PHP-FIG's PER Coding Style (version 3.1) extends, expands and replaces it, adding rules for enums, attributes, property hooks and other newer syntax.
Which PSRs are deprecated?
PSR-0 (autoloading) and PSR-2 (coding style) are deprecated. PSR-8, PSR-9 and PSR-10 are abandoned.
What is PSR-4 used for?
It defines how fully qualified class names map to file paths, so an autoloader can find classes. Composer generates a PSR-4 autoloader from the
autoloadsection ofcomposer.json.What is PSR-20?
The Clock interface, with a single
now()method returningDateTimeImmutable. Depending on it instead of callingnew DateTimeImmutable()makes time-based code easy to test.Do I need to implement PSRs in my application?
Use them at boundaries, such as logging, HTTP clients, caches and middleware, where you want to swap implementations. Inside your own domain code, concrete classes are often simpler.
Which PSRs are still drafts?
According to php-fig.org, PSR-5 (PHPDoc Standard), PSR-19 (PHPDoc tags), PSR-21 (Internationalization) and PSR-22 (Application Tracing) are drafts.
Your next customer could start right here.
Introduce your tool, product or business to readers exploring technology.
Explore placements and request a private quote
Sources
- PHP Standards Recommendations — PHP-FIG
- PSR-4: Autoloader — PHP-FIG
- PER Coding Style 3.1 — PHP-FIG
- The composer.json schema — Composer
- Autoloader optimization — Composer
- PHP-CS-Fixer: List of available rule sets — PHP-CS-Fixer
Comments
No comments yet. Be the first to join the conversation.
Leave a comment
Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.
More from Cloudsoft Zone

Industry updatePHP
PHP 8.5.11 security release: what production teams should check
PHP 8.5.11, 8.4.26, 8.3.35 and 8.2.34 fix eleven advisories, including a SoapServer crash. CVEs, triage, version checks and upgrade steps for packages and Docker.
By Cloudsoft Zone

TutorialPHP
PHPStan tutorial: catch PHP bugs before production with levels
Install PHPStan 2.3, choose a rule level, adopt a baseline for legacy code and fail CI only on new errors, with real output from a worked PHP 8.5 example.
By Cloudsoft Zone

Resource guidePHP
PHP 8.2 reaches end of life on 31 December 2026: an upgrade checklist
PHP 8.2 stops receiving security fixes on 31 December 2026. Here is the current support table from php.net, how to choose between PHP 8.4 and 8.5, and a step-by-step checklist for a safe upgrade.
By Cloudsoft Zone
