Loading...

Mastering PHP Union Types 2.0

Jayram Prajapati  ·   19 Jan 2024
Mastering PHP Union Types
service-banner

In PHP, union types are like a great new feature. They let programmers use a variable or function parameter that can have different types of values all at once. So, instead of saying a variable can only be a number or a string, it can be both with union types! Even though PHP already has some special union types, people wanted more flexibility. So, they started using phpdoc annotations (basically notes in the code) to achieve this flexibility.

We will discuss how union types work in PHP right now, what changes are being suggested to improve them, and how they can make our code easier to understand. It's like giving PHP a power-up for handling different data types more innovatively.

What is PHP Union 2.0?

The word "union" refers to a set operation that creates a new array with distinct members from both arrays by combining the values of the two arrays. The + operator is frequently used to execute the union operation in PHP.

Before PHP 8.0, we could only declare a single type for properties, parameters, and return types. But after release, union type 2.0 and newer versions have nullable types, which means we can declare the type null with a type declaration similar to ?string.

In PHP 8.0, we can declare multiple types for arguments, return types, and class properties. This allows for more flexibility and precision in defining the expected types of values. For example, we can now specify that a parameter can accept a string or an integer or that a property can hold either an array or null. This enhancement significantly improves the versatility and robustness of PHP code.

Types of PHP 2.0 Unions

In PHP, we already have two union types that help make coding well:

1. Type or null: A variable can be a specific type or null. We show this using the ?Type syntax.

2. array or Traversable: This is a bit technical, but it means a variable can be either a regular array or something that can be looped through (like an iterable). We use the ?iterable type to show this.

Before PHP 8 came out, if we wanted to say that a variable could be of different types, we had to use phpdoc annotations. It's like adding special notes in the code to explain what types a variable could have. Here's an example from a Request for Comments (RFC):

class Number {
	/**
	 * @var int|float $number
	 */
	private $number;
	/**
	 * @param int|float $number
	 */
	public function setNumber($number) {
		$this->number = $number;
	}
	/**
	 * @return int|float
	 */
	public function getNumber() {
		return $this->number;
	}
}

The Union Types 2.0 Request for Comments (RFC) is a significant step towards improving the support for union types in PHP. It proposes a shift in syntax, focusing on the T1|T2|... syntax, which simplifies the declaration process and reduces the cognitive load on developers. Here is the use of union 2.0

