diff --git a/releases/8.4/release.inc b/releases/8.4/release.inc
index b8d5bec2e7..3821713aa9 100644
--- a/releases/8.4/release.inc
+++ b/releases/8.4/release.inc
@@ -1,6 +1,8 @@
'property_hooks',
+ 'title' => message('property_hooks_title', $lang),
+ 'description' => message('property_hooks_description', $lang),
+ 'links' => [
+ 'RFC|https://wiki.php.net/rfc/property-hooks',
+ message('documentation', $lang) . "|/manual/$documentation/migration84.new-features.php#migration84.new-features.core.property-hooks",
+ ],
+ 'before' => <<<'PHP'
+ class Locale
+ {
+ private string $languageCode;
+ private string $countryCode;
+
+ public function __construct(string $languageCode, string $countryCode)
+ {
+ $this->setLanguageCode($languageCode);
+ $this->setCountryCode($countryCode);
+ }
+
+ public function getLanguageCode(): string
+ {
+ return $this->languageCode;
+ }
+
+ public function setLanguageCode(string $languageCode): void
+ {
+ $this->languageCode = $languageCode;
+ }
+
+ public function getCountryCode(): string
+ {
+ return $this->countryCode;
+ }
+
+ public function setCountryCode(string $countryCode): void
+ {
+ $this->countryCode = strtoupper($countryCode);
+ }
+
+ public function setCombinedCode(string $combinedCode): void
+ {
+ [$languageCode, $countryCode] = explode('_', $combinedCode, 2);
+
+ $this->setLanguageCode($languageCode);
+ $this->setCountryCode($countryCode);
+ }
+
+ public function getCombinedCode(): string
+ {
+ return \sprintf("%s_%s", $this->languageCode, $this->countryCode);
+ }
+ }
+
+ $brazilianPortuguese = new Locale('pt', 'br');
+ var_dump($brazilianPortuguese->getCountryCode()); // BR
+ var_dump($brazilianPortuguese->getCombinedCode()); // pt_BR
+ PHP,
+ 'after' => <<<'PHP'
+ class Locale
+ {
+ public string $languageCode;
+
+ public string $countryCode
+ {
+ set (string $countryCode) {
+ $this->countryCode = strtoupper($countryCode);
+ }
+ }
+
+ public string $combinedCode
+ {
+ get => \sprintf("%s_%s", $this->languageCode, $this->countryCode);
+ set (string $value) {
+ [$this->languageCode, $this->countryCode] = explode('_', $value, 2);
+ }
+ }
+
+ public function __construct(string $languageCode, string $countryCode)
+ {
+ $this->languageCode = $languageCode;
+ $this->countryCode = $countryCode;
+ }
+ }
+
+ $brazilianPortuguese = new Locale('pt', 'br');
+ var_dump($brazilianPortuguese->countryCode); // BR
+ var_dump($brazilianPortuguese->combinedCode); // pt_BR
+ PHP,
+ ],
+ [
+ 'id' => 'asymmetric_visibility',
+ 'title' => message('asymmetric_visibility_title', $lang),
+ 'description' => message('asymmetric_visibility_description', $lang),
+ 'links' => [
+ 'RFC|https://wiki.php.net/rfc/asymmetric-visibility-v2',
+ message('documentation', $lang) . "|/manual/$documentation/language.oop5.visibility.php#language.oop5.visibility-members-aviz",
+ ],
+ 'before' => <<<'PHP'
+ class PhpVersion
+ {
+ private string $version = '8.3';
+
+ public function getVersion(): string
+ {
+ return $this->version;
+ }
+
+ public function increment(): void
+ {
+ [$major, $minor] = explode('.', $this->version);
+ $minor++;
+ $this->version = "{$major}.{$minor}";
+ }
+ }
+ PHP,
+ 'after' => <<<'PHP'
+ class PhpVersion
+ {
+ public private(set) string $version = '8.4';
+
+ public function increment(): void
+ {
+ [$major, $minor] = explode('.', $this->version);
+ $minor++;
+ $this->version = "{$major}.{$minor}";
+ }
+ }
+ PHP,
+ ],
+ [
+ 'id' => 'deprecated_attribute',
+ 'title' => message('deprecated_attribute_title', $lang),
+ 'description' => message('deprecated_attribute_description', $lang),
+ 'links' => [
+ 'RFC|https://wiki.php.net/rfc/deprecated_attribute',
+ message('documentation', $lang) . "|/manual/$documentation/class.deprecated.php",
+ ],
+ 'before' => <<<'PHP'
+ class PhpVersion
+ {
+ /**
+ * @deprecated 8.3 use PhpVersion::getVersion() instead
+ */
+ public function getPhpVersion(): string
+ {
+ return $this->getVersion();
+ }
+
+ public function getVersion(): string
+ {
+ return '8.3';
+ }
+ }
+
+ $phpVersion = new PhpVersion();
+ // No indication that the method is deprecated.
+ echo $phpVersion->getPhpVersion();
+ PHP,
+ 'after' => <<<'PHP'
+ class PhpVersion
+ {
+ #[\Deprecated(
+ message: "use PhpVersion::getVersion() instead",
+ since: "8.4",
+ )]
+ public function getPhpVersion(): string
+ {
+ return $this->getVersion();
+ }
+
+ public function getVersion(): string
+ {
+ return '8.4';
+ }
+ }
+
+ $phpVersion = new PhpVersion();
+ // Deprecated: Method PhpVersion::getPhpVersion() is deprecated since 8.4, use PhpVersion::getVersion() instead
+ echo $phpVersion->getPhpVersion();
+ PHP,
+ ],
+ [
+ 'id' => 'dom_additions_html5',
+ 'title' => message('dom_additions_html5_title', $lang),
+ 'description' => message('dom_additions_html5_description', $lang),
+ 'links' => [
+ 'RFC|https://wiki.php.net/rfc/dom_additions_84',
+ 'RFC|https://wiki.php.net/rfc/domdocument_html5_parser',
+ message('documentation', $lang) . "|/manual/$documentation/migration84.new-features.php#migration84.new-features.dom",
+ ],
+ 'before' => <<<'PHP'
+ $dom = new DOMDocument();
+ $dom->loadHTML(
+ <<<'HTML'
+
+ PHP 8.4 is a feature-rich release!
+ PHP 8.4 adds new DOM classes that are spec-compliant, keeping the old ones for compatibility.
+
+ HTML,
+ LIBXML_NOERROR,
+ );
+
+ $xpath = new DOMXPath($dom);
+ $node = $xpath->query(".//main/article[not(following-sibling::*)]")[0];
+ $classes = explode(" ", $node->className); // Simplified
+ var_dump(in_array("featured", $classes)); // bool(true)
+ PHP,
+ 'after' => <<<'PHP'
+ $dom = Dom\HTMLDocument::createFromString(
+ <<<'HTML'
+
+ PHP 8.4 is a feature-rich release!
+ PHP 8.4 adds new DOM classes that are spec-compliant, keeping the old ones for compatibility.
+
+ HTML,
+ LIBXML_NOERROR,
+ );
+
+ $node = $dom->querySelector('main > article:last-child');
+ var_dump($node->classList->contains("featured")); // bool(true)
+ PHP,
+ ],
+ [
+ 'id' => 'bcmath',
+ 'title' => message('bcmath_title', $lang),
+ 'description' => message('bcmath_description', $lang),
+ 'links' => ['RFC|https://wiki.php.net/rfc/support_object_type_in_bcmath'],
+ 'before' => <<<'PHP'
+ $num1 = '0.12345';
+ $num2 = '2';
+ $result = bcadd($num1, $num2, 5);
+
+ echo $result; // '2.12345'
+ var_dump(bccomp($num1, $num2) > 0); // false
+ PHP,
+ 'after' => <<<'PHP'
+ use BcMath\Number;
+
+ $num1 = new Number('0.12345');
+ $num2 = new Number('2');
+ $result = $num1 + $num2;
+
+ echo $result; // '2.12345'
+ var_dump($num1 > $num2); // false
+ PHP,
+ ],
+ [
+ 'id' => 'new_array_find',
+ 'title' => message('new_array_find_title', $lang),
+ 'description' => message('new_array_find_description', $lang),
+ 'links' => ['RFC|https://wiki.php.net/rfc/array_find'],
+ 'before' => <<<'PHP'
+ $animal = null;
+ foreach (['dog', 'cat', 'cow', 'duck', 'goose'] as $value) {
+ if (str_starts_with($value, 'c')) {
+ $animal = $value;
+ break;
+ }
+ }
+
+ var_dump($animal); // string(3) "cat"
+ PHP,
+ 'after' => <<<'PHP'
+ $animal = array_find(
+ ['dog', 'cat', 'cow', 'duck', 'goose'],
+ static fn(string $value): bool => str_starts_with($value, 'c'),
+ );
+
+ var_dump($animal); // string(3) "cat"
+ PHP,
+ ],
+ [
+ 'id' => 'pdo_driver_specific_subclasses',
+ 'title' => message('pdo_driver_specific_subclasses_title', $lang),
+ 'description' => message('pdo_driver_specific_subclasses_description', $lang),
+ 'links' => ['RFC|https://wiki.php.net/rfc/pdo_driver_specific_subclasses'],
+ 'before' => <<<'PHP'
+ $connection = new PDO(
+ 'sqlite:foo.db',
+ $username,
+ $password,
+ ); // object(PDO)
+
+ $connection->sqliteCreateFunction(
+ 'prepend_php',
+ static fn($string) => "PHP {$string}",
+ );
+
+ $connection->query('SELECT prepend_php(version) FROM php');
+ PHP,
+ 'after' => <<<'PHP'
+ $connection = PDO::connect(
+ 'sqlite:foo.db',
+ $username,
+ $password,
+ ); // object(Pdo\Sqlite)
+
+ $connection->createFunction(
+ 'prepend_php',
+ static fn($string) => "PHP {$string}",
+ ); // Does not exist on a mismatching driver.
+
+ $connection->query('SELECT prepend_php(version) FROM php');
+ PHP,
+ ],
+ [
+ 'id' => 'new_without_parentheses',
+ 'title' => message('new_without_parentheses_title', $lang),
+ 'description' => message('new_without_parentheses_description', $lang),
+ 'links' => [
+ 'RFC|https://wiki.php.net/rfc/new_without_parentheses',
+ message('documentation', $lang) . "|/manual/$documentation/migration84.new-features.php#migration84.new-features.core.new-chaining",
+ ],
+ 'before' => <<<'PHP'
+ class PhpVersion
+ {
+ public function getVersion(): string
+ {
+ return 'PHP 8.3';
+ }
+ }
+
+ var_dump((new PhpVersion())->getVersion());
+ PHP,
+ 'after' => <<<'PHP'
+ class PhpVersion
+ {
+ public function getVersion(): string
+ {
+ return 'PHP 8.4';
+ }
+ }
+
+ var_dump(new PhpVersion()->getVersion());
+ PHP,
+ ],
+];
+
?>
-
-
-
-
-
-
PHP < 8.4
-
- setLanguageCode($languageCode);
- $this->setCountryCode($countryCode);
- }
-
- public function getLanguageCode(): string
- {
- return $this->languageCode;
- }
-
- public function setLanguageCode(string $languageCode): void
- {
- $this->languageCode = $languageCode;
- }
-
- public function getCountryCode(): string
- {
- return $this->countryCode;
- }
-
- public function setCountryCode(string $countryCode): void
- {
- $this->countryCode = strtoupper($countryCode);
- }
-
- public function setCombinedCode(string $combinedCode): void
- {
- [$languageCode, $countryCode] = explode('_', $combinedCode, 2);
-
- $this->setLanguageCode($languageCode);
- $this->setCountryCode($countryCode);
- }
-
- public function getCombinedCode(): string
- {
- return \sprintf("%s_%s", $this->languageCode, $this->countryCode);
- }
-}
-
-$brazilianPortuguese = new Locale('pt', 'br');
-var_dump($brazilianPortuguese->getCountryCode()); // BR
-var_dump($brazilianPortuguese->getCombinedCode()); // pt_BR
-PHP
-
- ); ?>
-
-
-
-
-
PHP 8.4
-
- countryCode = strtoupper($countryCode);
- }
- }
-
- public string $combinedCode
- {
- get => \sprintf("%s_%s", $this->languageCode, $this->countryCode);
- set (string $value) {
- [$this->languageCode, $this->countryCode] = explode('_', $value, 2);
- }
- }
-
- public function __construct(string $languageCode, string $countryCode)
- {
- $this->languageCode = $languageCode;
- $this->countryCode = $countryCode;
- }
-}
-
-$brazilianPortuguese = new Locale('pt', 'br');
-var_dump($brazilianPortuguese->countryCode); // BR
-var_dump($brazilianPortuguese->combinedCode); // pt_BR
-PHP
- ); ?>
-
-
-
-
- = message('property_hooks_description', $lang) ?>
-
-
-
-
-
-
-
PHP < 8.4
-
- version;
- }
-
- public function increment(): void
- {
- [$major, $minor] = explode('.', $this->version);
- $minor++;
- $this->version = "{$major}.{$minor}";
- }
-}
-PHP
-
- ); ?>
-
-
-
-
-
PHP 8.4
-
- version);
- $minor++;
- $this->version = "{$major}.{$minor}";
- }
-}
-PHP
- ); ?>
-
-
-
-
- = message('asymmetric_visibility_description', $lang) ?>
-
-
-
-
-
-
-
PHP < 8.4
-
- getVersion();
- }
-
- public function getVersion(): string
- {
- return '8.3';
- }
-}
-
-$phpVersion = new PhpVersion();
-// No indication that the method is deprecated.
-echo $phpVersion->getPhpVersion();
-PHP
-
- ); ?>
-
-
-
-
-
PHP 8.4
-
- getVersion();
- }
-
- public function getVersion(): string
- {
- return '8.4';
- }
-}
-
-$phpVersion = new PhpVersion();
-// Deprecated: Method PhpVersion::getPhpVersion() is deprecated since 8.4, use PhpVersion::getVersion() instead
-echo $phpVersion->getPhpVersion();
-PHP
- ); ?>
-
-
-
-
- = message('deprecated_attribute_description', $lang) ?>
-
-
-
-
-
-
-
PHP < 8.4
-
- loadHTML(
- <<<'HTML'
-
- PHP 8.4 is a feature-rich release!
- PHP 8.4 adds new DOM classes that are spec-compliant, keeping the old ones for compatibility.
-
- HTML,
- LIBXML_NOERROR,
-);
-
-$xpath = new DOMXPath($dom);
-$node = $xpath->query(".//main/article[not(following-sibling::*)]")[0];
-$classes = explode(" ", $node->className); // Simplified
-var_dump(in_array("featured", $classes)); // bool(true)
-PHP
-
- ); ?>
-
-
-
-
-
PHP 8.4
-
-
-
PHP 8.4 is a feature-rich release!
-
PHP 8.4 adds new DOM classes that are spec-compliant, keeping the old ones for compatibility.
-
- HTML,
- LIBXML_NOERROR,
-);
-
-$node = $dom->querySelector('main > article:last-child');
-var_dump($node->classList->contains("featured")); // bool(true)
-PHP
- ); ?>
-
-
-
-
- = message('dom_additions_html5_description', $lang) ?>
-
-
-
-
- = message('bcmath_title', $lang) ?>
- RFC
-
-
-
-
PHP < 8.4
-
- 0); // false
-PHP
-
- ); ?>
-
-
-
-
-
PHP 8.4
-
- $num2); // false
-PHP
- ); ?>
-
-
-
-
- = message('bcmath_description', $lang) ?>
-
-
-
-
- = message('new_array_find_title', $lang) ?>
- RFC
-
-
-
-
-
-
PHP 8.4
-
- str_starts_with($value, 'c'),
-);
-
-var_dump($animal); // string(3) "cat"
-PHP
- ); ?>
-
-
-
-
- = message('new_array_find_description', $lang) ?>
-
-
-
-
- = message('pdo_driver_specific_subclasses_title', $lang) ?>
- RFC
-
-
-
-
PHP < 8.4
-
- sqliteCreateFunction(
- 'prepend_php',
- static fn($string) => "PHP {$string}",
-);
-
-$connection->query('SELECT prepend_php(version) FROM php');
-PHP
- ); ?>
-
-
-
-
-
PHP 8.4
-
- createFunction(
- 'prepend_php',
- static fn($string) => "PHP {$string}",
-); // Does not exist on a mismatching driver.
-
-$connection->query('SELECT prepend_php(version) FROM php');
-PHP
- ); ?>
-
-
-
-
- = message('pdo_driver_specific_subclasses_description', $lang) ?>
-
-
-
-
-
-
-
PHP < 8.4
-
- getVersion());
-PHP
-
- ); ?>
-
-
-
-
-
PHP 8.4
-
- getVersion());
-PHP
- ); ?>
-
-
-
-
- = message('new_without_parentheses_description', $lang) ?>
-
-
-
-
+ = feature_comparisons($comparisons, 'PHP 8.4', 'PHP < 8.4') ?>
diff --git a/tests/Visual/SmokeTest.spec.ts-snapshots/tests-screenshots-releases-8-3-index-php-chromium.png b/tests/Visual/SmokeTest.spec.ts-snapshots/tests-screenshots-releases-8-3-index-php-chromium.png
index 452e5caa82..b190e7c86a 100644
Binary files a/tests/Visual/SmokeTest.spec.ts-snapshots/tests-screenshots-releases-8-3-index-php-chromium.png and b/tests/Visual/SmokeTest.spec.ts-snapshots/tests-screenshots-releases-8-3-index-php-chromium.png differ