As we prepare for the official release a month away, let's deep dive into all the anticipated new features of the PHP 8.1 release.
PHP 8.1 Enums
Enumerations (enums) is a data type that has a fixed number of possible values. This is a feature that was needed for a long time in the community and it has now arrived.
If you have no idea how enums would work, imagine how you normally represent the status of an order in an e-commerce application. The status could be PENDING, CONFIRMED, SHIPPED or DELIVERED.
With enums it will be easier to read and also maintain these constant values:
use App\Enums\OrderStatus;
// Normally you would do this
if ($order->status == 2) {
// ...
}
// But with enums
if ($order->status == OrderStatus::confirmed) {
// ...
}
Creating enums
When defining enums, you are going to declare all the possible cases using the case
keyword:
enum OrderStatus
{
case PENDING;
case CONFIRMED;
case CANCELLED;
case SHIPPED;
case DELIVERED;
}
This is the most basic form of an enum. Any order can be initialized with an order status:
use App\Enums\OrderStatus;
class Order
{
public function __construct(
public OrderStatus $status,
) {}
}
$order = new Order(OrderStatus::PENDING);
Since enums are represented by objects, we can make them have values and as a result this will help in serialization in the database. To do that, let's make use of backed enums (enums with values):
enum OrderStatus: int
{
case PENDING = 1;
case CONFIRMED = 2;
case CANCELLED = 3;
case SHIPPED = 4;
case DELIVERED = 5;
}
Now you can retrieve the value and store it in the database. For every enum, there is a value
read-only property:
$valueToSerialize = OrderStatus::SHIPPED->value; // 4
Conversely, you can obtain the status from the value using the from
and tryFrom
methods:
$statusObject = OrderStatus::from(4); // OrderStatus::SHIPPED
$statusObject = OrderStatus::tryFrom(90); // null if the value is unknown. Unlike from, it won't throw an exception.
Methods in enums
One of the most powerful features of enums is the ability to use custom methods in them. A perfect example would be to make use of the PHP 8.0 match
method to find the progress of status:
enum OrderStatus: int
{
case PENDING = 1;
case CONFIRMED = 2;
case CANCELLED = 3;
case SHIPPED = 4;
case DELIVERED = 5;
public function progress(): float
{
return match($this)
{
self::PENDING => 0.1,
self::CONFIRMED => 0.5,
self::CANCELLED => 0,
self::SHIPPED => 0.75,
self::DELIVERED => 1,
};
}
}
Now you can find the progress of the status by chaining the progress property:
$progress = OrderStatus::SHIPPED->progress();
// Or by using the serialized value
$progress = OrderStatus::tryFrom(4)?-> progress();
Readonly Properties
In PHP 8.1 you can define class properties as readonly
, meaning once they are written they can't be changed:
class User {
public function __construct(
public readonly string $name,
public readonly DateTimeImmutable $birthday,
) {}
}
The next time you want to change the values of the name or birthday, you will encounter an error. Note, for a property to be readonly
, it must have a type declared.
new
in Initializers
When initializing default values, it is now possible to use the new keyword to cleanup your function definitions and arguments:
class OrderRepository
{
// Instead of this
public function save(array $arguments, Order $order = null)
{
$order ??= new Order;
$order->fill($arguments)->save();
return $order;
}
// We do this
public function save(array $arguments, Order $order = new Order)
{
$order->fill($arguments)->save();
return $order;
}
}
Pure Intersection Types
We've already seen how union types worked in PHP 8.0, where either of the type belongs to the parameter or return value.
With intersection types, all of the declared types must belong to the parameter or return value:
public function notify(Authenticatable & Notifiable $user)
{
// ...
}
Array Unpacking with String Keys
This is my second favorite feature in this release. After introducing array unpacking (only with numeric keys) in PHP 7.4, I found myself constantly trying to achieve the same with string keys only to realize it's still not possible.
$array1 = ["a" => 1];
$array2 = ["b" => 2];
$array = ["a" => 0, ...$array1, ...$array2];
var_dump($array); // ["a" => 1, "b" => 2] Since there are two "a" keys, the last one will stand
First Class Callable Syntax
Starting PHP 7.1, it was possible to make a function like strlen
a closure using the format below:
use \Closure;
$fn = Closure::fromCallable('strlen');
$fn('hey there'); // 9
With this new syntax intoduced, we can shorten it to:
$fn = strlen(...);
$fn('hey there'); // 9
This is not limited to function names as we can see below:
// class methods
$fn = $object->method(...);
// static methods
$fn = $object::method();
// anonymous functions
function squareRoot() {};
$fn = squareRoot(...);
never
Return Type
The new never
type can be used to indicate the flow of a program will stop, unlike the void
which allows continuation of a program.
This is achievable if there's no return value and if an exception is thrown or program ends with exit
/ die
terminators, otherwise PHP will throw a TypeError
:
function dump($value): never
{
dd($value);
exit();
}
New array_is_list
Fuction
This new function checks if the keys of the array are ordered numerically starting from the 0 index:
$list1 = ["a", "b", "c"];
array_is_list($list1); // true
$list2 = [1 => "a", 2 => "b", 3 => "c"];
array_is_list($list2); // false
Final Class Constants
In PHP 8.1 its is now possible to prevent overriding constants by inheritance using final
constants:
class Foo
{
final public const X = "foo";
}
class Bar extends Foo
{
public const X = "bar";
// Fatal error: Bar::X cannot override final constant Foo::X
}
These are some of the interesting features of the upcoming release. One thing to take note before migrating in the future are the deprecations.
Happy PHP-ing ✨
Join to participate in the discussion