PHP 8.0 was officially released on 26 November 2020. I wrote about the previews in July – now that I have the final version, it is time for first practical impressions. I check what works as promised, what surprised me, and what to watch out for when migrating older projects.
Match expression – I use it immediately
<?php
declare(strict_types=1);
// Old code - switch was silent on an unknown status
function getStatusLabel(int $status): string
{
switch ($status) {
case 1: return 'Active';
case 2: return 'Inactive';
// forgot case 3 - switch returns null, code continues silently
}
}
// PHP 8.0 - match throws UnhandledMatchError on unknown status
function getStatusLabel(int $status): string
{
return match($status) {
1 => 'Active',
2 => 'Inactive',
3 => 'Pending verification',
default => throw new \InvalidArgumentException("Unknown status: {$status}"),
};
}
Named Arguments in practice – code readability
<?php // Who remembers the parameter order of array_slice? $page = array_slice($items, offset: ($currentPage - 1) * $pageSize, length: $pageSize); // str_contains, str_starts_with, str_ends_with - new in PHP 8.0 $sku = 'MG-RED-XL-42'; if (str_starts_with($sku, 'MG-')) echo 'Magento product'; if (str_ends_with($sku, '-42')) echo 'Size 42'; if (str_contains($sku, '-RED-')) echo 'Colour: red'; // Replaces: strpos($sku, '-RED-') !== false // Clearly more readable
str_contains, str_starts_with, str_ends_with – underrated additions
<?php
// Old approach - unreadable
if (strpos($url, '/admin') === 0) { /* ... */ }
if (strpos($email, '@example.com') !== false) { /* ... */ }
if (substr($filename, -4) === '.php') { /* ... */ }
// PHP 8.0 - intent is clear immediately
if (str_starts_with($url, '/admin')) { /* ... */ }
if (str_contains($email, '@example.com')) { /* ... */ }
if (str_ends_with($filename, '.php')) { /* ... */ }
Constructor Promotion in Magento 2 – proceed with care
<?php
declare(strict_types=1);
// Safe - DTO/Value Object outside DI
class OrderExportDto
{
public function __construct(
public readonly int $orderId,
public readonly string $status,
public readonly float $grandTotal,
public readonly \DateTimeImmutable $createdAt
) {}
}
// Caution - class injected by Magento DI
// Keep the old style for safety on older Magento versions
class OrderExportService
{
private OrderRepositoryInterface $orderRepository;
private \Psr\Log\LoggerInterface $logger;
public function __construct(
OrderRepositoryInterface $orderRepository,
\Psr\Log\LoggerInterface $logger
) {
$this->orderRepository = $orderRepository;
$this->logger = $logger;
}
}
Nullsafe operator – practical use
<?php
// Real Magento usage - method chains that can return null
$countryId = $order?->getShippingAddress()?->getCountryId();
$customerGroupCode = $quote?->getCustomer()?->getGroupId();
// Note: nullsafe does not replace exception handling
// getById() throws NoSuchEntityException, it does not return null
try {
$product = $productRepository->getById(42);
$price = $product->getPrice();
} catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
$price = 0.0;
}
Summary
PHP 8.0 is the best PHP version ever. Match, named arguments, str_contains and the nullsafe operator enter code immediately. Union types and constructor promotion need more thought when integrating with Magento. Migration from PHP 7.4 is painless if the project does not use deprecated functions – run PHP Compatibility Checker before upgrading.