class Number {
	private int|float $number;
	public function setNumber(int|float $number): void {
		$this->number = $number;
	}
	public function getNumber(): int|float {
		return $this->number;
	}

Supported Union Types by PHP 8

Union types in PHP support all the types that PHP currently uses. However, there are a few things you should be aware of, which are explained below.

The void type is not accepted in a union

The void type, which suggests no return value, is a particular substance not included in unions. Attempts to involve void in union types, such as T|void, are prohibited in all circumstances, including return types. Developers are instead advised to use?T, which allows for the return of either T or null.

// Illegal use of void in a union type
function exampleFunction(): int|void {
    // Error: 'void' type cannot be part of a union type
}
// Instead, use nullable type to allow either int or null
function correctedFunction(): ?int {
}
Nullable Union Types:

Using null as part of unions, exemplified by T1|T2|null, provides a powerful mechanism for creating nullable unions. The familiar ?T notation is a shorthand for the typical T|null case. Although an alternative syntax, ?(T1|T2), was proposed, community feedback favoured the T1|T2|null notation, aligning with standard phpdoc comment conventions.

// Nullable union type using T1|T2|null notation
function processValue(string|int|null $value): void {
}
// Shorthand notation using ?T
function alternativeProcessValue(?string|int $value): void {
}
False Pseudo-Type Inclusion:

Considering previous practices, the false pseudo-type is included in union types to accommodate situations where false serves as a return value indicating an error or absence when the true pseudo-type is not present, as there aren't past justifications similar to those observed for false.

// Using false pseudo-type in a union type
function searchValue(string $haystack, string $needle): int|false {
    $position = strpos($haystack, $needle);
    // Returns int if found, false otherwise
    return $position;
}
Duplicate and Redundant Types:

The concept suggests implementing compile-time checks to improve code stability. This refers to avoiding duplicated types, such as preventing the repetition of name-resolved types. Additionally, it restricts certain combinations, like using bool with a false, object with class types, and iterable with both array and traversable.

You can't declare something like int|int or int|INT because they're identical. Also, you're not allowed to use int|?int.

Union types do not permit redundant class types except for certain exceptional cases. For instance:

1. bool|false is not valid because false is a type of bool.

2. Using an object with a class name is not allowed, as all class objects are already of type object.

3. iterable cannot be combined with array or Traversable because iterable is essentially a union type of array|Traversable.

It's worth noting that class names can be used in union types even if one class extends another. The validation of union-type declarations occurs at compile-time, so there won't be errors thrown for using both a parent class and its child class in a union, as resolving class hierarchies would be required.

Here are some examples to illustrate these points:

// Compile-time error for redundant types
function exampleFunction(int|string|int $value): void {
    // Error: Redundant types in union declaration
}
// Compile-time error for disallowed bool with false
function processResult(bool $success): bool|false {
    // Error: 'false' pseudo-type cannot be used with 'bool'
    return $success;
}
// Compile-time error for disallowed object with class types
function handleObject(object|MyClass $item): void {
    // Error: 'object' type cannot be used with class types
}
// Compile-time error for disallowed iterable with array and Traversable
function iterateItems(iterable|array|Traversable $items): void {
    // Error: 'iterable' type cannot be used with 'array' and 'Traversable'
}
Type Grammar:

The type grammar for union types in PHP follows the "type1|type2|type3..." where "|" is the separator between types.

Example:

function exampleFunction(int|string $param): void {
    // function logic
}

Variance in Union 2.0

In union-type variance, follow-through to the Liskov Substitution Principle (LSP) is maintained. This principle dictates that all subclasses and interface implementations must retain the program's behaviour and comply with the specified contract. This enforcement exists in PHP even without the explicit use of union types.

The following lists the methods used to manage variance:

  • Return types are covariant: The return type in a subclass or implementation can be a subtype of the return type declared in the parent class or interface. It allows for a more specific return type in the child class.
  • Parameter types are contravariant: In the context of union types, the parameter type in a subclass or implementation can be a supertype of the parameter type declared in the parent class or interface. It allows for a broader parameter type in the child class.
  • Property types are invariant: This means that the property type in a class remains unchanged and cannot be a subtype or supertype of the type declared in the parent class. The type remains fixed.

This dedication to variance principles helps maintain consistency and reliability in the operation of programs while allowing flexibility in certain aspects of type handling.

Property type Variance

In PHP, property types are invariant. This means types remain constant during inheritance. Legal changes were previously expressed using pseudo-classes.

Union types expand the possibilities for expressing the "same" type. For instance, int|string and string|int are the same type.

Check out the following example for more detail:

class A {}
class B extends A {}
class Test {
    public A|B $property;
}
class Test2 extends Test {
    public A $property;
}

In this example, the union type A|B is considered the same type as just A. Surprisingly, this inheritance is legal, even though the types are not syntactically identical.

Adding and removing union types

class Shape {
    public function calculateArea(float $side): float {}
}
class Square extends Shape {
    public function calculateArea(float|int $side): float {
        // Square can calculate area, but now accepts both float and int
        // ...
    }
}

In this example:

  • The parameter type in Square::calculateArea is widened to include both float and int.
  • This widening is acceptable because any program using class Square expects it to handle all types that class Shape accepts.
class Shape {
    public function calculateArea(float $side): float {}
}
class Square extends Shape {
    public function calculateArea(string $side): float {
        // Square's parameter type is changed to string, violating the contract
        // ...
    }
}

In this modified example:

  • The parameter type in Square::calculateArea is changed to string.
  • This change violates the contract established by class Shape, triggering a fatal error. In this case, class Square no longer fulfils the contract of class Shape.

Best Practices for Using Union Types 2.0

When working with union types in PHP 8, consider the following best practices:

