PHP 8.4 nieuwe features: een praktische deep-dive
PHP 8.4 is uitgebracht in november 2024 en brengt features die de manier waarop we enterprise applicaties bouwen fundamenteel veranderen. Property hooks elimineren boilerplate code, nieuwe array functies maken collectie-operaties expressiever, en lazy objects openen de deur naar betere performance. In dit artikel duiken we diep in deze features met productie-ready code voorbeelden.
Property hooks: getters en setters zonder boilerplate
Property hooks zijn de meest impactvolle toevoeging in PHP 8.4. Ze vervangen de traditionele getter/setter patronen en magic methods met een elegante, declaratieve syntax:
class Product
{
private array $priceHistory = [];
public string $sku {
set (string $value) {
if (!preg_match('/^[A-Z]{2}-\d{6}$/', $value)) {
throw new InvalidArgumentException('SKU must match format: XX-000000');
}
$this->sku = strtoupper($value);
}
}
public float $price {
set (float $value) {
if ($value < 0) {
throw new InvalidArgumentException('Price cannot be negative');
}
$this->priceHistory[] = ['price' => $value, 'date' => new DateTimeImmutable()];
$this->price = $value;
}
get => round($this->price, 2);
}
public float $priceWithVat {
get => $this->price * 1.21;
}
public private(set) string $createdBy;
public function __construct(string $sku, float $price, string $createdBy)
{
$this->sku = $sku;
$this->price = $price;
$this->createdBy = $createdBy;
}
}De private(set) syntax is asymmetric visibility: de property is publiek leesbaar maar alleen privé schrijfbaar. Dit elimineert de noodzaak voor aparte getter methods terwijl encapsulation behouden blijft.
Nieuwe array functies voor expressievere code
PHP 8.4 introduceert vier nieuwe array functies die veelvoorkomende patronen vereenvoudigen. In Magento en Pimcore context zijn deze bijzonder nuttig:
use Pimcore\Model\DataObject\Product;
class ProductService
{
public function findFirstInStock(array $products): ?Product
{
return array_find(
$products,
fn(Product $p) => $p->getStock() > 0
);
}
public function findDiscountedProductKey(array $products): string|int|null
{
return array_find_key(
$products,
fn(Product $p) => $p->getDiscount() > 0
);
}
public function hasOutOfStock(array $products): bool
{
return array_any(
$products,
fn(Product $p) => $p->getStock() === 0
);
}
public function allPublished(array $products): bool
{
return array_all(
$products,
fn(Product $p) => $p->getPublished()
);
}
public function getValidProducts(array $products): array
{
if (!array_all($products, fn($p) => $p instanceof Product)) {
throw new InvalidArgumentException('All items must be Products');
}
return array_filter(
$products,
fn(Product $p) => $p->getPublished() && $p->getStock() > 0
);
}
}Deze functies vervangen verbose constructies met array_filter + array_key_first of foreach loops. De intentie van de code wordt direct duidelijk.
Het #[Deprecated] attribuut voor API versioning
Het native #[Deprecated] attribuut maakt deprecation warnings onderdeel van je code in plaats van alleen docblocks. Dit integreert met IDEs en static analysis tools:
namespace Ten50\Module\Api;
class ProductApi
{
#[\Deprecated(
message: 'Use getProductBySku() instead',
since: '2.0'
)]
public function getProduct(int $id): ?array
{
trigger_deprecation(
'ten50/module',
'2.0',
'Method %s is deprecated, use %s instead',
__METHOD__,
'getProductBySku()'
);
return $this->productRepository->find($id)?->toArray();
}
public function getProductBySku(string $sku): ?array
{
return $this->productRepository->findBySku($sku)?->toArray();
}
#[\Deprecated('Bulk operations moved to BatchApi::importProducts()')]
public function bulkImport(array $products): ImportResult
{
return $this->batchApi->importProducts($products);
}
}
class LegacyProductController
{
#[\Deprecated(
message: 'This entire controller is deprecated. Use ProductApiController instead.',
since: '1.5'
)]
public function __construct(
private ProductApi $api
) {}
}PHPStan en Psalm herkennen het attribuut automatisch. IDEs tonen doorgestreepte methods en warnings bij gebruik. Dit maakt gefaseerde API migraties beheersbaar.
Lazy objects voor betere performance
Lazy objects initialiseren pas wanneer ze daadwerkelijk gebruikt worden. Dit is krachtig voor dependency injection en service containers:
namespace Ten50\Module\Service;
class ExpensiveReportService
{
private ?ReportGenerator $generator = null;
private ?DataAggregator $aggregator = null;
public function __construct(
private readonly ReportGeneratorFactory $generatorFactory,
private readonly DataAggregatorFactory $aggregatorFactory
) {}
private function getGenerator(): ReportGenerator
{
return $this->generator ??= $this->generatorFactory->create();
}
public function generateReport(array $criteria): Report
{
return $this->getGenerator()->generate($criteria);
}
}
class LazyServiceContainer
{
private array $services = [];
private array $factories = [];
public function register(string $id, callable $factory): void
{
$this->factories[$id] = $factory;
}
public function get(string $id): object
{
if (!isset($this->services[$id])) {
if (!isset($this->factories[$id])) {
throw new ServiceNotFoundException($id);
}
$reflector = new ReflectionClass($this->factories[$id]());
$this->services[$id] = $reflector->newLazyGhost(
function (object $instance) use ($id) {
$real = ($this->factories[$id])();
foreach ((new ReflectionObject($real))->getProperties() as $prop) {
$prop->setAccessible(true);
$prop->setValue($instance, $prop->getValue($real));
}
}
);
}
return $this->services[$id];
}
}Met ReflectionClass::newLazyGhost() en newLazyProxy() kun je objecten maken die pas initialiseren bij eerste property access of method call. In een Magento module met 50+ dependencies kan dit de bootstrap tijd significant reduceren.
PHP 8.4 migratie nodig?
Wij helpen bij het upgraden van uw applicatie naar PHP 8.4 en het implementeren van moderne PHP patterns.
Neem contact op