PHP is the most used language for developing web pages, applications and web services, although you can also use it for desktop applications, IoT, artificial intelligence and more, for a long time this language was very popular, but between the years 2009 and 2012, it went from being very popular to being very criticized; In 2011, Taylor Otwell launched the first version of the Laravel framework, an event that represented a new birth for PHP, it rose from the ashes like the Phoenix, the impressive community that emerged around Laravel and Symfony, returned much strength to the language, PHP is currently a modern language, very powerful and versatile, with a learning curve that allows rapid evolution, an aspect that is also projected in Laravel.
The most outstanding features are the typing of properties and methods or functions and return values with variance and contra variance, anonymous classes, modern cryptography, arrow functions, array destructuring and merging, operators for handling null values, preloading, FFI, JIT compiler, attributes, named arguments, improvements in class constructors, union and intersection of types, the match expression, enumerations, read-only properties and classes, and much more.
This article compiles all the features of modern PHP with code examples to strengthen your knowledge, from version 7.0 to 8.3:
Type declarations
Being a dynamically typed language, PHP allows passing any type of parameter in properties, methods or functions, from version 7.0 onwards, type declarations were added and have been improved with each release.
<?php
namespace App\Models\User;
use Core\ORM\Model;
class User extends Model
{
private string $table = 'users';
protected array $columns = [
'id',
'name',
'email',
'password',
'verified_at',
];
public function getVerifiedAt(): string|null
{
return $this->attributes['verified_at'];
}
public function isVerified(): bool
{
return !empty($this->getVerifiedAt())
}
}
Other types: void, parent, callable, self, iterable, object, null, true, false, never, enumerations, classes and interfaces. This is what a class written in modern PHP looks like. More information about types here.
In the example above we can see type binding, but it is possible to take advantage of this feature with objects as well:
<?php
use App\Models\User;
class CreateUserAction
{
public function execute(): User|false
{
// Code
}
}
Type intersection
It consists of the ability in which an argument must satisfy multiple constraints, for example, multiple contracts:
<?php
interface Arrayable
{
public function toArray(): array;
}
interface Formattable
{
public function format(): string;
}
class Invoice implements Formattable, Arrayable
{
...
}
class Quote implements Formattable, Arrayable
{
...
}
class Printer
{
public function __construct(
private readonly Formattable&Arrayable $document
) {
// ..
}
}
Can we combine union and intersection of types? Of course, this is called Disjunctive Normal Form.
<?php
class Printer
{
public function __construct(
private readonly (Formattable&Arrayable)|null $document
) {
// ..
}
}
We now have the ability to add constraints and combine with PHP's native types.
Null handling
A common problem in all languages is null handling, PHP has one of the best mechanisms for this purpose.
Null fusion operator
<?php
$data = ['key' => 'value'];
echo $data['unknown'] ?? 'default' . PHP_EOL;
Even though the key of the array does not exist, the default value will be displayed.
Passing and returning nulls
Allows a property, method or function to receive or return a null value.
<?php
function gretting (?string $name): ?string {
if (!$name) {
return null;
}
return "Hola, {$name}";
}
This code can be written using type union:
<?php
function gretting (string|null $name): string|null {
if (!$name) {
return null;
}
return "Hola, {$name}";
}
Null coalescing assignment operator
This powerful operator saves us when we try to access unassigned or null values.
<?php
class Cache
{
private array $data;
public function remember (string $key, Closure $closure): mixed
{
return $this->data[$key] ??= $closure();
}
}
In the example, the value is returned if it exists, or it is assigned and returned; in either case, a value will be returned.
Null safe Operator
If you have tried to use a method that returns an object or null, then you will understand the importance of this powerful operator, in Laravel we have the Auth facade, with which we can access the authenticated user:
<?php
use Illuminate\Support\Facades\Auth;
$userName = Auth::user()?->name;
If there is no authenticated user, the variable will be assigned a null value.
Anonymous classes
This feature allows you to change the behavior of a class, a mock, for example; or just define an entire class. Laravel takes advantage of anonymous classes to prevent name collisions in migrations.
<?php
$date = date('Y-m-d');
$dateFormatter = new class($date) extends DateFormatter {
public function __construct(string $date)
{
$this->date = date_create($date);
}
public function format(string $format = 'd/m/Y'): string
{
return date_format($this->date, $format);
}
};
echo $dateFormatter->format() . PHP_EOL;
In PHP 8.3, anonymous classes can be read-only:
<?php
$dateFormatter = new readonly class($date) {
...
};
echo $dateFormatter->format() . PHP_EOL;
Very exceptional exceptions
PHP allows you to add multiple catch blocks to a try block, or simply catch multiple exceptions in the same catch block.
<?php
try {
// Code
} catch (ErrorException $e) {
echo $e->getMessage() . PHP_EOL;
} catch (RuntimeException $e) {
echo $e->getMessage() . PHP_EOL;
}
// The above code can be written like this
try {
// Code
} catch (ErrorException | RuntimeException $e) {
echo $e->getMessage() . PHP_EOL;
}
If the catch block variable will not be used, you can simply omit its declaration.
<?php
try {
// Code
} catch (ErrorException | RuntimeException) {
// ..
}
Arrays
Destructuring
It is the mechanism to break the structure of an array and access its contents:
<?php
$user = [
'John Doe',
'email@domain.com',
];
[$name, $email] = $user;
echo $name . PHP_EOL;
echo $email . PHP_EOL;
// Associative array
$user = [
'name' => 'John Doe',
'email' => 'email@domain.com',
];
['name' => $name, 'email' => $email] = $user;
echo $name . PHP_EOL;
echo $email . PHP_EOL;
// Collections
$users = [
['John Doe', 'email@domain.com'],
...
];
foreach ($users as [$name, $email]) {
echo $name . PHP_EOL;
echo $email . PHP_EOL;
}
Unpacking
PHP has the array_merge() and array_merge_recursive() functions for merging arrays, but now it is much simpler and more elegant:
<?php
$web = ['php', 'javascript'];
$desktop = ['java', 'c#'];
$languages = [...$web, ...$desktop];
Class constants
Constants are essential to structure the code professionally, in PHP we can add visibility, and since version 8.3 we have the following types: string, array, int, float, bool.
<?php
class File
{
public const string DEFAULT_DISK = 'public';
public const int BYTE_RATE = 1024;
}
Dynamic access is allowed in the same way it works for properties and methods:
<?php
// Property access
echo $object->{$propertyName} . PHP_EOL;
// Method access
echo $object->{$methodName}() . PHP_EOL;
$name = 'DEFAULT_DISK';
// Constant access
echo FILE::{$name} . PHP_EOL;
Arrow functions
Very famous in JavaScript, an improvement to callables and Closure, PHP allows one-line arrow functions:
<?php
$names = ['php', 'laravel'];
$data = array_map(fn ($name) => strtoupper($name), $names);
Numeric literal separator
Trying to read a large number is no longer a problem in PHP:
<?php
echo 1_000_000 . PHP_EOL;
echo 6.674_083e-11 . PHP_EOL;
echo 0xCAFE_F00D . PHP_EOL;
Named arguments
Some of the best additions, especially when we have null or optional parameters:
<?php
function setcookie(
string $name,
string $value = "",
int $expires = 0,
string $path = "",
string $domain = "",
bool $secure = false,
bool $httponly = false
): bool {
....
}
setcookie(name: 'cool', value: 'PHP', secure: true);
In the example, the native function setcookie in which we can skip many optional parameters and pass an argument directly to the parameter we need to assign.
Constructor property promotion
Personally I can say that I like this new feature making mixed use of its syntax.
<?php
class File
{
private string $disk;
public function __construct (
private string $path,
) {
$this->disk = config('storage.default');
}
}
The $path variable is defined and assigned in the constructor, while $disk is only assigned.
The Match expression
The evolution of the switch/case is indeed match, in conjunction with enums, this feature is simply powerful:
<?php
$age = 18;
$message = match (true) {
$age < 18 => 'Teenager',
$age >= 18 && $age < 35 => 'Young',
default => 'Adult',
};
echo $message . PHP_EOL;
Enumerations
Enums can have methods, types, implement contracts, use traits, constants, and more.
<?php
enum Status
{
case APPROVED;
case PENDING;
case REJECTED;
case FAILED;
}
class Payment
{
public function isApproved(Status $status): bool{
return $status === Status::APPROVED;
}
}
$status = Status::APPROVED;
echo $status->name . PHP_EOL; // APPROVED
If we use class constants, the isApproved function would receive a string and technically could receive any value as a string, with enumerations we achieve a greater degree of control in the definition of parameters. Another type of enumerations are those that have cases with values, and are called backed enumerations, these support the int and string types:
<?php
enum Status: string
{
case APPROVED = 'A';
case PENDING = 'P';
case REJECTED = 'R';
case FAILED = 'F';
public function trans(): string
{
return match($this) {
self::APPROVED => trans('payments.status.approved'),
self::PENDING => trans('payments.status.pending'),
self::REJECTED => trans('payments.status.rejected'),
self::FAILED => trans('payments.status.failed'),
};
}
public static function toArray(): array{
return array_column(self::cases(), 'value');
}
}
$status = Status::APPROVED;
echo $status->name . PHP_EOL; // APPROVED
echo $status->value . PHP_EOL; // A
A couple of methods have been added to demonstrate the power of enums. It is also possible to initialize them from values:
<?php
// Will throw an error if it is NOT a valid value
$status = Status::from('A');
// A null value will be assigned if it is NOT a valid value
$status = Status::tryFrom('X') ?? Status::APPROVED;
Read-only classes and properties
When we want to initialize properties only the first time, we could declare them read-only, they can be specific properties or set all properties to be read-only, this is known as a read-only class.
<?php
class QueryBuilder
{
protected array $clauses;
public function __construct(
protected readonly string $table
) {
$this->clasuses = [];
}
}
A DTO is an excellent example for a read-only class:
<?php
readonly class UserData
{
public function __construct(
protected string $name,
protected string $email
) {
// ..
}
}
I invite you to write high-performance and scalable applications with modern PHP, and don't forget that sharing makes us grow.