Get your next remote job on LaraJobs.
1 contributor Edit on GitHub PHP

PHP 8.3 is out, now! Here's what's new and changed.

Table of contents:

PHP 8.3 is out, now! Here's what's new and changed.


PHP is an open-source project. Knowing what’s going on for the next version only takes a minute of research. For instance, this page lists all the accepted RFCs for PHP 8.3.

Below, you will find a condensed list of what’s new, with code samples that make sense.

Oh and by the way, I already started writing about PHP 8.4’s release date, new features and changes.

When was PHP 8.3 released?

PHP 8.3 was released on November 23, 2023. It has been tested through three alpha releases, three beta, and six release candidates.

Date Release
June 8, 2023 Alpha 1
June 22, 2023 Alpha 2
July 6, 2023 Alpha 3
July 18, 2023 Feature freeze
July 20, 2023 Beta 1
August 03, 2023 Beta 2
August 17, 2023 Beta 3
August 31, 2023 RC 1
September 14, 2023 RC 2
September 28, 2023 RC 3
October 12, 2023 RC 4
October 26, 2023 RC 5
November 9, 2023 RC 6
November 23, 2023 GA

How to install and test PHP 8.3 on Mac

If you are using Laravel Herd, this only takes one click!

Upgrading PHP to version 8.3 using Laravel Herd.

If you are using Laravel Valet, here’s how to proceed:

  1. Install the Homebrew package manager if it’s not done already.
  2. Run brew update to make sure Homebrew and the formulae are up to date.
  3. Add a new tap (basically a GitHub repository) for PHP 8.3’s formula: brew tap shivammathur/php.
  4. Install the pre-compiled binary for PHP 8.3 (also called “a bottle” in Homebrew’s context). This will make the install so much faster. brew install php@8.3.
  5. Link it to make sure that the php alias targets the right binary: brew link --overwrite --force php@8.3.

If you want to learn more about how to install PHP on your Mac, I wrote something for you: PHP for Mac: get started fast using Laravel Valet

What’s new in PHP 8.3: new features and changes

The new Override attribute

When you are using PHP 8.3’s new Override attribute, here’s what you tell other developers or your future self:

  • I’m overriding a method from the parent class or one of the implemented interfaces.
  • I have correctly overridden the method. If, for instance, I made a typo, the code would throw an error.

I had so much trouble understanding this one. But in fact, it’s dead simple. 😅

Here’s an example:

class SomeClass
public function someMethod()
class SomeOtherClass extends SomeClass
public function someMethod()

In this example, SomeOtherClass overrides the someMethod method from its parent class, SomeClass. But what happens if we change someMethod in SomeClass? Our override will be completely nullified.

But as you guessed it, the solution to this problem is the Override attribute:

class SomeClass
// We renamed the method.
public function someRenamedMethod()
class SomeOtherClass extends SomeClass
// But now, thanks to Override, we get an error message saying
// that our override doesn't override any method anymore.
// Fatal error: SomeOtherClass::someMethod() has #[\Override] attribute, but no matching parent method exists
public function someMethod()

How neat is that? 👌

