Skip to main content

Cloudsoft Zone Build, deploy and scale with practical guides to PHP, Node.js, React, DevOps and AI automation. Explore the guides

Cloudsoft Zone
PHP

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.

Cloudsoft Zone

7 min read

Assorted Lego bricks in many colours and sizes piled together
Photograph: Benjamin D. Esham ( bdesham ) / CC BY-SA 4.0, via Wikimedia Commons
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.

PSRNameStatusUse it for
1Basic Coding StandardAcceptedBaseline rules: <?php tags, UTF-8, StudlyCaps classes, camelCase methods
3Logger InterfaceAcceptedLoggerInterface with eight log levels
4Autoloading StandardAcceptedMapping namespaces to directories
6 / 16Caching Interface / Simple CacheAcceptedPool-based cache / simple get-set cache
7 / 17HTTP Message Interface / HTTP FactoriesAcceptedImmutable request and response objects and their factories
11Container InterfaceAcceptedget() and has() on dependency injection containers
12Extended Coding Style GuideAcceptedSuperseded in practice by PER Coding Style
13Hypermedia LinksAcceptedLink objects for HAL-style APIs
14Event DispatcherAcceptedFramework-agnostic events and listeners
15HTTP HandlersAcceptedRequest handlers and middleware
18HTTP ClientAcceptedsendRequest() for swappable HTTP clients
20ClockAcceptedClockInterface::now() for testable time
0 / 2Autoloading / Coding Style GuideDeprecatedReplaced by PSR-4 and PSR-12
8, 9, 10Huggable, Security Advisories, Security ReportingAbandonedDo not use
5, 19, 21, 22PHPDoc, PHPDoc tags, Internationalization, Application TracingDraftWatch, 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:

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.

Transparent ruler with centimetre markings lying on a light wooden table
PER Coding Style sets a 120-character soft line limit and four-space indentation.

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, so fn($x) => $x * 2.
  • The pipe operator: |> is formatted as a binary operator.
  • Compound types: null last in unions, and ?T preferred 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
// .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
<?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
<?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.

PHP Barcelona Conference banner on a brick building above a round Citilab Cornellà sign
PSRs come from PHP-FIG, where framework and library maintainers agree on shared interfaces.

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
<?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() and has(). 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 provide section (for example psr/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 autoload section of composer.json.

  • What is PSR-20?

    The Clock interface, with a single now() method returning DateTimeImmutable. Depending on it instead of calling new 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.

Advertise on CloudSoftZone

Your next customer could start right here.

Introduce your tool, product or business to readers exploring technology.

Advertise with us

Explore placements and request a private quote

Sources

  1. PHP Standards Recommendations — PHP-FIG
  2. PSR-4: Autoloader — PHP-FIG
  3. PER Coding Style 3.1 — PHP-FIG
  4. The composer.json schema — Composer
  5. Autoloader optimization — Composer
  6. 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.

Never published. Used only to contact you about this comment.

Loading the security check…

Comments are moderated before publication.