PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

PHP 8.4 last RC – Lazy Objects, BcMath\Number, Dom\HTMLDocument

by Henryk Tews / Tuesday, 03 September 2024 / Published in PHP

PHP 8.4 final RC is here with three features that did not get enough attention in the preview posts. Lazy Objects let the DI container defer expensive object construction until first use – without manually writing Proxy classes. BcMath\Number finally gives PHP a clean API for arbitrary precision arithmetic. Dom\HTMLDocument brings proper HTML5 parsing to PHP’s DOM extension. I show all three in code.

Lazy Objects – framework-grade proxies without Proxy classes

<?php

declare(strict_types=1);

// The problem Lazy Objects solve:
class HeavyService
{
    private array $cache = [];

    public function __construct(
        private \Magento\Framework\App\ResourceConnection $resourceConnection
    ) {
        // Imagine this loads a large config from DB on construction
        // Every bin/magento list triggers this even if we never use this service
        $this->preloadCache();
    }

    private function preloadCache(): void
    {
        // Expensive: hits the database
        $this->cache = ['config' => 'loaded'];
    }

    public function doWork(): string { return 'result'; }
}

// PHP 8.4 Lazy Objects via Reflection
$reflector = new \ReflectionClass(HeavyService::class);

// Create a lazy ghost - the object is not initialised until first method/property access
$lazyService = $reflector->newLazyGhost(
    function(HeavyService $instance) use ($resourceConnection): void {
        // This initialiser runs ONLY when the object is first accessed
        // equivalent to what Magento's generated \Proxy classes do
        $instance->__construct($resourceConnection);
    }
);

// At this point: no DB query has run, no memory for cache array
$result = $lazyService->doWork(); // NOW it initialises, NOW the DB query runs
<?php

// Lazy Proxy - slightly different: initialises a separate real instance
$reflector = new \ReflectionClass(HeavyService::class);

$lazyProxy = $reflector->newLazyProxy(
    function(HeavyService $proxy) use ($resourceConnection): HeavyService {
        // Returns the real object that the proxy delegates to
        return new HeavyService($resourceConnection);
    }
);

// Use case in DI container:
// Instead of generating vendor/code/.../HeavyService/Proxy.php
// the DI container can use Lazy Objects directly - no generated files needed

// Current Magento approach (di.xml):
// HeavyService\Proxy
// Generated class: generated/code/Vendor/Module/Model/HeavyService/Proxy.php

// Future approach (PHP 8.4 native Lazy Objects):
// DI container creates a lazy ghost - zero generated code

BcMath\Number – OOP arbitrary precision arithmetic

<?php

declare(strict_types=1);

// Before PHP 8.4 - bcmath procedural functions
$price    = '99.99';
$taxRate  = '0.23';
$quantity = '3';

$subtotal = bcmul($price, $quantity, 2);           // '299.97'
$tax      = bcmul($subtotal, $taxRate, 2);         // '68.99'
$total    = bcadd($subtotal, $tax, 2);             // '368.96'

// PHP 8.4 - BcMath\Number OOP interface
use BcMath\Number;

$price    = new Number('99.99');
$taxRate  = new Number('0.23');
$quantity = new Number('3');

$subtotal = $price * $quantity;          // Number('299.97')
$tax      = $subtotal * $taxRate;        // Number('68.9931') - full precision
$total    = $subtotal + $tax;            // Number('368.9631')
$rounded  = $total->round(2);            // Number('368.96')

echo $rounded; // '368.96'

// Operator overloading - BcMath\Number supports +, -, *, /, **, %
$discount = new Number('0.10'); // 10%
$discounted = $price * (new Number('1') - $discount); // 99.99 * 0.90 = 89.991

// Comparison operators also work
$min = new Number('0.01');
if ($price > $min) { echo "Price is above minimum\n"; }

// Practical: financial calculations in Magento modules
class TaxCalculator
{
    public function calculate(string $netAmount, string $taxRate): array
    {
        $net = new Number($netAmount);
        $rate = new Number($taxRate);

        $taxAmount = ($net * $rate)->round(2);
        $gross     = ($net + $taxAmount)->round(2);

        return [
            'net'   => (string) $net,
            'tax'   => (string) $taxAmount,
            'gross' => (string) $gross,
        ];
    }
}

Dom\HTMLDocument – proper HTML5 parsing

<?php

declare(strict_types=1);

// Before PHP 8.4 - DOMDocument with libxml quirks
$dom = new \DOMDocument();
@$dom->loadHTML('<main><article><p>Hello</p></article></main>');
// Wraps in html/body tags, mishandles many HTML5 elements

// PHP 8.4 - Dom\HTMLDocument with proper HTML5 parser (libgumbo)
$doc = \Dom\HTMLDocument::createFromString(
    '<main><article><p>Hello</p></article></main>',
    LIBXML_NOERROR
);

// querySelector support
$article = $doc->querySelector('article');
$p = $doc->querySelector('article p');
echo $p->textContent; // 'Hello'

$allLinks = $doc->querySelectorAll('a[href]');

// Practical: parsing product descriptions for SEO audit
function extractHeadings(string $html): array
{
    $doc = \Dom\HTMLDocument::createFromString($html, LIBXML_NOERROR);
    $headings = [];

    foreach (['h1', 'h2', 'h3', 'h4'] as $tag) {
        foreach ($doc->querySelectorAll($tag) as $node) {
            $headings[$tag][] = trim($node->textContent);
        }
    }

    return $headings;
}

$productDescription = '<h2>Features</h2><p>...</p><h3>Technical specs</h3>...';
$headings = extractHeadings($productDescription);
// ['h2' => ['Features'], 'h3' => ['Technical specs']]

Summary

PHP 8.4’s lesser-known features have real practical value. Lazy Objects are a framework-level feature that will improve DI container performance once frameworks adopt them – no more generated Proxy files for deferred initialisation. BcMath\Number makes financial calculations readable and less error-prone with operator overloading on arbitrary precision numbers. Dom\HTMLDocument’s querySelector support is something frontend developers have had for years; now PHP has it too with proper HTML5 parsing.

About Henryk Tews

What you can read next

PHP 7.2 – object type hint, sodium instead of mcrypt, deprecations

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}