Skip to content

PHP PSR Standards — Complete Guide to PHP-FIG Coding Standards

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about PHP PSR Standards. We cover key concepts, practical examples, and best practices to help you master this topic.

PHP PSR standards are a set of PHP-FIG (PHP Framework Interop Group) recommendations that define coding styles, autoloading strategies, and common interfaces so PHP libraries and frameworks work together seamlessly. This tutorial covers the most important PSRs: PSR-1 for basic coding style, PSR-4 for autoloading, PSR-12 for extended coding style, PSR-7 for HTTP messages, PSR-11 for container interfaces, and PSR-14 for event dispatching.

What You'll Learn

  • Understanding what PHP-FIG is and why PSRs exist
  • Following PSR-1 and PSR-12 coding style rules
  • Configuring PSR-4 autoloading with Composer
  • Implementing PSR-7 HTTP message interfaces
  • Using PSR-11 ContainerInterface for dependency injection
  • Applying PSR-14 EventDispatcherInterface
  • Validating your code with PHP_CodeSniffer

Why It Matters

Without standards, every PHP project and framework invents its own conventions. A class in Laravel is namespaced differently than the same concept in Symfony. PSRs solve this at the ecosystem level: any PSR-4 compliant autoloader can load any PSR-4 compliant class. Any PSR-7 message can be passed between middleware from different frameworks. Following PSRs makes your code predictable, familiar, and reusable across the entire PHP ecosystem.

Real-World Use

Durga Antivirus Pro uses PSR-7 messages for its REST API layer. Because the request and response objects implement the standard interfaces, the team can swap routing libraries without rewriting controllers. The same PSR-7 request that enters through Nginx middleware can be passed to a Symfony controller or a slim PHP script without modification.

Learning Path

flowchart LR
  A[Composer] --> B[PSR Standards\nYou are here]
  B --> C[Laravel Basics]
  style B fill:#f90,color:#fff

PSR-1: Basic Coding Standard

PSR-1 sets the foundation for all other PSRs. Key rules:

  • Files must use <?php tags (or <?= for templates).
  • Files must use UTF-8 without BOM encoding.
  • Namespaces and classes must follow PSR-4 autoloading.
  • Class names must be declared in StudlyCaps.
  • Method names must be declared in camelCase.
  • Class constants must be all uppercase with underscore separators.

Example of compliant code:

<?php
namespace App\Http;

class UserController
{
  public const MAX_LOGIN_ATTEMPTS = 5;

  public function login(string $email, string $password): bool
  {
    // Implementation
    return false;
  }
}

PSR-4: Autoloading

PSR-4 maps namespaces to directory structures. The namespace prefix corresponds to a base directory, and sub-namespaces map to subdirectories.

Consider this directory structure:

src/
  App/
    Models/
      User.php     → App\Models\User
      Order.php    → App\Models\Order
    Services/
      Mailer.php   → App\Services\Mailer

Configure PSR-4 in composer.json:

{
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  }
}

A class at src/Models/User.php:

<?php
namespace App\Models;

class User
{
  public function __construct(
    private string $name,
    private string $email
  ) {}

  public function display(): string
  {
    return "{$this->name} <{$this->email}>";
  }
}

Use it without manual requires:

<?php
require 'vendor/autoload.php';

$user = new \App\Models\User('Bob', 'bob@example.com');
echo $user->display();

Output:

Bob <bob@example.com>

PSR-12: Extended Coding Style

PSR-12 extends PSR-1 with detailed formatting rules. Key requirements:

  • Use 4 spaces for indentation, no tabs.
  • Line length should be under 120 characters (80 preferred).
  • One blank line after the namespace declaration.
  • Opening braces for classes go on the next line.
  • Opening braces for methods go on the next line.
  • Control structure keywords have one space after them.
  • Visibility (public, protected, private) must be declared on all properties and methods.
<?php
namespace App\Services;

use App\Models\User;

class AuthService
{
    public function __construct(
        private User $user
    ) {}

    public function verify(string $password): bool
    {
        return password_verify($password, $this->user->getPasswordHash());
    }
}

Validate with PHP_CodeSniffer:

composer require --dev squizlabs/php_codesniffer
vendor/bin/phpcs --standard=PSR12 src/

PSR-7: HTTP Message Interfaces

PSR-7 defines interfaces for HTTP requests and responses. The key interfaces are:

  • Psr\Http\Message\RequestInterface
  • Psr\Http\Message\ResponseInterface
  • Psr\Http\Message\ServerRequestInterface
  • Psr\Http\Message\StreamInterface
  • Psr\Http\Message\UriInterface

Using a PSR-7 implementation like guzzlehttp/psr7:

composer require guzzlehttp/psr7
<?php
require 'vendor/autoload.php';

use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\ServerRequest;

