Kritim Yantra
Dec 24, 2025
Modern PHP explained with realistic, production-style examples
PHP 8 didn’t just add features — it changed how PHP applications are designed. From cleaner syntax to real immutability, from better typing to dramatically improved debugging, PHP 8.x lets you write code that is easier to read, harder to break, and nicer to maintain.
This article goes version by version, explains what problems each feature solves, and shows realistic examples you’d see in real applications (especially APIs, services, and Laravel-style code).
PHP 8.0 is the foundation. Almost everything after it builds on these ideas.
Many PHP functions and framework methods have lots of optional parameters. Positional arguments quickly become unreadable.
Mail::send($view, $data, $callback, true);
What does true mean?
Mail::send(
view: 'emails.welcome',
data: $payload,
callback: $callback,
html: true
);
You’ll see this a lot in framework internals, HTTP clients, and helpers.
APIs often accept multiple input types, but PHP forced you to lie:
function findUser($id) {
// could be int or string
}
function findUser(int|string $id): User {
return User::where('id', $id)
->orWhere('uuid', $id)
->firstOrFail();
}
/**
* @Route("/users")
* @Method("GET")
*/
class UserController {}
These were comments — PHP didn’t understand them.
#[Route('/users', methods: ['GET'])]
class UserController {}
Frameworks now use attributes for:
They’re faster, safer, and refactorable.
class CreateUserDTO {
public string $email;
public string $name;
public function __construct(string $email, string $name) {
$this->email = $email;
$this->name = $name;
}
}
class CreateUserDTO {
public function __construct(
public string $email,
public string $name
) {}
}
This feature single-handedly improved PHP OOP quality.
$statusLabel = match ($order->status) {
OrderStatus::Pending => 'Waiting for payment',
OrderStatus::Paid => 'Paid',
OrderStatus::Cancelled => 'Cancelled',
};
This is perfect for business rules.
$name = $user?->profile?->address?->city;
if blocksYou’ll use this daily in real apps.
PHP 8.1 pushed PHP into proper domain-driven design territory.
class OrderStatus {
const PENDING = 'pending';
const PAID = 'paid';
}
Anyone could pass "banana" by mistake.
enum OrderStatus: string {
case Pending = 'pending';
case Paid = 'paid';
case Cancelled = 'cancelled';
}
function markAsPaid(Order $order): void {
if ($order->status !== OrderStatus::Pending) {
throw new DomainException('Invalid state');
}
$order->status = OrderStatus::Paid;
}
class Money {
public function __construct(
public readonly int $amount,
public readonly string $currency
) {}
}
Once created, it cannot be changed.
You usually don’t write fibers directly, but frameworks do.
Fibers allow non-blocking PHP without callbacks or promises everywhere.
readonly class UserResponse {
public function __construct(
public int $id,
public string $email,
public string $name
) {}
}
function isFeatureEnabled(): true {
return true;
}
Mostly for:
class Pagination {
public const int DEFAULT_LIMIT = 25;
}
#[Override] Attributeclass ApiUserRepository extends UserRepository {
#[Override]
public function find(int $id): User {}
}
If the parent method signature changes, PHP warns you.
$activeUser = array_find(
$users,
fn ($user) => $user->isActive()
);
Instead of manual loops:
foreach ($users as $user) {
if ($user->isActive()) {
$activeUser = $user;
break;
}
}
|>) — Transformations made readable$slug = $title
|> trim(...)
|> strtolower(...)
|> str_replace(' ', '-', ...)
|> preg_replace('/[^a-z0-9\-]/', '', ...);
$slug = preg_replace(
'/[^a-z0-9\-]/',
'',
str_replace(
' ',
'-',
strtolower(trim($title))
)
);
Before PHP 8.5:
Now:
This alone saves hours in production incidents.
php --ini=diff
Shows only overridden settings.
Modern PHP:
match, named args, nullsafe operatorNo comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google
Kritim Yantra
Kritim Yantra
Kritim Yantra