diff --git a/include/layout.inc b/include/layout.inc index 7f572da6bc..36bbba54aa 100644 --- a/include/layout.inc +++ b/include/layout.inc @@ -72,6 +72,148 @@ function highlight_php_trimmed($code, $return = false) return null; } +function get_rfc_link(string $namedLink): string +{ + // Use 'title|link' string since multiple links may have the same title + preg_match('!^((.+)\|)?(.+)$!', $namedLink, $matches); + $link = $matches[3]; + $title = $matches[2] ?: 'Link'; + + return '' . $title . ''; +} + +function feature_header(string $id, string $title, array $links): string +{ + $links = array_map(get_rfc_link(...), $links); + $linksHtml = implode("\n", $links); + + return << + $title + $linksHtml + + HTML; +} + +function get_code_block( + string $beforeCode, + string $afterCode, + string $version, + string $previousVersion, + bool $highlightCode, +): string { + $alignClass = ''; + $noBeforeCode = $beforeCode === ''; + + if (substr_count($beforeCode, "\n") - substr_count($afterCode, "\n") > 12) { + // if new code is substantially shorter, align to top of code block + $alignClass = 'align-start'; + } + + if ($highlightCode) { + $beforeCode = highlight_php_trimmed($beforeCode, true); + $afterCode = highlight_php_trimmed($afterCode, true); + } else { + $beforeCode = "$beforeCode"; + $afterCode = "$afterCode"; + } + + $version = htmlentities($version); + $previousVersion = htmlentities($previousVersion); + + if ($noBeforeCode) { + $codeMarkup = << +
$version
+
$afterCode
+ + HTML; + } else { + $codeMarkup = << +
$previousVersion
+
$beforeCode
+ +
+
+
$version
+
+ $afterCode +
+
+ HTML; + } + + return << + $codeMarkup + + HTML; +} + +function feature_comparison( + string $id, + string $title, + string $description, + string $version, + string $previousVersion, + string $beforeCode, + string $afterCode, + bool $highlightCode = true, + array $links = [], + string $beforeCode2 = '', + string $afterCode2 = '', +): string { + $header = feature_header($id, $title, $links); + $contentHtml = ''; + $codeBlock = get_code_block($beforeCode, $afterCode, $version, $previousVersion, $highlightCode); + $code2Block = ''; + $contentClass = 'php8-compare__content'; + + if ($afterCode2 !== '') { + $code2Block = get_code_block($beforeCode2, $afterCode2, $version, $previousVersion, $highlightCode); + $contentClass .= ' php8-compare__content--spaced'; + } + + if ($description !== '') { + $contentHtml = <<$description + HTML; + } + + return << + $header + $codeBlock + $contentHtml + $code2Block + + HTML; +} + +function feature_comparisons(array $comparisons, string $version, string $previousVersion): string +{ + $html = '
' . "\n"; + + foreach ($comparisons as $feature) { + $html .= feature_comparison( + id: $feature['id'], + title: $feature['title'], + description: $feature['description'] ?? '', + version: $version, + previousVersion: $previousVersion, + beforeCode: $feature['before'] ?? '', + afterCode: $feature['after'], + highlightCode: $feature['highlightCode'] ?? true, + links: $feature['links'] ?? [], + beforeCode2: $feature['before2'] ?? '', + afterCode2: $feature['after2'] ?? '', + ) . "\n"; + } + + return $html . "\n" . '
'; +} + // Resize the image using the output of make_image() function resize_image($img, $width = 1, $height = 1) { @@ -158,7 +300,7 @@ function make_submit($file, $alt = false, $align = false, $extras = false, return '%s", $url, $linktext ?: $url); diff --git a/releases/8.0/release.inc b/releases/8.0/release.inc index 84e5115ac7..3199a48668 100644 --- a/releases/8.0/release.inc +++ b/releases/8.0/release.inc @@ -20,6 +20,198 @@ common_header(message('common_header', $lang)); $ohNoText = message('oh_no', $lang); $expectedText = message('this_is_expected', $lang); +$comparisons = [ + [ + 'id' => 'named-arguments', + 'title' => message('named_arguments_title', $lang), + 'description' => '', + 'links' => [ + 'RFC|https://wiki.php.net/rfc/named_params', + message('documentation', $lang) . "|/manual/$documentation/functions.arguments.php#functions.named-arguments", + ], + 'before' => <<<'PHP' + htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false); + PHP, + 'after' => <<<'PHP' + htmlspecialchars($string, double_encode: false); + PHP, + ], + [ + 'id' => 'attributes', + 'title' => message('attributes_title', $lang), + 'description' => '

' . message('attributes_description', $lang) . '

', + 'links' => [ + 'RFC|https://wiki.php.net/rfc/attributes_v2', + message('documentation', $lang) . "|/manual/$documentation/language.attributes.php", + ], + 'before' => <<<'PHP' + class PostsController + { + /** + * @Route("/api/posts/{id}", methods={"GET"}) + */ + public function get($id) { /* ... */ } + } + PHP, + 'after' => <<<'PHP' + class PostsController + { + #[Route("/api/posts/{id}", methods: ["GET"])] + public function get($id) { /* ... */ } + } + PHP, + ], + [ + 'id' => 'constructor-property-promotion', + 'title' => message('constructor_promotion_title', $lang), + 'description' => '

' . message('constructor_promotion_description', $lang) . '

', + 'links' => [ + 'RFC|https://wiki.php.net/rfc/constructor_promotion', + message('documentation', $lang) . "|/manual/$documentation/language.oop5.decon.php#language.oop5.decon.constructor.promotion", + ], + 'before' => <<<'PHP' + class Point { + public float $x; + public float $y; + public float $z; + + public function __construct( + float $x = 0.0, + float $y = 0.0, + float $z = 0.0 + ) { + $this->x = $x; + $this->y = $y; + $this->z = $z; + } + } + PHP, + 'after' => <<<'PHP' + class Point { + public function __construct( + public float $x = 0.0, + public float $y = 0.0, + public float $z = 0.0, + ) {} + } + PHP, + ], + [ + 'id' => 'union-types', + 'title' => message('union_types_title', $lang), + 'description' => '

' . message('union_types_description', $lang) . '

', + 'links' => [ + 'RFC|https://wiki.php.net/rfc/union_types_v2', + message('documentation', $lang) . "|/manual/$documentation/language.types.declarations.php#language.types.declarations.union", + ], + 'before' => <<<'PHP' + class Number { + /** @var int|float */ + private $number; + + /** + * @param float|int $number + */ + public function __construct($number) { + $this->number = $number; + } + } + + new Number('NaN'); // + PHP + . ' ' . message('ok', $lang), + 'after' => <<<'PHP' + class Number { + public function __construct( + private int|float $number + ) {} + } + + new Number('NaN'); // TypeError + PHP, + ], + [ + 'id' => 'match-expression', + 'title' => message('match_expression_title', $lang), + 'description' => message('match_expression_description', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/match_expression_v2', + message('documentation', $lang) . "|/manual/$documentation/control-structures.match.php", + ], + 'before' => << {$ohNoText} + PHP, + 'after' => << "{$ohNoText}", + 8.0 => "{$expectedText}", + }; + //> {$expectedText} + PHP, + ], + [ + 'id' => 'nullsafe-operator', + 'title' => message('nullsafe_operator_title', $lang), + 'description' => '

' . message('nullsafe_operator_description', $lang) . '

', + 'links' => ['RFC|https://wiki.php.net/rfc/nullsafe_operator'], + 'before' => <<<'PHP' + $country = null; + + if ($session !== null) { + $user = $session->user; + + if ($user !== null) { + $address = $user->getAddress(); + + if ($address !== null) { + $country = $address->country; + } + } + } + PHP, + 'after' => <<<'PHP' + $country = $session?->user?->getAddress()?->country; + PHP, + ], + [ + 'id' => 'saner-string-to-number-comparisons', + 'title' => message('saner_string_number_comparisons_title', $lang), + 'description' => '

' . message('saner_string_number_comparisons_description', $lang) . '

', + 'links' => ['RFC|https://wiki.php.net/rfc/string_to_number_comparison'], + 'before' => <<<'PHP' + 0 == 'foobar' // true + PHP, + 'after' => <<<'PHP' + 0 == 'foobar' // false + PHP, + ], + [ + 'id' => 'consistent-type-errors-for-internal-functions', + 'title' => message('consistent_internal_function_type_errors_title', $lang), + 'description' => '

' . message('consistent_internal_function_type_errors_description', $lang) . '

', + 'links' => ['RFC|https://wiki.php.net/rfc/consistent_type_errors'], + 'before' => <<<'PHP' + strlen([]); // Warning: strlen() expects parameter 1 to be string, array given + + array_chunk([], -1); // Warning: array_chunk(): Size parameter expected to be greater than 0 + PHP, + 'after' => <<<'PHP' + strlen([]); // TypeError: strlen(): Argument #1 ($str) must be of type string, array given + + array_chunk([], -1); // ValueError: array_chunk(): Argument #2 ($length) must be greater than 0 + PHP, + ], +]; + ?>
@@ -39,356 +231,7 @@ $expectedText = message('this_is_expected', $lang);
-
-
-

- - RFC - -

-
-
-
PHP 7
-
- -
-
-
-
-
PHP 8
-
- -
-
-
-
-
    - -
-
-
- -
-

- - RFC - -

-
-
-
PHP 7
-
- -
-
-
-
-
PHP 8
-
- -
-
-
-
-

-
-
- -
-

- - RFC - -

-
-
-
PHP 7
-
- x = $x; - $this->y = $y; - $this->z = $z; - } - } - PHP - );?> -
-
-
-
-
PHP 8
-
- -
-
-
-
-

