PHP 7.1 → 7.213 language changes15 min readEnd of life

PHP 7.2 language changes since PHP 7.1

A migration-focused look at PHP 7.2 language changes: the object type, parameter widening, cast fixes, new warnings, reserved names and deprecations that affect everyday code.

PHP 7.2 is a modest release for syntax, but it matters for migration because it starts warning about habits that PHP 8 later turns into errors. Moving from PHP 7.1 is usually easy, provided you read the new warnings and deprecations instead of silencing them.

The additions are small and mostly optional. You can type a parameter or return value as object, omit parameter types when overriding a method or implementing an interface, redeclare an abstract method in an abstract subclass, and put a trailing comma at the end of a grouped use list. None of these forces a rewrite, but they let you describe designs that PHP 7.1 rejected with fatal errors.

The changes that can break an existing application are behavioral. object is now a hard-reserved word, so any class, interface or trait with that name stops compiling. Casting between arrays and objects now keeps numeric keys reachable, which fixes an old trap but changes what code sees. Calling count() on something that cannot be counted now emits a warning, and referring to an undefined constant now produces a warning rather than a notice. Both are early signs of PHP 8, where they become a TypeError and an Error respectively.

The deprecations are where the most long-term cleanup sits. each(), create_function(), the global __autoload() function, the (unset) cast and the fifth $errcontext argument of error handlers all keep working in 7.2 but emit deprecation notices, and PHP 8.0 removes them. The replacements are plain language features: foreach, closures, spl_autoload_register(), a literal null, and handlers with four parameters.

A sensible upgrade path is to run your test suite on PHP 7.2 with error_reporting set to E_ALL and treat every new warning or deprecation as a work item. Search the codebase for classes named object, for count() calls on values that might be null or scalars, for barewords used as strings, and for the deprecated constructs listed above. Fix those before moving on, and you will have done most of the preparation for PHP 8 as well. The examples under each change are written to run unchanged on PHP 7.2 and on PHP 8.5.10. Where PHP 8 later hardened a behavior, the explanation says so. Library functions, constants, INI settings and extension changes are intentionally left to other guides.

CHANGED BEHAVIOR

Undefined constants now raise a warning

Using an unquoted name that is not a defined constant still evaluates to a string, but PHP 7.2 now emits E_WARNING instead of E_NOTICE.

When PHP meets a bareword that is not a defined constant, it falls back to treating it as a string with the same text. In PHP 7.1 that fallback produced only an E_NOTICE, which many applications hide. In PHP 7.2 it produces an E_WARNING, and the message says it will become an error in a future version. That future arrived with PHP 8.0, where the same code throws an Error exception.

This matters because the pattern is often an accident: an array index written without quotes, a misspelled constant, or a constant defined in a file that did not load. Hidden notices masked these bugs before; now they show up in logs. Fix them by quoting real strings and by defining constants before use. If a constant may legitimately be missing, test it with defined() first. The example prints the string explicitly instead of relying on the fallback, so it behaves the same in PHP 7.2 and in PHP 8.

Before: Bareword fallback
<?php

var_dump(NONEXISTENT);

PHP 7.1 emits E_NOTICE, PHP 7.2 emits E_WARNING, and PHP 8.0 and later throw an Error. All versions evaluate to the string only before PHP 8. Shown for comparison; not executed on this site.

After: Define constants and quote strings
<?php

const GREETING = 'hello';

echo GREETING, "\n";
echo defined('MISSING') ? 'defined' : 'not defined', "\n";
echo 'MISSING', "\n";
Output on PHP 8.5.10
hello
not defined
MISSING

Official migration notes

CHANGED BEHAVIOR

count() warns on non-countable values

Calling count() or sizeof() on null, scalars or objects without Countable now emits E_WARNING in PHP 7.2 instead of silently returning 0 or 1.

Before PHP 7.2, count(null) quietly returned 0, and count('abc') or count(1) returned 1. Code often relied on this by counting the result of a function that sometimes returned null or false. PHP 7.2 emits an E_WARNING for anything that is not an array or an object implementing Countable, though it still returns the old numbers. The same applies to the sizeof() alias.

