Nicolas Dabene
Back to blog
16 November 2025 Nicolas Dabene 9 min

PHP 8.5: Boost Your App Performance – Essential Features Developers Need

PHP 8.5: The Silent Revolution That Transforms Your Code

PHP 8.5 performance security pipe operator new features modernization PHP development
PHP 8.5: Boost Your App Performance – Essential Features Developers Need

PHP 8.5: The Silent Revolution That Transforms Your Code

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:

  1. Classic cloning: clone creates a shallow copy of the object
  2. Call to __clone(): if defined, your custom cloning logic executes
  3. Property override: the with array overwrites the specified values
  4. 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=1 from 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 %2F vs / 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() with array_first() / array_last()
  • Adopt the pipe operator for data transformations
  • Use clone with for 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

LinkedIn

Follow my AI and e-commerce analysis

I share practical notes on AI agents, PrestaShop architecture, MCP and automation for e-commerce teams.

Follow on LinkedIn