| name | PHP Modern Features |
| user-invocable | false |
| description | Use when modern PHP features including typed properties, union types, match expressions, named arguments, attributes, enums, and patterns for writing type-safe, expressive PHP code with latest language improvements. |
| allowed-tools | [] |
PHP Modern Features
Introduction
Modern PHP (7.4+, 8.0+, 8.1+, 8.2+) has evolved dramatically with features that
improve type safety, expressiveness, and developer experience. These additions
transform PHP from a loosely-typed scripting language into a powerful,
type-safe platform for building robust applications.
Key improvements include strict typing, property and parameter types, union and
intersection types, match expressions, named arguments, attributes (annotations),
enumerations, and readonly properties. These features enable clearer intent,
better IDE support, and fewer runtime errors.
This skill covers typed properties, union/intersection types, match expressions,
enums, attributes, named arguments, and patterns for leveraging modern PHP
effectively.
Typed Properties and Parameters
Type declarations improve code reliability by enforcing types at runtime and
enabling better static analysis.
<?php
class User {
public string $name;
public int $age;
public ?string $email = null;
private array $roles = [];
protected bool $active = true;
}
$user = new User();
$user->name = "Alice";
$user->age = 30;
class Product {
public function __construct(
public string $name,
public float $price,
public int $stock,
private ?string $sku = null
) {}
}
$product = new Product("Laptop", 999.99, 10);
echo $product->name;
function calculateTotal(float $price, int $quantity): float {
return $price * $quantity;
}
function findUser(int $id): ?User {
return $id > 0 ? new User() : null;
}
function getUsers(): array {
return [new User(), new User()];
}
function logMessage(string $message): void {
echo $message . "\n";
}
function fail(string $message): never {
throw new Exception($message);
}
function process(mixed $value): mixed {
return $value;
}
class Builder {
public function setName(string $name): static {
return $this;
}
public function build(): static {
return new static();
}
}
function greet(
string $name,
int $age,
bool $formal = false
): string {
$greeting = $formal ? "Good day" : "Hello";
return "$greeting, $name ($age)";
}
function sum(int ...$numbers): int {
return array_sum($numbers);
}
$total = sum(1, 2, 3, 4, 5);
Typed properties and parameters catch type errors early and provide clear
contracts for function interfaces.
Union and Intersection Types
Union types allow multiple type possibilities, while intersection types require
all specified types simultaneously.
<?php
function processId(int|string $id): void {
if (is_int($id)) {
echo "Processing integer ID: $id\n";
} else {
echo "Processing string ID: $id\n";
}
}
processId(123);
processId("ABC-456");
class Response {
public function __construct(
public int|string $code,
public array|string $data
) {}
}
function findProduct(int $id): Product|null {
return $id > 0 ? new Product("Item", , ) : ;
}
{
() {
() => ,
() => ,
() => ,
};
}
{
= (, FILTER_VALIDATE_INT);
!== ? : ;
}
{
[ => ];
}
{
;
}
{
;
}
{
->();
->();
}
{
{
;
}
{
;
}
}
{
?->();
}
{
->();
}
Union types enable flexible parameter acceptance while intersection types
enforce multiple capabilities simultaneously.
Match Expressions
Match expressions provide pattern matching with strict comparisons and
exhaustive checking, improving upon switch statements.
<?php
$status = 200;
$message = match ($status) {
200 => "OK",
404 => "Not Found",
500 => "Server Error",
default => "Unknown",
};
echo $message;
$result = match ($status) {
200, 201, 202 => "Success",
400, 401, 403 => "Client Error",
500, 502, 503 => "Server Error",
default => "Unknown",
};
$age = 25;
$category = match (true) {
$age < 13 => "Child",
$age < 18 => "Teen",
$age < 65 => "Adult",
default => "Senior",
};
{
() {
=> ,
=> ,
=> ,
};
}
{
Draft;
Published;
Archived;
}
{
() {
:: => ,
:: => ,
:: => ,
};
}
{
(()) {
=> * ,
=> (),
=> (),
=> ,
};
}
= ;
() {
:
;
;
}
= () {
=> ,
=> ,
=> ,
};
;
Match expressions are safer than switch due to strict comparison and exhaustive
checking requirements.
Enumerations
Enums provide type-safe sets of possible values, replacing magic strings and
constants with explicit types.
<?php
enum Status {
case Pending;
case Approved;
case Rejected;
}
function updateOrder(Status $status): void {
echo "Order status: " . $status->name . "\n";
}
updateOrder(Status::Approved);
enum Priority: int {
case Low = 1;
case Medium = 2;
case High = 3;
case Critical = 4;
}
$priority = Priority::High;
echo $priority->value;
echo $priority->name;
enum Role: string {
case Admin = 'admin';
case User = 'user';
case Guest = 'guest';
}
{
OK = ;
Created = ;
BadRequest = ;
Unauthorized = ;
NotFound = ;
ServerError = ;
{
->value >= && ->value < ;
}
{
->value >= ;
}
{
() {
:: => ,
:: => ,
:: => ,
:: => ,
:: => ,
:: => ,
};
}
}
= ::;
->() ? : ;
->();
{
Red = ;
Green = ;
Blue = ;
{
(, , , );
}
{
= ::();
[()];
}
}
(::() ) {
->name . . ->value . ;
}
= ::();
= ::();
{
() {
:: => ,
:: => ,
:: => ,
:: => ,
};
}
Enums replace magic strings and constants with type-safe alternatives that
support IDE autocompletion and refactoring.
Attributes (Annotations)
Attributes attach metadata to declarations, enabling declarative configuration
and reflection-based frameworks.
<?php
#[Attribute]
class Route {
public function __construct(
public string $path,
public array $methods = ['GET']
) {}
}
#[Attribute]
class Validate {
public function __construct(
public array $rules
) {}
}
class UserController {
#[Route('/users', methods: ['GET'])]
public function index(): array {
return ['users' => []];
}
#[Route('/users/{id}', methods: ['GET'])]
#[Validate(rules: ['id' => 'required|integer'])
{
[ => [ => ]];
}
(, : [])
(: [ => , => ])
{
[ => ];
}
}
{
= ();
= [];
(->() ) {
= ->(::);
( ) {
= ->();
[] = [
=> ->path,
=> ->methods,
=> [, ->()],
];
}
}
;
}
= (::);
(::)
{
{}
}
(::)
{
{}
}
{
()
(: )
(: , : )
{
[ => []];
}
}
(::)
{
{}
}
(: )
{
;
;
}
(::)
{
{}
}
{
(: , : )
;
(: )
;
(: , : )
DateTime ;
}
Attributes enable declarative metadata for dependency injection, routing,
validation, ORM mapping, and other framework features.
Named Arguments and Other Modern Features
Named arguments improve readability and make optional parameters more usable.
<?php
function createUser(
string $name,
int $age,
string $email = '',
bool $active = true
): User {
$user = new User();
$user->name = $name;
$user->age = $age;
$user->email = $email;
$user->active = $active;
return $user;
}
$user1 = createUser("Alice", 30, "alice@example.com", true);
$user2 = createUser(name: "Bob", age: 25);
$user3 = createUser(age: 28, name: "Charlie", active: false);
$user4 = (, , : );
{
{}
}
= (, );
{
{}
}
= [, , , , ];
= (fn() => * , );
{
* ;
}
= ((...), );
{
{}
}
= ?->()?->()?->();
= ?? ();
= ;
((, )) {
;
}
((, )) {
;
}
((, )) {
;
}
Named arguments make function calls self-documenting and enable skipping
optional parameters in the middle of parameter lists.
Best Practices
-
Use strict types declaration at file start to enable strict type checking
and catch type errors early
-
Leverage typed properties for all class properties to document expected
types and enable validation
-
Prefer match over switch for value-based dispatch to benefit from strict
comparison and exhaustiveness
-
Replace magic constants with enums to create type-safe, self-documenting
sets of allowed values
-
Use named arguments for optional parameters to improve readability and
skip unnecessary defaults
-
Apply readonly for immutable data to prevent accidental mutations and
express immutability intent
-
Use union types for flexible parameters when functions accept multiple
types instead of accepting mixed
-
Leverage attributes for metadata instead of docblocks for
reflection-based frameworks and tools
-
Use constructor property promotion to reduce boilerplate in classes with
many simple properties
-
Apply nullsafe operator for safe property access on potentially null
objects without explicit null checks
Common Pitfalls
-
Forgetting declare(strict_types=1) allows type coercion that defeats
purpose of type declarations
-
Overusing union types with too many alternatives reduces type safety
benefits and complicates logic
-
Not handling all enum cases in match without default causes runtime
errors when new cases added
-
Using enums for non-exhaustive sets like user IDs where values aren't
known at compile time
-
Mixing positional and named arguments incorrectly can cause errors;
positional must come before named
-
Not marking readonly when appropriate allows mutations that break
immutability assumptions
-
Using attributes without understanding reflection leads to unused
metadata that has no effect
-
Overusing mixed type instead of specific unions or generics reduces
type safety benefits
-
Not checking enum::tryFrom() return value causes crashes when invalid
values provided
-
Using void when never is appropriate for functions that always throw
affects control flow analysis
When to Use This Skill
Use modern PHP features when building applications on PHP 7.4+ or preferably
PHP 8.0+ to leverage improved type safety and developer experience.
Apply typed properties and parameters when designing classes and functions to
catch type errors early and improve IDE support.
Employ enums when modeling fixed sets of values like statuses, priorities, or
roles instead of using string or integer constants.
Leverage match expressions for cleaner conditional logic based on values,
especially with enums or typed values.
Use attributes for framework-level metadata like routing, validation, ORM
mapping, or dependency injection configuration.
Resources