-
-
- -
-

- - RFC - -

-
-
-
PHP 7
-
- number = $number; - } - } - - new Number('NaN'); // - PHP - . ' ' . message('ok', $lang), - );?> -
-
-
-
-
PHP 8
-
- -
-
-
-
-

-
-
- -
-

- - RFC - -

-
-
-
PHP 7
-
- {$ohNoText} - PHP - );?> -
-
-
-
-
PHP 8
-
- "{$ohNoText}", - 8.0 => "{$expectedText}", - }; - //> {$expectedText} - PHP - );?> -
-
-
-
- -
-
- -
-

- - RFC -

-
-
-
PHP 7
-
- user; - - if ($user !== null) { - $address = $user->getAddress(); - - if ($address !== null) { - $country = $address->country; - } - } - } - PHP - );?> -
-
-
-
-
PHP 8
-
- user?->getAddress()?->country; - PHP - );?> -
-
-
-
-

-
-
- -
-

- - RFC -

-
-
-
PHP 7
-
- -
-
-
-
-
PHP 8
-
- -
-
-
-
-

-
-
- -
-

- - RFC -

-
-
-
PHP 7
-
- -
-
-
-
-
PHP 8
-
- -
-
-
-
-

-
-
-
+

diff --git a/releases/8.1/release.inc b/releases/8.1/release.inc index 2485d64995..58074f20cf 100644 --- a/releases/8.1/release.inc +++ b/releases/8.1/release.inc @@ -17,6 +17,300 @@ include_once __DIR__ . '/common.php'; common_header(message('common_header', $lang)); +$comparisons = [ + [ + 'id' => 'enumerations', + 'title' => message('enumerations_title', $lang), + 'description' => message('enumerations_content', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/enumerations', + message('documentation', $lang) . "|/manual/$documentation/language.enumerations.php", + ], + 'before' => <<<'PHP' + class Status + { + const DRAFT = 'draft'; + const PUBLISHED = 'published'; + const ARCHIVED = 'archived'; + } + function acceptStatus(string $status) {...} + PHP, + 'after' => <<<'PHP' + enum Status + { + case Draft; + case Published; + case Archived; + } + function acceptStatus(Status $status) {...} + PHP, + ], + [ + 'id' => 'readonly_properties', + 'title' => message('readonly_properties_title', $lang), + 'description' => message('readonly_properties_content', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/readonly_properties_v2', + message('documentation', $lang) . "|/manual/$documentation/language.oop5.properties.php#language.oop5.properties.readonly-properties", + ], + 'before' => <<<'PHP' + class BlogData + { + private Status $status; + + public function __construct(Status $status) + { + $this->status = $status; + } + + public function getStatus(): Status + { + return $this->status; + } + } + PHP, + 'after' => <<<'PHP' + class BlogData + { + public readonly Status $status; + + public function __construct(Status $status) + { + $this->status = $status; + } + } + PHP, + ], + [ + 'id' => 'first_class_callable_syntax', + 'title' => message('first_class_callable_syntax_title', $lang), + 'description' => message('first_class_callable_syntax_content', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/first_class_callable_syntax', + message('documentation', $lang) . "|/manual/$documentation/functions.first_class_callable_syntax.php", + ], + 'before' => <<<'PHP' + $foo = [$this, 'foo']; + + $fn = Closure::fromCallable('strlen'); + PHP, + 'after' => <<<'PHP' + $foo = $this->foo(...); + + $fn = strlen(...); + PHP, + ], + [ + 'id' => 'new_in_initializers', + 'title' => message('new_in_initializers_title', $lang), + 'description' => message('new_in_initializers_content', $lang), + 'links' => ['RFC|https://wiki.php.net/rfc/new_in_initializers'], + 'before' => <<<'PHP' + class Service + { + private Logger $logger; + + public function __construct( + ?Logger $logger = null, + ) { + $this->logger = $logger ?? new NullLogger(); + } + } + PHP, + 'after' => <<<'PHP' + class Service + { + private Logger $logger; + + public function __construct( + Logger $logger = new NullLogger(), + ) { + $this->logger = $logger; + } + } + PHP, + 'before2' => <<<'PHP' + class User + { + /** + * @Assert\All({ + * @Assert\NotNull, + * @Assert\Length(min=5) + * }) + */ + public string $name = ''; + } + PHP, + 'after2' => <<<'PHP' + class User + { + #[\Assert\All( + new \Assert\NotNull, + new \Assert\Length(min: 5)) + ] + public string $name = ''; + } + PHP, + ], + [ + 'id' => 'pure_intersection_types', + 'title' => message('pure_intersection_types_title', $lang), + 'description' => message('pure_intersection_types_content', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/pure-intersection-types', + message('documentation', $lang) . "|/manual/$documentation/language.types.declarations.php#language.types.declarations.composite.intersection", + ], + 'before' => <<<'PHP' + function count_and_iterate(Iterator $value) { + if (!($value instanceof Countable)) { + throw new TypeError('value must be Countable'); + } + + foreach ($value as $val) { + echo $val; + } + + count($value); + } + PHP, + 'after' => <<<'PHP' + function count_and_iterate(Iterator&Countable $value) { + foreach ($value as $val) { + echo $val; + } + + count($value); + } + PHP, + ], + [ + 'id' => 'never_return_type', + 'title' => message('never_return_type_title', $lang), + 'description' => message('never_return_type_content', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/noreturn_type', + message('documentation', $lang) . "|/manual/$documentation/language.types.declarations.php#language.types.declarations.never", + ], + 'before' => <<<'PHP' + function redirect(string $uri) { + header('Location: ' . $uri); + exit(); + } + + function redirectToLoginPage() { + redirect('/login'); + echo 'Hello'; // <- dead code + } + PHP, + 'after' => <<<'PHP' + function redirect(string $uri): never { + header('Location: ' . $uri); + exit(); + } + + function redirectToLoginPage(): never { + redirect('/login'); + echo 'Hello'; // <- dead code detected by static analysis + } + PHP, + ], + [ + 'id' => 'final_class_constants', + 'title' => message('final_class_constants_title', $lang), + 'description' => message('final_class_constants_content', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/final_class_const', + message('documentation', $lang) . "|/manual/$documentation/language.oop5.final.php#language.oop5.final.example.php81", + ], + 'before' => <<<'PHP' + class Foo + { + public const XX = "foo"; + } + + class Bar extends Foo + { + public const XX = "bar"; // No error + } + PHP, + 'after' => <<<'PHP' + class Foo + { + final public const XX = "foo"; + } + + class Bar extends Foo + { + public const XX = "bar"; // Fatal error + } + PHP, + ], + [ + 'id' => 'explicit_octal_numeral_notation', + 'title' => message('octal_numeral_notation_title', $lang), + 'description' => message('octal_numeral_notation_content', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/explicit_octal_notation', + message('documentation', $lang) . "|/manual/$documentation/migration81.new-features.php#migration81.new-features.core.octal-literal-prefix", + ], + 'before' => <<<'PHP' + 016 === 16; // false because `016` is octal for `14` and it's confusing + 016 === 14; // true + PHP, + 'after' => <<<'PHP' + 0o16 === 16; // false — not confusing with explicit notation + 0o16 === 14; // true + PHP, + ], + [ + 'id' => 'fibers', + 'title' => message('fibers_title', $lang), + 'description' => message('fibers_content', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/fibers', + message('documentation', $lang) . "|/manual/$documentation/language.fibers.php", + ], + 'before' => <<<'PHP' + $httpClient->request('https://example.com/') + ->then(function (Response $response) { + return $response->getBody()->buffer(); + }) + ->then(function (string $responseBody) { + print json_decode($responseBody)['code']; + }); + PHP, + 'after' => <<<'PHP' + $response = $httpClient->request('https://example.com/'); + print json_decode($response->getBody()->buffer())['code']; + PHP, + ], + [ + 'id' => 'array_unpacking_support_for_string_keyed_arrays', + 'title' => message('array_unpacking_title', $lang), + 'description' => message('array_unpacking_content', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/array_unpacking_string_keys', + message('documentation', $lang) . "|/manual/$documentation/language.types.array.php#language.types.array.unpacking", + ], + 'before' => <<<'PHP' + $arrayA = ['a' => 1]; + $arrayB = ['b' => 2]; + + $result = array_merge(['a' => 0], $arrayA, $arrayB); + + // ['a' => 1, 'b' => 2] + PHP, + 'after' => <<<'PHP' + $arrayA = ['a' => 1]; + $arrayB = ['b' => 2]; + + $result = ['a' => 0, ...$arrayA, ...$arrayB]; + + // ['a' => 1, 'b' => 2] + PHP, + ], +]; + ?>
@@ -36,518 +330,7 @@ common_header(message('common_header', $lang));
-
-
-

- - RFC - -

-
-
-
PHP < 8.1
-
- -
-
-
-
-
PHP 8.1
-
- -
-
-
-
- -
-
- -
-

- - RFC - -

-
-
-
PHP < 8.1
-
- status = $status; - } - - public function getStatus(): Status - { - return $this->status; - } -} -PHP - - );?> -
-
-
-
-
PHP 8.1
-
- status = $status; - } -} -PHP - );?> -
-
-
-
- -
-
- -
-

