When I work in PHP, I do not treat code style as a small detail. Code style is part of the product. If code is easy to read, easy to load, easy to test, and easy to share across projects, development becomes much smoother. That is the main idea behind PSR. PHP-FIG describes PSR as shared recommendations for PHP projects, and its standards cover coding style, autoloading, logging, HTTP, containers, cache, events, and more.
PSR stands for PHP Standards Recommendation. In simple words, it is a common agreement that helps PHP developers write code in a similar way, so different projects and packages can work better together. That is why PSR matters in real development, especially when a project grows and more developers join the codebase.
Why PSR matters
A lot of PHP problems are not caused by bad logic. They are caused by messy structure. One developer writes one style, another developer writes a different style, and after some time the project becomes hard to read. PSR reduces that friction by giving a shared format and shared conventions. PHP-FIG’s own PSR pages say the goal is to reduce cognitive friction when reading code from different authors.
For me, PSR is not about looking fancy. It is about writing code that another developer can open and understand quickly, without wasting time on file loading, class naming, or random formatting differences.
Main PSR standards every PHP developer should know
PSR-1: Basic Coding Standard
PSR-1 defines the basic rules for shared PHP code. It requires PHP files to use only <?php and <?= tags, UTF-8 without BOM, and class names in StudlyCaps/PascalCase. It also says files should either declare symbols or cause side effects, but should not do both in the same file. It also points namespace and class loading to autoloading PSRs such as PSR-4.
PSR-4: Autoloading Standard
PSR-4 describes how to autoload classes from file paths. In simple words, it tells PHP how a class name maps to a file location, which makes project structure cleaner and removes the need for manual require in every place.
PSR-12: Extended Coding Style
PSR-12 is the modern coding style guide. It extends, expands, and replaces PSR-2, and it requires PSR-1 underneath it. PHP-FIG says its goal is to reduce cognitive friction while reading code from different authors. PSR-2 is marked deprecated, and PSR-12 is the recommended replacement.
PSR-3: Logger Interface
PSR-3 defines a common logger interface. The main goal is that libraries can receive a Psr\Log\LoggerInterface and write logs in a simple universal way. This is very useful when you want your code to work with different logging systems without rewriting business logic.
Other important PSRs
PHP-FIG also lists PSR-7 for HTTP messages, PSR-11 for containers, PSR-15 for HTTP handlers, PSR-17 for HTTP factories, PSR-18 for HTTP clients, PSR-16 for simple cache, PSR-14 for event dispatcher, and PSR-20 for clock. These standards help different parts of PHP applications follow common contracts.
One important note for today’s PHP world: PHP-FIG also publishes PER Coding Style 3.0, which extends and replaces PSR-12 as the newer coding style document. So, when you talk about modern code style, it is good to know PSR-12, but also understand that PER-CS is the next evolution.
A simple way to understand PSR
I explain PSR like this:
- PSR-1 tells me the basic shape of the file.
- PSR-4 tells me where the class file should live.
- PSR-12 tells me how to format the code.
- PSR-3 tells me how to log in a standard way.
- Other PSRs tell me how to handle HTTP, cache, containers, events, and more.
That is the beauty of PSR. It gives structure without forcing one framework on everybody.
Real PHP example: PSR-style mini user registration
This example is kept in one file so you can run it directly and understand the flow. In a real project, these classes would be split into separate files and loaded using PSR-4 autoloading.
<?php
declare(strict_types=1);
/*
|--------------------------------------------------------------------------
| PSR-style mini example
|--------------------------------------------------------------------------
| In a real project:
| - Each class should live in its own file
| - Namespaces should match folder structure
| - Composer usually handles PSR-4 autoloading
| - Code should follow PSR-1 and PSR-12 style rules
*/
/*
|--------------------------------------------------------------------------
| Contract / Interface
|--------------------------------------------------------------------------
| This is a small logger contract.
| In real projects, PSR-3 gives a common logger interface for logging.
*/
namespace App\Contracts;
interface LoggerInterface
{
public function info(string $message): void;
}
/*
|--------------------------------------------------------------------------
| Logger implementation
|--------------------------------------------------------------------------
| This class is responsible only for logging.
| That is clean separation of responsibility.
*/
namespace App\Services;
use App\Contracts\LoggerInterface;
class ConsoleLogger implements LoggerInterface
{
public function info(string $message): void
{
echo "[INFO] " . $message . PHP_EOL;
}
}
/*
|--------------------------------------------------------------------------
| Repository
|--------------------------------------------------------------------------
| This class pretends to save user data.
| In a real app, this could be a database repository.
*/
namespace App\Repositories;
use App\Contracts\LoggerInterface;
class UserRepository
{
public function __construct(
private LoggerInterface $logger
) {
}
public function save(string $name, string $email): array
{
// Simulate saving user data
$user = [
'id' => rand(1000, 9999),
'name' => $name,
'email' => $email,
];
$this->logger->info("User saved: {$name} ({$email})");
return $user;
}
}
/*
|--------------------------------------------------------------------------
| Service / Business logic
|--------------------------------------------------------------------------
| This class handles registration logic only.
| It does not care how logging or storage is implemented.
*/
namespace App\Services;
use App\Repositories\UserRepository;
use App\Contracts\LoggerInterface;
class RegistrationService
{
public function __construct(
private UserRepository $userRepository,
private LoggerInterface $logger
) {
}
public function register(array $data): array
{
$name = trim($data['name'] ?? '');
$email = trim($data['email'] ?? '');
if ($name === '' || $email === '') {
throw new \InvalidArgumentException('Name and email are required.');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('Invalid email address.');
}
$user = $this->userRepository->save($name, $email);
$this->logger->info("Registration complete for {$email}");
return $user;
}
}
/*
|--------------------------------------------------------------------------
| Application entry point
|--------------------------------------------------------------------------
| This section is like the controller or the main runner.
| PSR-1 prefers files to be simple and not mix too many unrelated things.
*/
namespace {
use App\Services\ConsoleLogger;
use App\Repositories\UserRepository;
use App\Services\RegistrationService;
$logger = new ConsoleLogger();
$repository = new UserRepository($logger);
$service = new RegistrationService($repository, $logger);
try {
$user = $service->register([
'name' => 'Milind',
'email' => 'milind@example.com',
]);
echo "User registered successfully" . PHP_EOL;
echo "ID: {$user['id']}" . PHP_EOL;
echo "Name: {$user['name']}" . PHP_EOL;
echo "Email: {$user['email']}" . PHP_EOL;
} catch (Throwable $e) {
echo "Error: " . $e->getMessage() . PHP_EOL;
}
}What this example teaches
This small example shows the PSR mindset clearly.
The logger is separated from the registration logic, which makes the code easier to change later. The repository handles storage, the service handles business rules, and the entry point only starts the process. That kind of separation is exactly what clean PHP design should look like.
In a real codebase, the same structure becomes even more powerful when PSR-4 autoloading loads each class from its own file automatically. That keeps the project organized and avoids repeated manual file includes.
Good habits I follow with PSR
When I write PHP in a professional project, I usually keep these habits in mind:
I keep file names and class names consistent with namespaces, because PSR-4 expects class names to map cleanly to file paths. I keep formatting predictable because PSR-12 is designed to make code easier to scan across different authors. I keep logging behind a contract because PSR-3 lets me swap logging systems without changing business code.
I also avoid mixing too many responsibilities in one file. PSR-1 already encourages files to either declare symbols or cause side effects, not both, and that rule helps keep code cleaner from the beginning.