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

What's new in PHP 8.5

What's new in PHP 8.5

Berlin PHP User Group, 23rd of July, 2025

Avatar for Volker Dusch

Volker Dusch

July 23, 2025
Tweet

More Decks by Volker Dusch

Other Decks in Programming

Transcript

  1. About me ~20 years of PHP I work on web

    applications with long life cycles
  2. About me ~20 years of PHP I work on web

    applications with long life cycles Working at @Tideways on PHP Performance Tooling
  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
  5. 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
  6. 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
  7. 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
  8. Uri Parsing Example Same with Uri\Rfc3986\Url $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");
  9. WHATWG example For URLs to render in a browser. Use

    WHATWG. For everything else use RFC 3986. <?php $url = new Uri\Whatwg\Url( "HTTPS://apple:p%63ss@exämple.com:433" . "/foobar?abc=abc#def" ); echo $url->getScheme(); // https echo $url->getUsername(); // apple echo $url->getPassword(); // pass echo $url->getAsciiHost(); // example.com echo $url->getUnicodeHost(); // example.com echo $url->getPort(); // 433 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
  10. 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
  11. 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, $sort); return $users; } 1 2 3 4 5 6 7 8 9 10 11 12
  12. 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
  13. 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
  14. 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; }
  15. 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 { } }
  16. 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');
  17. 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
  18. 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
  19. 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($greeeting); 1 2 3 4 5 6
  20. 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(...) }
  21. 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"
  22. 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.
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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}
  28. #[\NoDiscard] Authors: Tim Düsterhus, Volker Dusch Implementation & Review: Tim

    Düsterhus, Niels Dossche, Ilija Tovilo, Dmitry Stogov New Attribute and core functionality. Return values must be used or explicitly discarded. https://wiki.php.net/rfc/marking_return_value_as_important $x = new DateTimeImmutable('now'); $x->modify('+1 hours'); // 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
  29. 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 18
  30. 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
  31. 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];
  32. 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'
  33. 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 :)
  34. 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
  35. 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
  36. 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
  37. 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;' // Jul 18 2025 16:45:00 // https://github.com/docker-library/php
  38. 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
  39. 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
  40. Thank you Slides will be at I sometimes talk PHP

    on Mastodon @edorian.phpc.social https://speakerdeck.com/edorian
  41. Q&A