Blog
August 11, 2023
The PHP 8.3 release date was on November 23, 2023, and it brought with it an extensive list of new features and changes to the language. With teams around the world using PHP 8.3 in mission-critical applications, and many of those teams looking to upgrade to PHP 8.3 from unsupported versions or versions nearing end of life, it is important to understand how these changes impact ongoing projects and planned migrations.
In this blog, we give an overview of the new PHP 8.3 features, deprecations, and important changes teams can expect, and discuss who should consider migrating to PHP 8.3.
This blog was updated on December 26, 2024, to reflect updated PHP 8.3 release information.
PHP 8.3: Release Date and Timeline Overview
Every year, at the end of the year, the PHP project releases a new major or minor version of PHP. Approximately six months prior to the release, the contributors declare a feature freeze; prior to that, there’s a flurry of activity on the PHP internals mailing list and wiki as developers push for acceptance of changes to the language.
At a high level, there are not any huge additions in the PHP 8.3 release, particularly compared to PHP 8.1 and 8.2. For the most part, this release has focused on cleaning up the language, and making a number of features consistent with how the language has evolved in the past several years.
When Was PHP 8.3 Released?
PHP 8.3 released on November 23, 2023.
PHP 8.3 End of Life
PHP 8.3 will reach end of life (EOL) on December 31, 2027.
PHP 8.3 Release Timeline
PHP 8.3 follows the same rough release timeline as previous releases, with important PHP 8.3 release dates listed in the table below:
Date | Milestone |
---|---|
06/08/2023 | Alpha Release 1 |
06/22/2023 | Alpha Release 2 |
07/06/2023 | Alpha Release 3 |
07/18/2023 | Feature Freeze |
07/20/2023 | Beta Release 1 |
08/03/2023 | Beta Release 2 |
08/17/2023 | Beta Release 3 |
08/31/2023 | Release Candidate 1 |
09/14/2023 | Release Candidate 2 |
09/28/2023 | Release Candidate 3 |
10/12/2023 | Release Candidate 4 |
10/26/2023 | Release Candidate 5 |
11/09/2023 | Release Candidate 6 |
11/23/2023 | General Availability Release |
Back to topGet an Overview of the New PHP 8.3 Features
In this webinar, we look at the new features, deprecations, and changes to watch in PHP 8.3 - and the upcoming end of life for PHP 8.0.
PHP 8.3: New Features
PHP 8.3 includes a number of new changes. As noted above, however, it has relatively fewer features than PHP 8.1 or PHP 8.2. The main PHP 8.3 features to note are:
- Typed Class Constants
- Dynamic class constant and Enum member fetch support
json_validate()
function- Random extension additions
- Addition of
mb_str_pad()
- Addition of
#[\Override]
attribute
PHP 8.3: Typed Class Constants
Constants prior to PHP 8.3 could not declare their type, and were always inferred based on the value. Now you can declare them explicitly.
This has ramifications on interfaces, abstract classes, and child classes, as you can now enforce the type via inheritance, preventing implementations or extensions from altering the type.
PHP 8.3: Dynamic Class Constant and Enum Member Fetch Support
In all previous versions of PHP, the following were invalid:
$constantName = 'SOME_FLAG';
echo SomeClassDefiningTheConstant::{$constantName};
echo SomeEnumDefiningTheMemberName::{$constantName}->value;
The only way to access these was using the constant()
function:
echo constant("SomeClassDefiningTheConstant::{$constantName}");
echo constant("SomeEnumDefiningTheMemberName::${constantName}")->value;
Since properties can be accessed dynamically, this PHP 8.3 feature is now extended to class constants and Enum members as well.
PHP 8.3: json_validate() function
In order to validate a JSON string prior to PHP 8.3, you needed to pass it to json_decode()
and see if errors were emitted and/or exceptions thrown (depending on what flags you provide to the function). When using this approach to validate large JSON structures, you ran the risk of running out of memory prior to determining if it was valid. Additionally, this might cause you to hit PHP's memory limit prior to actually processing the structure. The new function is more performant and less susceptible to error.
The full signature of the function is:
function json_validate(string $json, int $depth = 512, int $flags = 0): bool
where the $flags argument largely matches the behavior of the json_decode()
function, without the ability to raise an exception (cases where an exception would be raised will result in a boolean false return value, after all).
Random Extension Additions in PHP 8.3
The "Random" extension was added in PHP 8.2 to provide a more flexible and extensible system for operations requiring random bytes, particularly those requiring cryptographically secure pseudo-random number generation (CSPRNG). PHP 8.3 adds several new methods:
Random\Randomizer::getBytesFromString(string $string, int $length)
: string returns a random number sequence of a specified length that only contains bytes from a specified string. This can be particularly useful for generating things like randomized short URLs.Random\Randomizer::getFloat(float $min, float $max, Random\IntervalBoundary $boundary = Random\IntervalBoundary::ClosedOpen): float and Random\Randomizer::nextFloat(): float
can be used to generate a random float value. In the case ofgetFloat()
, the value will be generated between$min
and$max
, either inclusively or exclusively based on the$boundary
value (IntervalBoundary
is a new Enum defining the various boundary conditions you can use).nextFloat()
generates always between 0 and 1, and is similar to JavaScript'sMath.random()
function.
Addition of mb_str_pad() in PHP 8.3
PHP has long had a str_pad()
function which allows padding the start, end, or both sides of a string with a "padding" string, until it reaches the requested length. However, this functionality only works for single-byte character encodings, which eliminates usage with UTF-8 and other multi-byte encodings.
PHP 8.3 adds a new function, mb_str_pad(string $string, int $length, string $pad_string = " ", int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string
. It mimics the functionality of str_pad()
, with the following differences:
- Multi-byte strings can be used for the string to pad as well as the string representing the padding.
- These will use the encoding of the string to pad by default, but you can specify a specific encoding as well.
Full Notes on Addition of mb_str_pad() >>
Addition of #[\Override] Attribute in PHP 8.3
When extending a class, when we define a method that overrides the same method of the parent class, this is generally intentional. However, in some cases, we could introduce errors:
- If the method was defined in our extending class first, and only later added to the parent class, it might not be immediately evident, particularly if they have the same signature, but the intent of the method is quite different.
- If we have a typo in the method name, it might not be immediately clear that this was intended to override the parent method, and thus it becomes unclear why the parent is executing instead of the overriding class.
- If a parent implementation changes a method name (e.g., because the interface it implements has changed), we might not realize we need to change our extension.
PHP 8.3 adds a new attribute, #[\Override]
. Developers can add this attribute to methods to demonstrate that they intend for the method to override a parent method. When present, the PHP engine will now check to ensure the method exists in the parent or an interface being implemented, with the same signature, and raise a compile time error if not.
This sort of feature can be a huge boon to developers, as it prevents mistakes when upgrading code.
Full notes on addition of #[\Override] attribute in PHP 8.3 >>
Back to topPHP 8.3 Breaking Changes and Deprecations
PHP 8.3 is also set to introduce a number of deprecations and changes, including:
class_alias
supports aliasing PHP built-insunserialize()
now emitsE_WARNING
instead ofE_NOTICE
- Fallback value support for php.ini environment variable syntax
gc_status()
extended information- Lint more than one file at a time
- Use exceptions by default in SQLite3 extension
- More appropriate Date/Time exceptions
- Improve semantics of
range()
arguments - Improve behavior of
array_sum()
andarray_product()
when provided unusable values - Assertion behavior deprecations
- Readonly behavior during cloning
- Allow dynamic static value initialization
- Increment/Decrement operator improvements
You can read more about the changes and deprecations in PHP 8.3 below.
class_alias Supports Aliasing PHP Built-ins in PHP 8.3
You can use class_alias()
to alias one class to another; when you do so, the aliased class behaves exactly the same as the original class. (This is a technique used by some frameworks and libraries to provide backwards compatibility to previous versions when renaming classes.)
Prior to PHP 8.3, aliasing to a built-in PHP class would raise an error. PHP 8.3 now allows these aliases.
unserialize() Now Emits E_WARNING Instead of E_NOTICE
Prior to PHP 8.3, passing an invalid string to unserialize()
would (generally) emit an E_NOTICE
(some scenarios started emitting E_WARNING
as of PHP 7.4, specifically around structures that exceed the unserialize_max_depth settings
). As of PHP 8.3, these now all emit E_WARNING
. As such, you may notice new logs captured in production that were not present previously. We strongly suggest evaluating these errors, determining what is causing the issue and correcting any serialization that is leading to the issue. There is a strong possibility these will be updated to throw exceptions in 9.0.
Fallback Value Support for php.ini Environment Variable Syntax
A little known feature supported in php.ini is the fact that you can use PHP's string interpolation syntax to reference environment variables when setting INI values. As an example, you might set the client host for XDebug using:
xdebug.client_host = "${XDEBUG_CLIENT_HOST}"
The problem with this is when the environment variable is not set. There may be a sane default to use (in this example, "localhost" might be a good fit), but without the environment variable, the setting is now empty. As of PHP 8.3, you can now specify a fallback value to use, using the :- notation that you may have used with Bash configuration or Makefiles:
xdebug.client_host = "${XDEBUG_CLIENT_HOST:-localhost}"
This can be particularly useful for defining deployment defaults, but allowing different environments (e.g., development, staging, CI/CD, etc.) to override the value via ENV variables.
gc_status() Extended Information
The function gc_status()
returns an associative array. Prior to PHP 8.3, the array included the following:
runs (int)
: number of times GC has occurredcollected (int)
: number of objects collectedthreshold (int)
: number of roots in the buffer that will trigger GC.roots (int)
: total number of roots in the buffer
PHP 8.3 expands this with 8 more keys:
running (bool)
protected (bool)
: Whether or not the GC is protected and forbidding root additions.full (bool)
: whether or not the GC buffer exceedsGC_MAX_BUF_SIZE
.buffer_size (int)
: current GC buffer size.application_time (float)
: total application time, in secondscollector_time (float)
: time spent collecting cycles, in secondsdestructor_time (float)
: time spent executing destructors during collection, in secondsfree_time (float)
: time spent freeing values during collection, in seconds
This change primarily affects tools that monitor applications, including profilers and debuggers.
Lint More Than One File at a Time
The -l switch to the PHP CLI binary allows you to lint a PHP file to ensure it has no syntax errors:
php -l index.php
Previous to PHP 8.3, it only allowed you to lint one file at a time, which meant you had to invoke it once per application file if you wanted to check an entire project. As of PHP 8.3, it now allows you to pass multiple files:
php -l src/**/*.php
Use Exceptions by Default in SQLite3 Extension
While PDO and several database extensions are already emitting exceptions by default in PHP 8, the SQLite3 extension has not. PHP 8.3 introduces SQLite3Exception, and alters the behavior of the extension when SQLite3::enableExceptions(true)
is called to emit this extension-specific exception (instead of a generic Exception). Additionally, calling SQLite3::enableExceptions(false)
will now raise an E_DEPRECATED
error.
Starting in PHP 9.0, the extension will always raise exceptions. Calling SQLite3::enableExceptions(true)
will raise an E_DEPRECATED error
, and calling SQLite3::enableExceptions(false)
will raise a fatal error. PHP 10.0 will remove the SQLite3::enableExceptions()
method entirely.
More Appropriate Date/Time Exceptions in PHP 8.3
The Date/Time extension has been throwing exceptions and errors, but using the most generic Exception and Error types. As of PHP 8.3, it will use a more fine-grained approach, raising any of the following under different circumstances:
Error
(primarily for serialization issues)ValueError
(when providing invalid country codes and sunrise/sunset values)TypeError
DateError
(a new type, used for unrecoverable errors that are specific to the extension)DateRangeError
(also a new type, primarily for reporting integer time values that are outside the range supported by the extension)
DateException
is a new interface for runtime exceptions:DateInvalidTimeZeonException
DateInvalidOperationException
DateMalformedStringException
DateMalformedIntervalStringException
DateMalformedPeriodStringException
There are a limited number of BC-incompatible changes arising from this:
- Previously, a
ValueError
with the messageEpoch doesn't fit in a PHP integer
could be raised. With this change, that error becomes aDateRangeError
, which does not extendValueError
. If you were catching this specific one, you will need to change your code. DateTime::sub()
anddate_sub()
previously emitted a warning when provided with an invalid interval specification; they now raise aDateInvalidOperationException
. This means that unless you wrap in a try/catch block, values that previously allowed execution to continue, even though invalid, will halt execution.- When creating a
DateInterval
instance, invalid formats or periods would raise a warning previously; these will now throwDateMalformedIntervalStringException
.
Read full notes on more appropriate Date/Time exceptions in PHP 8.3 >>
Improve Semantics of range() Arguments
The range()
function allows creating an array of values between a start and end value using a step interval. The start and end values can be integers, floats, or even string sequences (often used for generating grid sequences, similar to what you might see in a spreadsheet). Unfortunately, it has had a number of lingering issues, largely due to having undefined semantics for how to handle strings with numeric contents; start and/or end values that are neither strings, integers, or floats; and step values that are not valid for the start/end values provided (e.g. a float step value, when string start and end values are present).
PHP 8.3 provides a number of changes to the behavior of the range()
function to fix these issues. To understand the full impact on your code, we highly recommend reading the RFC. The primary thing to be aware of is that TypeError
and ValueError
can each now be thrown by the function in cases where the values presented cannot be used, and E_WARNING
may be emitted for issues where known edge cases could lead to unexpected behavior for the end-user.
Read full notes on the change in PHP 8.3 >>
Improve Behavior of array_sum() and array_product() When Provided Unusable Values
Prior to PHP 8.3, when using either of array_sum()
or array_product()
, these functions would skip any non-numeric value when performing their calculations. This allowed them to work, but also meant that users might not be aware that the array on which they were operating contained unusable values. In particular, objects that represent numeric values such as GMP would result in values where they were omitted from the calculation, while using things such as booleans, constants, or resources would lead to wildly unpredictable results.
In PHP 8.3, these functions have been updated such that:
- (a) if an object implements a numeric cast (which is only possible for internal classes or classes provided by extensions), they will cast to that value for the calculation
- (b) all other values which cannot be cast to an integer or float will continue to be skipped, but now also emit
E_WARNING
.
Read full notes on the change >>
Assertion Behavior Deprecations
A number of changes to the assertion system were introduced in PHP 8, but a number of INI settings related to the old assertions paradigm were not removed. As such, users who were familiar to the PHP 7 assertion system may be setting the old INI settings in ways that no longer have effects, but not getting any information from the engine that they do not work.
PHP 8.3 makes the following changes to address this:
assert_options()
now emits anE_DEPRECATED
- The
assert.active
INI setting and theASSERT_ACTIVE
constant are now deprecated, and the deprecation message points to thezend_assertions
INI setting as the replacement. - The following INI values will cause the engine to emit
E_DEPRECATED
at startup:assert.warning
(and the relatedASSERT_WARNING
constant)assert.bail
(and the relatedASSERT_BAIL
constant)assert.callback
(and the relatedASSERT_CALLBACK
constant)assert.exception
(and the relatedASSERT_EXCEPTION
constant)
- The
assert() method
now is marked to return void instead of bool.
Read more about assertion behavior deprecations >>
Readonly Behavior During Cloning
Readonly properties were introduced in PHP 8.1, and readonly classes in PHP 8.2. One issue that has arisen with these is when cloning objects: if the value was set in the original object, any attempt to overwrite it in the clone would lead to an error, unless the reflection API was used to override the readonly property.
With PHP 8.3, you can re-declare or unset readonly properties when operating within the __clone()
method (or within a method called within __clone()
).
Read more about changes to readonly behavior during cloning >>
Allow Dynamic Static Value Initialization
Within a function declaration, you can declare a variable to be static; when you do, any changes to the value during the function call will be retained, and the next call to that function will use the value from the previous invocation. Declaration of a static variable allows assigning it an initial value, which is used only on first invocation.
When declaring a static variable, you have only been able to declare it to a constant expression. This has meant that if you wanted to initialize the value based on another function call, you would need to do something along the lines of:
function operation(...string $values): string
{
static $message = null;
if (null === $message) {
$message = someFunctionProvidingADefaultMessage();
}
// do some additional work, e.g., concatenating something to $message
return vsprintf($message, $values);
}
With PHP 8.3, you can now use any expression to define a static variable.
This has implications on ReflectionFunction::getStaticVariables()
, however. Since the variable may be assigned from an expression, that information may only be available at runtime (e.g., if you use a function argument in the expression), which could be an issue if the function has never been invoked. As such, if the compiler is unable to resolve the expression, it will assign a null value.
Increment/Decrement Operator Improvements
PHP has allowed the increment (++) and decrement (--) operators to be used with any type. When the type is not an int or float, this can lead to surprising behavior at times. Arrays and resources result in a TypeError, boolean values will not be changed, and string values will be cast to int or float if numeric. For deprement operations, null and non-numeric, non-empty strings remain unchanged, while empty strings result in -1. For increment operations, null and empty strings result in 1, while non-empty strings will increment the string based on PERL string incrementation rules. Objects can be incremented and decremented if they implement a do_operation()
handle; however, some internal objects define _IS_NUMBER
without the do_operation()
handle, and the operators will not work with them.
PHP 8.3 makes the following changes:
- Introduces
str_increment()
andstr_decrement()
functions to provide symmetrical string incrementation and decrementation operations that follow the PERL string incrementation and decrementation rules. Incrementation and decrementation operations with non-numeric strings will now emitE_DEPRECATED
, and users will be directed to the new functions. - Incrementation and decrementation of objects that only define
_IS_NUMBER
, without thedo_operation()
handle. - The operators will emit
E_WARNING
for operations where there is no defined behavior (e.g. with null and boolean values).
Read more about the improvements >>
General Deprecations in PHP 8.3
A proposal was accepted to deprecate a wide number of PHP features, primarily for things like invalid arguments for internal functions, constants that are no longer used/valid, cryptographic functionality that is either obsolete (and thus dangerous!) or superceded by other algorithms, etc. Read the full proposal for details.
Back to topPHP 8.3 Migration and Upgrade Considerations
Considering the number of deprecations and changes introduced in PHP 8.3, PHP teams will want to closely inspect their code base when scoping their migration or upgrade. The good news for developers is that PHP typically releases documentation that outlines upgrading from the previous version. Once that documentation is released for migrating from PHP 8.2.x to 8.3.x, you'll have a good jump off point for your migration efforts.
Update 10/20/2023 -- The PHP community has released PHP 8.2.x to 8.3.x migration documentation. It's available here on php.net.
Back to topFinal Thoughts on PHP 8.3
While the new PHP 8.3 features might not be a big draw for migrations or upgrades, there are some good quality of life improvements worth noting. Depending on the application, the most apparent benefit in upgrading to PHP 8.3 is in enjoying the three full years of community support that comes with it.
If you do decide to upgrade or migrate to PHP 8.3, we highly recommend immediately starting your investigation into the changes that might impact your application, and then reviewing again once the official PHP documentation becomes available. And, if you're on unsupported PHP and unable to migrate to a supported PHP version like PHP 8.3 upon release, be sure to check out our PHP LTS or migration services options.
Rest Easy With Secure and Supported Runtimes
ZendPHP runtimes are fully supported and offer backported security patches, 24/7/365 support, and access to ZendHQ advanced monitoring tools. Learn more via the buttons below!
Additional Resources
- White Paper - The Hidden Costs of PHP Upgrades
- White Paper - Planning Your Next PHP Migration
- Solutions - PHP Hosting Providers Solutions
- Case Study - Zend Performs Bark.com PHP Migration
- Blog - PHP 8.4: Features, Deprecations, and Changes
- Blog - Are You Ready for PHP 8.0 EOL?
- Blog - Upgrading PHP 8.0: Upgrade Paths and Considerations
- Blog - PHP Migrations: When Is Migrating the Right Choice?