Paulund
2023-12-10 #php

Exploring Modern PHP: A Deep Dive into Features from PHP 7.0 to 8.3

PHP 8.3

Typed class constants

Before PHP 8.3 you were able to define class constants but you were not able to define the type of the constant.

class User
{
    const NAME = 'Paul';
}

Dynamic class constant fetch

class User
{
    const NAME = 'Paul';
}
$searchableConstant = 'NAME';
var_dump(User::{$searchableConstant});

Deep-cloning of readonly properties

Before PHP 8.3 if you had a class with a readonly property that was an object, when you cloned the object you would get a shallow clone of the object.

class PHP {
    public string $version = '8.2';
}

readonly class Foo {
    public function __construct(
        public PHP $php
    ) {}

    public function __clone(): void {
        $this->php = clone $this->php;
    }
}

$instance = new Foo(new PHP());
$cloned = clone $instance;

$cloned->php->version = '8.3';

json_validate function

Before PHP 8.3 if you wanted to validate a JSON string you would have to use the json_decode function and check if it returned an error.

$json = json_decode($jsonString);
if (json_last_error() !== JSON_ERROR_NONE) {
    throw new Exception('Invalid JSON');
}

// PHP 8.3
json_validate($jsonString);

PHP 8.2

Read Only Classes

Pre PHP 8.2 you were able to define class properties as readonly but now in PHP 8.2 you can define an entire class as readonly. This means that you can't change any of the properties of the class.

This is useful if you want to create a class that will be used to store data and you don't want the data to be changed, like DTO classes.

readonly class User
{
    public function __construct(
        public string $name,
        public string $email,
        public string $password,
    ) {}
}

Dynamic properties deprecation

Before PHP 8.2 you were able to get and set a public property of a class by using the following.

class User
{
    public string $name;
}

$user = new User();
$user->name = 'Paul'; // Setting the name property

But as of PHP 8.2 this is now deprecated and you will receive a deprecated warning.

You can opt out of this by using the #[AllowDynamicProperties] attribute.

You could also get around this be using __get and __set magic methods to be able to edit your class properties.

true and false standalone types

Now in PHP 8.2 you're able to a return type of true or false rather than just a boolean.


function alwaysFalse(): false {
    return false;
}

function alwaysTrue(): true {
    return true;
}


Traits constants

Traits can now have constants the same way as class constants, you can not access the constant through the name of the trait but you can access it via the class name that uses the trait.

trait T {
    public const C = 1;
}

PHP 8.1

Never return type

If your method will not return anything you can now use the never return type, this s used to indicate that a function or method will always throw an exception or terminate the program, and will never return a value.

function foo(): never {
    throw new Exception();
}

Read Only Properties

Read only properties allows you to define strict rules to change the value of the class properties.

In the past if you wanted to create readonly properties you would create a class that only has a get method and no set method, like this.

class BlogData
{
    private Status $status;
  
    public function __construct(Status $status)
    {
        $this->status = $status;
    }
   
    public function getStatus(): Status
    {
        return $this->status;   
    }
}

In PHP 8.1 you can do without having to create the getter method.

class BlogData
{
    public readonly Status $status;
  
    public function __construct(Status $status)
    {
        $this->status = $status;
    }
}

A good use case for this are for data transfer objects where you don't want to change the values of the properties of a class once the values have been set.

These properties will be set from the constructor and cannot be changed after that.

Enums

Enumerations allow you to define a custom type that can be limited to a specific number of values.

They are useful to use in the scenario where you want to restrict a type value to a specific list of values.

For example, in Blog applications, you might have different types of posts, such as post, page or custom types.

In your code, you might have something like a static defined on your post object of type to define these values.

class Blog
{
    const TYPES = [
        'BLOG' => 1,
        'PAGE' => 2,
        'CUSTOM' => 3,
    ];
}

You can access these in your code by using Blog::TYPES['BLOG'].

Now with PHP, you can use an Enum class for these values.

enum BlogTypes
{
    case Blog;
    case Page;
    case Custom;
}

