Property hooks, new array functions, the DOM API and more at a glance
PHP 8.4 bundles an unusually high number of language features into a single release: from property hooks to four new array functions to a rewritten DOM API. This overview places every one of the new PHP 8.4 features in context, shows real code, and flags what can actually break when upgrading to PHP 8.4.
Table of Contents
- 1. Why PHP 8.4 is an important release
- 2. Property hooks at a glance
- 3. Asymmetric visibility at a glance
- 4. New array functions: array_find, array_any, array_all, array_find_key
- 5. The #[\Deprecated] attribute for your own APIs
- 6. The new HTML5-compliant DOM API
- 7. Object instantiation with direct method calls
- 8. Performance improvements and JIT changes
- 9. Breaking changes and migration notes
- 10. Summary
- 11. FAQ
1. Why PHP 8.4 is an important release
PHP has followed a fixed yearly release cadence since PHP 8.0: a new minor version ships every November. PHP 8.4 was released on 21 November 2024, extending the PHP 8.x series, which by now spans several years of development. Every version receives two years of active support with bug fixes and security updates, followed by one additional year during which only critical security issues are patched. For PHP 8.4 that means active support until November 2026 and security-only support until November 2027. Anyone running production Magento or Symfony projects should know this timeline, because it determines migration pressure for the coming years.
Unlike many earlier minor releases, PHP 8.4 ships an unusually large number of language features. Property hooks and asymmetric visibility fundamentally change how classes will be written going forward. New array functions close gaps that developers have worked around for years with array_filter and array_values combinations. The new #[\Deprecated] attribute brings deprecation handling directly into the language itself instead of leaving it to docblocks. Taken together, this shows that the PHP 8.4 features are not just a maintenance package, they noticeably change the day-to-day code PHP developers write, and that is exactly why a structured overview is worthwhile before diving into individual topics.
For teams running production code, compatibility matters just as much as new capabilities. PHP 8.4 surfaces some silent legacy baggage from PHP 5 and PHP 7 eras, such as implicit nullable parameters or outdated DOM usage, and turns them into explicit deprecation warnings. Anyone planning to move to PHP 8.4 over the next years should therefore keep an eye not only on the new PHP 8.4 features, but also on which code paths will surface first during the upgrade.
2. Property hooks at a glance
Property hooks are among the most discussed new PHP 8.4 features: they let you declare get and set logic directly on a property, without needing classic getFoo()/setFoo() method pairs or magic __get()/__set() implementations. A property can therefore be computed, validate what is written to it, or transparently delegate to another property, while access from the outside still looks like a plain property.
Because property hooks are a topic of their own, with virtual properties, backing fields and their interplay with interfaces, this overview article deliberately does not dive into the syntax details. A dedicated deep-dive article on this blog covers property hooks including backing fields and inheritance in depth. For this overview, it is enough to note: property hooks are among the most important new PHP 8.4 features and noticeably reduce boilerplate in entities and DTOs.
3. Asymmetric visibility at a glance
Asymmetric visibility complements property hooks with a smaller, but equally influential addition: a property can now have a different visibility for reads than for writes, for example public for reading from the outside, but private(set) for writing only from within the class itself. This removes a very common boilerplate pattern: a private property plus a public getter method, just to allow controlled read access alongside internal write access.
Asymmetric visibility is also deliberately only introduced here, not fully explored. Its combination with readonly, constructor promotion and inheritance is the topic of a dedicated deep-dive article on this blog. For this overview of the PHP 8.4 features, it is enough to note: asymmetric visibility makes domain models and value objects more readable without forcing extra getter methods.
4. New array functions: array_find, array_any, array_all, array_find_key
Among the new PHP 8.4 features, the four new array functions array_find(), array_any(), array_all() and array_find_key() are arguably the ones used most often in everyday code. They solve a problem PHP developers have worked around for years: finding the first element matching a condition without combining array_filter() with a subsequent array_values() and index access. array_find() returns the first value for which the callback returns true, or null if no element matches.
array_any() and array_all() check whether at least one or all elements satisfy a condition respectively, and stop iterating as soon as the result is determined: array_any() stops at the first match, array_all() stops at the first non-match. array_find_key() mirrors array_find() but returns the key of the first matching element instead of the value, which is especially useful for associative arrays with meaningful keys. All four functions expect a callback with the signature function(mixed $value, int|string $key): bool, the key parameter is optional.
The advantage over the old array_filter()+array_values() pattern is not just readability but also performance: array_filter() always iterates the entire array and builds a new intermediate array, while array_find() and array_any() stop at the first match. For large collections, such as product lists in a Magento catalog import or order histories, this shows up as lower memory usage and less CPU time.
declare(strict_types=1);
final class OrderService
{
/**
* @param array<int, Order> $orders
*/
public function __construct(private readonly array $orders) {}
public function firstOverdue(): ?Order
{
// Old approach before PHP 8.4: array_filter + array_values + index access
// $overdue = array_values(array_filter($this->orders, fn (Order $o) => $o->isOverdue()));
// return $overdue[0] ?? null;
// PHP 8.4: array_find stops at the first match, no intermediate array
return array_find(
$this->orders,
fn (Order $order): bool => $order->isOverdue()
);
}
public function hasOverdueOrder(): bool
{
return array_any($this->orders, fn (Order $o) => $o->isOverdue());
}
public function allOrdersPaid(): bool
{
return array_all($this->orders, fn (Order $o) => $o->isPaid());
}
public function firstOverdueOrderId(): int|string|null
{
return array_find_key($this->orders, fn (Order $o) => $o->isOverdue());
}
}
5. The #[\Deprecated] attribute for your own APIs
With the #[\Deprecated] attribute, PHP gets a language-native way to mark functions, methods, class constants and enum cases as deprecated, instead of relying solely on the @deprecated docblock tag. The syntax accepts two optional named parameters: message for a human-readable explanation of what to use instead, and since for the version number from which the marking applies. When a marked function or method is called, PHP emits an E_DEPRECATED warning at runtime at the call site, not at the definition site.
The practical difference from a plain docblock comment is tooling support: IDEs such as PhpStorm and static analyzers such as PHPStan or Psalm can reliably evaluate the attribute without parsing docblock text, and mark affected calls as struck through directly in the editor. This reduces the risk that a method marked as deprecated keeps being used by accident because the docblock note was overlooked.
For migrating existing codebases: existing @deprecated docblocks do not need to be removed immediately, both mechanisms can coexist. It makes sense to adopt the native attribute directly for new code and gradually backfill existing docblock markers, because only the attribute produces an actual runtime warning and is machine-checkable by tooling. For library and API maintainers, this is one of the most practically useful new PHP 8.4 features, because it makes deprecation cycles significantly more reliable.
declare(strict_types=1);
final class PriceCalculator
{
#[\Deprecated(
message: 'use calculateNet() instead, calculate() will be removed in a future major version',
since: '8.4'
)]
public function calculate(float $gross, float $taxRate): float
{
return $this->calculateNet($gross, $taxRate);
}
public function calculateNet(float $gross, float $taxRate): float
{
return $gross / (1 + $taxRate);
}
}
$calculator = new PriceCalculator();
$calculator->calculate(119.0, 0.19); // triggers E_DEPRECATED at the call site, not at the definition
6. The new HTML5-compliant DOM API
PHP 8.4 introduces a completely new DOM API under the Dom\ namespace: Dom\HTMLDocument for HTML documents and Dom\XMLDocument for XML. Both classes parse documents according to the actual WHATWG and HTML5 specifications, instead of relying on the noticeably more lenient and inconsistent HTML parsing of libxml2 the way the old DOMDocument class does. Malformed or incomplete real-world HTML, such as unclosed tags or incorrect nesting, is therefore processed more predictably.
A second, very practical difference: the new classes ship with querySelector() and querySelectorAll() built in. This lets you find elements using real CSS selectors, without having to write cumbersome XPath expressions for simple queries, which DOMDocument used to force. For tasks like extracting prices or product data from HTML fragments, for example when parsing supplier feeds, this is a noticeable productivity gain.
The old DOMDocument class is not removed and remains usable for existing code, but for new code it is advisable to switch to the new DOM API once PHP 8.4 becomes the minimum supported version. Especially in content-heavy Magento or CMS projects, where HTML fragments are post-processed server-side, the new DOM API is one of the underrated PHP 8.4 features, because it improves both correctness and readability.
declare(strict_types=1);
use Dom\HTMLDocument;
$fragment = <<<'HTML'
<div class="product" data-sku="MS-1001">
<p class="title">Hyva Theme Bundle</p>
<span class="price">129.00 EUR</span>
</div>
HTML;
// New in PHP 8.4: HTML5-compliant parsing, no libxml quirks-mode guessing
$document = HTMLDocument::createFromString($fragment, LIBXML_NOERROR);
// querySelector/querySelectorAll: real CSS selectors, no XPath required
$priceNode = $document->querySelector('.product .price');
echo $priceNode?->textContent; // 129.00 EUR
foreach ($document->querySelectorAll('.product') as $product) {
echo $product->getAttribute('data-sku') . PHP_EOL;
}
7. Object instantiation with direct method calls
Up to and including PHP 8.3, a freshly instantiated object had to be wrapped in parentheses to call a method on it directly: (new Foo())->bar(). These parentheses were a pure syntax requirement carrying no semantic meaning, yet practically every PHP developer forgot them at some point and hit a parse error as a result. PHP 8.4 now allows new Foo()->bar() without the surrounding parentheses, removing this constant small stumbling block.
The new syntax works not only for method calls but equally for property access, accessing class constants through an instance, and array access on the result of a new expression. It also works with chained calls and constructor arguments, while changing only the syntax, not the semantics: new Foo()->bar() behaves exactly the same as (new Foo())->bar(), just without the parentheses.
Existing code does not need to change, the parenthesized form remains valid and most codebases will keep using it for a while anyway for compatibility with older PHP versions. But teams targeting PHP 8.4 as the minimum version can gradually drop this boilerplate. Among the new PHP 8.4 features, this is the smallest, purely cosmetic change, yet one that becomes visible daily in almost every codebase.
declare(strict_types=1);
final class PriceFormatter
{
public function __construct(private readonly string $locale = 'en_US') {}
public function format(float $amount): string
{
return number_format($amount, 2, '.', ',') . ' EUR';
}
}
// Before PHP 8.4: extra parentheses were mandatory
$formatted = (new PriceFormatter('en_US'))->format(129.0);
// PHP 8.4: direct method call without wrapping parentheses
$formatted = new PriceFormatter('en_US')->format(129.0);
// Also works with property access
$locale = new PriceFormatter()->locale;
8. Performance improvements and JIT changes
Beyond the visible language features, PHP 8.4 also brings structural improvements under the hood. Perhaps the most important of these are lazy objects: using ReflectionClass::newLazyGhost() and newLazyProxy(), you can create objects whose actual initialization only happens on the first property access. This is particularly relevant for ORMs and dependency injection containers, which often reference objects ahead of time without needing them fully populated immediately, for example when lazy-loading entity relations in Doctrine or similar mappers.
The JIT compiler in PHP 8.4 was switched to a new internal backend built on its own intermediate representation (IR) instead of the previous DynASM-based approach. In practice this means better code quality, improved support for ARM64 architectures, and a more maintainable foundation for future optimizations, but no dramatic speed jump for typical PHP-FPM web workloads, where the JIT rarely provides the biggest lever anyway. Compute-heavy scripts with lots of numeric operations benefit more noticeably than classic request-response cycles in Magento or Symfony.
In addition, numerous internal data structures and the Zend memory manager were further optimized, which shows up mostly in memory usage under high concurrency, for example on PHP-FPM workers handling many parallel requests. Combined with the new array functions, which internally avoid extra intermediate arrays, the PHP 8.4 features together deliver a noticeable, if not spectacular, improvement in resource efficiency over PHP 8.3.
9. Breaking changes and migration notes
The most important behavioral change in PHP 8.4 concerns implicit nullable parameter types. A signature such as function foo(int $x = null) was silently interpreted as ?int $x = null in PHP versions up to 8.3. PHP 8.4 marks this implicit behavior as deprecated and emits an E_DEPRECATED warning for every affected function definition. The fix is simple, but tedious in large codebases with hundreds of affected signatures: the nullable type must be written explicitly as ?int or int|null.
Beyond that, PHP 8.4 tightens some long-deprecated behaviors from older DOM and string functions, as well as individual INI directives that already triggered deprecation warnings back in PHP 8.1 or 8.2. Anyone who ignored those warnings in the past because the script still worked should expect actual fatal errors instead of mere warnings when jumping to PHP 8.4.
For migration, a two-stage approach is recommended: first run the existing codebase under PHP 8.3 with E_DEPRECATED error reporting enabled and systematically work through every message, then run automated refactoring tools such as Rector with the PHP 8.4 rule set over the code to fix implicit nullable types and similar patterns automatically. Only after that should composer.json be raised to "php": "^8.4", so that the new PHP 8.4 features can be tested in staging before production servers are switched over.
declare(strict_types=1);
// PHP 8.3 and earlier: implicit nullable, allowed without warning
function applyDiscount(int $percentage = null): float
{
return $percentage === null ? 0.0 : $percentage / 100;
}
// PHP 8.4: implicit nullable triggers E_DEPRECATED
// "Implicitly marking parameter type as nullable is deprecated"
// Fixed version: explicit nullable type
function applyDiscountFixed(?int $percentage = null): float
{
return $percentage === null ? 0.0 : $percentage / 100;
}
A direct comparison shows how some of the most important patterns change from PHP 8.3 to PHP 8.4 and what concrete benefit each new form of writing them brings.
| Task | PHP 8.3 approach | PHP 8.4 approach | Benefit |
|---|---|---|---|
| Find first matching element | array_values(array_filter($a, $cb))[0] ?? null |
array_find($a, $cb) |
No intermediate array, early exit |
| Mark a method as deprecated | /** @deprecated */ |
#[\Deprecated(message: '...')] |
Runtime warning, IDE detection |
| Parse an HTML fragment | DOMDocument::loadHTML() |
Dom\HTMLDocument::createFromString() |
HTML5-compliant, querySelector |
| Call a method right after new | (new Foo())->bar() |
new Foo()->bar() |
No parenthesis boilerplate |
| Declare a nullable parameter | int $x = null (implicit) |
?int $x = null (explicit) |
No deprecation warning |
10. Summary
The PHP 8.4 features overview shows this release is far more than an annual maintenance update. Property hooks and asymmetric visibility change how classes are written, even though both topics were deliberately only touched on here, not fully explored. The four new array functions array_find(), array_any(), array_all() and array_find_key() solve a years-old boilerplate problem. The #[\Deprecated] attribute, the new DOM API and simplified object instantiation round out the picture.
At the same time, PHP 8.4 introduces a breaking change with the deprecation of implicit nullable types, one that affects practically every older codebase and should be checked systematically before upgrading. Anyone wanting to use the new PHP 8.4 features should therefore keep not only the benefits in view, but also plan the migration with tools such as Rector and a proper staging phase, rather than upgrading production blindly.
PHP 8.4 features, the key takeaways at a glance
Seven features in focus
Property hooks, asymmetric visibility, array functions, the #[\Deprecated] attribute, the new DOM API, the new syntax and performance form the core of the PHP 8.4 features.
Array functions without workarounds
array_find, array_any, array_all and array_find_key replace array_filter+array_values combinations with early-exit behavior.
DOM API, HTML5-compliant
Dom\HTMLDocument and Dom\XMLDocument bring querySelector support and spec-compliant parsing.
Breaking change: make nullable explicit
Implicit nullable parameters are deprecated. Write ?int instead of int = null, ideally migrated automatically with Rector.
11. FAQ: PHP 8.4 features overview
1What are the most important PHP 8.4 features overview items?
2Do I have to use property hooks in PHP 8.4 right away?
3What is the difference between array_find and array_filter?
4How does the #[\Deprecated] attribute work in PHP 8.4?
5Do I need to replace DOMDocument with the new DOM API?
6What does new Foo()->bar() mean in PHP 8.4?
7Is PHP 8.4 noticeably faster than PHP 8.3?
8What are lazy objects in PHP 8.4?
9What breaks most often when upgrading to PHP 8.4?
10Until when is PHP 8.4 supported?
Mironsoft
PHP 8.4 migration, code modernization and legacy refactoring
Ready for the new PHP 8.4 features in your project?
We review existing PHP code for breaking changes, migrate Magento and Symfony projects cleanly to PHP 8.4, and modernize legacy classes with property hooks, asymmetric visibility and the new array functions.
PHP 8.4 migration audit
Analysis of all implicit nullable types, outdated DOM calls and other breaking changes before the upgrade
Code modernization
Refactoring existing classes with property hooks, asymmetric visibility and the #[\Deprecated] attribute
Performance review
Assessment of JIT configuration, lazy objects and memory usage for Magento and Symfony deployments