PHP 8+ Features
Introduction to PHP 8+ Features
PHP 8 introduced significant improvements to the language with new features that enhance developer productivity, code readability, and type safety. This guide covers the most important features introduced in PHP 8.0, 8.1, and 8.2.
Major PHP 8.0 Features
Union Types
Union types allow a value to be one of several types, providing more flexibility while maintaining type safety.
<?php
// Basic union types
function processId(int|string $id): int|string
{
if (is_int($id)) {
return $id * 2;
}
return strtoupper($id);
}
$result1 = processId(42); // Returns: 84
$result2 = processId("abc123"); // Returns: "ABC123"
// Class union types
class DatabaseConnection {}
class FileConnection {}
function connect(DatabaseConnection|FileConnection $connection): bool
{
if ($connection instanceof DatabaseConnection) {
// Handle database connection
return true;
} elseif ($connection instanceof FileConnection) {
// Handle file connection
return true;
}
return false;
}
// Union types in properties
class User
{
public int|string $id;
public string|null $email = null;
public function __construct(int|string $id)
{
$this->id = $id;
}
}
// Complex union types
function processData(array|object|null $data): string|null
{
if (is_array($data)) {
return json_encode($data);
}
if (is_object($data)) {
return serialize($data);
}
return null;
}
?>
Named Arguments
Named arguments allow passing arguments to functions based on parameter names rather than position.
<?php
// Traditional positional arguments
function createUser($name, $email, $age = null, $active = true, $role = 'user')
{
return [
'name' => $name,
'email' => $email,
'age' => $age,
'active' => $active,
'role' => $role
];
}
// Old way - hard to read and error-prone
$user1 = createUser('John', '[email protected]', null, true, 'admin');
// New way with named arguments - much clearer
$user2 = createUser(
name: 'John',
email: '[email protected]',
role: 'admin'
);
// Skip optional parameters easily
$user3 = createUser(
name: 'Jane',
email: '[email protected]',
active: false
);
// Mix positional and named arguments
$user4 = createUser('Bob', '[email protected]', age: 30, role: 'moderator');
// Real-world example: HTML helper
function createButton(
string $text,
string $type = 'button',
string $class = '',
bool $disabled = false,
array $attributes = []
): string {
$attrs = '';
foreach ($attributes as $key => $value) {
$attrs .= " {$key}=\"{$value}\"";
}
$disabledAttr = $disabled ? ' disabled' : '';
$classAttr = $class ? " class=\"{$class}\"" : '';
return "<button type=\"{$type}\"{$classAttr}{$disabledAttr}{$attrs}>{$text}</button>";
}
// Clear and readable
$submitButton = createButton(
text: 'Submit Form',
type: 'submit',
class: 'btn btn-primary',
attributes: ['data-loading' => 'true']
);
?>
Attributes (Annotations)
Attributes provide a way to add metadata to classes, methods, properties, and parameters.
<?php
// Define custom attributes
#[Attribute]
class Route
{
public function __construct(
public string $path,
public string $method = 'GET'
) {}
}
#[Attribute]
class Validate
{
public function __construct(
public array $rules
) {}
}
#[Attribute(Attribute::TARGET_PROPERTY)]
class Column
{
public function __construct(
public string $name,
public string $type = 'varchar',
public bool $nullable = false
) {}
}
// Use attributes
#[Route('/api/users', 'GET')]
class UserController
{
#[Route('/api/users/{id}', 'GET')]
public function show(
#[Validate(['required', 'integer'])] int $id
): array {
return ['id' => $id, 'name' => 'John Doe'];
}
#[Route('/api/users', 'POST')]
public function store(
#[Validate(['required', 'string', 'min:3'])] string $name,
#[Validate(['required', 'email'])] string $email
): array {
return ['name' => $name, 'email' => $email];
}
}
// Model with attributes
class User
{
#[Column('id', 'integer')]
public int $id;
#[Column('name', 'varchar', false)]
public string $name;
#[Column('email', 'varchar', false)]
public string $email;
#[Column('created_at', 'timestamp', true)]
public ?string $createdAt = null;
}
// Reading attributes with reflection
function getRoutes(string $className): array
{
$reflection = new ReflectionClass($className);
$routes = [];
// Class-level routes
$classAttributes = $reflection->getAttributes(Route::class);
foreach ($classAttributes as $attribute) {
$route = $attribute->newInstance();
$routes[] = [
'path' => $route->path,
'method' => $route->method,
'handler' => $className
];
}
// Method-level routes
foreach ($reflection->getMethods() as $method) {
$attributes = $method->getAttributes(Route::class);
foreach ($attributes as $attribute) {
$route = $attribute->newInstance();
$routes[] = [
'path' => $route->path,
'method' => $route->method,
'handler' => $className . '::' . $method->getName()
];
}
}
return $routes;
}
$routes = getRoutes(UserController::class);
// Returns array of route configurations
// Validation attribute processor
function validateRequest(object $controller, string $method, array $params): array
{
$reflection = new ReflectionMethod($controller, $method);
$errors = [];
foreach ($reflection->getParameters() as $parameter) {
$attributes = $parameter->getAttributes(Validate::class);
foreach ($attributes as $attribute) {
$validator = $attribute->newInstance();
$paramName = $parameter->getName();
$value = $params[$paramName] ?? null;
foreach ($validator->rules as $rule) {
if (!validateRule($value, $rule)) {
$errors[$paramName][] = "Validation failed for rule: {$rule}";
}
}
}
}
return $errors;
}
function validateRule($value, string $rule): bool
{
switch ($rule) {
case 'required':
return !empty($value);
case 'integer':
return is_int($value);
case 'email':
return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
default:
if (strpos($rule, 'min:') === 0) {
$min = (int) substr($rule, 4);
return strlen($value) >= $min;
}
return true;
}
}
?>
Match Expression
The match expression is similar to switch but with stricter comparison and expression return values.
<?php
// Basic match expression
function getHttpStatusMessage(int $code): string
{
return match($code) {
200, 201, 202 => 'Success',
400 => 'Bad Request',
401 => 'Unauthorized',
403 => 'Forbidden',
404 => 'Not Found',
500 => 'Internal Server Error',
default => 'Unknown Status'
};
}
// Match with complex expressions
function calculateShipping(string $method, float $weight): float
{
return match($method) {
'standard' => $weight * 0.5,
'express' => $weight * 1.5,
'overnight' => $weight * 3.0,
'pickup' => 0.0,
default => throw new InvalidArgumentException("Unknown shipping method: {$method}")
};
}
// Match with conditions
function getUserType(int $age, bool $isPremium): string
{
return match(true) {
$age < 18 => 'minor',
$age >= 18 && $age < 65 && $isPremium => 'premium_adult',
$age >= 18 && $age < 65 => 'adult',
$age >= 65 && $isPremium => 'premium_senior',
$age >= 65 => 'senior',
default => 'unknown'
};
}
// Match with object types
function processPayment(object $payment): string
{
return match(get_class($payment)) {
CreditCardPayment::class => 'Processing credit card...',
PayPalPayment::class => 'Processing PayPal...',
BankTransferPayment::class => 'Processing bank transfer...',
default => 'Unknown payment method'
};
}
// Match with instanceof
function handleException(Throwable $e): string
{
return match(true) {
$e instanceof ValidationException => 'Validation failed: ' . $e->getMessage(),
$e instanceof DatabaseException => 'Database error occurred',
$e instanceof NetworkException => 'Network error occurred',
$e instanceof Exception => 'General error: ' . $e->getMessage(),
default => 'Unknown error occurred'
};
}
// Comparison with switch
function oldWayWithSwitch(int $value): string
{
switch ($value) {
case 1:
case 2:
return 'Low';
case 3:
case 4:
case 5:
return 'Medium';
case 6:
case 7:
case 8:
case 9:
case 10:
return 'High';
default:
return 'Invalid';
}
}
function newWayWithMatch(int $value): string
{
return match(true) {
$value >= 1 && $value <= 2 => 'Low',
$value >= 3 && $value <= 5 => 'Medium',
$value >= 6 && $value <= 10 => 'High',
default => 'Invalid'
};
}
?>
Constructor Property Promotion
Simplified way to declare and initialize properties through constructor parameters.
<?php
// Old way - verbose
class OldUser
{
private string $name;
private string $email;
private int $age;
private bool $active;
public function __construct(string $name, string $email, int $age, bool $active = true)
{
$this->name = $name;
$this->email = $email;
$this->age = $age;
$this->active = $active;
}
public function getName(): string
{
return $this->name;
}
public function getEmail(): string
{
return $this->email;
}
}
// New way - concise
class User
{
public function __construct(
private string $name,
private string $email,
private int $age,
private bool $active = true
) {}
public function getName(): string
{
return $this->name;
}
public function getEmail(): string
{
return $this->email;
}
public function isActive(): bool
{
return $this->active;
}
public function setActive(bool $active): void
{
$this->active = $active;
}
}
// Mix promoted and regular properties
class Product
{
private float $discountedPrice;
public function __construct(
private string $name,
private float $price,
private string $description = '',
float $discount = 0.0
) {
$this->discountedPrice = $this->price * (1 - $discount);
}
public function getDiscountedPrice(): float
{
return $this->discountedPrice;
}
}
// With attributes and validation
class CreateUserRequest
{
public function __construct(
#[Validate(['required', 'string', 'min:2'])]
private readonly string $name,
#[Validate(['required', 'email'])]
private readonly string $email,
#[Validate(['integer', 'min:18'])]
private readonly int $age,
private readonly array $roles = ['user']
) {}
public function getName(): string
{
return $this->name;
}
public function getEmail(): string
{
return $this->email;
}
public function getAge(): int
{
return $this->age;
}
public function getRoles(): array
{
return $this->roles;
}
}
?>
Nullsafe Operator
The nullsafe operator (?->) allows safe method/property access on potentially null objects.
<?php
class User
{
public function __construct(
private ?Profile $profile = null
) {}
public function getProfile(): ?Profile
{
return $this->profile;
}
}
class Profile
{
public function __construct(
private ?Address $address = null
) {}
public function getAddress(): ?Address
{
return $this->address;
}
}
class Address
{
public function __construct(
private string $country
) {}
public function getCountry(): string
{
return $this->country;
}
}
// Old way - verbose null checks
function getCountryOld(?User $user): ?string
{
if ($user !== null) {
$profile = $user->getProfile();
if ($profile !== null) {
$address = $profile->getAddress();
if ($address !== null) {
return $address->getCountry();
}
}
}
return null;
}
// New way - nullsafe operator
function getCountry(?User $user): ?string
{
return $user?->getProfile()?->getAddress()?->getCountry();
}
// Real-world examples
class Order
{
public function __construct(
private ?User $user = null,
private ?ShippingAddress $shippingAddress = null
) {}
public function getUser(): ?User
{
return $this->user;
}
public function getShippingAddress(): ?ShippingAddress
{
return $this->shippingAddress;
}
}
class ShippingAddress
{
public function __construct(
private string $street,
private string $city,
private string $zipCode
) {}
public function getFormattedAddress(): string
{
return "{$this->street}, {$this->city} {$this->zipCode}";
}
}
function processOrder(Order $order): void
{
// Get user email safely
$email = $order->getUser()?->getEmail();
if ($email) {
sendEmailNotification($email);
}
// Get shipping address safely
$address = $order->getShippingAddress()?->getFormattedAddress();
if ($address) {
generateShippingLabel($address);
}
}
// Array access with nullsafe operator
class ApiResponse
{
public function __construct(
private ?array $data = null
) {}
public function getData(): ?array
{
return $this->data;
}
}
function extractNestedValue(ApiResponse $response): ?string
{
// This won't work - nullsafe doesn't work with array access
// return $response->getData()?['user']?['profile']?['name'];
// Use null coalescing instead
$data = $response->getData();
return $data['user']['profile']['name'] ?? null;
}
?>
PHP 8.1 Features
Readonly Properties
Readonly properties can only be initialized once and cannot be modified afterward.
<?php
class ImmutableUser
{
public readonly string $id;
public readonly string $email;
public readonly DateTime $createdAt;
public function __construct(string $id, string $email)
{
$this->id = $id;
$this->email = $email;
$this->createdAt = new DateTime();
}
// This would cause an error
// public function changeEmail(string $email): void
// {
// $this->email = $email; // Error: Cannot modify readonly property
// }
}
// With constructor property promotion
class ValueObject
{
public function __construct(
public readonly string $value,
public readonly string $type
) {}
}
// Mixed readonly and mutable properties
class CacheEntry
{
public int $hitCount = 0;
public function __construct(
public readonly string $key,
public readonly mixed $value,
public readonly DateTime $expiresAt
) {}
public function incrementHits(): void
{
$this->hitCount++; // This is fine - not readonly
}
}
// Readonly with inheritance
class BaseEntity
{
public function __construct(
public readonly string $id,
public readonly DateTime $createdAt
) {}
}
class User extends BaseEntity
{
public function __construct(
string $id,
public readonly string $name,
public readonly string $email
) {
parent::__construct($id, new DateTime());
}
}
?>
Enums
PHP 8.1 introduced first-class enum support.
<?php
// Basic enum
enum Status
{
case PENDING;
case APPROVED;
case REJECTED;
}
function processOrder(Status $status): string
{
return match($status) {
Status::PENDING => 'Order is being processed',
Status::APPROVED => 'Order has been approved',
Status::REJECTED => 'Order was rejected',
};
}
// Backed enums (with values)
enum StatusCode: int
{
case PENDING = 0;
case APPROVED = 1;
case REJECTED = 2;
public function getLabel(): string
{
return match($this) {
self::PENDING => 'Pending Review',
self::APPROVED => 'Approved',
self::REJECTED => 'Rejected',
};
}
public static function fromString(string $status): self
{
return match(strtolower($status)) {
'pending' => self::PENDING,
'approved' => self::APPROVED,
'rejected' => self::REJECTED,
default => throw new ValueError("Invalid status: {$status}")
};
}
}
// String-backed enums
enum PaymentMethod: string
{
case CREDIT_CARD = 'credit_card';
case PAYPAL = 'paypal';
case BANK_TRANSFER = 'bank_transfer';
case CRYPTO = 'crypto';
public function getFee(float $amount): float
{
return match($this) {
self::CREDIT_CARD => $amount * 0.029,
self::PAYPAL => $amount * 0.034,
self::BANK_TRANSFER => 1.50,
self::CRYPTO => 0.0,
};
}
public function isInstant(): bool
{
return match($this) {
self::CREDIT_CARD, self::PAYPAL, self::CRYPTO => true,
self::BANK_TRANSFER => false,
};
}
}
// Enum with traits
trait TimestampTrait
{
public function getTimestamp(): int
{
return time();
}
}
enum LogLevel: string
{
use TimestampTrait;
case DEBUG = 'debug';
case INFO = 'info';
case WARNING = 'warning';
case ERROR = 'error';
case CRITICAL = 'critical';
public function getColor(): string
{
return match($this) {
self::DEBUG => 'gray',
self::INFO => 'blue',
self::WARNING => 'yellow',
self::ERROR => 'red',
self::CRITICAL => 'purple',
};
}
public function getPriority(): int
{
return match($this) {
self::DEBUG => 0,
self::INFO => 1,
self::WARNING => 2,
self::ERROR => 3,
self::CRITICAL => 4,
};
}
}
// Enum usage examples
class Order
{
public function __construct(
private string $id,
private StatusCode $status = StatusCode::PENDING
) {}
public function getStatus(): StatusCode
{
return $this->status;
}
public function approve(): void
{
$this->status = StatusCode::APPROVED;
}
public function reject(): void
{
$this->status = StatusCode::REJECTED;
}
}
class Logger
{
public function log(LogLevel $level, string $message): void
{
$timestamp = date('Y-m-d H:i:s');
$color = $level->getColor();
$levelName = $level->value;
echo "[{$timestamp}] [{$levelName}] {$message}\n";
}
}
$logger = new Logger();
$logger->log(LogLevel::ERROR, 'Something went wrong');
?>
Fibers
Fibers enable cooperative multitasking and are the foundation for async programming in PHP.
<?php
// Basic fiber example
$fiber = new Fiber(function(): void {
echo "Fiber started\n";
Fiber::suspend('Hello');
echo "Fiber resumed\n";
Fiber::suspend('World');
echo "Fiber finished\n";
});
echo "Before start\n";
$value = $fiber->start();
echo "Received: {$value}\n";
$value = $fiber->resume();
echo "Received: {$value}\n";
$fiber->resume();
echo "After finish\n";
// Practical fiber example - async-like behavior
class AsyncTask
{
private array $tasks = [];
private array $fibers = [];
public function add(callable $task): void
{
$this->tasks[] = $task;
}
public function run(): array
{
$results = [];
// Start all fibers
foreach ($this->tasks as $index => $task) {
$this->fibers[$index] = new Fiber($task);
$results[$index] = $this->fibers[$index]->start();
}
// Resume fibers until all complete
$completed = [];
while (count($completed) < count($this->fibers)) {
foreach ($this->fibers as $index => $fiber) {
if (in_array($index, $completed)) {
continue;
}
if ($fiber->isTerminated()) {
$completed[] = $index;
continue;
}
if ($fiber->isSuspended()) {
try {
$results[$index] = $fiber->resume();
} catch (FiberError $e) {
$completed[] = $index;
$results[$index] = "Error: " . $e->getMessage();
}
}
}
// Simulate some work
usleep(1000);
}
return $results;
}
}
// Simulated async operations
function fetchUser(int $id): string
{
echo "Fetching user {$id}...\n";
Fiber::suspend("Started fetching user {$id}");
// Simulate network delay
usleep(100000); // 100ms
Fiber::suspend("User {$id} data received");
return "User {$id} data";
}
function fetchOrder(int $id): string
{
echo "Fetching order {$id}...\n";
Fiber::suspend("Started fetching order {$id}");
// Simulate network delay
usleep(150000); // 150ms
return "Order {$id} data";
}
// Usage
$asyncTask = new AsyncTask();
$asyncTask->add(fn() => fetchUser(1));
$asyncTask->add(fn() => fetchUser(2));
$asyncTask->add(fn() => fetchOrder(100));
$results = $asyncTask->run();
print_r($results);
?>
PHP 8.2 Features
Readonly Classes
Entire classes can be marked as readonly, making all properties readonly.
<?php
readonly class UserData
{
public function __construct(
public string $name,
public string $email,
public int $age,
public array $roles
) {}
}
readonly class Point
{
public function __construct(
public float $x,
public float $y
) {}
public function distanceTo(Point $other): float
{
return sqrt(
pow($this->x - $other->x, 2) +
pow($this->y - $other->y, 2)
);
}
}
// Readonly class with methods
readonly class Money
{
public function __construct(
public int $amount,
public string $currency
) {}
public function add(Money $other): Money
{
if ($this->currency !== $other->currency) {
throw new InvalidArgumentException('Currency mismatch');
}
return new Money(
$this->amount + $other->amount,
$this->currency
);
}
public function format(): string
{
return number_format($this->amount / 100, 2) . ' ' . $this->currency;
}
}
$price = new Money(1250, 'USD');
$tax = new Money(125, 'USD');
$total = $price->add($tax);
echo $total->format(); // 13.75 USD
?>
Disjunctive Normal Form (DNF) Types
More complex type combinations using parentheses.
<?php
// DNF types allow complex type combinations
class DataProcessor
{
public function process((Countable&Iterator)|null $data): array
{
if ($data === null) {
return [];
}
$result = [];
foreach ($data as $item) {
$result[] = $item;
}
return $result;
}
public function handleInput((string&Stringable)|(int&Countable)|null $input): string
{
if ($input === null) {
return 'null';
}
if (is_string($input) && $input instanceof Stringable) {
return (string) $input;
}
if (is_int($input) && $input instanceof Countable) {
return "Count: " . count($input);
}
return 'unknown';
}
}
// Practical example
interface Cacheable
{
public function getCacheKey(): string;
}
interface Serializable
{
public function serialize(): string;
}
class CacheManager
{
public function store((Cacheable&Serializable)|string $data): void
{
if (is_string($data)) {
$key = md5($data);
$value = $data;
} else {
$key = $data->getCacheKey();
$value = $data->serialize();
}
// Store in cache
echo "Storing {$key}: {$value}\n";
}
}
?>
Advanced Usage Patterns
Modern API Design with PHP 8+ Features
<?php
#[Attribute]
class ApiRoute
{
public function __construct(
public string $path,
public string $method = 'GET',
public array $middleware = []
) {}
}
#[Attribute]
class Validate
{
public function __construct(
public array $rules
) {}
}
readonly class ApiResponse
{
public function __construct(
public mixed $data,
public int $status = 200,
public array $headers = []
) {}
public function toJson(): string
{
return json_encode([
'data' => $this->data,
'status' => $this->status
]);
}
}
enum HttpMethod: string
{
case GET = 'GET';
case POST = 'POST';
case PUT = 'PUT';
case DELETE = 'DELETE';
case PATCH = 'PATCH';
public function isReadOnly(): bool
{
return match($this) {
self::GET => true,
default => false
};
}
}
class UserController
{
#[ApiRoute('/users', 'GET')]
public function index(
#[Validate(['integer', 'min:1'])] int $page = 1,
#[Validate(['integer', 'min:1', 'max:100'])] int $limit = 10
): ApiResponse {
$users = $this->getUsersPaginated($page, $limit);
return new ApiResponse($users);
}
#[ApiRoute('/users/{id}', 'GET')]
public function show(
#[Validate(['required', 'integer'])] int $id
): ApiResponse {
$user = $this->findUser($id);
return new ApiResponse($user ?? null, $user ? 200 : 404);
}
#[ApiRoute('/users', 'POST')]
public function store(
#[Validate(['required', 'string', 'min:2'])] string $name,
#[Validate(['required', 'email'])] string $email,
#[Validate(['integer', 'min:18'])] int $age = 18
): ApiResponse {
$user = $this->createUser(
name: $name,
email: $email,
age: $age
);
return new ApiResponse($user, 201);
}
private function getUsersPaginated(int $page, int $limit): array
{
// Implementation
return [];
}
private function findUser(int $id): ?array
{
// Implementation
return null;
}
private function createUser(string $name, string $email, int $age): array
{
// Implementation
return [
'id' => 1,
'name' => $name,
'email' => $email,
'age' => $age
];
}
}
// Modern service with all features combined
readonly class UserService
{
public function __construct(
private UserRepository $repository,
private EmailService $emailService,
private CacheManager $cache
) {}
public function createUser(CreateUserRequest $request): User|UserCreationError
{
// Validate using attributes
$errors = $this->validateRequest($request);
if (!empty($errors)) {
return new UserCreationError($errors);
}
// Create user
$user = new User(
id: $this->generateId(),
name: $request->getName(),
email: $request->getEmail(),
status: UserStatus::ACTIVE
);
// Save and notify
$this->repository->save($user);
$this->emailService->sendWelcomeEmail($user);
return $user;
}
public function findUser(int|string $id): ?User
{
$cacheKey = "user:" . $id;
return $this->cache->remember($cacheKey, function() use ($id) {
return $this->repository->find($id);
});
}
private function validateRequest(CreateUserRequest $request): array
{
// Implementation using reflection and attributes
return [];
}
private function generateId(): string
{
return uniqid('user_', true);
}
}
?>
Migration Strategies
Upgrading from PHP 7 to PHP 8+
<?php
// Before PHP 8
class OldStyleClass
{
private string $name;
private ?string $email;
private int $age;
public function __construct(string $name, ?string $email, int $age)
{
$this->name = $name;
$this->email = $email;
$this->age = $age;
}
public function getName(): string
{
return $this->name;
}
public function getEmail(): ?string
{
return $this->email;
}
public function processStatus($status): string
{
switch ($status) {
case 'pending':
return 'Waiting for approval';
case 'approved':
return 'Request approved';
case 'rejected':
return 'Request rejected';
default:
return 'Unknown status';
}
}
}
// After PHP 8 - modernized
enum Status: string
{
case PENDING = 'pending';
case APPROVED = 'approved';
case REJECTED = 'rejected';
public function getDescription(): string
{
return match($this) {
self::PENDING => 'Waiting for approval',
self::APPROVED => 'Request approved',
self::REJECTED => 'Request rejected',
};
}
}
readonly class ModernClass
{
public function __construct(
private string $name,
private ?string $email,
private int $age
) {}
public function getName(): string
{
return $this->name;
}
public function getEmail(): ?string
{
return $this->email;
}
public function getAge(): int
{
return $this->age;
}
public function processStatus(Status $status): string
{
return $status->getDescription();
}
}
// Gradual migration approach
class TransitionClass
{
// Keep old constructor for BC
public function __construct(
private string $name,
private ?string $email = null,
private int $age = 0
) {}
// Add new methods with modern features
public static function create(
string $name,
?string $email = null,
int $age = 0
): self {
return new self(
name: $name,
email: $email,
age: $age
);
}
public function withStatus(Status $status): array
{
return [
'name' => $this->name,
'email' => $this->email,
'age' => $this->age,
'status' => $status->getDescription()
];
}
}
?>
Best Practices for Modern PHP
- Use Union Types Sparingly: Only when truly needed for API flexibility
- Prefer Readonly for Value Objects: Immutable data structures are safer
- Use Named Arguments for Complex Functions: Improves readability
- Leverage Attributes for Metadata: Replace docblock annotations
- Use Match for Complex Conditionals: More expressive than switch
- Use Enums for Fixed Sets: Replace constants with type-safe enums
- Consider Nullsafe Operator: Reduces verbose null checks
- Use Constructor Promotion: Cleaner class definitions
PHP 8+ features significantly improve code quality, type safety, and developer experience. Adopting these features gradually will make your codebase more modern and maintainable.