$request = new ServerRequest('POST', '/api/login');
$request = $request->withHeader('Content-Type', 'application/json');

$body = json_encode(['email' => 'user@example.com', 'password' => 'secret']);
$request->getBody()->write($body);

$response = new Response(200, ['Content-Type' => 'application/json']);
$response->getBody()->write(json_encode(['token' => 'abc123']));

echo $response->getStatusCode();
echo $response->getHeaderLine('Content-Type');
echo $response->getBody();

Output:

200
application/json
{"token":"abc123"}

PSR-11: Container Interface

PSR-11 defines ContainerInterface with two methods: get and has. This is the standard for dependency injection containers.

<?php
namespace Psr\Container;

interface ContainerInterface
{
  public function get(string $id): mixed;
  public function has(string $id): bool;
}

A minimal PSR-11 container:

<?php
namespace App\Container;

use Psr\Container\ContainerInterface;

class SimpleContainer implements ContainerInterface
{
  private array $services = [];

  public function set(string $id, callable $factory): void
  {
    $this->services[$id] = $factory;
  }

  public function get(string $id): mixed
  {
    if (!$this->has($id)) {
      throw new \Psr\Container\NotFoundExceptionInterface("Service $id not found");
    }
    return ($this->services[$id])($this);
  }

  public function has(string $id): bool
  {
    return isset($this->services[$id]);
  }
}

Usage:

<?php
require 'vendor/autoload.php';

use App\Container\SimpleContainer;

class Logger
{
  public function log(string $message): void
  {
    echo "LOG: $message\n";
  }
}

$container = new SimpleContainer();
$container->set(Logger::class, fn() => new Logger());

$logger = $container->get(Logger::class);
$logger->log('Container is working');

Output:

LOG: Container is working

Common Mistakes

  1. Using tabs for indentation: PSR-12 requires 4 spaces. Most editors can be configured to convert tabs to spaces automatically.
  2. Mixing naming conventions: PHP allows both camelCase and snake_case for methods, but PSR-1 requires camelCase. Stick with one convention across your project.
  3. Ignoring visibility on properties: In PHP 8, you can use constructor promotion, but standalone properties must still declare public, protected, or private.
  4. Not running PHP_CodeSniffer in CI: Style violations accumulate quickly. Add phpcs to your CI pipeline to enforce PSR-12 automatically.
  5. Implementing PSR-7 from scratch: Always use an existing implementation like guzzlehttp/psr7 or nyholm/psr7. The interfaces are complex and easy to get wrong.

Practice Questions

  1. What does PSR-4 specify?

    • PSR-4 maps namespace prefixes to directory paths. The namespace App\Models\User maps to src/Models/User.php when App\ maps to src/.
  2. What is the difference between PSR-1 and PSR-12?

    • PSR-1 covers basic conventions (tags, encoding, naming). PSR-12 extends it with detailed formatting rules for braces, indentation, whitespace, and control structures.
  3. Why is the composer.lock file important for PSR-4 autoloading?

    • The lock file ensures every developer uses the same version of the autoloader generator, preventing subtle class-loading differences.
  4. What are the four PSR-7 message interfaces?

    • RequestInterface, ResponseInterface, ServerRequestInterface, and MessageInterface (base interface). StreamInterface and UriInterface are also commonly grouped with PSR-7.
  5. Challenge: Create a middleware pipeline that uses PSR-7 messages. Implement a RequestHandlerInterface that chains three middleware functions: authentication, logging, and routing.

Mini Project

Build a PSR-compliant micro-framework:

  • Use PSR-7 for request/response handling with guzzlehttp/psr7.
  • Implement a PSR-11 container that supports autowiring.
  • Create a router that uses PSR-7 ServerRequestInterface and returns PSR-7 ResponseInterface.
  • Validate all code against PSR-12 using PHP_CodeSniffer.
  • Write a controller and a middleware that uses the framework.

FAQ

What does PSR stand for?

PSR stands for PHP Standards Recommendation. These are specifications published by the PHP-FIG group to promote interoperability between PHP frameworks.

Do I need to follow all PSRs?

You should follow PSR-1 and PSR-12 for coding style in every project. PSR-4 is strongly recommended for autoloading. Other PSRs depend on your use case.

How do I check if my code follows PSR-12?

Install PHP_CodeSniffer and run vendor/bin/phpcs --standard=PSR12 src/. It will report every violation with file and line number.

What is the difference between PSR-0 and PSR-4?

PSR-0 is the deprecated predecessor. Both map namespaces to directories, but PSR-4 removes the requirement to mirror underscores as directory separators.

Can I use PSR-7 without a framework?

Yes. Install any PSR-7 implementation via Composer and use the interfaces directly. They work with plain PHP scripts.

What's Next

Now that you understand PHP standards, apply them in a real framework by learning Laravel Basics.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro