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

Upcoming changes in PHP 8.4 - Property Hooks im...

Volker Dusch
September 18, 2024

Upcoming changes in PHP 8.4 - Property Hooks improved performance and so much more

2024-09-18 - Symfony User Group Berlin

Volker Dusch

September 18, 2024
Tweet

More Decks by Volker Dusch

Other Decks in Technology

Transcript

  1. PHP 8.4 Property Hooks improved performance and so much more

    Volker "Edorian" Dusch Symfony User Group Berlin
  2. Hi

  3. Today 7 new and improved things in PHP 8.4 PHP

    performance and OPcodes Insights you wouldn't get from reading the announcement post (hopefully)
  4. About me Context so you know where I'm coming from

    20 years of PHP I work on web applications with long life cycles
  5. About me Context so you know where I'm coming from

    20 years of PHP I work on web applications with long life cycles Happily working at @Tideways - We do PHP performance
  6. About me Context so you know where I'm coming from

    20 years of PHP I work on web applications with long life cycles Happily working at @Tideways - We do PHP performance Manager and "Full Stack" Engineer
  7. Closure naming <?php namespace bar; class Foo { public function

    baz(): callable { if (random_int(0,1)) { // E.g. A/B test return function () { return $this->validate(); }; } return function () { return $this->validate(); }; } public function validate() { throw new \Exception("Failed"); } } $x = new Foo()->baz(); $x(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  8. Closure naming <?php namespace bar; class Foo { public function

    baz(): callable { if (random_int(0,1)) { // E.g. A/B test return function () { return $this->validate(); }; } return function () { return $this->validate(); }; } public function validate() { throw new \Exception("Failed"); } } $x = new Foo()->baz(); $x(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 return function () { return $this->validate(); }; return function () { return $this->validate(); }; <?php 1 namespace bar; 2 3 class Foo { 4 public function baz(): callable { 5 if (random_int(0,1)) { // E.g. A/B test 6 7 } 8 9 } 10 public function validate() { 11 throw new \Exception("Failed"); 12 } 13 } 14 $x = new Foo()->baz(); 15 $x(); 16
  9. Closure naming <?php namespace bar; class Foo { public function

    baz(): callable { if (random_int(0,1)) { // E.g. A/B test return function () { return $this->validate(); }; } return function () { return $this->validate(); }; } public function validate() { throw new \Exception("Failed"); } } $x = new Foo()->baz(); $x(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 return function () { return $this->validate(); }; return function () { return $this->validate(); }; <?php 1 namespace bar; 2 3 class Foo { 4 public function baz(): callable { 5 if (random_int(0,1)) { // E.g. A/B test 6 7 } 8 9 } 10 public function validate() { 11 throw new \Exception("Failed"); 12 } 13 } 14 $x = new Foo()->baz(); 15 $x(); 16 $x = new Foo()->baz(); <?php 1 namespace bar; 2 3 class Foo { 4 public function baz(): callable { 5 if (random_int(0,1)) { // E.g. A/B test 6 return function () { return $this->validate(); }; 7 } 8 return function () { return $this->validate(); }; 9 } 10 public function validate() { 11 throw new \Exception("Failed"); 12 } 13 } 14 15 $x(); 16
  10. Pre 8.4 Following a stack trace #1 closure.php(16): bar\Foo->bar\{closure}() Fatal

    error: Uncaught Exception: Failed in closure.php:12 1 Stack trace: 2 #0 closure.php(7): bar\Foo->validate() 3 4 #2 {main} 5 thrown in closure.php on line 12 6
  11. With 8.4 Fatal error: Uncaught Exception: Failed in closure.php:12 #1

    closure.php(16): bar\Foo->{closure:bar\Foo::method():7}() 1 Stack trace: 2 #0 closure.php(7): bar\Foo->validate() 3 4 #2 {main} 5 thrown in closure.php on line 12 6 Fatal error: Uncaught Exception: Failed in closure.php:12 #1 closure.php(16): bar\Foo->{closure:bar\Foo::method():9}() 1 Stack trace: 2 #0 closure.php(9): bar\Foo->validate() 3 4
  12. Complex example $a = function () { $b = function

    () { $c = function () { throw new \Exception(); }; $c(); }; $b(); }; $a(); ?>
  13. Pre 8.4 PHP Fatal error: Uncaught Exception in complex.php:5 Stack

    trace: #0 complex.php(7): {closure}() #1 complex.php(9): {closure}() #2 complex.php(11): {closure}() #3 {main}
  14. With 8.4 Fatal error: Uncaught Exception in complex.php:5 Stack trace:

    #0 complex.php(7): {closure:{closure:{closure:complex.php:2}:3}:4}() #1 complex.php(9): {closure:{closure:complex.php:2}:3}() #2 complex.php(11): {closure:complex.php:2}() #3 {main} thrown in complex.php on line 5
  15. Deprecated <?php #[\Deprecated("use foo() instead", since: "2.4")] function bar() {

    trigger_error( "Deprecated in 2.4 - Use foo() instead", E_USER_DEPRECATED, ); } bar(); 1 2 3 4 5 6 7 8 9 10 11 # Trigger error Deprecated: Deprecated in 2.4 - Use foo() instead in deprecated.php on line 5 # Attribute Deprecated: Function bar() is deprecated since 2.4, use foo() instead in deprecated.php on line 11 1 2 3 4 5 6 7
  16. Deprecated <?php #[\Deprecated("use foo() instead", since: "2.4")] function bar() {

    trigger_error( "Deprecated in 2.4 - Use foo() instead", E_USER_DEPRECATED, ); } bar(); 1 2 3 4 5 6 7 8 9 10 11 trigger_error( "Deprecated in 2.4 - Use foo() instead", E_USER_DEPRECATED, ); <?php 1 2 #[\Deprecated("use foo() instead", since: "2.4")] 3 function bar() { 4 5 6 7 8 } 9 10 bar(); 11 # Trigger error Deprecated: Deprecated in 2.4 - Use foo() instead in deprecated.php on line 5 # Attribute Deprecated: Function bar() is deprecated since 2.4, use foo() instead in deprecated.php on line 11 1 2 3 4 5 6 7
  17. Deprecated <?php #[\Deprecated("use foo() instead", since: "2.4")] function bar() {

    trigger_error( "Deprecated in 2.4 - Use foo() instead", E_USER_DEPRECATED, ); } bar(); 1 2 3 4 5 6 7 8 9 10 11 trigger_error( "Deprecated in 2.4 - Use foo() instead", E_USER_DEPRECATED, ); <?php 1 2 #[\Deprecated("use foo() instead", since: "2.4")] 3 function bar() { 4 5 6 7 8 } 9 10 bar(); 11 #[\Deprecated("use foo() instead", since: "2.4")] <?php 1 2 3 function bar() { 4 trigger_error( 5 "Deprecated in 2.4 - Use foo() instead", 6 E_USER_DEPRECATED, 7 ); 8 } 9 10 bar(); 11 # Trigger error Deprecated: Deprecated in 2.4 - Use foo() instead in deprecated.php on line 5 # Attribute Deprecated: Function bar() is deprecated since 2.4, use foo() instead in deprecated.php on line 11 1 2 3 4 5 6 7
  18. Deprecated <?php #[\Deprecated("use foo() instead", since: "2.4")] function bar() {

    trigger_error( "Deprecated in 2.4 - Use foo() instead", E_USER_DEPRECATED, ); } bar(); 1 2 3 4 5 6 7 8 9 10 11 trigger_error( "Deprecated in 2.4 - Use foo() instead", E_USER_DEPRECATED, ); <?php 1 2 #[\Deprecated("use foo() instead", since: "2.4")] 3 function bar() { 4 5 6 7 8 } 9 10 bar(); 11 #[\Deprecated("use foo() instead", since: "2.4")] <?php 1 2 3 function bar() { 4 trigger_error( 5 "Deprecated in 2.4 - Use foo() instead", 6 E_USER_DEPRECATED, 7 ); 8 } 9 10 bar(); 11 # Trigger error Deprecated: Deprecated in 2.4 - Use foo() instead in deprecated.php on line 5 # Attribute Deprecated: Function bar() is deprecated since 2.4, use foo() instead in deprecated.php on line 11 1 2 3 4 5 6 7 Use foo() instead in deprecated.php on line 5 use foo() instead in deprecated.php on line 11 # Trigger error 1 Deprecated: Deprecated in 2.4 - 2 3 4 # Attribute 5 Deprecated: Function bar() is deprecated since 2.4, 6 7
  19. Benefits Available via reflection Works for class constants Easy to

    parse for IDEs and analysis tools No need to have @deprecated for tools and trigger_error for runtime
  20. Benefits Available via reflection Works for class constants Easy to

    parse for IDEs and analysis tools No need to have @deprecated for tools and trigger_error for runtime https://wiki.php.net/rfc/deprecated_attribute
  21. Lazy Objects class Connection { private object $db; public function

    __construct() { echo "Connect", PHP_EOL; // $this->db = new ... # Init } public function __destruct() { echo "Close", PHP_EOL; } public function query($params) { echo "Querying...", PHP_EOL; $this->db->query($params); echo "Done", PHP_EOL; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  22. Lazy Objects class Connection { private object $db; public function

    __construct() { echo "Connect", PHP_EOL; // $this->db = new ... # Init } public function __destruct() { echo "Close", PHP_EOL; } public function query($params) { echo "Querying...", PHP_EOL; $this->db->query($params); echo "Done", PHP_EOL; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 echo "Connect", PHP_EOL; echo "Close", PHP_EOL; echo "Querying...", PHP_EOL; echo "Done", PHP_EOL; class Connection { 1 private object $db; 2 3 public function __construct() { 4 5 // $this->db = new ... # Init 6 } 7 8 public function __destruct() { 9 10 } 11 12 public function query($params) { 13 14 $this->db->query($params); 15 16 } 17 } 18
  23. Lazy Objects class Connection { private object $db; public function

    __construct() { echo "Connect", PHP_EOL; // $this->db = new ... # Init } public function __destruct() { echo "Close", PHP_EOL; } public function query($params) { echo "Querying...", PHP_EOL; $this->db->query($params); echo "Done", PHP_EOL; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 echo "Connect", PHP_EOL; echo "Close", PHP_EOL; echo "Querying...", PHP_EOL; echo "Done", PHP_EOL; class Connection { 1 private object $db; 2 3 public function __construct() { 4 5 // $this->db = new ... # Init 6 } 7 8 public function __destruct() { 9 10 } 11 12 public function query($params) { 13 14 $this->db->query($params); 15 16 } 17 } 18 $this->db->query($params); class Connection { 1 private object $db; 2 3 public function __construct() { 4 echo "Connect", PHP_EOL; 5 // $this->db = new ... # Init 6 } 7 8 public function __destruct() { 9 echo "Close", PHP_EOL; 10 } 11 12 public function query($params) { 13 echo "Querying...", PHP_EOL; 14 15 echo "Done", PHP_EOL; 16 } 17 } 18
  24. With lazy objects Do nothing case $lazy = new ReflectionClass(Connection::class)->newLazyGhost(

    static function($c) { echo "Callback", PHP_EOL; // Our job to call construct now // or initialize properties otherwise $c->__construct(); } ); $lazy = null; // No output
  25. With lazy objects Query case $lazy = new ReflectionClass(Connection::class)->newLazyGhost( static

    function($c) { echo "Callback", PHP_EOL; // Our job to call construct now // or initialize properties otherwise $c->__construct(); } ); $lazy->query("..."); Querying... Callback Connect Done Disconnect
  26. Lots more to read here Mostly useful for the framework

    layer. Knowing about this will help with debugging and understanding code https://wiki.php.net/rfc/lazy-objects
  27. Which is fastest? PHP 8.3 or below $foo = $bar

    = $baz = "some string"; $a = "$foo-$bar-$baz"; // or $b = $foo . "-" . $bar . "-" . $baz; // or $c = sprintf("%s-%s-%s", $foo, $bar, $baz); 1 2 3 4 5 6 7
  28. Which is fastest? PHP 8.3 or below $foo = $bar

    = $baz = "some string"; $a = "$foo-$bar-$baz"; // or $b = $foo . "-" . $bar . "-" . $baz; // or $c = sprintf("%s-%s-%s", $foo, $bar, $baz); 1 2 3 4 5 6 7 $a = "$foo-$bar-$baz"; $b = $foo . "-" . $bar . "-" . $baz; $c = sprintf("%s-%s-%s", $foo, $bar, $baz); $foo = $bar = $baz = "some string"; 1 2 3 // or 4 5 // or 6 7
  29. Benchmark - PHP 8.3 $a = "$foo-$bar-$baz"; # Fastest $b

    = $foo . "-" . $bar . "-" . $baz; # 40% slower $c = sprintf("%s-%s-%s", $foo, $bar, $baz); # 50% slower
  30. Benchmark - PHP 8.4 $a = "$foo-$bar-$baz"; # Fastest $b

    = $foo . "-" . $bar . "-" . $baz; # Slower $c = sprintf("%s-%s-%s", $foo, $bar, $baz); # Fastest
  31. Benchmark - Script hyperfine -L function a,b,c 'php strings.php {function}'

    $iterations = 1_000_000; $foo = $bar = $baz = "some string"; $function = $_SERVER['argv'][1](...); $function($iterations, $foo, $bar, $baz); function a($i, $foo, $bar, $baz) { while($i--) $a = "$foo-$bar-$baz"; } function b($i, $foo, $bar, $baz) { while($i--) $b = $foo . "-" . $bar . "-" . $baz; } function c($i, $foo, $bar, $baz) { while($i--) $c = \sprintf("%s-%s-%s", $foo, $bar, $baz); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  32. Benchmark - Script hyperfine -L function a,b,c 'php strings.php {function}'

    $iterations = 1_000_000; $foo = $bar = $baz = "some string"; $function = $_SERVER['argv'][1](...); $function($iterations, $foo, $bar, $baz); function a($i, $foo, $bar, $baz) { while($i--) $a = "$foo-$bar-$baz"; } function b($i, $foo, $bar, $baz) { while($i--) $b = $foo . "-" . $bar . "-" . $baz; } function c($i, $foo, $bar, $baz) { while($i--) $c = \sprintf("%s-%s-%s", $foo, $bar, $baz); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $iterations = 1_000_000; $function = $_SERVER['argv'][1](...); 1 $foo = $bar = $baz = "some string"; 2 3 4 $function($iterations, $foo, $bar, $baz); 5 6 function a($i, $foo, $bar, $baz) { 7 while($i--) 8 $a = "$foo-$bar-$baz"; 9 } 10 function b($i, $foo, $bar, $baz) { 11 while($i--) 12 $b = $foo . "-" . $bar . "-" . $baz; 13 } 14 function c($i, $foo, $bar, $baz) { 15 while($i--) 16 $c = \sprintf("%s-%s-%s", $foo, $bar, $baz); 17 } 18
  33. Benchmark - Script hyperfine -L function a,b,c 'php strings.php {function}'

    $iterations = 1_000_000; $foo = $bar = $baz = "some string"; $function = $_SERVER['argv'][1](...); $function($iterations, $foo, $bar, $baz); function a($i, $foo, $bar, $baz) { while($i--) $a = "$foo-$bar-$baz"; } function b($i, $foo, $bar, $baz) { while($i--) $b = $foo . "-" . $bar . "-" . $baz; } function c($i, $foo, $bar, $baz) { while($i--) $c = \sprintf("%s-%s-%s", $foo, $bar, $baz); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $iterations = 1_000_000; $function = $_SERVER['argv'][1](...); 1 $foo = $bar = $baz = "some string"; 2 3 4 $function($iterations, $foo, $bar, $baz); 5 6 function a($i, $foo, $bar, $baz) { 7 while($i--) 8 $a = "$foo-$bar-$baz"; 9 } 10 function b($i, $foo, $bar, $baz) { 11 while($i--) 12 $b = $foo . "-" . $bar . "-" . $baz; 13 } 14 function c($i, $foo, $bar, $baz) { 15 while($i--) 16 $c = \sprintf("%s-%s-%s", $foo, $bar, $baz); 17 } 18 $a = "$foo-$bar-$baz"; $b = $foo . "-" . $bar . "-" . $baz; $c = \sprintf("%s-%s-%s", $foo, $bar, $baz); $iterations = 1_000_000; 1 $foo = $bar = $baz = "some string"; 2 3 $function = $_SERVER['argv'][1](...); 4 $function($iterations, $foo, $bar, $baz); 5 6 function a($i, $foo, $bar, $baz) { 7 while($i--) 8 9 } 10 function b($i, $foo, $bar, $baz) { 11 while($i--) 12 13 } 14 function c($i, $foo, $bar, $baz) { 15 while($i--) 16 17 } 18
  34. Compilation and runtime Code gets compiled into lower-level instructions: "OPcodes"

    This is what OPcache caches The PHP VM then executes these OPcodes
  35. Compilation and runtime Code gets compiled into lower-level instructions: "OPcodes"

    This is what OPcache caches The PHP VM then executes these OPcodes We can look at these OPcodes using php -d opcache.opt_debug_level= 0x10000 = Before optimization 0x20000 = After optimization https://php.watch/articles/php-dump-opcodes
  36. String interpolation php -d opcache.enable_cli=1 \ -d opcache.opt_debug_level=0x10000 opcodes.php $foo

    = $bar = $baz = "some string"; $a = "$foo-$bar-$baz"; 0000 T4 = ASSIGN CV2($baz) string("some string") 0001 T5 = ASSIGN CV1($bar) T4 0002 ASSIGN CV0($foo) T5 0003 T8 = ROPE_INIT 5 CV0($foo) 0004 T8 = ROPE_ADD 1 T8 string("-") 0005 T8 = ROPE_ADD 2 T8 CV1($bar) 0006 T8 = ROPE_ADD 3 T8 string("-") 0007 T7 = ROPE_END 4 T8 CV2($baz) 0008 ASSIGN CV3($a) T7 1 2 3 4 5 6 7 8 9 10 11 12
  37. String interpolation php -d opcache.enable_cli=1 \ -d opcache.opt_debug_level=0x10000 opcodes.php $foo

    = $bar = $baz = "some string"; $a = "$foo-$bar-$baz"; 0000 T4 = ASSIGN CV2($baz) string("some string") 0001 T5 = ASSIGN CV1($bar) T4 0002 ASSIGN CV0($foo) T5 0003 T8 = ROPE_INIT 5 CV0($foo) 0004 T8 = ROPE_ADD 1 T8 string("-") 0005 T8 = ROPE_ADD 2 T8 CV1($bar) 0006 T8 = ROPE_ADD 3 T8 string("-") 0007 T7 = ROPE_END 4 T8 CV2($baz) 0008 ASSIGN CV3($a) T7 1 2 3 4 5 6 7 8 9 10 11 12 $foo = $bar = $baz = "some string"; 0000 T4 = ASSIGN CV2($baz) string("some string") 0001 T5 = ASSIGN CV1($bar) T4 0002 ASSIGN CV0($foo) T5 1 $a = "$foo-$bar-$baz"; 2 3 4 5 6 0003 T8 = ROPE_INIT 5 CV0($foo) 7 0004 T8 = ROPE_ADD 1 T8 string("-") 8 0005 T8 = ROPE_ADD 2 T8 CV1($bar) 9 0006 T8 = ROPE_ADD 3 T8 string("-") 10 0007 T7 = ROPE_END 4 T8 CV2($baz) 11 0008 ASSIGN CV3($a) T7 12
  38. String interpolation php -d opcache.enable_cli=1 \ -d opcache.opt_debug_level=0x10000 opcodes.php $foo

    = $bar = $baz = "some string"; $a = "$foo-$bar-$baz"; 0000 T4 = ASSIGN CV2($baz) string("some string") 0001 T5 = ASSIGN CV1($bar) T4 0002 ASSIGN CV0($foo) T5 0003 T8 = ROPE_INIT 5 CV0($foo) 0004 T8 = ROPE_ADD 1 T8 string("-") 0005 T8 = ROPE_ADD 2 T8 CV1($bar) 0006 T8 = ROPE_ADD 3 T8 string("-") 0007 T7 = ROPE_END 4 T8 CV2($baz) 0008 ASSIGN CV3($a) T7 1 2 3 4 5 6 7 8 9 10 11 12 $foo = $bar = $baz = "some string"; 0000 T4 = ASSIGN CV2($baz) string("some string") 0001 T5 = ASSIGN CV1($bar) T4 0002 ASSIGN CV0($foo) T5 1 $a = "$foo-$bar-$baz"; 2 3 4 5 6 0003 T8 = ROPE_INIT 5 CV0($foo) 7 0004 T8 = ROPE_ADD 1 T8 string("-") 8 0005 T8 = ROPE_ADD 2 T8 CV1($bar) 9 0006 T8 = ROPE_ADD 3 T8 string("-") 10 0007 T7 = ROPE_END 4 T8 CV2($baz) 11 0008 ASSIGN CV3($a) T7 12 $a = "$foo-$bar-$baz"; 0003 T8 = ROPE_INIT 5 CV0($foo) 0004 T8 = ROPE_ADD 1 T8 string("-") 0005 T8 = ROPE_ADD 2 T8 CV1($bar) 0006 T8 = ROPE_ADD 3 T8 string("-") 0007 T7 = ROPE_END 4 T8 CV2($baz) $foo = $bar = $baz = "some string"; 1 2 3 0000 T4 = ASSIGN CV2($baz) string("some string") 4 0001 T5 = ASSIGN CV1($bar) T4 5 0002 ASSIGN CV0($foo) T5 6 7 8 9 10 11 0008 ASSIGN CV3($a) T7 12
  39. String concatenation php -d opcache.enable_cli=1 \ -d opcache.opt_debug_level=0x10000 opcodes.php $foo

    = $bar = $baz = "some string"; $b = $foo . "-" . $bar . "-" . $baz; // Same as before 0000 T4 = ASSIGN CV2($baz) string("some string") 0001 T5 = ASSIGN CV1($bar) T4 0002 ASSIGN CV0($foo) T5 // Different 0003 T7 = CONCAT CV0($foo) string("-") 0004 T8 = CONCAT T7 CV1($bar) 0005 T9 = CONCAT T8 string("-") 0006 T10 = CONCAT T9 CV2($baz) 0007 ASSIGN CV3($b) T10 1 2 3 4 5 6 7 8 9 10 11 12 13
  40. String concatenation php -d opcache.enable_cli=1 \ -d opcache.opt_debug_level=0x10000 opcodes.php $foo

    = $bar = $baz = "some string"; $b = $foo . "-" . $bar . "-" . $baz; // Same as before 0000 T4 = ASSIGN CV2($baz) string("some string") 0001 T5 = ASSIGN CV1($bar) T4 0002 ASSIGN CV0($foo) T5 // Different 0003 T7 = CONCAT CV0($foo) string("-") 0004 T8 = CONCAT T7 CV1($bar) 0005 T9 = CONCAT T8 string("-") 0006 T10 = CONCAT T9 CV2($baz) 0007 ASSIGN CV3($b) T10 1 2 3 4 5 6 7 8 9 10 11 12 13 $b = $foo . "-" . $bar . "-" . $baz; 0003 T7 = CONCAT CV0($foo) string("-") 0004 T8 = CONCAT T7 CV1($bar) 0005 T9 = CONCAT T8 string("-") 0006 T10 = CONCAT T9 CV2($baz) 0007 ASSIGN CV3($b) T10 $foo = $bar = $baz = "some string"; 1 2 3 // Same as before 4 0000 T4 = ASSIGN CV2($baz) string("some string") 5 0001 T5 = ASSIGN CV1($bar) T4 6 0002 ASSIGN CV0($foo) T5 7 // Different 8 9 10 11 12 13
  41. sprintf - PHP 8.3 php -d opcache.enable_cli=1 \ -d opcache.opt_debug_level=0x10000

    opcodes.php $foo = $bar = $baz = "some string"; $c = sprintf("%s-%s-%s", $foo, $bar, $baz); // Same as before 0000 T4 = ASSIGN CV2($baz) string("some string") 0001 T5 = ASSIGN CV1($bar) T4 0002 ASSIGN CV0($foo) T5 // Different 0003 INIT_FCALL 4 144 string("sprintf") 0004 SEND_VAL string("%s-%s-%s") 1 0005 SEND_VAR CV0($foo) 2 0006 SEND_VAR CV1($bar) 3 0007 SEND_VAR CV2($baz) 4 0008 V7 = DO_ICALL 0009 ASSIGN CV3($c) V7 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  42. sprintf - PHP 8.3 php -d opcache.enable_cli=1 \ -d opcache.opt_debug_level=0x10000

    opcodes.php $foo = $bar = $baz = "some string"; $c = sprintf("%s-%s-%s", $foo, $bar, $baz); // Same as before 0000 T4 = ASSIGN CV2($baz) string("some string") 0001 T5 = ASSIGN CV1($bar) T4 0002 ASSIGN CV0($foo) T5 // Different 0003 INIT_FCALL 4 144 string("sprintf") 0004 SEND_VAL string("%s-%s-%s") 1 0005 SEND_VAR CV0($foo) 2 0006 SEND_VAR CV1($bar) 3 0007 SEND_VAR CV2($baz) 4 0008 V7 = DO_ICALL 0009 ASSIGN CV3($c) V7 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $c = sprintf("%s-%s-%s", $foo, $bar, $baz); 0003 INIT_FCALL 4 144 string("sprintf") 0004 SEND_VAL string("%s-%s-%s") 1 0005 SEND_VAR CV0($foo) 2 0006 SEND_VAR CV1($bar) 3 0007 SEND_VAR CV2($baz) 4 0008 V7 = DO_ICALL 0009 ASSIGN CV3($c) V7 $foo = $bar = $baz = "some string"; 1 2 3 // Same as before 4 0000 T4 = ASSIGN CV2($baz) string("some string") 5 0001 T5 = ASSIGN CV1($bar) T4 6 0002 ASSIGN CV0($foo) T5 7 // Different 8 9 10 11 12 13 14 15
  43. So what changed in 8.4? Let's take a look at

    the \sprintf OPcodes with 8.4
  44. sprintf opcodes in 8.4 php-8.4/sapi/cli/php -d zend_extension=opcache.so \ -d opcache.enable_cli=1

    \ -d opcache.opt_debug_level=0x10000 sprintf.php // Same as always 0000 T4 = ASSIGN CV2($baz) string("some string") 0001 T5 = ASSIGN CV1($bar) T4 0002 ASSIGN CV0($foo) T5 // Just like string interpolation! 0003 T8 = ROPE_INIT 5 CV0($foo) 0004 T8 = ROPE_ADD 1 T8 string("-") 0005 T8 = ROPE_ADD 2 T8 CV1($bar) 0006 T8 = ROPE_ADD 3 T8 string("-") 0007 T7 = ROPE_END 4 T8 CV2($baz) 0008 ASSIGN CV3($c) T7 1 2 3 4 5 6 7 8 9 10 11 12
  45. sprintf opcodes in 8.4 php-8.4/sapi/cli/php -d zend_extension=opcache.so \ -d opcache.enable_cli=1

    \ -d opcache.opt_debug_level=0x10000 sprintf.php // Same as always 0000 T4 = ASSIGN CV2($baz) string("some string") 0001 T5 = ASSIGN CV1($bar) T4 0002 ASSIGN CV0($foo) T5 // Just like string interpolation! 0003 T8 = ROPE_INIT 5 CV0($foo) 0004 T8 = ROPE_ADD 1 T8 string("-") 0005 T8 = ROPE_ADD 2 T8 CV1($bar) 0006 T8 = ROPE_ADD 3 T8 string("-") 0007 T7 = ROPE_END 4 T8 CV2($baz) 0008 ASSIGN CV3($c) T7 1 2 3 4 5 6 7 8 9 10 11 12 // Just like string interpolation! 0003 T8 = ROPE_INIT 5 CV0($foo) 0004 T8 = ROPE_ADD 1 T8 string("-") 0005 T8 = ROPE_ADD 2 T8 CV1($bar) 0006 T8 = ROPE_ADD 3 T8 string("-") 0007 T7 = ROPE_END 4 T8 CV2($baz) // Same as always 1 0000 T4 = ASSIGN CV2($baz) string("some string") 2 0001 T5 = ASSIGN CV1($bar) T4 3 0002 ASSIGN CV0($foo) T5 4 5 6 7 8 9 10 11 0008 ASSIGN CV3($c) T7 12
  46. sprintf optimized out Where possible, sprintf calls will be "turned

    into" interpolated strings Readability improvement with no performance cost // Our $c example sprintf("%s-%s-%s", $foo, $bar, $baz); // => "$foo-$bar-$baz";
  47. Constraints Only for %s and %d placeholders No modifier, %02d

    can't be optimized No optimization for other placeholders.
  48. Constraints Only for %s and %d placeholders No modifier, %02d

    can't be optimized No optimization for other placeholders. In namespaced code, you must use \sprintf
  49. Constraints Only for %s and %d placeholders No modifier, %02d

    can't be optimized No optimization for other placeholders. In namespaced code, you must use \sprintf Without the \ the sprintf function must be looked up in the local namespace at runtime
  50. Constraints Only for %s and %d placeholders No modifier, %02d

    can't be optimized No optimization for other placeholders. In namespaced code, you must use \sprintf Without the \ the sprintf function must be looked up in the local namespace at runtime use function sprintf; also works of course
  51. Constraints Only for %s and %d placeholders No modifier, %02d

    can't be optimized No optimization for other placeholders. In namespaced code, you must use \sprintf Without the \ the sprintf function must be looked up in the local namespace at runtime use function sprintf; also works of course https://tideways.com/profiler/blog/new-in-php-8-4-engine-optimization-of-sprintf-to- string-interpolation
  52. Hashing SHA-256 has hardware support these days $algorithm = $_SERVER['argv'][1];

    $data = str_repeat('x', $_SERVER['argv'][2]); $iterations = $_SERVER['argv'][3]; while($iterations--) { hash($algorithm, $data); } 1 2 3 4 5 6 7 hyperfine -L function md5,sha1,sha256 \ 'php8.3 hash.php {function} 100 499999' https://github.com/php/php-src/pull/15152
  53. Credits Colin Percival / Tarsnap for the assembly Implementation https://x.com/cperciva/status/1822395523049160942

    https://github.com/php/php-src/commit/6eca7839af74c5ce2de62bc38cec2769bb9de360
  54. Why get/set - Historical PHP has taken many inspirations from

    the Java world Symfony has strong Spring influences Same for Doctrine ORM and Hibernate We both mostly use full words names for userland Very similar object model
  55. Why get/set - Practical Encapsulation and the ability to change

    behavior later. If we'd know all future change, we could use public properties instead of empty get/set.
  56. Why get/set - Practical Encapsulation and the ability to change

    behavior later. If we'd know all future change, we could use public properties instead of empty get/set. But we don't.
  57. Why get/set - Practical Encapsulation and the ability to change

    behavior later. If we'd know all future change, we could use public properties instead of empty get/set. But we don't. Changing all callers every time is impractical and error prone
  58. Getters and Setters Classic class Foo { private string $foo

    = 'foo'; public function getFoo(): string { return $this->foo; } } echo new Foo()->getFoo();
  59. Property Hooks We've been here before /** * @property-read string

    $foo */ class Foo { private string $foo = 'foo'; public function __get($property) { return match($property) { 'foo' => $this->foo, }; } } echo new Foo()->foo;
  60. Property Hooks New syntax class Foo { public string $foo

    = 'foo' { // Replicating default behavior get { return $this->foo; } set { $this->foo = $value; // Magic $value } } } $foo = new Foo(); $foo->foo = 'bar'; echo $foo->foo; // bar 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  61. Property Hooks New syntax class Foo { public string $foo

    = 'foo' { // Replicating default behavior get { return $this->foo; } set { $this->foo = $value; // Magic $value } } } $foo = new Foo(); $foo->foo = 'bar'; echo $foo->foo; // bar 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 set { $this->foo = $value; // Magic $value } class Foo { 1 2 public string $foo = 'foo' { 3 // Replicating default behavior 4 get { 5 return $this->foo; 6 } 7 8 9 10 } 11 } 12 13 $foo = new Foo(); 14 $foo->foo = 'bar'; 15 echo $foo->foo; // bar 16
  62. Property Hooks Named setter value class Foo { public string

    $foo = 'foo' { // Replicating default behavior get { return $this->foo; } set (string $foo) { $this->foo = $foo; } } } $foo = new Foo(); $foo->foo = 'bar'; echo $foo->foo; // bar 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  63. Property Hooks Named setter value class Foo { public string

    $foo = 'foo' { // Replicating default behavior get { return $this->foo; } set (string $foo) { $this->foo = $foo; } } } $foo = new Foo(); $foo->foo = 'bar'; echo $foo->foo; // bar 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 set (string $foo) { $this->foo = $foo; } class Foo { 1 2 public string $foo = 'foo' { 3 // Replicating default behavior 4 get { 5 return $this->foo; 6 } 7 8 9 10 } 11 } 12 13 $foo = new Foo(); 14 $foo->foo = 'bar'; 15 echo $foo->foo; // bar 16
  64. Property Hooks Accept different types class Foo { public string

    $foo = 'foo' { get { return $this->foo; } set (string|Exception $foo) { $this->foo = $foo instanceof Exception ? $foo->getMessage() : $foo; } } } $foo = new Foo(); $foo->foo = new Exception("hai"); echo $foo->foo; // hai 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  65. Property Hooks Accept different types class Foo { public string

    $foo = 'foo' { get { return $this->foo; } set (string|Exception $foo) { $this->foo = $foo instanceof Exception ? $foo->getMessage() : $foo; } } } $foo = new Foo(); $foo->foo = new Exception("hai"); echo $foo->foo; // hai 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $this->foo = $foo instanceof Exception ? $foo->getMessage() : $foo; } class Foo { 1 2 public string $foo = 'foo' { 3 get { 4 return $this->foo; 5 } 6 set (string|Exception $foo) { 7 8 9 10 11 } 12 } 13 14 $foo = new Foo(); 15 $foo->foo = new Exception("hai"); 16 echo $foo->foo; // hai 17
  66. Property Hooks "Short closures" / Arrow function syntax class Foo

    { public string $foo = 'foo' { get => $this->foo; } } echo new Foo()->foo;
  67. Setter Magic $value & "match expression" return logic class Foo

    { public string $foo { // Implicit return // Like in match expressions set => $value . ' set'; } } $foo = new Foo(); $foo->foo = 'bar'; echo $foo->foo; // bar set
  68. Virtual Properties class User { public function __construct( public string

    $first, public string $last ) {} public string $fullName { get { return "$this->first $this->last"; } set (string $value) { [$this->first, $this->last] = explode(' ', $value, 2); } } } $user = new User('Larry', 'Garfield'); // RFC authors $user->fullName = 'Ilija Tovilo'; echo $user->first; // Ilija
  69. Classic definition class Foo { public private(set) string $bar =

    'baz'; } $foo = new Foo(); var_dump($foo->bar); // prints "baz" $foo->bar = 'beep'; // Visibility error
  70. Thank you Question? Slides will be at: I sometimes talk

    PHP on Mastodon @[email protected] https://speakerdeck.com/edorian https://phpc.social/@edorian