- - RFC - -

-
-
-
PHP < 8.1
-
- -
-
-
-
-
PHP 8.1
-
- foo(...); - -$fn = strlen(...); -PHP - );?> -
-
-
-
- -
-
- -
-

- - RFC -

-
-
-
PHP < 8.1
-
- logger = $logger ?? new NullLogger(); - } -} -PHP - );?> -
-
-
-
-
PHP 8.1
-
- logger = $logger; - } -} -PHP - );?> -
-
-
- -
- -
-
-
-
PHP < 8.1
-
- -
-
-
-
-
PHP 8.1
-
- -
-
-
-
- -
-

- - RFC - -

-
-
-
PHP < 8.1
-
- -
-
-
-
-
PHP 8.1
-
- -
-
-
-
- -
-
- -
-

- - RFC - -

-
-
-
PHP < 8.1
-
- -
-
-
-
-
PHP 8.1
-
- -
-
-
-
- -
-
- -
-

- - RFC - -

-
-
-
PHP < 8.1
-
- -
-
-
-
-
PHP 8.1
-
- -
-
-
-
- -
-
- -
-

- - RFC - -

-
-
-
PHP < 8.1
-
- -
-
-
-
-
PHP 8.1
-
- -
-
-
-
- -
-
- -
-

- - RFC - -

