What is new in PHP 8.0 vs PHP 7
| Named arguments | |
| Attributes | |
| Constructor property promotion | |
| Union types | |
| Match expression | |
| Nullsafe operator | |
| New string functions | |
| Throw as an expression | |
| JIT compiler | |
| Other changes |
Named arguments
PHP 7 only supports positional arguments, so calls with many optional parameters turn into a guessing game. PHP 8.0 lets you name them:
htmlspecialchars($string, double_encode: false); array_fill(start_index: 0, count: 100, value: 50);
Named arguments also make boolean flags readable at the call site, and argument order no longer matters. Unpacking with string keys works the same way:
$params = ["count" => 3, "value" => "x"]; var_dump(array_fill(start_index: 0, ...$params));
Attributes
Doc-comment annotations like @Route("/path") were invisible to the engine. Attributes replace them with real syntax that reflection can read:
#[Route("/api/users", methods: ["GET"])] function listUsers(): array { return []; }
The old doc-comment style still parses, but frameworks now read ReflectionFunction::getAttributes() instead of parsing comment text with regular expressions.
Constructor property promotion
The most-typed boilerplate in PHP 7 - declare the property, accept the argument, assign it - collapses into the constructor signature:
class User { public function __construct( public string $name, public string $email, private int $id = 0, ) {} }
Promoted parameters accept visibility modifiers, defaults, and attributes. The property, the type check, and the assignment are all generated from that one line.
Union types
PHP 7.1 added nullable types, but a parameter accepting int or float had no declaration. PHP 8.0 adds real union types:
function toNumber(int|float $value): int|float { return $value; } class Cache { private array|false $result = false; }
The pseudo-type mixed arrived in the same release: it accepts everything including null, unlike an empty declaration which still rejects nothing only in weak mode.
Match expression
switch falls through, compares loosely, and is a statement. match is an expression with strict comparison and no fall-through:
$label = match ($status) { 200, 201 => "ok", 404 => "missing", default => "error", };
Arms can hold several comma-separated conditions, the subject is evaluated once, and an unhandled value throws UnhandledMatchError instead of silently doing nothing.
Nullsafe operator
Chains of null checks collapse into one operator. If anything on the left is null, the whole chain short-circuits to null:
$city = $order?->getCustomer()?->getAddress()?->city;
This replaces the classic PHP 7 pyramid:
$city = null; if ($order !== null && $order->getCustomer() !== null) { $city = $order->getCustomer()->getAddress()?->city; }
New string functions
Three helpers every project used to polyfill finally exist in core:
var_dump(str_contains("hello world", "world")); // true var_dump(str_starts_with("hello world", "hello")); // true var_dump(str_ends_with("hello world", "world")); // true
They are binary-safe and far cheaper than a regular expression for a plain substring test.
Throw as an expression
throw used to be a statement, so it never fit into arrow functions, ternaries, or null coalescing. Now it does:
$port = $_ENV["PORT"] ?? throw new RuntimeException("PORT is not set"); $handler = fn($x) => $x > 0 ? $x : throw new InvalidArgumentException();
JIT compiler
PHP 8.0 ships a just-in-time compiler (tracing JIT, two modes: function and tracing). Typical web workloads gain little - the win shows up in CPU-heavy code like parsers, serializers, and math:
php -d opcache.enable_cli=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=100M script.php
Practical advice did not change: enable OPcache first, measure, and only then experiment with JIT settings.
Other changes
- Trailing comma in function calls: foo($a, $b,) is legal, like in arrays already.
- ::class on objects: $obj::class returns the class name without get_class($obj).
- Stringable interface for anything with __toString; static return type; WeakMap for ephemeral object-keyed caches.
- Sane string-to-number comparison: 0 == "foo" is now false (non-strict comparison casts the number to string instead).
- Engine warnings became Error exceptions: ValueError for bad function arguments, TypeError for internal parameter parsing, so try/catch finally works uniformly.
The full list is much longer - resource-to-object migrations, consistent type errors, and removed curly-brace offsets. Check what is new in PHP 8.5 for the other end of the timeline.
Article author: Andrei Olegovich