Upgrade to Pro — share decks privately, control downloads, hide ads and more …

What's new in PHP 8.5 - IPC Munich 2025

What's new in PHP 8.5 - IPC Munich 2025

Avatar for Volker Dusch

Volker Dusch

October 29, 2025
Tweet

More Decks by Volker Dusch

Other Decks in Technology

Transcript

  1. PHP 8.5 Release on the 20th of November 2025 8.5.0RC3

    (Release Candidate) is available for testing Ecosystem news Why deprecations matter https://www.php.net/archive/2025.php#2025-10-23-1
  2. Today How do features get added to PHP What's coming

    in PHP 8.5 Dealing with deprecations
  3. About me ~20 years of PHP I work on web

    applications with long life cycles Working at @Tideways on PHP Performance Tooling Head of Engineering and "Full Stack" Engineer
  4. How does PHP get new features? People build them! RFC

    process for features Karma / Commit-Bit to php-src for maintenance If a PR looks non-trivial, an RFC can be requested Policy RFC process
  5. RFC process https://wiki.php.net/rfc/howto Have an idea Validate through discussion Write

    an RFC Basically a form to carefully fill out A reference implementation helps, you can find help with that Discuss on the mailing list Vote Technical review on GitHub
  6. URI Parsing API Author: Máté Kocsis Implementation & Review: Máté

    Kocsis, Niels Dossche, Tim Düsterhus RFC 3986: Defines URIs WHATWG: Specifies how browsers should treat URLs Web Hypertext Application Technology Working Group https://wiki.php.net/rfc/url_parsing_api
  7. URI Parsing Example Same with Uri\Rfc3986\Uri $url = new Uri\WhatWg\Url("https://example.com");

    $url = Uri\WhatWg\Url::parse("https://example.com"); // Throws Uri\WhatWg\InvalidUrlException $url = new Uri\WhatWg\Url("invalid url");
  8. WHATWG example For URLs to render in a browser. Use

    WHATWG. For everything else use RFC 3986. <?php $url = new Uri\Whatwg\Url( "HTTPS://user:pass@exämple.com:443" . "/foobar?abc=abc#def" ); echo $url->getScheme(); // https echo $url->getUsername(); // user echo $url->getPassword(); // pass echo $url->getAsciiHost(); // xn--exmple-cua.com echo $url->getUnicodeHost(); // exämple.com echo $url->getPort(); // 443 echo $url->getPath(); // /foobar echo $url->getQuery(); // abc=abc echo $url->getFragment(); // def 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  9. With-ers <?php $url = new Uri\WhatWg\Url("https://example.com/"); $url = $url->withQuery("?foo"); $url

    = $url->withFragment("#bar"); echo $url->getQuery(); // foo echo $url->getFragment(); // bar 1 2 3 4 5 6 7
  10. Closures in Constant Expressions Authors: Tim Düsterhus, Volker Dusch Implementation:

    Tim Düsterhus, Volker Dusch, Ilija Tovilo, Arthur Kurbidaev Unfamiliar to read at first glance https://wiki.php.net/rfc/closures_in_const_expr <?php function getSortedUsers( $s = static function($a, $b) { return $a['id'] <=> $b['id']; } ): array { $users = [ ['id' => 3, 'name' => 'Alice'], ['id' => 1, 'name' => 'Bob'], ['id' => 2, 'name' => 'Charlie'], ]; usort($users, $s); return $users; } 1 2 3 4 5 6 7 8 9 10 11 12
  11. One interesting use case: Attributes <?php final class Locale {

    #[Validator\Custom(static function ($languageCode) { return \preg_match('/^[a-z][a-z]$/', $languageCode); })] public string $languageCode; } 1 2 3 4 5 6 7 8 <?php final class LogEntry { #[Serialize\Custom(static function ($severity) { return \strtoupper($severity); })] public string $severity; } 1 2 3 4 5 6 7 8
  12. No short closures Short closures ("arrow functions") are not supported,

    because they perform implicit capturing. $bar = random_int(0, 100); // Fatal error: Constant expression contains // invalid operations in ... on line 5 const FOO = fn() => $bar; 1 2 3 4 5
  13. First Class Callables in constant expressions Authors: Tim Düsterhus, Volker

    Dusch Implementation & Review: Tim Düsterhus, Volker Dusch, Ilija Tovilo, Dmitry Stogov Following the last RFC: https://wiki.php.net/rfc/fcc_in_const_expr final class LogEntry { public string $message; #[Serialize\Custom(self::myMethod(...))] public string $severity; }
  14. Potential use case: PHPUnit data provider IDE Autocomplete. "Method not

    found" static analysis. class MyTest { // Before #[DataProvider('dataProvider')] // After #[DataProvider(self::dataProvider(...))] public testThing($arg1, $arg2, $arg3): void { } }
  15. Just because you can... What's also technically possible now: But

    maybe don't? :) Or tell me where it's useful to you! const LOG = MyLogger::log(...); (LOG)('message');
  16. Pipes Author: Larry Garfield Implementation & Review: Larry Garfield, Gina

    Peter Banyard, Arnaud Le Blanc, Tim Düsterhus Trying to make function call chains more readable. https://wiki.php.net/rfc/pipe-operator-v3 $greeting = " Hello World "; $length = $greeting |> trim(...) |> strlen(...); 2 3 4 5 $greeting = " Hello World "; $length = strlen(trim($greeting)); 2 3
  17. Remember how PHP used to look? $greeting = 'hello world';

    $greeting = implode( ' ', array_map( 'ucfirst', explode( ' ', $greeting, ) ) ); var_dump($greeting); 1 2 3 4 5 6 7 8 9 10 11 12
  18. Using multiple assignments Now expressed as pipes $greeting = 'hello

    world'; $greeting = explode(' ', $greeting); $greeting = array_map(ucfirst(...), $greeting); $greeting = implode(' ', $greeting); var_dump($greeting); 1 2 3 4 5 $greeting = 'hello world' |> (fn(string $greeting) => explode(' ', $greeting)) |> (fn(array $parts) => array_map(ucfirst(...), $parts)) |> (fn(array $parts) => implode(' ', $parts)); var_dump($greeting);` 1 2 3 4 5 6
  19. Processing pipeline The statements are treated as nested function calls.

    Exceptions work by leaving the whole pipe. public function totalCostCalculator(Price $listPrice): Price { return $price |> $this->addShipping(...) |> $this->applyCoupons(...) |> $this->applyMemberDiscounts(...) |> $this->addTax(...) }
  20. php --ini=diff Implementation & Review: Tim Düsterhus, Gina Peter Banyard,

    Jakub Zelenka Remember your CLI and web server settings might be different! ➜ php --ini=diff Non-default INI settings: html_errors: "1" -> "0" implicit_flush: "0" -> "1" max_execution_time: "30" -> "0"
  21. Non existent settings are ignored php -d opcache.enable=1 -d opcache.enable_cli=1

    \ -d opcache.opt_debug_level=0x20000 --ini=diff If there is no output, something is missing. # We've been missing: zend_extension=opcache.so # ... except with PHP 8.5 that's solved as well
  22. Always on OPcache Authors: Tim Düsterhus, Arnaud Le Blanc, Ilija

    Tovilo Implementation & Review: Arnaud Le Blanc, Niels Dossche, Dmitry Stogov, Tim Düsterhus Statically built into PHP - Easier "single binary" setups. Simplified production setups. No more "is OPcache available?" checks. https://wiki.php.net/rfc/make_opcache_required
  23. Attributes on Constants Author: Daniel Scherzer Implementation & Review: Daniel

    Scherzer, Dmitry Stogov, Ilija Tovilo, Tim Düsterhus, Niels Dossche https://wiki.php.net/rfc/attributes-on-constants <?php #[\Deprecated] const FOO = 1; echo FOO; // Deprecated: Constant FOO is deprecated in $file on line 6 1 2 3 4 5 6 7 8
  24. Error Backtraces v2 Authors: Eric Norris Implementation & Review: Eric

    Norris, Niels Dossche, Dmitry Stogov, Gina Peter Banyard, Jakub Zelenka, Calvin Buckley, Bob Weinand, Derick Rethans, Ilija Tovilo, Tim Düsterhus, Remi Collet We already had stack traces for undefined functions, classes and failed requires. Now we have them for: Timeouts Out of Memory errors Class redefinitions https://wiki.php.net/rfc/error_backtraces_v2
  25. Infinite loop example <?php set_time_limit(1); function bar() { foo(); }

    function foo() { for(;;) {} } (fn () => bar())(); 1 2 3 4 5 6 7 8 9 10 11
  26. New with Backtraces PHP 8.4 PHP 8.5 // Fatal error:

    Maximum execution time of 1 second exceeded in... // Fatal error: Maximum execution time of 1 second exceeded in... // Stack trace: // #0 .../fatal-error-backtrace.php(4): foo() // #1 .../fatal-error-backtrace.php(11): bar() // #2 .../fatal-error-backtrace.php(11): {closure:/.../fatal-error-backtrace.php:11}() // #3 {main}
  27. #[\NoDiscard] Authors: Tim Düsterhus, Volker Dusch Implementation & Review: Tim

    Düsterhus, Niels Dossche, Ilija Tovilo, Dmitry Stogov Enforce return values to be used or explicitly discarded https://wiki.php.net/rfc/marking_return_value_as_important function addOneHour($x) { /* Missing return */ $x->modify('+1 hours'); } $x = new DateTimeImmutable('now'); addOneHour($x); // Warning: The return value of method DateTimeImmutable::modify() // should either be used or intentionally ignored by casting // it as (void), as DateTimeImmutable::modify() // does not modify the object itself // Missing assignment here undetected as addOneHour doesn't have #[\
  28. Patching up APIs <?php class File {} class FileFormatConverter {

    #[\NoDiscard('as the file is not converted in place')] public function convert(File $file): File { return new File(); } } $converter = new FileFormatConverter(); // Warning: The return value of method // FileFormatConverter::convert() should either be used // or intentionally ignored by casting it as (void), // as the file is not converted in place in ... $converter->convert(new File()); // No warning: (void)$converter->convert(new File()); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  29. Persistent Curl Handles Author: Eric Norris Implementation & Review: Eric

    Norris, Niels Dossche, Gina Peter Banyard, Tim Düsterhus, Máté Kocsis Connection reuse between curl requests across script runs (of the same worker process). https://wiki.php.net/rfc/curl_share_persistence_improvement
  30. array_first & array_last Author: Niels Dossche Implementation & Review: Niels

    Dossche, Tim Düsterhus https://wiki.php.net/rfc/array_first_last // A common, stable way to do this before: foreach ($array as $first) { break; } // Works, but changes the array pointer: $first = reset($array); // Takes up additional memory: $first = array_values($array)[0];
  31. It just works function array_first(array $array): mixed {} function array_last

    (array $array): mixed {} array_first([1 => 'a', 0 => 'b', 3 => 'c', 2 => 'd']); // 'a' array_last ([1 => 'a', 0 => 'b', 3 => 'c', 2 => 'd']); // 'd'
  32. Why only now? Because nobody built it before. RFC was

    voted in with 35:0. Discussion was short Just makes sense This is to say: If you have an array_first grade improvement. Please build it! Let me know if you need help with the process :)
  33. Clone with Authors: Volker Dusch, Tim Düsterhus Implementation & Review:

    Volker Dusch, Tim Düsterhus, Niels Dossche, Ilija Tovilo Rounds out the readonly/immutable feature set Avoids having to cram a lot of code in __clone https://wiki.php.net/rfc/clone_with_v2
  34. Example final readonly class Response { public function __construct( public

    int $statusCode, public string $reasonPhrase, // ... ) {} public function withStatus( int $code, string $reasonPhrase = '', ): self { return clone($this, [ "statusCode" => $code, "reasonPhrase" => $reasonPhrase, ]); } } $x = new Response(); $newResponse = $x->withStatus(500, 'Error'); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  35. PHP_BUILD_PROVIDER & PHP_BUILD_DATE Implementation & Review: Calvin Buckley, Peter Kokot,

    Jakub Zelenka, Gina Peter Banyard, Tim Düsterhus, Christoph M. Becker, Ayesh Karunaratne Where does your PHP come from? The PHP project only ships source code. Useful for bug reports and debugging. docker run -it --rm php:8.5-rc-cli \ php -r 'echo PHP_BUILD_DATE, PHP_EOL, PHP_BUILD_PROVIDER;' Oct 21 2025 01:23:19 https://github.com/docker-library/php
  36. PIE 1.0 Meet the new PECL: 🥧 Extension distribution via

    Packagist. pie install apcu/apcu gh release --repo php/pie download --pattern pie.phar gh attestation verify --repo php/pie pie.phar
  37. FrankenPHP as part of the PHP GitHub org Supported by

    the PHP Foundation A new single binary runtime for PHP, using Caddy Think Apache with mod_php but with Caddy DX HTTP 103 support, static binaries, worker mode, etc php-fpm continues to be a first class citizen https://frankenphp.dev
  38. Deprecations Full insights in src: https://github.com/php/php-src/blob/PHP-8.5/UPGRADING The PHP Project takes

    great care to ensure minimal impact. Proving time windows for migrations. Nothing gets removed 8.5! Removing certain engine behaviors with PHP 9 allows for optimizations and speedups!
  39. The big cleanup RFC <3 Deprecations can get added throughout

    the year. Recent releases have had one collection RFC. Great effort by Gina Peter Banyard managing all that. One discussion, lots of votes, keeps things focused, moving and effective. https://wiki.php.net/rfc/deprecations_php_8_5
  40. Dealing with deprecation Reminder: A newly added deprecation is not

    considered a BC break. Don't write code that breaks because of deprecations. Throwing in error handlers for deprecations means you own the upgrade/BC path.
  41. Backticks Anyone remember what these do in PHP? $foo =

    `bar`; 1 shell_exec() without looking at return code Minimal refactoring thanks to <<<'NOWDOC' Free's up backtick for new ideas $foo = shell_exec('bar'); // Or better exec('bar 2>&1', $output, $returnCode); if ($returnCode) { echo "Fail ..."; } 1 2 3 4 https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_backticks_as_an_alias_for_shell_
  42. Disable Classes Breaks assumptions on how PHP works No benefit

    for reasonable setups php -d disable_classes=Exception -r 'throw new Exception();' Warning: Exception() has been disabled for security reasons in... Fatal error: Uncaught Error: Cannot throw objects that do not implement Throwable in ... Stack trace: #0 {main} thrown in Command line code on line 1 https://wiki.php.net/rfc/deprecations_php_8_5#remove_disable_classes_ini_setting
  43. Removal of "close" functions PHP 8.0 migrated resources to objects.

    So cleanup now happens automatically. All manual resource "freeing" functions can go now. finfo_close() xml_parser_free() curl_close() curl_share_close() imagedestroy() https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_no- op_functions_from_the_resource_to_object_conversion
  44. Null as Array Offset Inconsistent and unexpected type juggling $x

    = []; $x[null] = 1; var_dump($x); array(1) { [""]=> int(1) } https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_using_values_null_as_an_array_o
  45. String increment & decrement All common cases still work $x

    = "bar"; $x++; // Deprecated: Increment on non-numeric string is deprecated, // use str_increment() instead in ... var_dump($x); // string(3) "bas" 1 2 3 4 5 6 $x = null; // $x++ => 1 $x = "123"; // $x++ => "124" https://wiki.php.net/rfc/deprecations_php_8_5#enact_follow- up_phase_of_the_path_to_saner_incrementdecrement_operators_rfc
  46. PDO - Driver specific constants Since PHP 8.4 PDO has

    driver specific classes class PdoMySql extends PDO { public function getWarningCount(): int {} } PDO::MYSQL_ATTR_FOUND_ROWS => Pdo\Mysql::ATTR_FOUND_ROWS PDO::MYSQL_ATTR_IGNORE_SPACE => Pdo\Mysql::ATTR_IGNORE_SPACE PDO::MYSQL_ATTR_SSL_KEY => Pdo\Mysql::ATTR_SSL_KEY PDO::MYSQL_ATTR_SSL_CERT => Pdo\Mysql::ATTR_SSL_CERT https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_driver_specific_pdo_constants_a
  47. Thank you Slides will be at I sometimes talk PHP

    on Mastodon @edorian.phpc.social https://speakerdeck.com/edorian https://phpc.social/@edorian