-
-
-
PHP < 8.1
-
- request('https://example.com/') - ->then(function (Response $response) { - return $response->getBody()->buffer(); - }) - ->then(function (string $responseBody) { - print json_decode($responseBody)['code']; - }); -PHP - - );?> -
-
-
-
-
PHP 8.1
-
- request('https://example.com/'); -print json_decode($response->getBody()->buffer())['code']; -PHP - );?> -
-
-
-
- -
-
- -
-

- - RFC - -

-
-
-
PHP < 8.1
-
- 1]; -$arrayB = ['b' => 2]; - -$result = array_merge(['a' => 0], $arrayA, $arrayB); - -// ['a' => 1, 'b' => 2] -PHP - );?> -
-
-
-
-
PHP 8.1
-
- 1]; -$arrayB = ['b' => 2]; - -$result = ['a' => 0, ...$arrayA, ...$arrayB]; - -// ['a' => 1, 'b' => 2] -PHP - );?> -
-
-
-
- -
-
-
+
diff --git a/releases/8.2/release.inc b/releases/8.2/release.inc index 7fbacf9a4b..81bd8abd94 100644 --- a/releases/8.2/release.inc +++ b/releases/8.2/release.inc @@ -14,6 +14,198 @@ include_once __DIR__ . '/common.php'; common_header(message('common_header', $lang)); +$comparisons = [ + [ + 'id' => 'readonly_classes', + 'title' => message('readonly_classes_title', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/readonly_classes', + message('documentation', $lang) . "|/manual/$lang/language.oop5.basic.php#language.oop5.basic.class.readonly", + ], + 'before' => <<<'PHP' + class BlogData + { + public readonly string $title; + + public readonly Status $status; + + public function __construct(string $title, Status $status) + { + $this->title = $title; + $this->status = $status; + } + } + PHP, + 'after' => <<<'PHP' + readonly class BlogData + { + public string $title; + + public Status $status; + + public function __construct(string $title, Status $status) + { + $this->title = $title; + $this->status = $status; + } + } + PHP, + ], + [ + 'id' => 'dnf_types', + 'title' => message('dnf_types_title', $lang), + 'description' => message('dnf_types_description', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/dnf_types', + message('documentation', $lang) . "|/manual/$lang/migration82.new-features.php#migration82.new-features.core.type-system", + ], + 'before' => <<<'PHP' + class Foo { + public function bar(mixed $entity) { + if ((($entity instanceof A) && ($entity instanceof B)) || ($entity === null)) { + return $entity; + } + + throw new Exception('Invalid entity'); + } + } + PHP, + 'after' => <<<'PHP' + class Foo { + public function bar((A&B)|null $entity) { + return $entity; + } + } + PHP, + ], + [ + 'id' => 'null_false_true_types', + 'title' => message('null_false_true_types_title', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/null-false-standalone-types', + 'RFC|https://wiki.php.net/rfc/true-type', + ], + 'before' => <<<'PHP' + class Falsy + { + public function almostFalse(): bool { /* ... */ *} + + public function almostTrue(): bool { /* ... */ *} + + public function almostNull(): string|null { /* ... */ *} + } + PHP, + 'after' => <<<'PHP' + class Falsy + { + public function alwaysFalse(): false { /* ... */ *} + + public function alwaysTrue(): true { /* ... */ *} + + public function alwaysNull(): null { /* ... */ *} + } + PHP, + ], + [ + 'id' => 'random_extension', + 'title' => message('random_title', $lang), + 'description' => message('random_description', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/rng_extension', + 'RFC|https://wiki.php.net/rfc/random_extension_improvement', + message('documentation', $lang) . "|/manual/$lang/book.random.php", + ], + 'after' => <<<'PHP' + use Random\Engine\Xoshiro256StarStar; + use Random\Randomizer; + + $blueprintRng = new Xoshiro256StarStar( + hash('sha256', "Example seed that is converted to a 256 Bit string via SHA-256", true) + ); + + $fibers = []; + for ($i = 0; $i < 8; $i++) { + $fiberRng = clone $blueprintRng; + // Xoshiro256**'s 'jump()' method moves the blueprint ahead 2**128 steps, as if calling + // 'generate()' 2**128 times, giving the Fiber 2**128 unique values without needing to reseed. + $blueprintRng->jump(); + + $fibers[] = new Fiber(function () use ($fiberRng, $i): void { + $randomizer = new Randomizer($fiberRng); + + echo "{$i}: " . $randomizer->getInt(0, 100), PHP_EOL; + }); + } + + // The randomizer will use a CSPRNG by default. + $randomizer = new Randomizer(); + + // Even though the fibers execute in a random order, they will print the same value + // each time, because each has its own unique instance of the RNG. + $fibers = $randomizer->shuffleArray($fibers); + foreach ($fibers as $fiber) { + $fiber->start(); + } + PHP, + ], + [ + 'id' => 'constants_in_traits', + 'title' => message('constants_in_traits_title', $lang), + 'description' => message('constants_in_traits_description', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/constants_in_traits', + message('documentation', $lang) . "|/manual/$lang/migration82.new-features.php#migration82.new-features.core.constant-in-traits", + ], + 'after' => <<<'PHP' + trait Foo + { + public const CONSTANT = 1; + } + + class Bar + { + use Foo; + } + + var_dump(Bar::CONSTANT); // 1 + var_dump(Foo::CONSTANT); // Error + PHP, + ], + [ + 'id' => 'deprecate_dynamic_properties', + 'title' => message('deprecate_dynamic_properties_title', $lang), + 'description' => message('deprecate_dynamic_properties_description', $lang), + 'links' => [ + 'RFC|https://wiki.php.net/rfc/deprecate_dynamic_properties', + message('documentation', $lang) . "|/manual/$lang/migration82.deprecated.php#migration82.deprecated.core.dynamic-properties", + ], + 'before' => <<<'PHP' + class User + { + public $name; + } + + $user = new User(); + $user->last_name = 'Doe'; + + $user = new stdClass(); + $user->last_name = 'Doe'; + PHP, + 'after' => <<<'PHP' + class User + { + public $name; + } + + $user = new User(); + $user->last_name = 'Doe'; // Deprecated notice + + $user = new stdClass(); + $user->last_name = 'Doe'; // Still allowed + PHP, + ], +]; + ?>
@@ -33,311 +225,7 @@ common_header(message('common_header', $lang));
-
-
-

- - RFC - -

-
-
-
PHP < 8.2
-
- title = $title; - $this->status = $status; - } -} -PHP - - ); ?> -
-
-
-
-
PHP 8.2
-
- title = $title; - $this->status = $status; - } -} -PHP - ); ?> -
-
-
-
- -
-

