What PHP 8.0 Actually Changed for Developers
PHP 8.0 arrived in November 2020 and represented the most significant shift in PHP's language design since PHP 7.0 introduced scalar type hints. Beyond the performance improvements that drew attention, this release brought a complete overhaul of the type system, new syntax features that reduce boilerplate, and the long-awaited Just-In-Time compiler. It also removed dozens of deprecated functions and patterns that had accumulated over years of backward compatibility.
If you maintain or develop PHP applications, understanding these changes helps you decide when to upgrade and how to prepare your codebase for a smoother transition. For teams still on PHP 7.4, reviewing the PHP 7.4 features worth knowing provides useful context on where the language was heading before PHP 8.0 arrived.
The JIT Compiler in PHP 8.0
The headline feature of PHP 8.0 is the Just-In-Time compiler. Unlike traditional PHP execution, which interprets opcodes line by line, JIT compiles frequently used code paths to native machine instructions at runtime. This eliminates the overhead of repeated interpretation for code that runs in loops or intensive calculations.
For typical web requests serving dynamic pages, the performance improvement is modest. Most applications see around 10-20% faster execution, which may not translate to dramatic user-facing changes. However, for long-running CLI scripts, data processing tasks, or mathematical computations, the JIT compiler can deliver substantial speed gains. Benchmarks on computation-heavy workloads have shown improvements of two to five times in some cases.
JIT configuration lives in your php.ini file. The default settings attempt to detect whether JIT is beneficial for your workload, but you can tune it for specific use cases.
opcache.enable=1
opcache.jit_buffer_size=100M
opcache.jit=tracking
If you are on shared hosting, JIT availability depends entirely on your provider. Many managed WordPress hosts disable JIT by default because it conflicts with certain opcache preloading setups and some third-party PHP extensions. VPS or dedicated server users have full control over these settings.
The JIT compiler does not magically speed up database queries, network requests, or file system operations. Those remain bound by external factors. What JIT accelerates is pure PHP computation, so measure your specific workload before assuming the JIT will solve performance problems elsewhere in your stack.
Named Arguments
Named arguments let you pass parameters to a function by specifying their names rather than relying on positional order. This sounds simple, but it significantly improves code readability when working with functions that have multiple optional parameters.
Consider the htmlspecialchars function, which historically required passing parameters in strict order even when you only wanted to change the last option.
// Before PHP 8.0: must specify all parameters in order
htmlspecialchars($string, ENT_COMPAT | ENT_HTML5, 'UTF-8', false);
// PHP 8.0: specify only the parameter you want to change
htmlspecialchars($string, double_encode: false);
Named arguments also make code more maintainable. If a function signature changes and new parameters are inserted, calls using named arguments continue working correctly as long as the named parameter exists. This matters in large codebases where dependency updates might introduce new optional parameters.
Named arguments pair well with configuration arrays, but they offer one advantage arrays lack: the PHP interpreter validates parameter names at compile time, catching typos immediately rather than silently ignoring them.
// Set a cookie with only the options you need
setcookie(
name: 'session_id',
value: 'abc123',
httponly: true,
secure: true,
samesite: 'Strict'
);
Attributes in PHP 8.0
Attributes provide a standardised way to attach structured metadata to classes, methods, functions, properties, and parameters. Before PHP 8.0, developers used docblock annotations like @Route("/api") or @NotNull, which were purely strings that frameworks parsed manually. Attributes replace this with proper PHP syntax that the interpreter understands, making the metadata machine-readable from the start.
use Symfony\Component\Validator\Constraints as Assert;
class User
{
#[Assert\NotBlank]
#[Assert\Email]
public string $email;
#[Assert\Positive]
#[Assert\LessThan(120)]
public int $age;
}
The framework validates these attributes at compile time or runtime, depending on the attribute definition. This means typos in attribute names produce errors rather than silent failures. Most modern PHP frameworks, including Symfony, Laravel, and Doctrine, have adopted attributes extensively since PHP 8.0.
Attributes also enable powerful metaprogramming patterns. You can inspect class definitions at runtime using the Reflection API and read attribute values to build dynamic behaviour without relying on string parsing or naming conventions. This opens up possibilities for code generation, automated documentation, and declarative configurations that were cumbersome before.
Union Types
PHP 8.0 introduced native union types, allowing a parameter, return type, or class property to accept multiple data types. Previously, you could use nullable types with ?string, but custom unions required docblock annotations or external libraries. Union types bring this capability into the core language with full type checking support.
// Accept either int or float, return the same type
function processQuantity(int|float $quantity): int|float
{
return $quantity * 1.5;
}
// Nullable return: string or null
function getName(?string $name): string|null
{
return $name ?? 'Anonymous';
}
The mixed pseudo-type serves as a shortcut for a union of all possible types. Using mixed instead of removing type hints entirely preserves type checking while allowing any input. This is particularly useful when refactoring legacy code that previously used no type hints.
function processInput(mixed $input): mixed
{
// Handle any type without losing type information
return $input;
}
Union types catch more errors during development and make function signatures more expressive. Instead of documentation explaining acceptable types, the code itself declares them explicitly. This reduces the gap between what documentation says and what the code actually accepts.
Match Expressions
The match expression replaces the traditional switch statement with a more concise alternative that returns a value directly. It also eliminates common switch pitfalls like accidental fallthrough, where missing break statements caused unexpected code execution.
// Traditional switch statement
switch ($status) {
case 'draft':
$label = 'Draft';
break;
case 'published':
$label = 'Published';
break;
default:
$label = 'Unknown';
}
// Match expression returns the value directly
$label = match ($status) {
'draft' => 'Draft',
'published' => 'Published',
default => 'Unknown',
};
Match expressions support multiple conditions per arm by combining them with commas and use strict comparison by default, matching the behaviour of === rather than the loose comparison of switch. This prevents subtle bugs where string comparisons like '1' == 1 unexpectedly match.
$result = match ($httpCode) {
200, 201, 204 => 'Success',
400 => 'Bad Request',
401, 403 => 'Authentication Error',
404 => 'Not Found',
default => 'Unknown Response',
};
Because match expressions return values, they work well in assignments, function returns, and expressions where switch would require temporary variables. This makes code more compact and easier to follow at a glance.
Constructor Property Promotion
Constructor property promotion reduces boilerplate in classes where the constructor simply assigns its parameters to properties. Instead of declaring properties separately and assigning them, you combine both steps in the parameter list. This feature alone saves significant typing in data-oriented code.
// Traditional PHP 7.x approach
class Point {
public float $x;
public float $y;
public function __construct(float $x, float $y) {
$this->x = $x;
$this->y = $y;
}
}
// PHP 8.0 with constructor property promotion
class Point {
public function __construct(
public float $x,
public float $y,
) {}
}
Property promotion works with any visibility modifier: public, protected, or private. The promoted parameter creates a typed property with the specified visibility and assigns the argument automatically. You can also combine this with the readonly modifier for immutable properties.
class Config {
public function __construct(
public readonly string $apiKey,
protected string $baseUrl,
private ?int $timeout = 30,
) {}
}
This feature reduces repetitive code in value objects, data transfer objects, and dependency injection containers. Less code means fewer places for bugs to hide and easier maintenance over time. It also makes refactoring simpler when you need to change property visibility or add validation logic.
The Nullsafe Operator
The nullsafe operator ?-> short-circuits method calls and property access when the left side evaluates to null. Instead of throwing an error or returning null unexpectedly, the entire expression returns null immediately. This eliminates the pyramid of nested conditionals that plagued PHP code dealing with optional relationships.
// Before PHP 8.0: verbose nested null checks
$country = null;
if ($session !== null) {
$user = $session->getUser();
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$country = $address->getCountry();
}
}
}
// PHP 8.0: clean chained calls with nullsafe operator
$country = $session?->getUser()?->getAddress()?->getCountry();
The nullsafe operator works with method calls, property access, and array access. It does not work as a replacement for the null coalescing operator ??, which handles missing values differently. Use nullsafe for chained method calls, and keep coalescing for providing fallback values.
// Combine both operators for clean null handling
$city = $user?->getAddress()?->getCity() ?? 'Unknown City';
Breaking Changes in PHP 8.0
PHP 8.0 removed many features that were deprecated during the PHP 7.x cycle. If your application runs on PHP 7.4 without deprecation warnings, it is a good candidate for upgrading. Deprecation warnings indicate code that will break in PHP 8.0, so address those first before attempting the upgrade.
Key removals affecting existing codebases include:
- The
realtype cast andis_real()function: Use(float)andis_float()instead. - The
each()function: Replaced byforeachloops, which have been the recommended approach since PHP 5. create_function(): Anonymous functions usingfunction() {}syntax replaced this years ago.- Dynamic static method calls: Calling
$class::$method()where $class is a variable string no longer works. Use($class)::$method()instead. - Several mbstring function aliases: The PHP manual lists the preferred function names for each removed alias.
- The
parentkeyword outside of class context: This previously returned the parent class name as a string but now causes a compile error. - Integer division by zero: Previously returned false, now throws an
DivisionByZeroError.
Most of these removals affect legacy code written before PHP 7.0. Applications that follow modern PHP practices are unlikely to use any of these removed features, which is why keeping up to date with deprecation warnings during minor upgrades pays off.
Error Handling Changes Worth Knowing
PHP 8.0 introduced consistent error handling across the language. Many operations that previously returned false or null now throw exceptions instead. This shift from silent failures to explicit errors makes debugging easier because problems surface immediately rather than propagating through your application.
Type errors now throw TypeError exceptions instead of recoverable fatal errors. Arithmetic operations on non-numeric strings that previously coerces values now emit warnings. These changes catch bugs earlier in development, though they may surface issues that previous PHP versions silently tolerated.
The @ operator no longer silences fatal errors. This is a positive change because suppressed fatal errors made debugging difficult. Review any code that uses error suppression and handle errors properly instead.
How to Upgrade Your Application Safely
Upgrading PHP versions requires careful testing. Start by setting up a development environment running PHP 8.0 and running your full test suite against it. Unexpected errors at this stage reveal compatibility issues before they reach production, where fixing them costs more time and effort.
Static analysis tools catch many issues automatically. Installing phpstan or phan with strict rules enabled identifies type mismatches, deprecated API usage, and potential runtime errors that tests might miss.
composer require --dev phpstan/phpstan
./vendor/bin/phpstan analyse src/ --level=max
For WordPress sites or plugins, check compatibility using tools designed for that ecosystem. The WordPress 5.7 PHP 8 compatibility guide covers the specific changes WordPress developers need to address before upgrading.
Update your continuous integration pipeline to test against PHP 8.0 alongside your current version. This catches regressions before code merges reach your main branch and prevents production issues from reaching end users.
Many hosting providers offer PHP version switching through their control panels. This lets you test your application under PHP 8.0 on the same server configuration before committing to the upgrade across your entire deployment. Test on staging first, then roll out to production gradually if possible.
What Comes After PHP 8.0
PHP 8.0 was the foundation, but the PHP 8.x series has continued releasing improvements. PHP 8.1 added enums, readonly properties, and intersection types, while PHP 8.2 brought constants in traits and DNF types. Each release builds on the features introduced in PHP 8.0, making the upgrade path from PHP 7.x progressively more valuable over time.
The PHP 8 upgrade guide covers the full series including PHP 8.1, 8.2, and 8.3 changes that follow PHP 8.0. Planning your upgrade with future releases in mind helps you write code that ages better.
If you are specifically interested in what PHP 8.1 brought, the PHP 8.1 new features guide details enums, readonly properties, and other additions that complement PHP 8.0 changes. Similarly, the PHP 8.2 new features overview covers the improvements that came after.