| name | php-modernization-php84 |
| description | PHP 8.4 specific features, deprecations, and migration patterns. Property hooks, asymmetric visibility, new array functions, and breaking changes. |
| version | 1.0.0 |
| php_compatibility | 8.4+ |
| related_skills | ["php-modernization"] |
| triggers | ["php 8.4","php84","property hooks","asymmetric visibility","array_find","array_any","array_all","deprecated attribute","php upgrade"] |
PHP 8.4 Modernization Guide
Compatibility: PHP 8.4+ (released November 2024)
Related Skill: php-modernization - General PHP 8.x patterns
TYPO3: v14 supports PHP 8.4, v13 supports PHP 8.2-8.4
This skill covers PHP 8.4 specific features, deprecations, and migration patterns.
1. Property Hooks (RFC: Property Hooks)
The most significant PHP 8.4 feature. Define get and set hooks directly on properties.
Basic Property Hooks
<?php
declare(strict_types=1);
class User
{
private string $email;
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): void
{
$this->email = strtolower(trim($email));
}
}
class User
{
public string $email {
get => $this->email;
set (string $value) => $this->email = strtolower(trim($value));
}
}
Virtual Properties (No Backing Store)
<?php
declare(strict_types=1);
class Rectangle
{
public function __construct(
public int $width,
public int $height,
) {}
public int $area {
get => $this->width * $this->height;
}
}
$rect = new Rectangle(10, 20);
echo $rect->area;
Validation with Set Hooks
<?php
declare(strict_types=1);
class Product
{
public string $sku {
set (string $value) {
if (!preg_match('/^[A-Z]{3}-\d{4}$/', $value)) {
throw new \InvalidArgumentException('Invalid SKU format');
}
$this->sku = $value;
}
}
public float $price {
set (float $value) {
if ($value < 0) {
throw new \InvalidArgumentException('Price cannot be negative');
}
$this->price = $value;
}
}
}
Lazy Initialization
<?php
declare(strict_types=1);
class ExpensiveService
{
private ?Connection $connection = null;
public Connection $db {
get => $this->connection ??= $this->createConnection();
}
private function createConnection(): Connection
{
return new Connection();
}
}
Property Hooks in Interfaces
<?php
declare(strict_types=1);
interface HasFullName
{
public string $fullName { get; }
}
class Person implements HasFullName
{
public function __construct(
public string $firstName,
public string $lastName,
) {}
public string $fullName {
get => $this->firstName . ' ' . $this->lastName;
}
}
2. Asymmetric Visibility
Control read and write visibility separately.
Public Read, Private Write
<?php
declare(strict_types=1);
class Counter
{
private int $value = 0;
public function getValue(): int
{
return $this->value;
}
public function increment(): void
{
$this->value++;
}
}
class Counter
{
public private(set) int $value = 0;
public function increment(): void
{
$this->value++;
}
}
$counter = new Counter();
echo $counter->value;
$counter->value = 5;
Protected Set
<?php
declare(strict_types=1);
class Entity
{
public protected(set) int $id;
public protected(set) \DateTimeImmutable $createdAt;
public function __construct()
{
$this->createdAt = new \DateTimeImmutable();
}
}
class User extends Entity
{
public function setId(int $id): void
{
$this->id = $id;
}
}
Readonly Alternative
<?php
declare(strict_types=1);
final readonly class UserDTO
{
public function __construct(
public int $id,
public string $name,
) {}
}
final class UserDTO
{
public function __construct(
public private(set) int $id,
public private(set) string $name,
) {}
}
3. New Array Functions
array_find()
Find the first element matching a callback.
<?php
declare(strict_types=1);
$users = [
['id' => 1, 'name' => 'Alice', 'active' => false],
['id' => 2, 'name' => 'Bob', 'active' => true],
['id' => 3, 'name' => 'Charlie', 'active' => true],
];
$activeUser = null;
foreach ($users as $user) {
if ($user['active']) {
$activeUser = $user;
break;
}
}
$activeUser = array_find($users, fn($user) => $user['active']);
array_find_key()
Find the key of the first matching element.
<?php
declare(strict_types=1);
$products = [
'apple' => ['price' => 1.50, 'stock' => 100],
'banana' => ['price' => 0.75, 'stock' => 0],
'cherry' => ['price' => 3.00, 'stock' => 50],
];
$outOfStockKey = array_find_key($products, fn($p) => $p['stock'] === 0);
array_any()
Check if ANY element matches a condition.
<?php
declare(strict_types=1);
$numbers = [1, 2, 3, 4, 5];
$hasEven = false;
foreach ($numbers as $n) {
if ($n % 2 === 0) {
$hasEven = true;
break;
}
}
$hasEven = array_any($numbers, fn($n) => $n % 2 === 0);
array_all()
Check if ALL elements match a condition.
<?php
declare(strict_types=1);
$prices = [10.00, 25.50, 15.00, 30.00];
$allPositive = true;
foreach ($prices as $price) {
if ($price <= 0) {
$allPositive = false;
break;
}
}
$allPositive = array_all($prices, fn($price) => $price > 0);
Practical TYPO3 Examples
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\Service;
use Vendor\MyExtension\Domain\Model\Product;
final class ProductService
{
public function findFirstAvailable(array $products): ?Product
{
return array_find($products, fn(Product $p) => $p->isInStock());
}
public function hasAnyOnSale(array $products): bool
{
return array_any($products, fn(Product $p) => $p->isOnSale());
}
public ():
{
(, fn(Product ) => ->()->() > );
}
}
4. #[\Deprecated] Attribute
Mark code as deprecated with structured metadata.
Basic Usage
<?php
declare(strict_types=1);
#[\Deprecated(
message: 'Use NewService instead',
since: '2.0.0'
)]
class OldService
{
#[\Deprecated('Use processV2() instead', since: '2.0.0')]
public function process(): void
{
}
}
Deprecating Constants and Properties
<?php
declare(strict_types=1);
class Configuration
{
#[\Deprecated('Use DEFAULT_TIMEOUT instead', since: '1.5.0')]
public const TIMEOUT = 30;
public const DEFAULT_TIMEOUT = 30;
#[\Deprecated('Access via getSettings() instead')]
public array $settings = [];
}
Conditional Deprecation
<?php
declare(strict_types=1);
class LegacyAdapter
{
#[\Deprecated(
message: 'This method will be removed in v3.0. Use the new API.',
since: '2.5.0'
)]
public function legacyMethod(): mixed
{
trigger_error(
'legacyMethod() is deprecated, use newMethod() instead',
E_USER_DEPRECATED
);
return $this->newMethod();
}
public function newMethod(): mixed
{
}
}
5. New in Initializers Enhancement
Use new expressions in more places.
<?php
declare(strict_types=1);
class Service
{
public Logger $logger = new NullLogger();
public function __construct(
private CacheInterface $cache = new ArrayCache(),
private LoggerInterface $logger = new NullLogger(),
) {}
}
class Config
{
public static Options $defaults = new Options(timeout: 30, retries: 3);
}
6. Deprecations & Breaking Changes
Implicit Nullable Types Deprecated
<?php
declare(strict_types=1);
function process(string $value = null): void {}
function process(?string $value = null): void {}
function process(string|null $value = null): void {}
E_STRICT Removed
E_STRICT error level is removed. All former E_STRICT notices are now regular notices or warnings.
Underscore as Class Name Reserved
<?php
class _ {}
class _Helper {}
class My_Class {}
Deprecated Functions
<?php
$result = strtoupper(implode('', $array));
7. Other PHP 8.4 Features
Improved HTML5 Parsing
<?php
declare(strict_types=1);
$doc = DOM\HTMLDocument::createFromString($html);
$doc = DOM\HTMLDocument::createFromFile('page.html');
BCMath Improvements
<?php
declare(strict_types=1);
use BcMath\Number;
$a = new Number('123.456');
$b = new Number('789.012');
$sum = $a + $b;
PDO Driver-Specific Subclasses
<?php
declare(strict_types=1);
$pdo = new Pdo\Mysql($dsn, $user, $pass);
$pdo = new Pdo\Sqlite($dsn);
$pdo = new Pdo\Pgsql($dsn, $user, $pass);
8. Migration Checklist for PHP 8.4
Pre-Migration
Code Updates
Rector Rules for PHP 8.4
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/Classes',
])
->withSets([
LevelSetList::UP_TO_PHP_84,
]);
PHPStan for PHP 8.4
# phpstan.neon
parameters:
phpVersion: 80400
level: 10
composer.json Update
{
"require": {
"php": "^8.4"
},
"config": {
"platform": {
"php": "8.4.0"
}
}
}
9. TYPO3 v14 & PHP 8.4
TYPO3 v14 officially supports PHP 8.4 and uses several new features:
Core Usage Examples
<?php
declare(strict_types=1);
class SiteConfiguration
{
public string $identifier {
get => $this->identifier;
set (string $value) {
if (!preg_match('/^[a-z][a-z0-9_-]*$/', $value)) {
throw new \InvalidArgumentException('Invalid site identifier');
}
$this->identifier = $value;
}
}
}
Extension Compatibility
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'My Extension',
'constraints' => [
'depends' => [
'typo3' => '13.0.0-14.99.99',
'php' => '8.2.0-8.4.99',
],
],
];
References
Credits & Attribution
This skill is part of the webconsulting.at TYPO3 skills collection.
Based on official PHP 8.4 RFCs and documentation.