code quality and tooling
PSR-12
PSR-12 is an extended PHP coding style standard. PSR-1 gives the basic naming and file rules; PSR-12 adds detailed layout rules for modern PHP code.
You will not hand-format every line forever. Real teams usually use PHP_CodeSniffer, PHP-CS-Fixer, or framework tooling. Still, you need to understand the style these tools are enforcing so reviews and tool errors make sense.
File layout
A typical class file follows a predictable order:
<?php
declare(strict_types=1);
namespace App\Reporting;
use DateTimeImmutable;
class PriceReport
{
}
The important shape is:
- opening
<?phptag - file-level declare statement
- namespace
- imports
- class, interface, trait, enum, or other code
Blank lines separate those sections so the file is easy to scan.
Indentation and braces
PSR-12 uses four spaces for indentation. Classes and methods put the opening brace on the next line.
<?php
declare(strict_types=1);
class ReceiptFormatter
{
public function formatLine(string $name, int $price): string
{
return $name . ': GBP ' . number_format($price / 100, 2);
}
}
This layout makes the class body and method body obvious.
Control structures keep the brace on the same line
Control structures such as if, foreach, for, while, and switch use a different brace style from classes and methods.
<?php
declare(strict_types=1);
function activeProductTotal(array $products): int
{
$total = 0;
foreach ($products as $product) {
if ($product['active']) {
$total += $product['price'];
}
}
return $total;
}
Notice the blank line before return. PSR-12 does not require blank lines everywhere, but thoughtful spacing helps separate setup, work, and return values.
Visibility should be explicit
Class properties and methods should declare visibility with public, protected, or private.
<?php
declare(strict_types=1);
class Product
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function name(): string
{
return $this->name;
}
}
Explicit visibility makes the public API of a class easier to see. It also avoids accidental assumptions when someone changes the class later.
Keywords are lowercase
PHP keywords and type keywords should be lowercase.
<?php
declare(strict_types=1);
function hasItems(array $items): bool
{
return count($items) > 0;
}
var_dump(hasItems(['Notebook']));
// Prints:
// bool(true)
Use true, false, null, array, int, string, and bool in lowercase.
Method arguments and line breaks
When a call or declaration becomes too long, split it across multiple lines in a predictable way.
<?php
declare(strict_types=1);
function formatReceiptLine(
string $name,
int $quantity,
int $unitPrice
): string {
return $quantity . ' x ' . $name . ': GBP ' . number_format(($quantity * $unitPrice) / 100, 2);
}
Each argument sits on its own line, and the closing parenthesis lines up with the start of the function declaration.
Style is not behaviour
Formatting should not change what the code does. This is why automated formatters are useful: they make mechanical style changes consistently, leaving developers to review behaviour.
When reviewing a diff, separate the questions:
- Does the code follow the project style?
- Does the code behave correctly?
- Did the diff mix formatting changes with logic changes?
If a large formatting change is needed, it is often better as a separate commit or pull request.
What to remember
PSR-12 gives PHP projects a consistent visual structure. The main beginner-level points are four-space indentation, predictable file sections, explicit visibility, lowercase keywords, clear brace placement, and readable line breaks.
Before moving on, make sure you can reformat a messy class into PSR-12-style PHP without changing its output.
Practice
Task: Reformat A Class With PSR-12
Reformat this class so it follows the PSR-12 style covered in the lesson. Do not change the output.
<?php
declare(strict_types=1);
class ReceiptFormatter{
private string $currency;
public function __construct(string $currency){$this->currency=$currency;}
public function formatLine(string $name,int $quantity,int $unitPrice): string{if($quantity<1){return 'Invalid quantity';}return $quantity.' x '.$name.': '.$this->currency.' '.number_format(($quantity*$unitPrice)/100,2);}
}
$formatter = new ReceiptFormatter('GBP');
echo $formatter->formatLine('Notebook', 2, 499);
// Prints:
// 2 x Notebook: GBP 9.98
Requirements
- Keep
declare(strict_types=1);. - Use four-space indentation.
- Put class and method opening braces on their own lines.
- Put control-structure braces on the same line.
- Keep property and method visibility explicit.
- Keep the output the same.
Show solution
<?php
declare(strict_types=1);
class ReceiptFormatter
{
private string $currency;
public function __construct(string $currency)
{
$this->currency = $currency;
}
public function formatLine(string $name, int $quantity, int $unitPrice): string
{
if ($quantity < 1) {
return 'Invalid quantity';
}
return $quantity . ' x ' . $name . ': ' . $this->currency . ' ' . number_format(($quantity * $unitPrice) / 100, 2);
}
}
$formatter = new ReceiptFormatter('GBP');
echo $formatter->formatLine('Notebook', 2, 499);
// Prints:
// 2 x Notebook: GBP 9.98
The refactor changes layout, spacing, and readability only. The class still produces the same output for the same input.