BreadcrumbHomeResourcesBlog PHP 8.3: Features, Deprecations, and Changes To Watch August 11, 2023 PHP 8.3: Features, Deprecations, and Changes to WatchPHP DevelopmentBy Matthew Weier O’PhinneyWith PHP 8.3 having now reached feature freeze, and the first beta releases already in the books, PHP 8.3 is getting closer and closer to its general availability release. This means teams around the world are starting to assess how those changes might impact their projects, and whether upgrading or migrating to PHP 8.3 upon GA release might be in the cards.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 this latest PHP release when it arrives in November.Table of ContentsPHP 8.3 Release OverviewNew PHP 8.3 FeaturesPHP 8.3 Breaking Changes and DeprecationsPHP 8.3 Migration and Upgrade ConsiderationsFinal Thoughts on PHP 8.3Table of Contents1 - PHP 8.3 Release Overview2 - New PHP 8.3 Features3 - PHP 8.3 Breaking Changes and Deprecations4 - PHP 8.3 Migration and Upgrade Considerations5 - Final Thoughts on PHP 8.3Back to topPHP 8.3 Release OverviewEvery 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.Now that PHP 8.3 has reached feature freeze, we now roughly what will be included in the general availability release. At a high level, there are not any huge additions in th PHP 8.3 release -- at least 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.PHP 8.3 Release TimelinePHP 8.3 follows the same rough release timeline as previous releases. At the time of publication, it has progressed to Beta Release 2. After another round in beta it will enter the release candidate stages before progressing to a GA release in November.DateMilestone06/08/2023Alpha Release 106/22/2023Alpha Release 207/06/2023Alpha Release 307/18/2023Feature Freeze07/20/2023Beta Release 108/03/2023Beta Release 208/17/2023Beta Release 308/31/2023Release Candidate 109/14/2023Release Candidate 209/28/2023Release Candidate 310/12/2023Release Candidate 410/26/2023Release Candidate 511/09/2023Release Candidate 611/23/2023General Availability ReleasePHP 8.3 Release DateTHe PHP 8.3 release occurred on November 23, 2023.Get an Overview of the New PHP 8.3 FeaturesIn 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.Back to topNew PHP 8.3 FeaturesPHP 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 ConstantsDynamic class constant and Enum member fetch supportjson_validate() functionRandom extension additionsAddition of mb_str_pad()Addition of #[\Override]attributeTyped Class Constants in PHP 8.3Constants 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.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.json_validate() functionIn 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 AdditionsThe "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 of getFloat(), 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's Math.random() function.Addition of mb_str_pad()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] AttributeWhen 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 >>Back to topPHP 8.3 Breaking Changes and DeprecationsPHP 8.3 is also set to introduce a number of deprecations and changes, including:class_alias supports aliasing PHP built-insunserialize() now emits E_WARNING instead of E_NOTICEFallback value support for php.ini environment variable syntaxgc_status() extended informationLint more than one file at a timeUse exceptions by default in SQLite3 extensionMore appropriate Date/Time exceptionsImprove semantics of range() argumentsImprove behavior of array_sum() and array_product() when provided unusable valuesAssertion behavior deprecationsReadonly behavior during cloningAllow dynamic static value initializationIncrement/Decrement operator improvementsYou can read more about the changes and deprecations in PHP 8.3 below.class_alias Supports Aliasing PHP Built-insYou 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 anE_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 InformationThe 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 bufferPHP 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 exceeds GC_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 secondsThis change primarily affects tools that monitor applications, including profilers and debuggers. Lint More Than One File at a TimeThe -l switch to the PHP CLI binary allows you to lint a PHP file to ensure it has no syntax errors: php -l index.phpPrevious 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/**/*.phpUse Exceptions by Default in SQLite3 ExtensionWhile 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 anE_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.Full notes on this change >>More Appropriate Date/Time ExceptionsThe 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:DateInvalidTimeZeonExceptionDateInvalidOperationExceptionDateMalformedStringExceptionDateMalformedIntervalStringExceptionDateMalformedPeriodStringExceptionThere are a limited number of BC-incompatible changes arising from this:Previously, a ValueError with the message Epoch doesn't fit in a PHP integer could be raised. With this change, that error becomes a DateRangeError, which does not extend ValueError. If you were catching this specific one, you will need to change your code.DateTime::sub() and date_sub()previously emitted a warning when provided with an invalid interval specification; they now raise a DateInvalidOperationException. 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 throw DateMalformedIntervalStringException.Read full notes on more appropriate Date/Time exceptions >>Improve Semantics of range() ArgumentsThe 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 >>Improve Behavior of array_sum() and array_product() When Provided Unusable ValuesPrior to 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 DeprecationsA 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 an E_DEPRECATEDThe assert.activeINIsetting and the ASSERT_ACTIVE constant are now deprecated, and the deprecation message points to the zend_assertions INI setting as the replacement.The following INI values will cause the engine to emit E_DEPRECATED at startup:assert.warning (and the related ASSERT_WARNING constant)assert.bail (and the related ASSERT_BAIL constant)assert.callback (and the related ASSERT_CALLBACK constant)assert.exception (and the related ASSERT_EXCEPTION constant)The assert() method now is marked to return void instead of bool.Read more about assertion behavior deprecations >>Readonly Behavior During CloningReadonly 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 toreadonly behavior during cloning >>Allow Dynamic Static Value InitializationWithin 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.Read more about the change >>Increment/Decrement Operator ImprovementsPHP 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_NUMBERwithout 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 emit E_DEPRECATED, and users will be directed to the new functions.Incrementation and decrementation of objects that only define _IS_NUMBER, without the do_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 DeprecationsA 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 ConsiderationsConsidering 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.3While 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.Need Help With Your EOL PHP?Zend can help. Explore our PHP LTS and migration services options via the links below.SEE PHP LTS OPTIONS EXPLORE MIGRATION SERVICESAdditional ResourcesBlog - Are You Ready for PHP 8.0 EOL?White Paper - The Hidden Costs of PHP UpgradesWhite Paper - Planning Your Next PHP MigrationSolutions - PHP Hosting Providers SolutionsBlog - Unpacking the Drupal PHP Support LifecycleBlog - Upgrading PHP 8.0: Upgrade Paths and ConsiderationsBlog - The State of WordPress PHP SupportBlog - How to Assess and Prevent PHP VulnerabilitiesBlog - PHP Migrations: When Is Migrating the Right Choice?Blog - How to Improve PHP PerformanceCase Study - Zend Performs Bark.com PHP MigrationBack to top
Matthew Weier O’Phinney Senior Product Manager, OpenLogic and Zend by Perforce Matthew began developing on Zend Framework (ZF) before its first public release, and led the project for Zend from 2009 through 2019. He is a founding member of the PHP Framework Interop Group (PHP-FIG), which creates and promotes standards for the PHP ecosystem — and is serving his second elected term on the PHP-FIG Core Committee.