The warning exposes a type confusion in the caller. The cleanest fix is to make the data source always return an array. If you must tolerate mixed input, check the value explicitly and decide what each case means. In PHP 8.0 and later the call throws a TypeError, so a guard like the one below keeps working across versions. The example reproduces the old numeric results but makes the intent visible and raises no warning.

Before: Counting anything
<?php

echo count(null), count('abc'), "\n";

Accepted silently in PHP 7.1; PHP 7.2 warns for each call; PHP 8.0 and later throw TypeError. Shown for comparison; not executed on this site.

After: Guard before counting
<?php

function safeCount($value): int
{
    if (is_array($value) || $value instanceof Countable) {
        return count($value);
    }
    return $value === null ? 0 : 1;
}

echo safeCount([1, 2]), safeCount(null), safeCount('abc'), "\n";
echo safeCount(new ArrayObject([1, 2, 3])), "\n";
Output on PHP 8.5.10
201
3

Official migration notes

DEPRECATED

each() is deprecated

The each() function emits a deprecation in PHP 7.2; iterate with foreach instead.

each() returned the current key and value pair of an array and advanced the internal pointer. It was typically used inside while (list($k, $v) = each($array)) loops. PHP 7.2 deprecates it because it is slower than foreach and complicates some planned language changes. PHP 8.0 removes it, so leftover calls become fatal errors there.

The replacement is almost always foreach, which also leaves the internal array pointer alone and copes with by-reference iteration when you need it. One pitfall is that old loops sometimes depended on the pointer position, for example resuming after a reset() or next() call. Review such code rather than translating it mechanically. Another is that each() returned false at the end, which doubled as the loop condition; foreach handles termination for you. The example uses the plain key and value form.

Before: Loop with each()
<?php

$data = ['a' => 1, 'b' => 2];
while (list($key, $value) = each($data)) {
    echo $key, '=', $value, "\n";
}

Works in PHP 7.1; deprecated in PHP 7.2; removed in PHP 8.0. Shown for comparison; not executed on this site.

After: Loop with foreach
<?php

$data = ['a' => 1, 'b' => 2];
foreach ($data as $key => $value) {
    echo $key, '=', $value, "\n";
}
Output on PHP 8.5.10
a=1
b=2

Official migration notes

DEPRECATED

create_function() is deprecated

Building functions from strings with create_function() now emits a deprecation; use anonymous functions.

create_function() compiled a function from a parameter string and a body string. It was a thin wrapper around eval(), so any untrusted text in either string meant possible code execution, and errors inside the body were hard to trace. PHP 7.2 deprecates it, and PHP 8.0 removes it.

An anonymous function does the same job with real syntax that your editor and static analysis tools understand. The main difference is scope: create_function() code could not see surrounding variables, while a closure captures them only when you list them in use, which is more explicit and safer. When migrating, convert the string body into real code, and replace any interpolated values with use variables rather than concatenation. The example captures $factor explicitly and gives the parameter and return value types, which works on PHP 7.2 and later.

Before: Function built from strings
<?php

$multiply = create_function('$n', 'return $n * 3;');
echo $multiply(5), "\n";

Works in PHP 7.1; deprecated in PHP 7.2; removed in PHP 8.0. Shown for comparison; not executed on this site.

After: Closure with use
<?php

$factor = 3;
$multiply = function (int $n) use ($factor): int {
    return $n * $factor;
};

echo $multiply(5), "\n";
Output on PHP 8.5.10
15

Official migration notes

DEPRECATED

The __autoload() function is deprecated

A global __autoload() function triggers a deprecation in PHP 7.2; register loaders with spl_autoload_register() so they can be chained.

Defining a global function named __autoload() was the original way to load classes on demand. It allows only one loader, it cannot be chained with others, and it does not cooperate with loaders registered through the SPL. This is why PHP 7.2 deprecates it and PHP 8.0 removes it.