The better of this is now you can strongly type these values in a function.

function validateBlogType(BlogTypes $blogTypes)
{
    //
}

$value = BlogTypes:Blog;

validateBlogType($value); // valid method call

validateBlogType('Blog'); // This to return Error of $blogTypes must be of type BlogTypes

Cases by default are not backed by a scalar value, each case is backed by a singleton object for that case name.

echo BlogTypes::Blog; // Prints "Blog"

Enums also allow you to add methods you can use on to return additional information about the cases.

enum BlogTypes
{
    case Blog;
    case Page;
    case Custom;

    public function titlePrefix()
    {
        if ($this === BlogTypes::Page) {
            return 'Page - ';
        }
        return '';
    }
}

echo BlogTypes::Blog->titlePrefix(); // return nothing
echo BlogTypes::Page->titlePrefix(); // return Page -

Enums implement an interface of UnitEnum which has a cases method that can be used to fetch all of the cases on an enum.

var_dump(BlogTypes::cases());

array(4) {
    [0]=>
    enum(BlogTypes::Blog)
    [1]=>
    enum(BlogTypes::Page)
    [2]=>
    enum(BlogTypes::Custom)
}

Enums will also implement the BackedEnum interface if you define a scalar value for each case.

enum BlogTypes: int
{
    case Blog = 1;
    case Page = 2;
    case Custom = 3;
}

// Create an enum case from a scalar value
$blog = BlogTypes::from(1); // Returns BlogTypes::Blog

// Get the scalar value of an enum case
$value = BlogTypes::Blog->value; // Returns '1'

PHP 8

Union Types

This allows developers to specify multiple types for a parameter or property.

function sum(int|float $a, int|float $b): int|float {
    return $a + $b;
}

Match Expressions

This allows developers to match a value against a set of patterns, similar to a switch statement.

$statusCode = match ($response->getStatusCode()) {
    200, 201 => 'success',
    400 => 'bad request',
    401 => 'unauthorized',
    403 => 'forbidden',
    404 => 'not found',
    500 => 'internal server error',
    default => 'unknown status code',
};

Named Arguments

This allows developers to specify the name of an argument when calling a function, rather than its position.

function getPost(int $id, string $title, string $content): Post {
    // ...
}

$post = getPost(id: 1, title: 'Hello World', content: 'Lorem ipsum dolor sit amet');

Null Safe Operator

This allows developers to access properties and methods on objects without having to check if the object is null.

$length = $user?->getAddress()?->getStreet()?->getLength();

String utils

$str = 'Hello World';

if (str_starts_with($string, 'Hello')) {}
if (str_ends_with($string, 'World')) {}
if (str_contains($string, 'World')) {}

PHP 7.4

Arrow Functions

The arrow functions are introduced as a shorthand for anonymous functions.

$greet = fn($name) => "Hello $name";

Spread Operator in Array Expressions

This allows developers to use the spread operator to unpack arrays in array expressions.

$numbers = [1, 2, 3];
$moreNumbers = [4, 5, 6];

$allNumbers = [...$numbers, ...$moreNumbers];

Typed Properties

This allows developers to specify the type of a class property.

class User {
    public string $name;
    public int $age;
}

Numeric Literal Separator

This allows developers to use an underscore as a separator between digits in numeric literals.

$number = 1_000_000;

Null coalescing assignment operator

This allows developers to assign a value to a variable if it is not set.

$foo = $foo ?? 'bar';

// is equivalent to
$foo ??= 'bar';

PHP 7.3

Support for JSON_THROW_ON_ERROR

This option allows you to throw an exception when an error occurs instead of returning NULL.

$json = json_decode($json, true, 512, JSON_THROW_ON_ERROR);

Support for the trailing comma in function calls

This allows developers to add a trailing comma at the end of function call parameters.

foo(
    'bar',
    'baz',
);

Support for the is_countable() function

This allows developers to check if a variable is an array or an instance of Countable.

if (is_countable($var)) {
    // ...
}