- - RFC - -

-
-
-
PHP < 8.2
-
- -
-
-
-
-
PHP 8.2
-
- -
-
-
-
-
- -
- -
-

- - RFC - RFC -

-
-
-
PHP < 8.2
-
- -
-
-
-
-
PHP 8.2
-
- -
-
-
-
- -
-

- - RFC - RFC - -

-
-
-
PHP 8.2
-
- jump(); - - $fibers[] = new Fiber(function () use ($fiberRng, $i): void { - $randomizer = new Randomizer($fiberRng); - - echo "{$i}: " . $randomizer->getInt(0, 100), PHP_EOL; - }); -} - -// The randomizer will use a CSPRNG by default. -$randomizer = new Randomizer(); - -// Even though the fibers execute in a random order, they will print the same value -// each time, because each has its own unique instance of the RNG. -$fibers = $randomizer->shuffleArray($fibers); -foreach ($fibers as $fiber) { - $fiber->start(); -} -PHP - ); ?> -
-
-
-
-
- -
- -
-

- - RFC - -

-
-
-
PHP 8.2
-
- -
-
-
-
-
- -
- -
-

- - RFC - -

-
-
-
PHP < 8.2
-
- last_name = 'Doe'; - -$user = new stdClass(); -$user->last_name = 'Doe'; -PHP - - ); ?> -
-
-
-
-
PHP 8.2
-
- last_name = 'Doe'; // Deprecated notice - -$user = new stdClass(); -$user->last_name = 'Doe'; // Still allowed -PHP - ); ?> -
-
-
-
-
- -
- -
+
diff --git a/releases/8.3/release.inc b/releases/8.3/release.inc index 0277ce66d6..aa2b60174a 100644 --- a/releases/8.3/release.inc +++ b/releases/8.3/release.inc @@ -14,6 +14,281 @@ include_once __DIR__ . '/common.php'; common_header(message('common_header', $lang)); +$comparisons = [ + [ + 'id' => 'typed_class_constants', + 'title' => message('typed_class_constants_title', $lang), + 'links' => ['RFC|https://wiki.php.net/rfc/typed_class_constants'], + 'before' => <<<'PHP' + interface I { + // We may naively assume that the PHP constant is always a string. + const PHP = 'PHP 8.2'; + } + + class Foo implements I { + // But implementing classes may define it as an array. + const PHP = []; + } + PHP, + 'after' => <<<'PHP' + interface I { + const string PHP = 'PHP 8.3'; + } + + class Foo implements I { + const string PHP = []; + } + + // Fatal error: Cannot use array as value for class constant + // Foo::PHP of type string + PHP, + ], + [ + 'id' => 'dynamic_class_constant_fetch', + 'title' => message('dynamic_class_constant_fetch_title', $lang), + 'links' => ['RFC|https://wiki.php.net/rfc/dynamic_class_constant_fetch'], + 'before' => <<<'PHP' + class Foo { + const PHP = 'PHP 8.2'; + } + + $searchableConstant = 'PHP'; + + var_dump(constant(Foo::class . "::{$searchableConstant}")); + PHP, + 'after' => <<<'PHP' + class Foo { + const PHP = 'PHP 8.3'; + } + + $searchableConstant = 'PHP'; + + var_dump(Foo::{$searchableConstant}); + PHP, + ], + [ + 'id' => 'override_attribute', + 'title' => message('override_title', $lang), + 'description' => message('override_description', $lang), + 'links' => ['RFC|https://wiki.php.net/rfc/marking_overriden_methods'], + 'before' => <<<'PHP' + use PHPUnit\Framework\TestCase; + + final class MyTest extends TestCase { + protected $logFile; + + protected function setUp(): void { + $this->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, + ], + [ + '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, + ], + [ + '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, + ], + [ + '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, + ], + [ + '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, + ], + [ + '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, + ], +]; + ?>
@@ -33,446 +308,7 @@ common_header(message('common_header', $lang));
-
-
-

- - RFC -

-
-
-
PHP < 8.3
-
- -
-
-
-
-
PHP 8.3
-
- -
-
-
-
- -
-

- - RFC -

-
-
-
PHP < 8.3
-
- -
-
-
-
-
PHP 8.3
-
- -
-
-
-
- -
-

- - RFC -

-
-
-
PHP < 8.3
-
- 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 - - ); ?> -
-
-
-
-
PHP 8.3
-
- 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 - ); ?> -
-
-
- -
- -
-
- -
-

- - RFC -

-
-
-
PHP < 8.3
-
- php = clone $this->php; - } -} - -$instance = new Foo(new PHP()); -$cloned = clone $instance; - -// Fatal error: Cannot modify readonly property Foo::$php -PHP - - ); ?> -
-
-
-
-
PHP 8.3
-
- php = clone $this->php; - } -} - -$instance = new Foo(new PHP()); -$cloned = clone $instance; - -$cloned->php->version = '8.3'; -PHP - ); ?> -
-
-
-
- -
-
- -
-