Learn more: PHP RFC: Marking overridden methods (#[\Override])


This RFC proposes the addition of a new function called json_validate(). The purpose of this function is to check if a given string is a valid JSON object.

Currently, most PHP programmers use the json_decode() function for this task, but this uses memory and processing power that isn’t required for just checking validity.

The proposed json_validate() function will use the existing JSON parser in PHP, ensuring that it will be fully compatible with json_decode().

The function accepts a JSON string, a maximum nesting depth and flags, and returns a Boolean value indicating whether the string represents valid JSON.

If there are any errors during validation, these can be fetched using the json_last_error() or json_last_error_msg() functions.

$json = '{ "foo": "bar" }';
if (json_validate($json)) {
// Valid JSON.
} else {
// Invalid JSON.

Learn more: PHP RFC: json_validate

Improved unserialize() error handling

The RFC proposes two main changes to PHP’s unserialize function to enhance its error handling.

The current error handling in unserialize() is inconsistent, as it may emit an E_NOTICE, an E_WARNING, or throw an arbitrary Exception or Error depending on how the input string is malformed.

This makes it challenging to reliably handle errors during unserialization.

The first proposal is to introduce a new wrapper exception, UnserializationFailedException.

Whenever a Throwable is thrown during unserialize, this Throwable will be wrapped in a new instance of UnserializationFailedException.

This allows developers to use a single catch(UnserializationFailedException $e) to handle all possible Throwable happening during unserialization.

The original Throwable is accessible through the $previous property of UnserializationFailedException, allowing the developer to learn about the actual cause of the unserialization failure.

The second proposal is to increase the error reporting severity in the unserialize() parser.

In the current state, unserialization can fail due to a syntax error in the input string, out-of-range integers, and similar issues, which result in an E_NOTICE or E_WARNING.

This RFC proposes to increase the severity of these notices/warnings, and ultimately have them throw the new UnserializationFailedException.

Before the proposed changes, handling unserialization errors in PHP could look like this:

try {
set_error_handler(static function ($severity, $message, $file, $line) {
throw new \ErrorException($message, 0, $severity, $file, $line);
$result = unserialize($serialized);
} catch (\Throwable $e) {
// Unserialization failed. Catch block optional if the error should not be handled.
} finally {
var_dump($result); // Do something with the $result.
// Must not appear in the 'try' to not 'catch' unrelated errors.

And several typical cases can look like this:

unserialize('foo'); // Notice: unserialize(): Error at offset 0 of 3 bytes in php-src/test.php on line 3
unserialize('i:12345678901234567890;'); // Warning: unserialize(): Numerical result out of range in php-src/test.php on line 4
unserialize('E:3:"foo";'); // Warning: unserialize(): Invalid enum name 'foo' (missing colon) in php-src/test.php on line 5
// Notice: unserialize(): Error at offset 0 of 10 bytes in php-src/test.php on line 5

After the proposed changes, handling unserialization errors in PHP would be as follows:

function unserialize(string $data, array $options = []): mixed
try {
// The existing unserialization logic happens here.
} catch (\Throwable $e) {
throw new \UnserializationFailedException(previous: $e);

And the implementation of the new exception:

class UnserializationFailedException extends \Exception

The RFC has been partly accepted for PHP 8.3.

Learn more: PHP RFC: Improve unserialize() error handling

Randomizer additions

The RFC for PHP titled “Randomizer Additions” proposes three new methods and an accompanying enum for the \Random\Randomizer class.

The getBytesFromString() method allows you to generate a string of a specified length using randomly selected bytes from a given string.

The getFloat() method returns a float between a defined $min and $max, with the interval boundaries being open or closed depending on the value of the $boundary parameter.

The nextFloat() method is equivalent to ->getFloat(0, 1, \Random\IntervalBoundary::ClosedOpen).

The enum IntervalBoundary is used to determine the nature of the interval boundaries in the getFloat() method.

For example, with the getBytesFromString() method:


$randomString = '';
$characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
$charactersLength = strlen($characters);
for ($i = 0; $i < 16; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
$randomDomain = $randomString . "";


$randomizer = new \Random\Randomizer();
$randomDomain = $randomizer->getBytesFromString('abcdefghijklmnopqrstuvwxyz0123456789', 16) . "";

Learn more: PHP RFC: Randomizer Additions

Dynamic class constant fetch

This RFC proposes to allow class constants to be accessed dynamically using variables.

Instead of accessing class constants with a static string value (e.g. ClassName::CONSTANT), you could use a variable containing the constant name.

$constant = 'CONSTANT';

This change would make it easier to access class constants dynamically and programmatically.

Learn more: PHP RFC: Dynamic class constant fetch

More appropriate Date/Time exceptions

The RFC proposes to introduce Date/Time extension specific exceptions and errors in PHP, instead of the more generic warnings, errors, and exceptions currently used.

This aims to provide better specificity and handling for Date/Time related exceptions.

The proposal includes various specific exceptions, such as DateInvalidTimeZoneException, DateInvalidOperationException, DateMalformedStringException, DateMalformedIntervalStringException, and others.

Notably, this change only affects object-oriented style use of date/time functions, as procedural style usage will continue to use warnings and errors as it currently does.

Below are examples of how the new exceptions work:

Before the change:

For creating a DateInterval with a malformed string, the function call would return a warning and false:

try {
$i = DateInterval::createFromDateString("foo");
} catch (Exception $e) {
echo $e::class, ': ', $e->getMessage(), "\n";

For trying to subtract a non-special relative time specification from a DateTimeImmutable object, the function call would return a warning and null:

$now = new DateTimeImmutable("2022-04-22 16:25:11 BST");

After the change:

For creating a DateInterval with a malformed string, the function now throws a DateMalformedIntervalStringException:

try {
$i = DateInterval::createFromDateString("foo");
} catch (DateMalformedIntervalStringException $e) {
echo $e::class, ': ', $e->getMessage(), "\n";

For trying to subtract a non-special relative time specification from a DateTimeImmutable object, the function now throws a DateInvalidOperationException:

$now = new DateTimeImmutable("2022-04-22 16:25:11 BST");
try {
} catch (DateInvalidOperationException $e) {
echo $e::class, ': ', $e->getMessage(), "\n";

Learn more: PHP RFC: More Appropriate Date/Time Exceptions

Read only amendments

This is a two parts RFC and only the part where read only properties can now be reinitialized during cloning has been accepted.

This proposal aims to eliminate the limitation that prevents readonly properties from being “deep-cloned”.

It allows readonly properties to be reinitialized during the execution of the __clone() magic method call. Here is an example of this change:


class Foo {
public function __construct(
public readonly DateTime $bar,
public readonly DateTime $baz
) {}
public function __clone()
$this->bar = clone $this->bar; // Doesn't work, an error is thrown.


class Foo {
public function __construct(
public readonly DateTime $bar,
public readonly DateTime $baz
) {}
public function __clone()
$this->bar = clone $this->bar; // Works.
private function cloneBaz()
unset($this->baz); // Also works.
$foo = new Foo(new DateTime(), new DateTime());
$foo2 = clone $foo;
// No error, Foo2::$bar is cloned deeply, while Foo2::$baz becomes uninitialized.

Learn more: PHP RFC: Read only amendements

Saner array_sum() and array_product()

This RFC introduces changes to the behavior of array_sum() and array_product() functions to make them more consistent with their userland implementations using array_reduce().

Currently, array_sum() and array_product() skip array or object entries, while their userland implementations using array_reduce() throw a TypeError.

The proposed changes include:

  1. Using the same behavior for array_sum() and array_product() as the array_reduce() variants.
  2. Emitting an E_WARNING for incompatible types.
  3. Supporting objects with numeric cast to be added/multiplied.

Learn more: PHP RFC: Saner array_(sum|product)()

PHP RFC: Typed class constants

Typed class constants are finally coming in PHP 8.3!

They will help reduce bugs and confusion arising from child classes overriding parent class constants.

Typed class constants can be declared in classes, interfaces, traits, and enums.

Key points:

  • Class constant type declarations support all PHP type declarations except void, callable, and never.
  • Strict_types mode does not affect the behavior since type checks are always performed in strict mode.
  • Class constants are covariant, meaning that their types are not allowed to widen during inheritance.
  • Constant values must match the type of the class constant, with the exception that float class constants can also accept integer values.
  • ReflectionClassConstant is extended with two methods: getType() and hasType().

As you may imagine, typed constants are super easy to use, just like typed properties:

interface Foo {
public const string BAR = 'baz';
class Bar extends Foo {
// Doesn't work anymore! You cannot change the
// type and assign a value of another type.
public const array BAR = ['foo', 'bar', 'baz'];
// OK.
public const string BAR = 'foo';

Learn more: PHP RFC: Typed class constants

Arbitrary static variable initializers

The RFC proposes a change that would enable the static variable initializer to contain arbitrary expressions, rather than just constant expressions.

This makes the language more flexible and less confusing for users. For example, before this change, a static variable could only be initialized with a constant value:

function foo() {
static $i = 1;
echo $i++, "\n";

This would output:


With the proposed change, a static variable can be initialized with a function call:

function bar() {
echo "bar() called\n";
return 1;
function foo() {
static $i = bar();
echo $i++, "\n";

The proposal also introduces several changes to the semantics of static variable initialization, including the treatment of exceptions during initialization, the order of operations when destructors are involved, and the handling of recursion during initialization.

Redeclaring static variables would not be allowed, though, and it changes how ReflectionFunction::getStaticVariables() works with static variables.

Learn more: PHP RFC: Arbitrary static variable initializers

Improvements to the range() function

This RFC proposes changes to the semantics of the range() function, which currently generates an array of values from a start value to an end value with stipulated behavior for integers, floats, and strings.

The current range() function has shown to produce unexpected results with certain kinds of inputs and the RFC seeks to make the behavior more predictable and consistent.

The proposed changes include:

  1. Checking for and throwing errors when unusable arguments are passed to range().
  2. Handling non-finite float values such as -INF, INF, NAN.
  3. Throwing a more descriptive error when stepping is zero.
  4. Emit warnings when trying to create a range of characters with a float step.
  5. Conduct a stricter check on the start and end parameters to ensure they are int, float, or string. Any other types would result in a TypeError.
  6. Existing calls to range() that return arrays of floats when the step can be an integer will now return an array of integers.

Learn more: PHP RFC: Define proper semantics for range() function

Benjamin Crozat

Written by Benjamin Crozat

Indie hacker, blogger, and AI enthusiast building things with the TALL stack. 🔥

Follow me on:

Recommended articles

An early look at PHP 9.0's new features and changes An early look at PHP 9.0's new features and changes

PHP 9.0 is still far in the future. We don't know a lot, but we have a few breaking changes planned for it.

Published on Nov 3, 2023

PHP 8.3's Override attribute in a nutshell PHP 8.3's Override attribute in a nutshell

Discover a neat new addition to PHP 8.3 that will help express your intent: the Override attribute.

Published on Nov 23, 2023 Audio available

PHP 8.4: new features and release date PHP 8.4: new features and release date

PHP 8.4 is expected to drop on November 21, 2024. Let's review the known new features and changes.

Modified on Nov 3, 2023

Methods with the same name as their class will not be constructors in a future version of PHP Methods with the same name as their class will not be constructors in a future version of PHP

Learn why and how to fix "Methods with the same name as their class will not be constructors in a future version of PHP" warnings.

Modified on Nov 23, 2022

20+ Laravel best practices, tips and tricks to use in 2023 20+ Laravel best practices, tips and tricks to use in 2023

Learning a framework can be overwhelming, but time and execution will make you a master. Here are some best practices to help you toward your goal.

Modified on Oct 17, 2023

Gold sponsors New

  • Wire Elements
    Beautiful handcrafted Livewire components.
    Check site
Your business here


If you buy from one of my partners below, I will be compensated at no cost to you. These are services I use or used, and 100% stand behind.

  • Scalable and reliable VPS hosting.
    Bonus: $200 of free credits
    Check site
  • The Google Analytics alternative without compromise.
    Free trial: 30 days
    Bonus: $10 off your first invoice
    Check site
  • Flare
    Track PHP and JavaScript errors in one place.
    Free trial: 10 days
    Check site
  • Keep track of your Google rankings.
    Free trial: 7 days
    Promo code: WELCOME30
    Check site
- / -