spl_autoload_register() accepts any callable, including closures, and you can register several loaders that run in order. This matters when libraries bring their own autoloaders, for example one installed by a package manager. A pitfall during migration is that mixing the two styles does not work: once an SPL loader is registered, the old __autoload() function is no longer called. Convert completely and test class loading paths that run only rarely. The example avoids the filesystem by having the loader create an alias, which still shows when the loader fires.

Before: Global __autoload function
<?php

function __autoload($class)
{
    echo "Loading $class\n";
}

new Missing();

Accepted in PHP 7.1; deprecated in PHP 7.2; removed in PHP 8.0. Shown for comparison; not executed on this site.

After: Register a loader
<?php

class RealGreeter
{
    public function hi(): string { return 'hi'; }
}

spl_autoload_register(function (string $class): void {
    if ($class === 'Greeter') {
        class_alias('RealGreeter', 'Greeter');
    }
});

echo (new Greeter())->hi(), "\n";
Output on PHP 8.5.10
hi

Official migration notes

REMOVED

object is a reserved class name

The name object was soft-reserved in PHP 7.0 and is hard-reserved in PHP 7.2, so it can no longer name a class, interface or trait.

PHP 7.0 only discouraged using object as a type name. From PHP 7.2 the word is fully reserved because it is now a real type declaration. Any class, interface or trait called object causes a fatal error at compile time, and that includes code in third-party packages and in legacy code that happened to use the name for a generic wrapper.

The fix is a rename, but it can touch many files: the declaration, new expressions, instanceof checks, type hints and string class references such as those in configuration or service containers. Search for case-insensitive matches, since PHP class names are not case-sensitive. Namespaced code is also affected for the declaration itself, so putting such a class in a namespace does not help. The example uses a descriptive name instead and takes advantage of the new object type for the parameter.

Before: Class named object
<?php

class object
{
}

Accepted in PHP 7.1 with no complaint; a fatal error in PHP 7.2 and later. Shown for comparison; not executed on this site.

After: Use a different name
<?php

final class ObjectBox
{
}

function className(object $value): string
{
    return get_class($value);
}

echo className(new ObjectBox()), "\n";
Output on PHP 8.5.10
ObjectBox

Official migration notes

CHANGED BEHAVIOR

Numeric keys survive array and object casts

Casting arrays to objects and objects to arrays now converts integer-like keys properly, so they stay reachable on both sides.

In PHP 7.1, (object) [0 => 'a'] created a property that could not be reached with $obj->{'0'} or $obj->{0}, and (array) applied to an object with a numeric property produced a string key that $arr[0] could not read. PHP 7.2 fixes both directions. An integer key becomes a string property name when going to an object, and an integer-like string property becomes an integer key when going to an array. The same conversion applies to settype().

This is a bug fix, yet it can change behavior: code that worked around the old limitation, for instance by looping with foreach or by using get_object_vars(), still works, but code that expected the old string keys after a cast may now see integers. Be careful when comparing with strict equality or when serializing results. The example shows access through both spellings and a JSON result whose keys do not depend on the key type.

Before: Inaccessible numeric property
<?php

$obj = (object) [0 => 'a'];
var_dump(isset($obj->{'0'}));

In PHP 7.1 the numeric key could not be read as a property; PHP 7.2 and later return true here. Shown for comparison; not executed on this site.

After: Numeric keys round trip
<?php

$obj = (object) [0 => 'a', 'x' => 'b'];
echo $obj->{'0'}, $obj->{0}, "\n";

$arr = (array) $obj;
echo $arr[0], $arr['0'], $arr['x'], "\n";
echo json_encode($arr), "\n";
Output on PHP 8.5.10
aa
aab
{"0":"a","x":"b"}

Official migration notes

NEW

Parameter types may be omitted when overriding

A child method or interface implementation can drop a parameter type that the parent declared, which is consistent with contravariance.

PHP 7.1 required an overriding method to repeat the parent's parameter types exactly. PHP 7.2 permits removing a parameter type in the child class or in the implementing class. This follows the Liskov substitution principle: a method that accepts anything can safely stand in for one that accepts only arrays, because it still handles every call the parent's contract allows.