- - RFC - -

-
-
-
PHP < 8.3
-
- -
-
-
-
-
PHP 8.3
-
- -
-
-
-
- -
-
- -
-

- - RFC - -

-
-
-
PHP < 8.3
-
- -
-
-
-
-
PHP 8.3
-
- getBytesFromString( - 'abcdefghijklmnopqrstuvwxyz0123456789', - 16, - ), -); - -echo $randomDomain; -PHP - ); ?> -
-
-
- -
- -
-
- -
-

- - RFC - -

-
-
-
PHP < 8.3
-
- -
-
-
-
-
PHP 8.3
-
- 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 - ); ?> -
-
-
-
- -
- -
- -
-

- - PR - -

-
-
-
PHP < 8.3
-
- -php -l foo.php bar.php -No syntax errors detected in foo.php - -
-
-
-
-
PHP 8.3
-
- -php -l foo.php bar.php -No syntax errors detected in foo.php -No syntax errors detected in bar.php - -
-
-
-
- -
- -
- - -
+
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!
+ +
+ 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!
+ +
+ 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, + ], +]; + ?>
@@ -34,502 +376,7 @@ common_header(message('common_header', $lang));
-
-
-

- - RFC - -

-
-
-
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 - ); ?> -
-
-
-
- -
-
-
-

