Five PHP Features That Make You Write Better Code
PHP has evolved dramatically over the past few years, introducing features that significantly improve your coding experience. Modern PHP gives you tools to write cleaner, more readable code while reducing the number of bugs you introduce. The most powerful features from PHP 8.0 and PHP 8.1 let you express intent more clearly and catch errors earlier. You can maintain your code with confidence. Let's explore the top features that can enhance your development process and help you build better applications.
Readonly Properties: Enforce Immutability#
Readonly properties, introduced in PHP 8.1, allow you to define properties that can only be written once. Once a property is set during initialization, it cannot be changed. This is useful for creating immutable objects where the state should remain constant after creation. This approach helps ensure data integrity and reduces bugs in your code.
Here's a practical example of how to use readonly properties:
class User {
public readonly string $username;
public function __construct(string $username) {
$this->username = $username;
}
}
$user = new User('dharmesh');
echo $user->username; // Outputs: dharmesh
// Trying to modify the readonly property will result in an error
$user->username = 'dharm'; // Error: Cannot modify readonly property Readonly properties shine in scenarios where you want to guarantee that certain values remain constant after they've been set. This is especially common when an object's state should not change once initialized. They promote immutability throughout your codebase.
Why Readonly Properties Matter#
Data Integrity: Readonly properties protect the integrity of the data within your objects. Once set, the value cannot be tampered with, which is particularly important in secure and critical applications like financial systems.
Simplified Debugging: Since the value of a readonly property cannot change after initialization, debugging becomes simpler. The property value remains consistent throughout the object's entire lifecycle. This makes it easier to reason about your code's behavior.
Thread Safety: In multi-threaded environments, readonly properties prevent race conditions. Multiple threads cannot modify the same property simultaneously. This ensures safer concurrent code execution.
Real-World Example: Financial Transactions#
Imagine you're building a financial application where each Transaction object has an amount and transactionId. Once these are set, they shouldn't be changed, as altering them could lead to serious financial discrepancies and compliance issues.
class Transaction {
public readonly float $amount;
public readonly string $transactionId;
public function __construct(float $amount, string $transactionId) {
$this->amount = $amount;
$this->transactionId = $transactionId;
}
} Readonly properties provide a guarantee in this scenario. The transaction amount and ID remain exactly as recorded. This protects the integrity of your financial records.
Enums: Type-Safe Value Sets#
Enums (short for "Enumerations") were introduced in PHP 8.1. They provide a way to define a set of named values that a property can have. Enums represent a fixed set of possible values in a type-safe way. This makes your code more readable, maintainable, and less error-prone.
Understanding Enum Types#
An enum in PHP is defined using the enum keyword. Enums can be pure (without data) or backed (mapped to scalar values). Pure enums contain just named cases. Backed enums map to strings or integers. Each approach serves different needs.
Pure Enums
Pure enums are simple and don't have any associated values. They are useful when you just need a named set of constants. Use them when you want type-safe status values or state markers without needing to persist them to a database.
enum Status {
case Pending;
case InProgress;
case Completed;
}
$status = Status::Pending;
if ($status === Status::Pending) {
echo "The task is pending.";
} Backed Enums
Backed enums associate each case with a specific scalar value (like a string or an integer). This is useful when the enum needs to interface with other systems or when you need to persist these values, such as storing them in a database column.
enum OrderStatus: string {
case Pending = 'pending';
case Shipped = 'shipped';
case Delivered = 'delivered';
case Cancelled = 'cancelled';
}
$orderStatus = OrderStatus::Shipped;
echo $orderStatus->value; // Outputs: shipped
// You can also create an enum instance from a scalar value
$orderStatus = OrderStatus::from('delivered');
echo $orderStatus->value; // Outputs: delivered Benefits of Using Enums#
Type Safety: Enums ensure that only predefined values are used. This prevents bugs from invalid values. If you try to use a value outside the enum, you get a type system error.
Better Code Readability: Enums give names to constants, making the code more descriptive and easier to understand. Instead of comparing string literals like "pending", you write OrderStatus::Pending, which is self-documenting.
Data Validation: Enums automatically validate property values. Only predefined values are accepted. This reduces the need for extra validation logic across your application.
Database Integration: Backed enums are useful for storing values in databases. You can map enum values to strings or integers that store in database columns. Converting them back on retrieval is straightforward.
Practical Application: Payment Processing#
Suppose you're building a payment processing system. Users choose payment methods like Credit Card, PayPal, or Bank Transfer. Enums enforce valid selections. This prevents invalid attempts and improves reliability.
Match Expressions: Modern Conditional Logic#
Match expressions, introduced in PHP 8.0, simplify switch statements. They compare values and return results in one step. Match expressions are shorter and reduce errors. They allow strict type comparison while returning values directly. This makes them ideal for modern PHP.
Basic Match Expression Structure#
A match expression compares a value against multiple cases and returns a result. Unlike switch statements, each case is separated by commas and directly returns a value without requiring break statements.
Simple Day Classification#
Here's a straightforward example that classifies days of the week:
$day = 'Monday';
$typeOfDay = match($day) {
'Saturday', 'Sunday' => 'Weekend',
'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday' => 'Weekday',
default => 'Unknown',
};
echo $typeOfDay; // Outputs: Weekday Returning Complex Data Types#
Match expressions return simple values like strings and numbers. They can also return complex types: arrays, objects, and function results. This flexibility handles detailed operations. You can return structured data based on the matched case.
class Animal {
public function __construct(public $type, public $sound) {}
}
function getBirdDetails() {
return ['type' => 'animal', 'sound' => 'tweet'];
}
$input = 'dog';
$result = match ($input) {
'cat' => ['type' => 'animal', 'sound' => 'meow'],
'dog' => new Animal('dog', 'bark'),
'bird' => getBirdDetails(),
default => null,
}; Key Advantages Over Switch#
Strict Comparison: Match expressions use strict comparison (===). Types must match exactly. This avoids bugs from loose comparison in switch statements. You're protected from unexpected type coercion.
Returning Values: Unlike switch statements, match expressions directly return a value, making them usable in assignments or as function arguments. This makes your code more functional and expressive.
No Fallthrough: In switch statements, you have to manually add a break to avoid fallthrough to the next case. Match expressions don't require this, eliminating a common source of bugs.
Cleaner and Simpler: Match expressions are more compact and easier to read, especially when handling many conditions. Your code becomes more focused on the logic rather than syntax.
Match expressions offer a cleaner way to handle conditional logic. They make code easier to read. They reduce repetitive code. They ensure strict comparisons. This leads to fewer errors and maintainable applications.
Constructor Property Promotion: Reduce Boilerplate#
Constructor Property Promotion, introduced in PHP 8.0, simplifies property definition. It lets you declare and assign properties in the constructor. This cuts down on repetitive code. Your classes become shorter and easier to read while reducing boilerplate.
The Traditional Approach#
In the traditional way, you have to declare class properties and then assign values to them inside the constructor. This often leads to repetitive code that doesn't add clarity:
class User {
private string $name;
private int $age;
public function __construct(string $name, int $age) {
$this->name = $name;
$this->age = $age;
}
} Using Constructor Property Promotion#
With Constructor Property Promotion, you can declare and initialize properties directly within the constructor's parameter list, dramatically simplifying the code:
class User {
public function __construct(
private string $name,
private int $age
) {}
} How It Works#
In the constructor signature, use visibility keywords (public, protected, private). Specify the property type and name as usual. PHP automatically creates the property. It assigns the value passed during object creation. No separate declarations needed.
Real Benefits#
Less Code: Write and maintain significantly less code, especially in classes with many properties. You eliminate redundancy without losing clarity.
Improved Readability: The class is clearer because property declarations and their setup are all in the constructor. You see at a glance what the class owns and how it's initialized.
Reduced Errors: Fewer lines of code mean fewer opportunities for typos or mistakes, such as forgetting to assign a property. The connection between parameter and property is explicit and automatic.
Better Refactoring: When refactoring, changes are easier to make since property declarations and assignments are in a single location. You don't have to keep parameter declarations and property assignments in sync.
Constructor Property Promotion makes class creation easier. It cuts down on repetitive code. Your code becomes clearer. It helps avoid mistakes. This feature is valuable for developers working on data objects and models. It makes code more elegant and maintainable.
Named Arguments: Explicit Parameter Passing#
Named arguments, introduced in PHP 8.0, let you pass arguments by parameter name. This makes code clearer and more flexible. Your code shows exactly which arguments you're using, regardless of order. Named arguments improve readability without sacrificing flexibility.
Basic Named Arguments Syntax#
Here's a simple example of using named arguments with a function:
function createUser(string $name, int $age, string $email = 'not_provided') {
// Function body
}
createUser(name: 'John Doe', age: 30, email: 'john.doe@example.com'); Why Named Arguments Matter#
Improved Readability: Named arguments clarify which values go to which parameters. Your code is easier to read and less ambiguous. When you see createUser(name: 'John', age: 30), you immediately understand each value.
Flexibility with Parameter Order: You can pass arguments in any order. This is helpful for functions with many optional parameters. It reduces errors when working with flexible function signatures.
Reduced Error Potential: Named arguments reduce mistakes. They clearly show which value goes to which parameter. This lowers the chance of wrong argument order. It's especially valuable when parameters have similar types.
Complex Function Example#
Consider a function that generates a report with various options. Named arguments shine when you have multiple optional parameters and want to skip some while setting others:
function generateReport(
string $title,
string $author,
string $format = 'pdf',
bool $includeSummary = false,
bool $includeCharts = true
) {
// Function body
}
// Calling with named arguments
generateReport(
title: 'Annual Sales Report',
author: 'Jane Smith',
includeSummary: true,
format: 'docx'
); The format and includeSummary parameters are specified in a different order than declared. This makes the function call clearer. It's more flexible. Readability improves as functions gain more optional parameters.
Important Considerations#
Parameter Order Rules: Required parameters must come before optional ones. Named arguments allow flexibility in optional parameter order. Required ones still follow their declared order.
Compatibility: Named arguments are only available in PHP 8.0 and later. They won't work in older versions of PHP, so check your minimum version requirements.
Readability vs. Verbosity: Named arguments enhance readability. Using them excessively can make code verbose. Use them strategically to clarify intent. Avoid over-engineering simple calls.
Named arguments in PHP 8.0 enhance code readability and flexibility. They let you specify arguments by name, not position. Function calls become clearer. They're easier to maintain, especially for functions with many parameters or optional values.
If you want the surrounding context, read PHP vs JavaScript for web development: how to choose and Laravel Queues at Scale: Reliable Background Jobs.
If you would rather have this done than do it: this is the kind of work behind our custom software development and maintenance and support.