Use this when an implementation genuinely handles a wider range of input than the interface requires. Do not use it merely to avoid writing types, since an untyped parameter gives callers and static analyzers less information. Only omission is allowed in 7.2; replacing array with an unrelated type, or adding a type that the parent lacked, is still an incompatibility. Return types are separate and must stay compatible, as the example shows by keeping string. Later PHP versions extended variance further, but this form is stable.

Before: Type must match
<?php

interface Handler
{
    public function handle(array $input): string;
}

class Loose implements Handler
{
    public function handle($input): string { return 'x'; }
}

Fatal error for incompatible declaration in PHP 7.1; accepted in PHP 7.2 and later. Shown for comparison; not executed on this site.

After: Omit the type in the implementation
<?php

interface Handler
{
    public function handle(array $input): string;
}

class Loose implements Handler
{
    public function handle($input): string
    {
        return gettype($input);
    }
}

echo (new Loose())->handle([1]), "\n";
echo (new Loose())->handle('text'), "\n";
Output on PHP 8.5.10
array
string

Official migration notes

NEW

The object type declaration

Parameters and return values can now be typed as object, accepting an instance of any class.

Before PHP 7.2 there was no way to say that a parameter must be an object of any class. Developers used an untyped parameter with a manual is_object() check, or an interface that most classes did not implement. The new object type does this in the signature and works for both parameters and return values.

It follows the usual rules: with coercive typing a non-object value, such as a string, throws a TypeError, and null is rejected unless the type is nullable, written ?object. Prefer a specific class or interface when you know one, because object says little about what methods are available. It is useful for generic containers, registries and serializers. It also interacts with inheritance: a parent method declared with object can be overridden by a child with a narrower return type, and a child may widen a parameter by dropping the type. The example shows both a valid call and the failure path.

Before: Manual check
<?php

function describe($value)
{
    if (!is_object($value)) {
        throw new InvalidArgumentException('object expected');
    }
    return $value;
}

This is how PHP 7.1 code had to enforce an object; the object type declaration is rejected as a syntax error before PHP 7.2. Shown for comparison; not executed on this site.

After: Declare object
<?php

function describe(object $o): object
{
    return $o;
}

$result = describe(new ArrayObject([1]));
echo get_class($result), "\n";

try {
    describe('x');
} catch (TypeError $e) {
    echo "TypeError\n";
}
Output on PHP 8.5.10
ArrayObject
TypeError

Official migration notes

DEPRECATED

The $errcontext error handler argument is deprecated

Custom error handlers should stop relying on the fifth $errcontext argument holding the local variables of the error site.

A handler registered with set_error_handler() historically received a fifth argument, $errcontext, an array of every variable in scope where the error occurred. PHP 7.2 deprecates relying on it. It is rarely used, and keeping the array available hampers engine optimizations. PHP 8.0 stops passing it.

Declare handlers with the four arguments that matter: the error number, message, file and line. If your handler used $errcontext to log variables, switch to a debugger, or log specific values at the call site where you know what matters. Be careful with handlers that are written with five parameters and no default, since removing the argument in later versions turns them into a source of argument count errors. The example uses a four-parameter closure with a user-level warning, which gives a stable number, 512, on every version.

Before: Handler using $errcontext
<?php

set_error_handler(function ($no, $str, $file, $line, $context) {
    echo count($context), "\n";
    return true;
});

Works in PHP 7.1; using the context is deprecated in PHP 7.2; PHP 8.0 no longer passes it. Shown for comparison; not executed on this site.

After: Four-parameter handler
<?php

set_error_handler(function (int $no, string $str, string $file, int $line): bool {
    echo "handled $no: $str\n";
    return true;
});

trigger_error('custom', E_USER_WARNING);
Output on PHP 8.5.10
handled 512: custom

Official migration notes

DEPRECATED

The (unset) cast is deprecated