- - RFC - -

-
-
-
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 - ); ?> -
-
-
-
- -
-
-
-

- - RFC - -

-
-
-
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 - ); ?> -
-
-
-
- -
-
-
-

- - RFC - RFC - -

-
-
-
PHP < 8.4
-
- loadHTML( - <<<'HTML' -
-
PHP 8.4 is a feature-rich release!
- -
- 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!
- - - HTML, - LIBXML_NOERROR, -); - -$node = $dom->querySelector('main > article:last-child'); -var_dump($node->classList->contains("featured")); // bool(true) -PHP - ); ?> -
-
-
-
- -
-
-
-

- - RFC -

-
-
-
PHP < 8.4
-
- 0); // false -PHP - - ); ?> -
-
-
-
-
PHP 8.4
-
- $num2); // false -PHP - ); ?> -
-
-
-
- -
-
-
-

- - RFC -

-
-
-
PHP < 8.4
-
- -
-
-
-
-
PHP 8.4
-
- str_starts_with($value, 'c'), -); - -var_dump($animal); // string(3) "cat" -PHP - ); ?> -
-
-
-
- -
-
-
-

- - 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 - ); ?> -
-
-
-
- -
-
-
-

- - RFC - -

-
-
-
PHP < 8.4
-
- getVersion()); -PHP - - ); ?> -
-
-
-
-
PHP 8.4
-
- getVersion()); -PHP - ); ?> -
-
-
-
- -
-
- -
+
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