code quality and tooling
PER Coding Style Orientation
PER Coding Style is PHP-FIG's evolving coding style recommendation. PSR-12 is fixed, but PHP keeps gaining language features. PER exists so the shared style guidance can move forward with modern PHP.
The beginner-level point is simple: PSR-12 is still useful background, but modern teams may say they follow PER Coding Style, PER-CS, or a tool preset based on PER. You should know what that means when you see it in a repository.
Why PER exists
PSR-12 was written before several common modern PHP features became part of daily work. Newer projects may use constructor property promotion, attributes, enums, match, union types, readonly properties, and short closures.
PER Coding Style keeps the formatting conversation current. It reduces the need for every team to invent its own rules for newer syntax.
The same idea as PSR-12
PER does not change the goal. It still exists to make PHP code consistent and reviewable.
<?php
declare(strict_types=1);
class ReceiptFormatter
{
public function formatLine(string $name, int $price): string
{
return $name . ': GBP ' . number_format($price / 100, 2);
}
}
The familiar PSR-12 habits still matter: predictable file layout, explicit visibility, lowercase keywords, four-space indentation, and readable line breaks.
Modern syntax needs style rules
Constructor property promotion lets a class define and assign properties directly in the constructor. A formatter needs to know how to lay that out clearly.
<?php
declare(strict_types=1);
class MoneyFormatter
{
public function __construct(
private string $currency,
) {
}
public function format(int $pennies): string
{
return $this->currency . ' ' . number_format($pennies / 100, 2);
}
}
The syntax is compact, but the formatting still leaves each promoted property easy to see.
Enums are part of modern PHP style
Enums give a fixed set of named values. PER-style formatting keeps enum cases readable.
<?php
declare(strict_types=1);
enum OrderStatus: string
{
case Draft = 'draft';
case Paid = 'paid';
case Shipped = 'shipped';
}
echo OrderStatus::Paid->value;
// Prints:
// paid
Enums are common in modern PHP applications for statuses, modes, roles, and other limited sets of values.
Match expressions should stay readable
match is an expression, not a statement. It returns a value. Formatting should make every arm easy to scan.
<?php
declare(strict_types=1);
function shippingLabel(string $status): string
{
return match ($status) {
'draft' => 'Not ready',
'paid' => 'Ready to pack',
'shipped' => 'Already shipped',
default => 'Unknown status',
};
}
echo shippingLabel('paid');
// Prints:
// Ready to pack
The trailing comma after the last arm is useful because adding a new arm later creates a smaller diff.
Attributes have their own shape
Attributes add metadata to classes, methods, functions, properties, parameters, or constants.
<?php
declare(strict_types=1);
#[Attribute]
class Route
{
public function __construct(
public string $path,
) {
}
}
You do not need to use attributes yet, but you should recognise them. Frameworks and libraries often use them for routing, validation, serialization, or dependency injection metadata.
What to do in a real project
Do not guess the standard from memory. Check the repository tooling:
composer.jsonscripts- PHP_CodeSniffer rulesets
- PHP-CS-Fixer config
- Laravel Pint config
- CI jobs that run format or style checks
The project's configured tool is the source of truth. PER gives the shared language, but the local repository decides the exact preset and extra rules.
What to remember
PER Coding Style is the evolving successor path for PHP coding style guidance. It is especially relevant for modern PHP syntax that PSR-12 did not fully cover.
Before moving on, make sure you can identify modern syntax such as enums, match, attributes, and constructor property promotion, and explain why style tooling needs rules for them.
Practice
Task: Format Modern PHP Syntax
Reformat this modern PHP example so the enum, constructor property promotion, and match expression are easy to read.
<?php
declare(strict_types=1);
enum OrderStatus:string{case Draft='draft';case Paid='paid';case Shipped='shipped';}
class StatusPresenter{public function __construct(private string $prefix){} public function label(OrderStatus $status): string{return match($status){OrderStatus::Draft=>$this->prefix.' Not ready',OrderStatus::Paid=>$this->prefix.' Ready to pack',OrderStatus::Shipped=>$this->prefix.' Already shipped',};}}
$presenter = new StatusPresenter('Order:');
echo $presenter->label(OrderStatus::Paid);
// Prints:
// Order: Ready to pack
Requirements
- Keep the output the same.
- Format the enum cases on separate lines.
- Format the promoted constructor property clearly.
- Format the
matcharms on separate lines. - Keep all PHP syntax valid.
Show solution
<?php
declare(strict_types=1);
enum OrderStatus: string
{
case Draft = 'draft';
case Paid = 'paid';
case Shipped = 'shipped';
}
class StatusPresenter
{
public function __construct(
private string $prefix,
) {
}
public function label(OrderStatus $status): string
{
return match ($status) {
OrderStatus::Draft => $this->prefix . ' Not ready',
OrderStatus::Paid => $this->prefix . ' Ready to pack',
OrderStatus::Shipped => $this->prefix . ' Already shipped',
};
}
}
$presenter = new StatusPresenter('Order:');
echo $presenter->label(OrderStatus::Paid);
// Prints:
// Order: Ready to pack
The behaviour is unchanged. The modern PHP features are now laid out so each enum case, promoted property, and match arm is easy to scan.