Casting an expression with (unset) always produced null, so PHP 7.2 deprecates it; write null directly.

The (unset) cast evaluates its operand and yields null. It does not unset the variable, which led many people to misread it as the unset() language construct. Since the result is always null, PHP 7.2 deprecates the cast, and PHP 8.0 removes it, so code using it fails to compile there.

To replace it, assign null where the cast result was used. If the operand had side effects that you relied on, such as a function call, call the function in a separate statement and then use null. If you actually wanted to remove a variable from scope, use unset($variable) as a statement instead. Because the cast is rare, a project-wide search for the literal text is a quick way to find every use. The example leaves the original variable unchanged and gives the copy a plain null.

Before: The (unset) cast
<?php

$value = 'secret';
$copy = (unset) $value;
var_dump($copy);

Works in PHP 7.1; deprecated in PHP 7.2; a compile error in PHP 8.0 and later. Shown for comparison; not executed on this site.

After: Assign null directly
<?php

$value = 'secret';
$copy = null;

echo $value, "\n";
var_dump($copy);
Output on PHP 8.5.10
secret
NULL

Official migration notes

NEW

Abstract methods can be overridden by abstract classes

An abstract class can now redeclare an inherited abstract method, adjusting the signature within the usual variance rules.

In PHP 7.1, once a parent class declared an abstract method, an abstract child could not declare it again; the child had to wait for the concrete class to supply it. PHP 7.2 allows an abstract subclass to redeclare the method as abstract. This lets an intermediate class refine the contract, for example by dropping a parameter type or adding a return type, while leaving the implementation to a later subclass.

The usual compatibility rules still apply: parameters may be widened and return types may be narrowed or added, but the redeclaration cannot make the method incompatible with the parent. The concrete class at the bottom must satisfy the most recent declaration. A pitfall is forgetting that every concrete descendant must be updated when the intermediate class tightens the contract. In the example, the middle class adds an int return type and removes the parameter type, and the concrete class implements it.

Before: Redeclaring abstract method
<?php

abstract class A
{
    abstract public function test(string $s);
}

abstract class B extends A
{
    abstract public function test($s): int;
}

PHP 7.1 rejects the redeclaration with a fatal error; PHP 7.2 and later accept it. Shown for comparison; not executed on this site.

After: Refine in an abstract subclass
<?php

abstract class A
{
    abstract public function test(string $s);
}

abstract class B extends A
{
    abstract public function test($s): int;
}

class C extends B
{
    public function test($s): int { return strlen((string) $s); }
}

echo (new C())->test('four'), "\n";
Output on PHP 8.5.10
4

Official migration notes

NEW

Trailing comma in grouped use declarations

Grouped namespace imports, introduced in PHP 7.0, may now end with a trailing comma.

Group use syntax lets you import several names from one namespace in a single statement. PHP 7.0 and 7.1 treated a comma after the last item as a parse error, which made diffs noisier: appending an import changed the previous line as well. PHP 7.2 accepts a trailing comma in this list, so each import can live on its own line, and adding one touches only a single line of the version control diff.

This change affects syntax only; it does not change how names are resolved. It applies to the braces form of use, and it does not make other lists, such as function call arguments, accept trailing commas in this version. A common pitfall is that code with a trailing comma will not parse on PHP 7.1, which matters for libraries that still support it. The example puts two namespaces in one file with bracketed blocks to keep it self-contained and deterministic.

Before: No trailing comma
<?php

use Lib\{Foo, Bar};

This form is accepted in PHP 7.0 and later; PHP 7.1 rejects the trailing comma shown in the new form. Shown for comparison; not executed on this site.

After: Trailing comma allowed
<?php

namespace Lib {
    class Foo {}
    class Bar {}
}

namespace App {
    use Lib\{
        Foo,
        Bar,
    };

    echo get_class(new Foo()), "\n", get_class(new Bar()), "\n";
}
Output on PHP 8.5.10
Lib\Foo
Lib\Bar

Official migration notes

← PHP 7.1 changes PHP 7.3 changes →