Caption: Native array_last() and Uri\Rfc3986\Uri are fair game. Arr::first($arr, $callback) still belongs to Illuminate. CLI php -v is not the FPM socket Nginx uses.
I did not need another changelog recitation. PHP 8.5.0 shipped 20 November 2025. Nine months later I still have Laravel boxes whose CLI and whose Nginx socket disagree. The change I would make this week is not “rewrite the app in pipes.” It is: make FPM actually run 8.5, stop treating array_first() like Laravel’s old helper, and only then pick the two language features that earn their keep in app code.
This is a lab note, not a claim I rebuilt a client production cluster tonight. The failure shapes below are the ones Ubuntu 24.04 + Laravel installs are famous for, plus one Laravel 13 upgrade-guide trap I would hit the first time I grepped array_first(.
What changed
php.net’s 8.5 announcement is dated 20 Nov 2025. Headline language that actually shows up in a Laravel app:
- Pipe operator
|>— left-to-right callable chaining. RFC dated 5 Feb 2025, implemented in 8.5 (wiki.php.net/rfc/pipe-operator-v3). array_first()/array_last()— first or last value,nullon empty. Not a callback filter. RFC: wiki.php.net/rfc/array_first_last.clone($object, [...])— with-er pattern forreadonlyclasses. Clone-with v2 RFC, vote closed 18 Jun 2025, 16–4 (internals note; php.watch RFC page).- Always-on URI extension —
Uri\Rfc3986\UriandUri\WhatWg\Url. RFC dated 11 Jun 2024, implemented (wiki.php.net/rfc/url_parsing_api). PHP Foundation recap 10 Oct 2025: thephp.foundation/blog/2025/10/10/php-85-uri-extension.
The patch I would actually install this week is 8.5.10, released 27 Aug 2026 (php.watch 8.5.10). That tag is a security/bugfix drop, not a feature drop. PHP 8.5 stays in active support until 31 Dec 2027 and security-only until 31 Dec 2029 (php.net supported versions, retrieved 4 Sep 2026). PHP 8.2 security ends 31 Dec 2026 — four months from this post. PHP 8.4 active support ends the same day.
Laravel’s side of the floor, from the current docs:
| Framework | PHP floor | Notes I would not skip |
|---|---|---|
| Laravel 12 | 8.2–8.5 | Released 24 Feb 2025. Bug fixes ended 13 Aug 2026. Security until 24 Feb 2027. |
| Laravel 13 | 8.3–8.5 | Released 17 Mar 2026. Bug fixes through Q3 2027, security until 17 Mar 2028. PHP 8.3 minimum. |
Sources: laravel.com/docs/12.x/releases (the 12.x page now banners you toward 13.x) and laravel.com/docs/13.x/releases. Laravel 12 already grew 8.5 compatibility in framework PRs such as laravel/framework#57835 (PDO constant / 8.5 prep). I would not invent a “Laravel does not run on 8.5” story.
What I would change in a real Laravel/PHP app this week
Three edits. Not thirty.
1. Stop calling native array_first() like the old helper.
Laravel 13’s upgrade guide is blunt: symfony/polyfill-php85 defines global array_first() / array_last() on PHP below 8.5, and those functions only return the first/last element. The historical laravel/helpers array_first($array, $callback) is a different function. Mix them and you get “the first user in the array,” not “the first active user.” Documented under “Symfony PHP 8.5 Polyfill and Global Function Conflicts” on laravel.com/docs/13.x/upgrade.
use App\Models\User;
use Illuminate\Support\Arr;
/** @var list<User> $users */
// Before — looks familiar, lies on PHP 8.5 / Laravel 13 polyfill.
// Native/polyfill array_first() does not take a callback.
$active = array_first($users, fn (User $user): bool => $user->is_active);
// After — callback form stays on Illuminate.
$active = Arr::first($users, fn (User $user): bool => $user->is_active);
// Native array_last() is fine when I truly want the last value.
$latestComment = array_last($post->comments->all()) ?? null;
If that first line is still in app/, I would fix it before I touch |>. PHPStan will not always save me if the callback is untyped mixed.
2. Use the pipe on a pure transform. Leave Eloquent alone.
The php.net 8.5 page slug example is the right size: trim, replace, lower. Arrow functions in a pipe must be parenthesized (php.net functional operators).
$title = ' PHP 8.5 Released ';
// Before
$slug = strtolower(str_replace('.', '', str_replace(' ', '-', trim($title))));
// After — 8.5. Same opcodes as nested calls. Not a new capability.
$slug = $title
|> trim(...)
|> (fn (string $str): string => str_replace(' ', '-', $str))
|> (fn (string $str): string => str_replace('.', '', $str))
|> strtolower(...);
I would not pipe User::query()->.... Collections already chain. The pipe is for functions that take one required argument and do not take by-ref. array_filter needs a wrapping closure. That is the limitation, not a style debate.
3. clone() for readonly DTOs. Uri\Rfc3986\Uri for webhook URLs. Not for models.
Eloquent is not a readonly value object. Cloning a model does not clone relations, does not reset exists, and will lie in a queued job. I would keep with-ers on DTOs:
readonly class Money
{
public function __construct(
public int $cents,
public string $currency = 'USD',
) {}
public function withCurrency(string $currency): self
{
return clone($this, ['currency' => $currency]);
}
}
For inbound webhook URLs I still see parse_url() plus FILTER_VALIDATE_URL. The URI RFC’s own motivation is that parse_url() matches no current standard, and FILTER_VALIDATE_URL vs cURL disagreement is a known parsing-confusion class. On 8.5:
use Uri\Rfc3986\Uri;
use Uri\InvalidUriException;
try {
$uri = new Uri($request->string('callback_url')->toString());
} catch (InvalidUriException $e) {
abort(422, 'Callback URL did not parse as RFC 3986.');
}
$host = $uri->getHost();
I would still allow-list hosts in app code. A parser is not a policy.
Composer: after FPM is actually 8.5, I would set config.platform.php to 8.5.10 (or the patch I installed) so CI cannot resolve packages against an 8.2 phantom.
What would bite on a self-hosted VPS
Ubuntu 24.04 LTS still ships PHP 8.3 from distro packages. PHP 8.5 on that box is ppa:ondrej/php and a new FPM unit. The failure this upgrade is famous for:
# php -v
PHP 8.5.10 (cli) ...
# curl -sI https://app.example.com | grep -i x-powered
# or: phpinfo() in the browser still says 8.3
Diagnosis: I installed php8.5-cli (so Artisan and Composer look modern) and left Nginx on the old socket.
# still this:
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
Fix: install the 8.5 FPM stack and the extensions Laravel actually loads, then point the vhost at the new socket.
sudo add-apt-repository -y ppa:ondrej/php
sudo apt update
sudo apt install -y php8.5-cli php8.5-fpm php8.5-mbstring php8.5-xml \
php8.5-curl php8.5-mysql php8.5-zip php8.5-intl php8.5-bcmath \
php8.5-gd php8.5-redis php8.5-soap
sudo systemctl enable --now php8.5-fpm
ls -l /run/php/php8.5-fpm.sock
# Nginx: unix:/run/php/php8.5-fpm.sock
sudo nginx -t && sudo systemctl reload nginx php8.5-fpm
Caption: php -v can print 8.5 while Nginx still talks to php8.3-fpm.sock. Composer platform.php has to match FPM, not only CLI.
Second bite: missing php8.5-* extensions. ext-xml and ext-mbstring are how you get “Class Illuminate... not found” that is really “DOMDocument is missing.” Composer platform checks pass on the laptop; FPM does not load the same modules.
Third bite, from the 8.5 migration incompatibilities: OPcache is always built into the binary. Copying an old php.ini with zend_extension=opcache.so now emits a warning. opcache.enable still matters. After a git pull, I still reload FPM or I serve stale files — that part did not change.
Deprecations I would grep before I call the box “done” (migration85.deprecated):
- backticks as
shell_exec() (integer)/(boolean)castsnullas an array offset /array_key_exists(null, $arr)__sleep()/__wakeup()(soft-deprecated; prefer__serialize()/__unserialize())- PDO driver-specific constants on the
PDOclass — this is the class of breakage Laravel already patched for 8.5
Shared hosting: if the panel cannot give me 8.5 FPM, I do not “upgrade the app” and hope. Laravel 13 will not even install on PHP 8.2. Laravel 12 will still run there until 24 Feb 2027, but it is already past bugfixes.
Support windows, not vibes
Caption: PHP 8.5 is already in active support (GA 20 Nov 2025 → 31 Dec 2027). Laravel 12 bugfixes ended 13 Aug 2026. Laravel 13 (17 Mar 2026) is the current major. Dates from php.net supported-versions and Laravel 13 release notes.
If the VPS is still on PHP 8.2 in September 2026, the language feature list is a distraction. That branch’s security window closes 31 Dec 2026.
What is running in my head as the honest target: Laravel 13 + PHP 8.5.10 FPM on Ubuntu 24.04 via ondrej, Nginx socket matching /run/php/php8.5-fpm.sock, Arr::first for callbacks, native array_last where I mean it, pipes only on string/array transforms, clone() on readonly DTOs, URI classes on untrusted URLs. Next: composer audit on the same box — that is a different queue item.
Are you on this in production?
If Nginx already talks to php8.5-fpm.sock and array_first( in app/ is only the native one-argument form, you are on this. If php -v prints 8.5.10 and the browser phpinfo() is still 8.3 — or the host will not give you an 8.5 pool at all — you are not. That second camp is the one I expect more of. I did not flip a client cluster tonight; I want to know which side of that line you are on.
Did you hit the same wall?
I would get stuck on CLI 8.5 / FPM 8.3 or on array_first($arr, $callback) silently meaning “first element”. Did you hit the same thing, or a different one — missing php8.5-xml, zend_extension=opcache.so warnings, a package that still constrains <8.5, shared hosting that will not give you a socket? Tell me in the comments. I read them.
If the blocker is the VPS itself (PPA, FPM pools, a CodeCanyon app frozen on Laravel 12), I do this class of deploy for a living: Hire for deploy.
References
- PHP 8.5 release announcement, 20 Nov 2025: https://www.php.net/releases/8.5/en.php
- PHP 8.5 new features (manual): https://www.php.net/manual/en/migration85.new-features.php
- PHP 8.5 backward incompatible changes: https://www.php.net/manual/en/migration85.incompatible.php
- PHP 8.5 deprecated features: https://www.php.net/manual/en/migration85.deprecated.php
- PHP supported versions (retrieved 4 Sep 2026): https://www.php.net/supported-versions.php
- PHP 8.5.10, 27 Aug 2026: https://php.watch/versions/8.5/releases/8.5.10
- Pipe operator RFC (published 5 Feb 2025): https://wiki.php.net/rfc/pipe-operator-v3
- Pipe operator notes: https://php.watch/versions/8.5/pipe-operator
array_first/array_lastRFC: https://wiki.php.net/rfc/array_first_last- Clone with v2 (vote ended 18 Jun 2025): https://php.watch/rfcs/clone_with_v2
- URI parsing API RFC (dated 11 Jun 2024): https://wiki.php.net/rfc/url_parsing_api
- PHP Foundation URI extension post, 10 Oct 2025: https://thephp.foundation/blog/2025/10/10/php-85-uri-extension/
- Laravel 12 support policy: https://laravel.com/docs/12.x/releases
- Laravel 13 support policy (13 released 17 Mar 2026): https://laravel.com/docs/13.x/releases
- Laravel 13 upgrade guide (
array_firstpolyfill conflict): https://laravel.com/docs/13.x/upgrade - Laravel framework PHP 8.5 compatibility PR: https://github.com/laravel/framework/pull/57835