  • Keep it simple: Limit the number of types in a union to two or three. Adding more types can make your code more challenging to read and comprehend.
  • Exercise Caution: While union types enhance code flexibility, they can also raise complexity. Use them judiciously, employing them only when they genuinely improve your code.
  • Maintain Consistency: If you use union types in your code, strive for consistency in declaration. This consistency contributes to improved code readability and ease of maintenance.

Providing clear and descriptive names for the union types is recommended to enhance code understanding. This can help other developers who may need to work with your code in the future

Advantages of Using Union Types 2.0

This addition allows for the migration of type information from PHPDoc into function signatures and unlocks many advantages that contribute to a more robust and error-free programming experience.

Enforcing Data Types for Early Error Detection:

One of the primary advantages of adding union types to the language is the ability to implement data types directly within function signatures. This move helps catch potential mistakes at an early stage of development, promoting a more confident coding experience and reducing the likelihood of runtime errors..

A-Pillar of Robust Software Systems:

In any resilient software system, having comprehensive type information is essential. By introducing union types, we elevate the robustness of our codebase. This enforced type of information minimises the risk of overlooking edge cases or working with outdated type specifications, thereby improving the overall reliability and predictability of the software..

Liskov Substitution Principle during Inheritance:

Union types play a pivotal role in maintaining adherence to the Liskov Substitution Principle during inheritance. The rigorous type-checking ensures that derived classes can substitute their base classes, contributing to a more structured and maintainable codebase.

Types Facilitate Reflection:

Union types bring an added layer of power to reflection, providing better access to types during runtime. This capability opens up new possibilities for dynamic and introspective programming, enabling developers to build more flexible and adaptable software systems.

Cleaner and more concise syntax:

Compared to PHPDoc, the syntax for union types is notably cleaner and more concise. By moving type information directly into function signatures, developers benefit from improved code readability and maintainability. This streamlined syntax facilitates a more straightforward understanding of the expected input types, reducing the cognitive load during code reviews and maintenance.

This change is a significant leap forward in providing PHP developers with highly flexible and expressive type systems. As we continue to support these advancements, we anticipate a more confident, error-resistant, and enjoyable programming journey in the PHP ecosystem. Stay tuned for the positive impact that union types will undoubtedly bring to our coding practices.

Conclusion:

The evolution of Union Types 2.0 in PHP has significantly impacted the Laravel development prospect. Union types, now an integral part of PHP, easily blend with Laravel's development ecosystem, offering Laravel developers an optimised toolkit to elevate their coding practices.

Laravel developers who understand union types better have a great tool to improve code readability, maintainability, and general robustness. Combining PHP's union types and Laravel development principles creates an environment that balances precision and efficiency.

Whether you're a skilled Laravel developer or just seeking the best Laravel development company, Elightwalk can help you with a team of experienced Laravel developers. Hire a Laravel developer from Elightwalk and experience the power of efficient Laravel development.

FAQs about Union Types 2.0

What is a union type in PHP?

How does a union type differ from other PHP data types?

Can a union type be applied to parameters in a PHP function?

Are there any limitations to using union types in PHP?

How can I declare a union type in PHP?

Jayram Prajapati
Full Stack Developer

Jayram Prajapati brings expertise and innovation to every project he takes on. His collaborative communication style, coupled with a receptiveness to new ideas, consistently leads to successful project outcomes.

Most Visited Blog

Constructor Property Promotion in PHP 8
Explore the power of Constructor Property Promotion in PHP 8 to streamline class property declaration and initialization. Learn the advantages and see an example demonstrating how to effortlessly add custom values during object creation for efficient coding practices.
The Power of Traits in PHP: Simplifying Common Property Injection

Discover the power of PHP Traits! Our guide unveils the potential of characteristics by simplifying property injection and providing a simplified solution to typical code difficulties.

Unlock The Power Of Plugins In Magento 2: A Comprehensive Guide

Get started on utilizing Magento 2 plugins with our comprehensive tutorial. Master the art of plugin building and integration to improve the functionality and customization of your e-commerce shop.