logFile = fopen('/tmp/logfile', 'w'); } protected function taerDown(): void { fclose($this->logFile); unlink('/tmp/logfile'); } } // The log file will never be removed, because the // method name was mistyped (taerDown vs tearDown). PHP, after: <<<'PHP' use PHPUnit\Framework\TestCase; final class MyTest extends TestCase { protected $logFile; protected function setUp(): void { $this->logFile = fopen('/tmp/logfile', 'w'); } #[\Override] protected function taerDown(): void { fclose($this->logFile); unlink('/tmp/logfile'); } } // Fatal error: MyTest::taerDown() has #[\Override] attribute, // but no matching parent method exists PHP, ), new FeatureComparison( id: 'readonly_classes', title: message('readonly_title', $lang), description: message('readonly_description', $lang), links: ['RFC|https://wiki.php.net/rfc/readonly_amendments'], before: <<<'PHP' class PHP { public string $version = '8.2'; } readonly class Foo { public function __construct( public PHP $php ) {} public function __clone(): void { $this->php = clone $this->php; } } $instance = new Foo(new PHP()); $cloned = clone $instance; // Fatal error: Cannot modify readonly property Foo::$php PHP, after: <<<'PHP' class PHP { public string $version = '8.2'; } readonly class Foo { public function __construct( public PHP $php ) {} public function __clone(): void { $this->php = clone $this->php; } } $instance = new Foo(new PHP()); $cloned = clone $instance; $cloned->php->version = '8.3'; PHP, ), new FeatureComparison( id: 'json_validate', title: message('json_validate_title', $lang), description: message('json_validate_description', $lang), links: [ 'RFC|https://wiki.php.net/rfc/json_validate', message('documentation', $lang) . "|/manual/$lang/function.json-validate.php", ], before: <<<'PHP' function json_validate(string $string): bool { json_decode($string); return json_last_error() === JSON_ERROR_NONE; } var_dump(json_validate('{ "test": { "foo": "bar" } }')); // true PHP, after: <<<'PHP' var_dump(json_validate('{ "test": { "foo": "bar" } }')); // true PHP, ), new FeatureComparison( id: 'randomizer_get_bytes_from_string', title: message('randomizer_getbytesfromstring_title', $lang), description: message('randomizer_getbytesfromstring_description', $lang), links: [ 'RFC|https://wiki.php.net/rfc/randomizer_additions#getbytesfromstring', message('documentation', $lang) . "|/manual/$lang/random-randomizer.getbytesfromstring.php", ], before: <<<'PHP' // This function needs to be manually implemented. function getBytesFromString(string $string, int $length) { $stringLength = strlen($string); $result = ''; for ($i = 0; $i < $length; $i++) { // random_int is not seedable for testing, but secure. $result .= $string[random_int(0, $stringLength - 1)]; } return $result; } $randomDomain = sprintf( "%s.example.com", getBytesFromString( 'abcdefghijklmnopqrstuvwxyz0123456789', 16, ), ); echo $randomDomain; PHP, after: <<<'PHP' // A \Random\Engine may be passed for seeding, // the default is the secure engine. $randomizer = new \Random\Randomizer(); $randomDomain = sprintf( "%s.example.com", $randomizer->getBytesFromString( 'abcdefghijklmnopqrstuvwxyz0123456789', 16, ), ); echo $randomDomain; PHP, ), new FeatureComparison( id: 'randomizer_get_float', title: message('randomizer_getfloat_nextfloat_title', $lang), description: message('randomizer_getfloat_nextfloat_description', $lang), links: [ 'RFC|https://wiki.php.net/rfc/randomizer_additions#getfloat', message('documentation', $lang) . "|/manual/$lang/random-randomizer.getfloat.php", ], before: <<<'PHP' // Returns a random float between $min and $max, both including. function getFloat(float $min, float $max) { // This algorithm is biased for specific inputs and may // return values outside the given range. This is impossible // to work around in userland. $offset = random_int(0, PHP_INT_MAX) / PHP_INT_MAX; return $offset * ($max - $min) + $min; } $temperature = getFloat(-89.2, 56.7); $chanceForTrue = 0.1; // getFloat(0, 1) might return the upper bound, i.e. 1, // introducing a small bias. $myBoolean = getFloat(0, 1) < $chanceForTrue; PHP, after: <<<'PHP' $randomizer = new \Random\Randomizer(); $temperature = $randomizer->getFloat( -89.2, 56.7, \Random\IntervalBoundary::ClosedClosed, ); $chanceForTrue = 0.1; // Randomizer::nextFloat() is equivalent to // Randomizer::getFloat(0, 1, \Random\IntervalBoundary::ClosedOpen). // The upper bound, i.e. 1, will not be returned. $myBoolean = $randomizer->nextFloat() < $chanceForTrue; PHP, ), new FeatureComparison( id: 'command_line_linter', title: message('command_line_linter_title', $lang), description: message('command_line_linter_description', $lang), links: [ 'PR|https://github.com/php/php-src/issues/10024', message('documentation', $lang) . "|/manual/$lang/features.commandline.options.php", ], before: <<<'CODE' php -l foo.php bar.php No syntax errors detected in foo.php CODE, after: <<<'CODE' php -l foo.php bar.php No syntax errors detected in foo.php No syntax errors detected in bar.php CODE, highlightCode: false, ), ]; echo ReleasePage::getHeroSection( title: message('main_title', $lang), subtitle: message('main_subtitle', $lang), logoSvg: << SVG, upgradeNow: message('upgrade_now', $lang), ); echo ReleasePage::getFeatureComparisons($comparisons, 'PHP 8.3', 'PHP < 8.3'); ?>

false]);