PHP 7.2

Argon2 in password hashing

Argon2 is a password-hashing algorithm that is considered to be more secure than the previous default algorithm, bcrypt.

password_hash('password', PASSWORD_ARGON2I);

Object typehint

This allows developers to specify that a function or method parameter must be an object, rather than a specific class.

function foo(object $bar) {}

PHP 7.1

Array Destructing

This allows developers to assign values from an array to multiple variables in one line.

[$a, $b] = [1, 2];

Nullable types

This allows developers to specify that a function or method parameter can be either a specific type or null.

In PHP, a variable can be set to null, but it is of the same type as the variable it was assigned to. This means that, when you pass a variable to a function or method, you cannot be certain whether it is the expected type or null. With nullable types, you can specify that a parameter can be either a specific type or null.

function foo(?string $bar) {}

class User {
    public function __construct(?string $name) {}
}

Void return type

This allows developers to specify that a function or method does not return a value.

function foo(): void {
    // ...
}

class Foo {
    public function bar(): void {
        // ...
    }
}

When a function or method is declared with a void return type, it is not allowed to return any value. If a function or method with a void return type attempts to return a value, a TypeError exception will be thrown.

This feature helps to improve code readability and reduce the chance of programming errors. For example, if a function or method is declared with a void return type, it is clear to the developer that it does not return a value, and any return statement inside the function or method should be removed.

It's also worth noting that, functions or methods that don't have any return statement or don't have any explicit return type, are considered as void functions or methods by default.

Class constant visibility modifiers

Class constant visibility modifiers allow developers to specify the visibility of class constants, similar to properties and methods.

In PHP, class constants are defined using the const keyword, and by default, they are always public. However, with class constant visibility modifiers, you can now specify whether a class constant should be public, protected, or private.

class MyClass {
    public const MY_CONST = 'value';
    protected const MY_PROTECTED_CONST = 'value';
    private const MY_PRIVATE_CONST = 'value';
}

A public class constant can be accessed from anywhere, a protected constant can only be accessed within the class and its subclasses, and a private constant can only be accessed within the class that defines it.

This feature is useful for encapsulation and code organization. For example, you may use private constants for internal implementation details that should not be accessed from outside the class, and protected constants for values that should be shared with subclasses.

Support for the spaceship operator (<=>)

This operator compares two expressions and returns -1, 0 or 1 depending on whether the left-hand side is less than, equal to, or greater than the right-hand side.

$foo = 1 <=> 2; // -1
$bar = 2 <=> 2; // 0
$baz = 3 <=> 2; // 1

Multi catch exception handling

Multi catch exception handling is a feature that allows developers to catch multiple different types of exceptions in the same catch block.

This allows you to catch multiple different types of exceptions in a single catch block, rather than having to write separate catch blocks for each exception type.

try {
  // code
} catch (FirstException | SecondException $e) {
  // code
} catch (ThirdException $e) {
  // code
}

It allows you to handle different types of exceptions in the same way. This can make your code more readable and less repetitive, as you don't have to write separate catch blocks for each exception type.

It's worth noting that, the order of exception in catch block matters. You should place the most specific exception first and the most general one last, so if a catch block for a more specific exception is not found, the catch block for the more general exception will catch it.

PHP 7.0

Null coalescing operator

The null coalescing operator (??) is a shorthand way to check if a variable is set and not null in one step.

The null coalescing operator has the following syntax:

$result = $value1 ?? $value2;

This is equivalent to the following if-else statement:

$result = (isset($value1)) ? $value1 : $value2;

In simple words, the above statement checks whether $value1 is null or not set, if yes it assigns the value of $value2 to $result otherwise it assigns the value of $value1 to $result.

The ?? operator can also be chained to check multiple variables in one statement. For example:

$result = $value1 ?? $value2 ?? $value3 ?? $value4;

This will check $value1, $value2, $value3, and $value4 in order, and assign the first non-null value to $result.

It is a very useful operator when you want to assign default value in case the value is not set or null in a simple and readable way.