PHP 8.5: The Silent Revolution That Transforms Your Code
Imagine being able to write $users |> filter(...) |> count(...) instead of juggling temporary variables. Imagine cloning an object while modifying a property in one go. Imagine all your fatal errors finally displaying their true origin. This isn’t science fiction: it’s PHP 8.5, and it’s coming in November 2025.
In my PHP development practice over more than 15 years, I’ve rarely seen a minor version bring so many concrete innovations while remaining so discreet. PHP 8.5 isn’t a groundbreaking revolution like PHP 7 or 8 was. It’s an intelligent evolution that transforms your daily development routine without making noise, but with formidable efficiency.
Introduction
PHP 8.5 represents the culmination of the maturation begun with PHP 8.0. Where PHP 8.0 introduced the foundations (JIT, union types, attributes), PHP 8.5 refines the edifice with over 40 targeted improvements that touch every aspect of the language: syntax, performance, security, internationalization.
The philosophy of this version? Eliminate daily friction. Each new feature responds to a real developer frustration: verbose code, laborious debugging, obsolete or dangerous APIs, fragile URL parsing. PHP 8.5 doesn’t reinvent the wheel, it finally makes it perfectly round.
This version perfectly illustrates the principle of “Secure by Design”: bad practices become impossible or deprecated, good practices become natural. Mandatory OPcache, automatic resource closing, standardized URI API, systematic backtraces… everything works together to make your applications more robust by default.
Let’s dive into these innovations that will transform the way you code in PHP.
The Pipe Operator: Finally Readable Functional Code
The End of Useless Temporary Variables
How many times have you written this?
<span class="nv">$users</span> <span class="o">=</span> <span class="nv">$userRepository</span><span class="o">-></span><span class="nf">fetchUsers</span><span class="p">();</span>
<span class="nv">$admins</span> <span class="o">=</span> <span class="nb">array_filter</span><span class="p">(</span><span class="nv">$users</span><span class="p">,</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$u</span><span class="p">)</span> <span class="o">=></span> <span class="nv">$u</span><span class="o">-></span><span class="nf">isAdmin</span><span class="p">());</span>
<span class="nv">$count</span> <span class="o">=</span> <span class="nb">count</span><span class="p">(</span><span class="nv">$admins</span><span class="p">);</span>
Three lines. Two throwaway variables that pollute your scope. An interrupted flow of thought.
PHP 8.5 introduces the pipe operator |>, inspired by functional languages (F#, Elixir, Hack), which radically changes the game:
<span class="nv">$count</span> <span class="o">=</span> <span class="nv">$userRepository</span><span class="o">-></span><span class="nf">fetchUsers</span><span class="p">()</span>
<span class="o">|></span> <span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$list</span><span class="p">)</span> <span class="o">=></span> <span class="nb">array_filter</span><span class="p">(</span><span class="nv">$list</span><span class="p">,</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$u</span><span class="p">)</span> <span class="o">=></span> <span class="nv">$u</span><span class="o">-></span><span class="nf">isAdmin</span><span class="p">()))</span>
<span class="o">|></span> <span class="nb">count</span><span class="p">(</span><span class="mf">...</span><span class="p">);</span>
A single expression. The result from the left becomes the argument on the right. Your intention reads like a sentence: “Fetch the users, filter the admins, count them.” No noise, no distractions.
Point-Free Style and Expressiveness
The pipe operator shines particularly with first-class callables (the ... syntax introduced in PHP 8.1):
<span class="c1">// Elegant data transformation</span>
<span class="nv">$finalPrice</span> <span class="o">=</span> <span class="nv">$product</span><span class="o">-></span><span class="nf">getPrice</span><span class="p">()</span>
<span class="o">|></span> <span class="nf">applyDiscount</span><span class="p">(</span><span class="mf">...</span><span class="p">)</span>
<span class="o">|></span> <span class="nf">addTax</span><span class="p">(</span><span class="mf">...</span><span class="p">)</span>
<span class="o">|></span> <span class="nb">round</span><span class="p">(</span><span class="mf">...</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="c1">// Validation pipeline</span>
<span class="nv">$isValid</span> <span class="o">=</span> <span class="nv">$input</span>
<span class="o">|></span> <span class="nb">trim</span><span class="p">(</span><span class="mf">...</span><span class="p">)</span>
<span class="o">|></span> <span class="nb">strtolower</span><span class="p">(</span><span class="mf">...</span><span class="p">)</span>
<span class="o">|></span> <span class="nf">validateEmail</span><span class="p">(</span><span class="mf">...</span><span class="p">);</span>
Note how the code reads naturally from top to bottom, following the transformation flow. This is the very essence of functional programming: composing pure functions to create complex transformations from simple operations.
The Rules of the Game
The pipe operator isn’t magic, it has strict rules that guarantee its predictability:
- Left → right evaluation: each expression is calculated sequentially
- Single argument: the function on the right must accept exactly one parameter
- Consistent precedence:
|>is placed before comparisons, after arithmetic - No black magic: it’s syntactic sugar, no runtime overhead
<span class="c1">// ❌ ERROR: multi-argument function</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$data</span> <span class="o">|></span> <span class="nb">array_map</span><span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$x</span><span class="p">)</span> <span class="o">=></span> <span class="nv">$x</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="mf">...</span><span class="p">);</span> <span class="c1">// Compile error</span>
<span class="c1">// ✅ CORRECT: wrapper or partial application</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$data</span> <span class="o">|></span> <span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$arr</span><span class="p">)</span> <span class="o">=></span> <span class="nb">array_map</span><span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$x</span><span class="p">)</span> <span class="o">=></span> <span class="nv">$x</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="nv">$arr</span><span class="p">));</span>
This constraint may seem limiting, but it forces clear design: each step of the pipeline does one thing and does it well.
Real-World Use Cases
In PrestaShop, imagine processing orders with a pipeline:
<span class="nv">$monthlyRevenue</span> <span class="o">=</span> <span class="nc">Order</span><span class="o">::</span><span class="nf">fetchByMonth</span><span class="p">(</span><span class="nv">$month</span><span class="p">)</span>
<span class="o">|></span> <span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$orders</span><span class="p">)</span> <span class="o">=></span> <span class="nb">array_filter</span><span class="p">(</span><span class="nv">$orders</span><span class="p">,</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$o</span><span class="p">)</span> <span class="o">=></span> <span class="nv">$o</span><span class="o">-></span><span class="nf">isPaid</span><span class="p">()))</span>
<span class="o">|></span> <span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$orders</span><span class="p">)</span> <span class="o">=></span> <span class="nb">array_map</span><span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$o</span><span class="p">)</span> <span class="o">=></span> <span class="nv">$o</span><span class="o">-></span><span class="nf">getTotalPaid</span><span class="p">(),</span> <span class="nv">$orders</span><span class="p">))</span>
<span class="o">|></span> <span class="nb">array_sum</span><span class="p">(</span><span class="mf">...</span><span class="p">);</span>
Or building a dynamic pricing system:
<span class="nv">$displayPrice</span> <span class="o">=</span> <span class="nv">$basePrice</span>
<span class="o">|></span> <span class="nf">applyCustomerGroupDiscount</span><span class="p">(</span><span class="nv">$customer</span><span class="p">,</span> <span class="mf">...</span><span class="p">)</span>
<span class="o">|></span> <span class="nf">applyVolumeDiscount</span><span class="p">(</span><span class="nv">$quantity</span><span class="p">,</span> <span class="mf">...</span><span class="p">)</span>
<span class="o">|></span> <span class="nf">convertCurrency</span><span class="p">(</span><span class="nv">$targetCurrency</span><span class="p">,</span> <span class="mf">...</span><span class="p">)</span>
<span class="o">|></span> <span class="nf">formatPrice</span><span class="p">(</span><span class="mf">...</span><span class="p">);</span>
The benefit? The code becomes self-documenting. Each step is explicit, independently testable, and reusable. No more lengthy methods where you lose track.
Clone With: The End of the Immutable Object Ordeal
The Problem with Readonly Properties
PHP 8.1 introduced readonly properties, excellent for immutability… until you want to create a variant of an object:
<span class="k">readonly</span> <span class="kd">class</span> <span class="nc">User</span> <span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nv">$name</span><span class="p">,</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nv">$email</span><span class="p">,</span>
<span class="k">public</span> <span class="kt">int</span> <span class="nv">$age</span>
<span class="p">)</span> <span class="p">{}</span>
<span class="p">}</span>
<span class="c1">// How to create a User with just the email changed?</span>
<span class="c1">// Option 1: Verbose factory</span>
<span class="nv">$newUser</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">User</span><span class="p">(</span><span class="nv">$user</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="s1">'newemail@example.com'</span><span class="p">,</span> <span class="nv">$user</span><span class="o">-></span><span class="n">age</span><span class="p">);</span>
<span class="c1">// Option 2: Reflection (breaks readonly, anti-pattern)</span>
<span class="nv">$reflection</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ReflectionProperty</span><span class="p">(</span><span class="nv">$user</span><span class="p">,</span> <span class="s1">'email'</span><span class="p">);</span>
<span class="nv">$reflection</span><span class="o">-></span><span class="nf">setValue</span><span class="p">(</span><span class="nv">$user</span><span class="p">,</span> <span class="s1">'newemail@example.com'</span><span class="p">);</span> <span class="c1">// 😱</span>
Frustrating, right? Immutability is an excellent practice, but verbosity was killing adoption.
The Elegant Solution: clone with
PHP 8.5 solves this problem with syntax inspired (again) by functional languages:
<span class="nv">$newUser</span> <span class="o">=</span> <span class="k">clone</span> <span class="nv">$user</span> <span class="n">with</span> <span class="p">[</span><span class="s1">'email'</span> <span class="o">=></span> <span class="s1">'newemail@example.com'</span><span class="p">];</span>
One line. Readable. Safe. The new object is a perfect copy of $user, except for email which takes the new value.
In-Depth Operation
The magic operates in several steps:
- Classic cloning:
clonecreates a shallow copy of the object - Call to
__clone(): if defined, your custom cloning logic executes - Property override: the
witharray overwrites the specified values - Hook respect: if you use property hooks (PHP 8.4+), they are invoked
<span class="k">readonly</span> <span class="kd">class</span> <span class="nc">Money</span> <span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span>
<span class="k">public</span> <span class="kt">float</span> <span class="nv">$amount</span><span class="p">,</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nv">$currency</span>
<span class="p">)</span> <span class="p">{}</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">__clone</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Custom logic if necessary</span>
<span class="k">echo</span> <span class="s2">"Cloning money object</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$euros</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Money</span><span class="p">(</span><span class="mf">100.0</span><span class="p">,</span> <span class="s1">'EUR'</span><span class="p">);</span>
<span class="nv">$dollars</span> <span class="o">=</span> <span class="k">clone</span> <span class="nv">$euros</span> <span class="n">with</span> <span class="p">[</span><span class="s1">'currency'</span> <span class="o">=></span> <span class="s1">'USD'</span><span class="p">,</span> <span class="s1">'amount'</span> <span class="o">=></span> <span class="mf">120.0</span><span class="p">];</span>
<span class="c1">// Output: "Cloning money object"</span>
<span class="c1">// $dollars->amount === 120.0</span>
<span class="c1">// $dollars->currency === 'USD'</span>
Clone as First-Class Callable
Cherry on top: clone becomes a first-class callable:
<span class="nv">$users</span> <span class="o">=</span> <span class="p">[</span><span class="cm">/* ... */</span><span class="p">];</span>
<span class="nv">$userCopies</span> <span class="o">=</span> <span class="nb">array_map</span><span class="p">(</span><span class="k">clone</span><span class="p">(</span><span class="mf">...</span><span class="p">),</span> <span class="nv">$users</span><span class="p">);</span>
<span class="c1">// Or with uniform modifications</span>
<span class="nv">$anonymized</span> <span class="o">=</span> <span class="nb">array_map</span><span class="p">(</span>
<span class="k">fn</span><span class="p">(</span><span class="nv">$user</span><span class="p">)</span> <span class="o">=></span> <span class="k">clone</span> <span class="nv">$user</span> <span class="n">with</span> <span class="p">[</span><span class="s1">'email'</span> <span class="o">=></span> <span class="s1">'redacted@example.com'</span><span class="p">],</span>
<span class="nv">$users</span>
<span class="p">);</span>
Architectural impact: Patterns like Event Sourcing, Value Objects, or Copy-on-Write become trivial to implement. No more complex builders or lengthy withX() methods (PSR-7 withers pattern).
Mandatory OPcache: Guaranteed Performance for Everyone
The End of an Anomaly
Historically, OPcache was an optional extension. You could compile PHP without it. Big mistake.
Why? Because without OPcache, PHP recompiles your code on every request. Imagine recompiling an e-commerce monolith on every visit: catastrophic.
Yet, production environments were still running without OPcache, due to lack of knowledge or deficient configuration. PHP 8.5 puts an end to this absurdity.
OPcache: Always There, Always Active
Since PHP 8.5, OPcache is:
- Compiled by default: impossible to disable at compilation
- Enabled by default:
opcache.enable=1from installation - Integral part of the engine: not a third-party extension
Consequence? All PHP applications automatically benefit:
- Drastic CPU reduction: bytecode is cached
- Response times divided by 3 to 10 depending on complexity
- Improved scalability: less load per request
What This Changes for You
If you develop with Docker, Kubernetes, or deploy on cloud platforms:
<span class="c"># Before PHP 8.5: mandatory manual configuration</span>
<span class="k">RUN </span>docker-php-ext-install opcache
<span class="k">RUN </span><span class="nb">echo</span> <span class="s2">"opcache.enable=1"</span> <span class="o">>></span> /usr/local/etc/php/conf.d/opcache.ini
<span class="k">RUN </span><span class="nb">echo</span> <span class="s2">"opcache.memory_consumption=128"</span> <span class="o">>></span> /usr/local/etc/php/conf.d/opcache.ini
<span class="c"># With PHP 8.5: OPcache already there, just optimize parameters</span>
<span class="k">RUN </span><span class="nb">echo</span> <span class="s2">"opcache.memory_consumption=256"</span> <span class="o">>></span> /usr/local/etc/php/conf.d/opcache.ini
For PrestaShop? It’s huge. PrestaShop loads hundreds of classes per request. Without OPcache, each request recompiles megabytes of code. With mandatory OPcache, zero risk of botched config = guaranteed production performance.
JIT: Incremental but Real Gains
The JIT (Just-In-Time compiler), introduced in PHP 8.0, continues to evolve. PHP 8.5 brings:
- 5-10% improvement on standard web applications
- Memory optimizations: reduced footprint in certain cases
- Better profiling: refined internal diagnostics
JIT remains especially beneficial for intensive computing (image processing, massive XML/JSON parsing, complex algorithms). For classic e-commerce (I/O-bound), the impact is moderate but cumulative.
Backtraces on Fatal Errors: Debugging Revolutionized
The Nightmare of “Fatal Error in Unknown on Line 42”
Who hasn’t seen this in production?
Fatal error: Allowed memory size exhausted in /var/www/classes/Product.php on line 1247
Perfect. You know where it died. But why? What chain of calls led to this disaster?
Before PHP 8.5, you had to:
- Attempt to reproduce locally (good luck)
- Add logging everywhere (powerful but time-consuming)
- Enable Xdebug in production (🔥 performance hit)
The Solution: Automatic Backtraces
PHP 8.5 solves this problem elegantly: all fatal errors now generate a complete backtrace.
<span class="nb">register_shutdown_function</span><span class="p">(</span><span class="k">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nv">$error</span> <span class="o">=</span> <span class="nb">error_get_last</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$error</span> <span class="o">&&</span> <span class="nv">$error</span><span class="p">[</span><span class="s1">'type'</span><span class="p">]</span> <span class="o">===</span> <span class="kc">E_ERROR</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// PHP 8.5 novelty: 'trace' key available!</span>
<span class="nv">$trace</span> <span class="o">=</span> <span class="nv">$error</span><span class="p">[</span><span class="s1">'trace'</span><span class="p">]</span> <span class="o">??</span> <span class="p">[];</span>
<span class="c1">// Log the complete stack</span>
<span class="nb">error_log</span><span class="p">(</span><span class="s2">"Fatal error stack trace:</span><span class="se">\n</span><span class="s2">"</span> <span class="mf">.</span> <span class="nb">print_r</span><span class="p">(</span><span class="nv">$trace</span><span class="p">,</span> <span class="kc">true</span><span class="p">));</span>
<span class="c1">// Send to your monitoring system</span>
<span class="nc">Sentry</span><span class="o">::</span><span class="nf">captureException</span><span class="p">(</span><span class="k">new</span> <span class="nc">ErrorException</span><span class="p">(</span>
<span class="nv">$error</span><span class="p">[</span><span class="s1">'message'</span><span class="p">],</span>
<span class="mi">0</span><span class="p">,</span>
<span class="nv">$error</span><span class="p">[</span><span class="s1">'type'</span><span class="p">],</span>
<span class="nv">$error</span><span class="p">[</span><span class="s1">'file'</span><span class="p">],</span>
<span class="nv">$error</span><span class="p">[</span><span class="s1">'line'</span><span class="p">]</span>
<span class="p">),</span> <span class="p">[</span><span class="s1">'stacktrace'</span> <span class="o">=></span> <span class="nv">$trace</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">});</span>
Result? In case of a fatal error, you immediately see:
Fatal error: Memory exhausted in Product.php:1247
Stack trace:
#0 CartController.php(89): Product->getImages()
#1 FrontController.php(156): CartController->displayCart()
#2 Dispatcher.php(412): FrontController->run()
#3 index.php(28): Dispatcher::dispatch()
Ah! The cart controller loads too many images. Diagnosis in 10 seconds instead of 2 hours.
Impact on Frameworks
For PrestaShop, Symfony, Laravel… it’s a game-changer:
- Improved monitoring: native integration with Sentry, Rollbar, Bugsnag
- Production debugging: trace root causes without disrupting service
- Junior training: understanding errors becomes educational
URI Extension: Goodbye parse_url(), Hello Security
The Hidden Flaws of parse_url()
parse_url() has served loyally for 25 years. But it has a shameful secret: it’s broken.
A few examples that will give you chills:
<span class="c1">// Confusion with similar Unicode characters</span>
<span class="nb">parse_url</span><span class="p">(</span><span class="s1">'http://раураl.com/account'</span><span class="p">);</span> <span class="c1">// Cyrillic 'а' vs Latin 'a'</span>
<span class="c1">// Can bypass poorly implemented whitelists</span>
<span class="c1">// Inconsistent parsing with browsers</span>
<span class="nb">parse_url</span><span class="p">(</span><span class="s1">'http://user@evil.com:user@legit.com/'</span><span class="p">);</span>
<span class="c1">// PHP: host = "evil.com", browsers: host = "legit.com"</span>
<span class="c1">// Open redirections possible</span>
<span class="c1">// Ambiguous encoding</span>
<span class="nb">parse_url</span><span class="p">(</span><span class="s1">'http://example.com/%2F../admin'</span><span class="p">);</span>
<span class="c1">// Non-standard path normalization</span>
These quirks have caused real security vulnerabilities: open redirections, filter bypasses, SSRF injections.
The URI Extension: Standard and Robust
PHP 8.5 introduces a new ext/uri extension with two immutable classes:
<span class="kn">use</span> <span class="nc">Uri\Rfc3986\Uri</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Uri\WhatWg\Url</span><span class="p">;</span>
<span class="c1">// RFC 3986 parsing (generic URIs)</span>
<span class="nv">$uri</span> <span class="o">=</span> <span class="nc">Uri</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'https://user:pass@example.com:8080/path?q=1#frag'</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$uri</span><span class="o">-></span><span class="nf">getScheme</span><span class="p">();</span> <span class="c1">// "https"</span>
<span class="k">echo</span> <span class="nv">$uri</span><span class="o">-></span><span class="nf">getHost</span><span class="p">();</span> <span class="c1">// "example.com"</span>
<span class="k">echo</span> <span class="nv">$uri</span><span class="o">-></span><span class="nf">getPort</span><span class="p">();</span> <span class="c1">// 8080</span>
<span class="k">echo</span> <span class="nv">$uri</span><span class="o">-></span><span class="nf">getPath</span><span class="p">();</span> <span class="c1">// "/path"</span>
<span class="k">echo</span> <span class="nv">$uri</span><span class="o">-></span><span class="nf">getQuery</span><span class="p">();</span> <span class="c1">// "q=1"</span>
<span class="k">echo</span> <span class="nv">$uri</span><span class="o">-></span><span class="nf">getFragment</span><span class="p">();</span> <span class="c1">// "frag"</span>
<span class="c1">// WHATWG parsing (modern web URLs)</span>
<span class="nv">$url</span> <span class="o">=</span> <span class="nc">Url</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'https://example.com/search?q=php 8.5'</span><span class="p">);</span>
<span class="c1">// Automatic normalization</span>
<span class="k">echo</span> <span class="nv">$url</span><span class="o">-></span><span class="nf">getSearchParams</span><span class="p">()</span><span class="o">-></span><span class="nf">get</span><span class="p">(</span><span class="s1">'q'</span><span class="p">);</span> <span class="c1">// "php 8.5" (decoded)</span>
Immutable and Safe Manipulation
The Uri and Url objects are immutable (pattern similar to PSR-7):
<span class="nv">$base</span> <span class="o">=</span> <span class="nc">Uri</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'https://api.example.com/v1'</span><span class="p">);</span>
<span class="nv">$endpoint</span> <span class="o">=</span> <span class="nv">$base</span>
<span class="o">-></span><span class="nf">withPath</span><span class="p">(</span><span class="s1">'/v2/users'</span><span class="p">)</span>
<span class="o">-></span><span class="nf">withQuery</span><span class="p">(</span><span class="s1">'filter=active&limit=10'</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$endpoint</span><span class="p">;</span> <span class="c1">// "https://api.example.com/v2/users?filter=active&limit=10"</span>
<span class="k">echo</span> <span class="nv">$base</span><span class="p">;</span> <span class="c1">// "https://api.example.com/v1" (unchanged)</span>
Relative URL resolution (finally!):
<span class="nv">$base</span> <span class="o">=</span> <span class="nc">Uri</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'https://example.com/blog/'</span><span class="p">);</span>
<span class="nv">$relative</span> <span class="o">=</span> <span class="nc">Uri</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'../images/logo.webp'</span><span class="p">);</span>
<span class="nv">$resolved</span> <span class="o">=</span> <span class="nv">$base</span><span class="o">-></span><span class="nf">resolve</span><span class="p">(</span><span class="nv">$relative</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$resolved</span><span class="p">;</span> <span class="c1">// "https://example.com/images/logo.webp"</span>
Security by Design
Unlike parse_url(), the URI extension:
- Follows standards to the letter (RFC 3986, WHATWG URL)
- Normalizes encodings: no
%2Fvs/ambiguity - Strictly validates: malformed URLs = exception
- Consistency with browsers: predictable behavior
<span class="c1">// Strict validation</span>
<span class="k">try</span> <span class="p">{</span>
<span class="nv">$malicious</span> <span class="o">=</span> <span class="nc">Uri</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'http://раураl.com'</span><span class="p">);</span> <span class="c1">// Cyrillic lookalike</span>
<span class="c1">// Exception raised if non-ASCII characters without encoding</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nc">ValueError</span> <span class="nv">$e</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Handle gracefully</span>
<span class="p">}</span>
<span class="c1">// Canonical comparison</span>
<span class="nv">$url1</span> <span class="o">=</span> <span class="nc">Url</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'https://Example.COM/Path'</span><span class="p">);</span>
<span class="nv">$url2</span> <span class="o">=</span> <span class="nc">Url</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="s1">'https://example.com/Path'</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$url1</span><span class="o">-></span><span class="nf">equals</span><span class="p">(</span><span class="nv">$url2</span><span class="p">);</span> <span class="c1">// true (automatic normalization)</span>
For PrestaShop? Validating redirection URLs, parsing payment webhooks, managing deep links in the mobile app… all these operations become robust and safe by default.
New Utility Functions: Small Joys
array_first() and array_last()
How many times have you written reset($array) or end($array) while cursing the side effect on the internal pointer?
<span class="c1">// Before PHP 8.5: verbose and fragile</span>
<span class="nv">$firstUser</span> <span class="o">=</span> <span class="nb">reset</span><span class="p">(</span><span class="nv">$users</span><span class="p">);</span> <span class="c1">// Modifies internal pointer 😡</span>
<span class="nv">$lastUser</span> <span class="o">=</span> <span class="nb">end</span><span class="p">(</span><span class="nv">$users</span><span class="p">);</span> <span class="c1">// Same</span>
<span class="c1">// PHP 8.5: simple and no surprises</span>
<span class="nv">$firstUser</span> <span class="o">=</span> <span class="nf">array_first</span><span class="p">(</span><span class="nv">$users</span><span class="p">);</span>
<span class="nv">$lastUser</span> <span class="o">=</span> <span class="nf">array_last</span><span class="p">(</span><span class="nv">$users</span><span class="p">);</span>
<span class="c1">// Empty array? No warning, just null</span>
<span class="nv">$empty</span> <span class="o">=</span> <span class="p">[];</span>
<span class="nv">$first</span> <span class="o">=</span> <span class="nf">array_first</span><span class="p">(</span><span class="nv">$empty</span><span class="p">);</span> <span class="c1">// null (no error)</span>
PrestaShop use case:
<span class="c1">// Get first image of a product</span>
<span class="nv">$coverImage</span> <span class="o">=</span> <span class="nf">array_first</span><span class="p">(</span><span class="nv">$product</span><span class="o">-></span><span class="nf">getImages</span><span class="p">());</span>
<span class="c1">// Customer's latest order</span>
<span class="nv">$latestOrder</span> <span class="o">=</span> <span class="nf">array_last</span><span class="p">(</span><span class="nv">$customer</span><span class="o">-></span><span class="nf">getOrders</span><span class="p">());</span>
get_error_handler() and get_exception_handler()
Before, retrieving the current handler required contortions:
<span class="c1">// "Dirty" method pre-8.5</span>
<span class="nv">$oldHandler</span> <span class="o">=</span> <span class="nb">set_error_handler</span><span class="p">(</span><span class="k">fn</span><span class="p">()</span> <span class="o">=></span> <span class="kc">null</span><span class="p">);</span>
<span class="nb">restore_error_handler</span><span class="p">();</span>
<span class="c1">// $oldHandler contains the previous handler</span>
PHP 8.5 simplifies:
<span class="nv">$currentErrorHandler</span> <span class="o">=</span> <span class="nf">get_error_handler</span><span class="p">();</span>
<span class="nv">$currentExceptionHandler</span> <span class="o">=</span> <span class="nf">get_exception_handler</span><span class="p">();</span>
<span class="c1">// null if no handler defined</span>
<span class="c1">// callable otherwise</span>
Usefulness? For frameworks that want to chain handlers:
<span class="kd">class</span> <span class="nc">ErrorMiddleware</span> <span class="p">{</span>
<span class="k">private</span> <span class="nv">$previousHandler</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">register</span><span class="p">()</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="n">previousHandler</span> <span class="o">=</span> <span class="nf">get_error_handler</span><span class="p">();</span>
<span class="nb">set_error_handler</span><span class="p">(</span><span class="k">function</span><span class="p">(</span><span class="nv">$errno</span><span class="p">,</span> <span class="nv">$errstr</span><span class="p">,</span> <span class="nv">$errfile</span><span class="p">,</span> <span class="nv">$errline</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Our custom logic</span>
<span class="nv">$this</span><span class="o">-></span><span class="nf">logError</span><span class="p">(</span><span class="nv">$errno</span><span class="p">,</span> <span class="nv">$errstr</span><span class="p">,</span> <span class="nv">$errfile</span><span class="p">,</span> <span class="nv">$errline</span><span class="p">);</span>
<span class="c1">// Delegate to previous handler if exists</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="n">previousHandler</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="n">previousHandler</span><span class="p">)(</span><span class="nv">$errno</span><span class="p">,</span> <span class="nv">$errstr</span><span class="p">,</span> <span class="nv">$errfile</span><span class="p">,</span> <span class="nv">$errline</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span> <span class="c1">// Default behavior</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
PHP_BUILD_DATE and PHP_BUILD_PROVIDER
Diagnosing differences between environments becomes trivial:
<span class="k">echo</span> <span class="s2">"PHP Version: "</span> <span class="mf">.</span> <span class="kc">PHP_VERSION</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"Built on: "</span> <span class="mf">.</span> <span class="kc">PHP_BUILD_DATE</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="k">echo</span> <span class="s2">"Provider: "</span> <span class="mf">.</span> <span class="kc">PHP_BUILD_PROVIDER</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="c1">// Example output:</span>
<span class="c1">// PHP Version: 8.5.0</span>
<span class="c1">// Built on: Nov 21 2025 14:32:10</span>
<span class="c1">// Provider: Ubuntu</span>
Why it’s useful? Because two servers with PHP 8.5 can have different behaviors depending on:
- Compiled extensions
- Distributor patches (Ubuntu vs Alpine vs official)
- Compilation flags (debug, ZTS, etc.)
These constants allow for quickly detecting discrepancies in production.
Enhanced Attributes: Simplified Metaprogramming
#[NoDiscard]: Force Return Usage
Some functions should NEVER be called without handling their return:
<span class="c1">#[\NoDiscard]</span>
<span class="k">function</span> <span class="n">executePayment</span><span class="p">(</span><span class="kt">Order</span> <span class="nv">$order</span><span class="p">):</span> <span class="kt">PaymentResult</span> <span class="p">{</span>
<span class="c1">// Critical logic</span>
<span class="k">return</span> <span class="k">new</span> <span class="nc">PaymentResult</span><span class="p">(</span><span class="nv">$success</span><span class="p">,</span> <span class="nv">$transactionId</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// ❌ This will trigger a warning</span>
<span class="nf">executePayment</span><span class="p">(</span><span class="nv">$order</span><span class="p">);</span> <span class="c1">// Warning: Result of executePayment() is not used</span>
<span class="c1">// ✅ Correct</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nf">executePayment</span><span class="p">(</span><span class="nv">$order</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$result</span><span class="o">-></span><span class="nf">isSuccess</span><span class="p">())</span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="c1">// ✅ Or explicitly ignore</span>
<span class="p">(</span><span class="n">void</span><span class="p">)</span> <span class="nf">executePayment</span><span class="p">(</span><span class="nv">$order</span><span class="p">);</span> <span class="c1">// Void cast = "I know what I'm doing"</span>
Use cases: validation, DB transactions, API calls, file operations. Everything that can fail silently.
#[Override] on Properties
Avoid typos and divergences in inheritance:
<span class="kd">class</span> <span class="nc">BaseProduct</span> <span class="p">{</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nv">$name</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">float</span> <span class="nv">$price</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">DiscountedProduct</span> <span class="kd">extends</span> <span class="nc">BaseProduct</span> <span class="p">{</span>
<span class="c1">#[\Override]</span>
<span class="k">public</span> <span class="kt">float</span> <span class="nv">$price</span><span class="p">;</span> <span class="c1">// ✅ OK, exists in BaseProduct</span>
<span class="c1">#[\Override]</span>
<span class="k">public</span> <span class="kt">float</span> <span class="nv">$discount</span><span class="p">;</span> <span class="c1">// ❌ Compile Error: no such property in parent</span>
<span class="p">}</span>
For PrestaShop? The module override system becomes safer. If the core changes a property, the #[Override] attribute immediately detects the breakage.
#[Deprecated] for Traits
Mark a trait as obsolete:
<span class="c1">#[\Deprecated("Use NewHelperTrait instead", since: "2.5.0")]</span>
<span class="kd">trait</span> <span class="nc">OldHelperTrait</span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">MyClass</span> <span class="p">{</span>
<span class="kn">use</span> <span class="nc">OldHelperTrait</span><span class="p">;</span> <span class="c1">// Warning: OldHelperTrait is deprecated</span>
<span class="p">}</span>
Helps teams manage technical debt progressively.
Advanced Internationalization with ext/intl
IntlListFormatter: Natural Lists
Display lists respectfully according to locale:
<span class="nv">$formatter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">IntlListFormatter</span><span class="p">(</span><span class="s1">'fr_FR'</span><span class="p">,</span> <span class="nc">IntlListFormatter</span><span class="o">::</span><span class="no">TYPE_AND</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$formatter</span><span class="o">-></span><span class="nf">format</span><span class="p">([</span><span class="s1">'pommes'</span><span class="p">,</span> <span class="s1">'bananes'</span><span class="p">,</span> <span class="s1">'oranges'</span><span class="p">]);</span>
<span class="c1">// "pommes, bananes et oranges"</span>
<span class="nv">$formatter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">IntlListFormatter</span><span class="p">(</span><span class="s1">'en_US'</span><span class="p">,</span> <span class="nc">IntlListFormatter</span><span class="o">::</span><span class="no">TYPE_OR</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$formatter</span><span class="o">-></span><span class="nf">format</span><span class="p">([</span><span class="s1">'red'</span><span class="p">,</span> <span class="s1">'green'</span><span class="p">,</span> <span class="s1">'blue'</span><span class="p">]);</span>
<span class="c1">// "red, green, or blue"</span>
PrestaShop? Display product attributes properly:
<span class="nv">$attributes</span> <span class="o">=</span> <span class="nv">$product</span><span class="o">-></span><span class="nf">getAttributeNames</span><span class="p">();</span> <span class="c1">// ['Taille', 'Couleur', 'Matière']</span>
<span class="nv">$formatted</span> <span class="o">=</span> <span class="p">(</span><span class="k">new</span> <span class="nc">IntlListFormatter</span><span class="p">(</span><span class="nv">$locale</span><span class="p">))</span><span class="o">-></span><span class="nf">format</span><span class="p">(</span><span class="nv">$attributes</span><span class="p">);</span>
<span class="c1">// FR: "Taille, Couleur et Matière"</span>
<span class="c1">// EN: "Size, Color, and Material"</span>
locale_is_right_to_left(): RTL Support
Automatically detect RTL languages:
<span class="k">if</span> <span class="p">(</span><span class="nf">locale_is_right_to_left</span><span class="p">(</span><span class="s1">'ar_SA'</span><span class="p">))</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s1">'<body dir="rtl">'</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s1">'<body dir="ltr">'</span><span class="p">;</span>
<span class="p">}</span>
No more hardcoded mappings! PHP knows RTL locales (Arabic, Hebrew, Persian…).
grapheme_levenshtein(): Unicode Distance
Calculate similarity between strings with accents and emojis:
<span class="c1">// Classic levenshtein() (bytes)</span>
<span class="k">echo</span> <span class="nb">levenshtein</span><span class="p">(</span><span class="s1">'café'</span><span class="p">,</span> <span class="s1">'cafe'</span><span class="p">);</span> <span class="c1">// 2 (é = 2 UTF-8 bytes)</span>
<span class="c1">// grapheme_levenshtein() (graphemes)</span>
<span class="k">echo</span> <span class="nf">grapheme_levenshtein</span><span class="p">(</span><span class="s1">'café'</span><span class="p">,</span> <span class="s1">'cafe'</span><span class="p">);</span> <span class="c1">// 1 (é = 1 grapheme)</span>
<span class="c1">// With emojis</span>
<span class="k">echo</span> <span class="nf">grapheme_levenshtein</span><span class="p">(</span><span class="s1">'hello👋'</span><span class="p">,</span> <span class="s1">'hello'</span><span class="p">);</span> <span class="c1">// 1 (not 4!)</span>
Typo-tolerant search: suggest “téléphone” when the user types “telephone”.
Partitioned Cookies (CHIPS): Modern Privacy
The Problem with Third-Party Cookies
Third-party (cross-site) cookies enable advertising tracking. Browsers are increasingly blocking them.
CHIPS (Cookies Having Independent Partitioned State) is the new standard: third-party cookies are isolated by top-level site.
Implementation in PHP 8.5
<span class="c1">// Partitioned cookie</span>
<span class="nb">setcookie</span><span class="p">(</span><span class="s1">'tracking'</span><span class="p">,</span> <span class="s1">'value'</span><span class="p">,</span> <span class="p">[</span>
<span class="s1">'expires'</span> <span class="o">=></span> <span class="nb">time</span><span class="p">()</span> <span class="o">+</span> <span class="mi">3600</span><span class="p">,</span>
<span class="s1">'path'</span> <span class="o">=></span> <span class="s1">'/'</span><span class="p">,</span>
<span class="s1">'secure'</span> <span class="o">=></span> <span class="kc">true</span><span class="p">,</span>
<span class="s1">'httponly'</span> <span class="o">=></span> <span class="kc">true</span><span class="p">,</span>
<span class="s1">'samesite'</span> <span class="o">=></span> <span class="s1">'None'</span><span class="p">,</span>
<span class="s1">'partitioned'</span> <span class="o">=></span> <span class="kc">true</span> <span class="c1">// 🆕 PHP 8.5</span>
<span class="p">]);</span>
<span class="c1">// Partitioned session cookie</span>
<span class="nb">session_set_cookie_params</span><span class="p">([</span>
<span class="s1">'lifetime'</span> <span class="o">=></span> <span class="mi">0</span><span class="p">,</span>
<span class="s1">'path'</span> <span class="o">=></span> <span class="s1">'/'</span><span class="p">,</span>
<span class="s1">'secure'</span> <span class="o">=></span> <span class="kc">true</span><span class="p">,</span>
<span class="s1">'httponly'</span> <span class="o">=></span> <span class="kc">true</span><span class="p">,</span>
<span class="s1">'samesite'</span> <span class="o">=></span> <span class="s1">'None'</span><span class="p">,</span>
<span class="s1">'partitioned'</span> <span class="o">=></span> <span class="kc">true</span> <span class="c1">// 🆕</span>
<span class="p">]);</span>
<span class="nb">session_start</span><span class="p">();</span>
When to use it? If your PrestaShop app is embedded in an iframe on another domain (e.g., cart widget), partitioned cookies guarantee functionality without compromising privacy.
Deprecations: Cleaning Up the Past
PHP 8.5 deprecates about fifty elements. Here are the most impactful:
Manual Resource Closing
<span class="c1">// ❌ Deprecated in 8.5</span>
<span class="nb">curl_close</span><span class="p">(</span><span class="nv">$ch</span><span class="p">);</span>
<span class="nb">imagedestroy</span><span class="p">(</span><span class="nv">$img</span><span class="p">);</span>
<span class="nb">finfo_close</span><span class="p">(</span><span class="nv">$finfo</span><span class="p">);</span>
<span class="nb">xml_parser_free</span><span class="p">(</span><span class="nv">$parser</span><span class="p">);</span>
<span class="c1">// ✅ Nothing to do, automatic destructor</span>
<span class="c1">// Objects clean themselves up</span>
Reason: Since PHP 7.4, resources are objects. Manual closing is redundant and error-prone (double-free, leaks).
Non-Canonical Casts
<span class="c1">// ❌ Deprecated</span>
<span class="nv">$bool</span> <span class="o">=</span> <span class="p">(</span><span class="n">boolean</span><span class="p">)</span> <span class="nv">$value</span><span class="p">;</span>
<span class="nv">$int</span> <span class="o">=</span> <span class="p">(</span><span class="n">integer</span><span class="p">)</span> <span class="nv">$value</span><span class="p">;</span>
<span class="nv">$float</span> <span class="o">=</span> <span class="p">(</span><span class="n">double</span><span class="p">)</span> <span class="nv">$value</span><span class="p">;</span>
<span class="nv">$str</span> <span class="o">=</span> <span class="p">(</span><span class="n">binary</span><span class="p">)</span> <span class="nv">$value</span><span class="p">;</span>
<span class="c1">// ✅ Use standard forms</span>
<span class="nv">$bool</span> <span class="o">=</span> <span class="p">(</span><span class="n">bool</span><span class="p">)</span> <span class="nv">$value</span><span class="p">;</span>
<span class="nv">$int</span> <span class="o">=</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nv">$value</span><span class="p">;</span>
<span class="nv">$float</span> <span class="o">=</span> <span class="p">(</span><span class="n">float</span><span class="p">)</span> <span class="nv">$value</span><span class="p">;</span>
<span class="nv">$str</span> <span class="o">=</span> <span class="p">(</span><span class="n">string</span><span class="p">)</span> <span class="nv">$value</span><span class="p">;</span>
Backtick Operator
<span class="c1">// ❌ Deprecated</span>
<span class="nv">$output</span> <span class="o">=</span> <span class="sb">`ls -la`</span><span class="p">;</span>
<span class="c1">// ✅ Use shell_exec() explicitly</span>
<span class="nv">$output</span> <span class="o">=</span> <span class="nb">shell_exec</span><span class="p">(</span><span class="s1">'ls -la'</span><span class="p">);</span>
Security: The backtick was a source of confusion and shell injections. shell_exec() is explicit.
__sleep() and __wakeup()
<span class="c1">// ⚠️ Soft-deprecation: still supported but discouraged</span>
<span class="kd">class</span> <span class="nc">User</span> <span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">__sleep</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">[</span><span class="s1">'name'</span><span class="p">,</span> <span class="s1">'email'</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">__wakeup</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Init logic</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// ✅ Prefer __serialize() / __unserialize()</span>
<span class="kd">class</span> <span class="nc">User</span> <span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">__serialize</span><span class="p">():</span> <span class="kt">array</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">[</span><span class="s1">'name'</span> <span class="o">=></span> <span class="nv">$this</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="s1">'email'</span> <span class="o">=></span> <span class="nv">$this</span><span class="o">-></span><span class="n">email</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">__unserialize</span><span class="p">(</span><span class="kt">array</span> <span class="nv">$data</span><span class="p">):</span> <span class="kt">void</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="n">name</span> <span class="o">=</span> <span class="nv">$data</span><span class="p">[</span><span class="s1">'name'</span><span class="p">];</span>
<span class="nv">$this</span><span class="o">-></span><span class="n">email</span> <span class="o">=</span> <span class="nv">$data</span><span class="p">[</span><span class="s1">'email'</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
Advantage: __serialize() supports nested objects and avoids __sleep() pitfalls.
PrestaShop and PHP 8.5: A Winning Combo
Concrete Performance Gains
PrestaShop 8.x loads approximately 150-300 classes per front-office request. With mandatory OPcache:
- Compilation time: 0 (cached bytecode)
- Response time: -30% on average
- Server capacity: +50% requests/second
JIT optimizations (+5-10%) add up for complex pages (product listings, filters).
Enhanced Security
PrestaShop manipulates:
- Redirection URLs (open redirections possible)
- Payment webhooks (critical parsing)
- Mobile deep links
The URI extension standardizes and secures these processes:
<span class="c1">// Before: fragile parse_url()</span>
<span class="nv">$redirect</span> <span class="o">=</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s1">'redirect'</span><span class="p">];</span>
<span class="nv">$parsed</span> <span class="o">=</span> <span class="nb">parse_url</span><span class="p">(</span><span class="nv">$redirect</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$parsed</span><span class="p">[</span><span class="s1">'host'</span><span class="p">]</span> <span class="o">===</span> <span class="s1">'monshop.com'</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">header</span><span class="p">(</span><span class="s2">"Location: </span><span class="nv">$redirect</span><span class="s2">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// ⚠️ Bypassable with Unicode, ambiguous encodings</span>
<span class="c1">// After: secure Uri</span>
<span class="k">try</span> <span class="p">{</span>
<span class="nv">$uri</span> <span class="o">=</span> <span class="nc">Uri\Rfc3986\Uri</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="s1">'redirect'</span><span class="p">]);</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$uri</span><span class="o">-></span><span class="nf">getHost</span><span class="p">()</span> <span class="o">===</span> <span class="s1">'monshop.com'</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">header</span><span class="p">(</span><span class="s2">"Location: "</span> <span class="mf">.</span> <span class="nv">$uri</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nc">ValueError</span> <span class="nv">$e</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Malformed URL = rejection</span>
<span class="p">}</span>
Secure Override System
PrestaShop allows modules to override core classes. The #[Override] attribute detects incompatibilities:
<span class="c1">// In a module</span>
<span class="kd">class</span> <span class="nc">Product</span> <span class="kd">extends</span> <span class="nc">ProductCore</span> <span class="p">{</span>
<span class="c1">#[\Override]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nv">$reference</span><span class="p">;</span> <span class="c1">// ✅ Exists in ProductCore</span>
<span class="c1">#[\Override]</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">getPrice</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// ✅ Parent method exists</span>
<span class="k">return</span> <span class="k">parent</span><span class="o">::</span><span class="nf">getPrice</span><span class="p">()</span> <span class="o">*</span> <span class="mf">0.9</span><span class="p">;</span> <span class="c1">// 10% discount</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// If the core changes, immediate compilation error</span>
<span class="c1">// Instead of a silent bug in production</span>
Improved Internationalization
PrestaShop supports 75+ languages. Intl improvements facilitate:
<span class="c1">// Product attribute display</span>
<span class="nv">$attributes</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'XL'</span><span class="p">,</span> <span class="s1">'Rouge'</span><span class="p">,</span> <span class="s1">'Coton'</span><span class="p">];</span>
<span class="nv">$formatter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">IntlListFormatter</span><span class="p">(</span><span class="nv">$customer</span><span class="o">-></span><span class="nf">getLocale</span><span class="p">());</span>
<span class="k">echo</span> <span class="nv">$formatter</span><span class="o">-></span><span class="nf">format</span><span class="p">(</span><span class="nv">$attributes</span><span class="p">);</span>
<span class="c1">// FR: "XL, Rouge et Coton"</span>
<span class="c1">// AR: "XL، أحمر و قطن" (automatic RTL order)</span>
<span class="c1">// RTL detection for themes</span>
<span class="k">if</span> <span class="p">(</span><span class="nf">locale_is_right_to_left</span><span class="p">(</span><span class="nv">$language</span><span class="o">-></span><span class="n">locale</span><span class="p">))</span> <span class="p">{</span>
<span class="nv">$smarty</span><span class="o">-></span><span class="nf">assign</span><span class="p">(</span><span class="s1">'text_direction'</span><span class="p">,</span> <span class="s1">'rtl'</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Tolerant search</span>
<span class="nv">$query</span> <span class="o">=</span> <span class="s1">'telefone'</span><span class="p">;</span> <span class="c1">// typo</span>
<span class="nv">$suggestions</span> <span class="o">=</span> <span class="nb">array_filter</span><span class="p">(</span><span class="nv">$products</span><span class="p">,</span> <span class="k">function</span><span class="p">(</span><span class="nv">$p</span><span class="p">)</span> <span class="k">use</span> <span class="p">(</span><span class="nv">$query</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">grapheme_levenshtein</span><span class="p">(</span><span class="nv">$query</span><span class="p">,</span> <span class="nv">$p</span><span class="o">-></span><span class="n">name</span><span class="p">)</span> <span class="o"><=</span> <span class="mi">2</span><span class="p">;</span>
<span class="p">});</span>
<span class="c1">// Suggests "téléphone" even with missing accents</span>
Migration and Compatibility
Migration Checklist
1. Check Deprecations
<span class="c"># Analyze code with PHPStan</span>
composer require <span class="nt">--dev</span> phpstan/phpstan
vendor/bin/phpstan analyse <span class="nt">--level</span><span class="o">=</span>8 src/
<span class="c"># Or Rector to automate fixes</span>
composer require <span class="nt">--dev</span> rector/rector
vendor/bin/rector process src/ <span class="nt">--dry-run</span>
2. Replace Deprecated Functions
Deprecated Replace with curl_close($ch) Nothing (auto) imagedestroy($img) Nothing (auto) (boolean) $x (bool) $x cmd shell_exec('cmd') __sleep() / __wakeup() __serialize() / __unserialize() #### 3. Test Edge Cases
<span class="c1">// Backtraces: verify your handlers</span>
<span class="nb">register_shutdown_function</span><span class="p">(</span><span class="k">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nv">$error</span> <span class="o">=</span> <span class="nb">error_get_last</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$error</span> <span class="o">&&</span> <span class="k">isset</span><span class="p">(</span><span class="nv">$error</span><span class="p">[</span><span class="s1">'trace'</span><span class="p">]))</span> <span class="p">{</span>
<span class="c1">// New in 8.5, adapt your code</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="c1">// URI: replace parse_url()</span>
<span class="c1">// Before</span>
<span class="nv">$parts</span> <span class="o">=</span> <span class="nb">parse_url</span><span class="p">(</span><span class="nv">$url</span><span class="p">);</span>
<span class="c1">// After</span>
<span class="k">try</span> <span class="p">{</span>
<span class="nv">$uri</span> <span class="o">=</span> <span class="nc">Uri\Rfc3986\Uri</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="nv">$url</span><span class="p">);</span>
<span class="nv">$parts</span> <span class="o">=</span> <span class="p">[</span>
<span class="s1">'scheme'</span> <span class="o">=></span> <span class="nv">$uri</span><span class="o">-></span><span class="nf">getScheme</span><span class="p">(),</span>
<span class="s1">'host'</span> <span class="o">=></span> <span class="nv">$uri</span><span class="o">-></span><span class="nf">getHost</span><span class="p">(),</span>
<span class="c1">// ...</span>
<span class="p">];</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nc">ValueError</span> <span class="nv">$e</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Clean error handling</span>
<span class="p">}</span>
4. Optimize with New Features
- Replace
reset() / end()witharray_first() / array_last() - Adopt the pipe operator for data transformations
- Use
clone withfor immutable objects
Compatibility with PHP 8.4
PHP 8.5 is backward compatible with 8.4. Your 8.4 code runs on 8.5 (except deprecations which emit warnings).
Progressive migration strategy:
- Dev/Staging: PHP 8.5 from December 2025
- Intensive testing: January-February 2026
- Production: March 2026 (after PHP 8.5.1-8.5.2 for bugfixes)
Docker Environment
<span class="k">FROM</span><span class="s"> php:8.5-fpm-alpine</span>
<span class="c"># OPcache already there, just configure</span>
<span class="k">RUN </span><span class="nb">echo</span> <span class="s2">"opcache.memory_consumption=256"</span> <span class="o">>></span> /usr/local/etc/php/conf.d/opcache.ini <span class="o">&&</span> <span class="se">\
</span> <span class="nb">echo</span> <span class="s2">"opcache.max_accelerated_files=20000"</span> <span class="o">>></span> /usr/local/etc/php/conf.d/opcache.ini <span class="o">&&</span> <span class="se">\
</span> <span class="nb">echo</span> <span class="s2">"opcache.validate_timestamps=0"</span> <span class="o">>></span> /usr/local/etc/php/conf.d/opcache.ini
<span class="c"># URI extension: included by default</span>
<span class="c"># Intl: install if not present</span>
<span class="k">RUN </span>apk add <span class="nt">--no-cache</span> icu-dev <span class="o">&&</span> <span class="se">\
</span> docker-php-ext-install intl
<span class="c"># Other classic extensions</span>
<span class="k">RUN </span>docker-php-ext-install pdo_mysql gd zip
Conclusion
PHP 8.5 isn’t a spectacular revolution. It’s a surgical evolution that corrects 20 years of accumulated minor frustrations.
The pipe operator transforms your verbose code into expressive pipelines. Clone with finally makes immutability practicable. Mandatory OPcache guarantees performance everywhere. Backtraces on fatal errors divide your debugging time by 10. The URI extension eliminates an entire class of security vulnerabilities.
Each new feature responds to a real pain point. No gimmicks, no hype. Just pragmatic solutions to write safer, faster, more maintainable code.
For PrestaShop and e-commerce applications, PHP 8.5 is a no-brainer: measurable performance gains, enhanced security, facilitated internationalization, improved debugging. The few hours of adaptation (deprecations to correct) are largely compensated by long-term benefits.
My advice? Test PHP 8.5 as soon as it’s released in November 2025. Adopt it in production after the first corrective versions (8.5.1-8.5.2, probably January 2026). You won’t go back.
PHP 8.5 proves one thing: the maturity of a language isn’t measured by its revolutions, but by its capacity for continuous evolution without breaking the existing. And from this perspective, PHP is at the peak of its art.
Article published on November 16, 2025 by Nicolas Dabène - PHP & PrestaShop Expert with 15+ years of experience