New PHP error messages in PHP 8.4

The upcoming PHP 8.4 is bringing new errors messages, and, also, removing some of them. The new messages originate from new features, deprecated ones, removed ones, and extra checks throughout the source code. Let’s review the new PHP error messages in PHP 8.4 and get our code ready for November 2024.

The big PHP error database

This post is build on top of the PHP error message database, which collect the PHP error messages, along with more context information, version information and possible solutions. If you like reading error messages, this is a great link. It is used to build a number of Exakat static analysis rules.

Evolution of error message counts

PHP error message count, per versionWith distinct 1919 error messages, PHP 8.4 is ranking number 1 most verbose PHP version ever! Although, the trend is quite steady from the last versions, and we can bet that the next PHP 8.5 will have even more error messages.

Note that this graph is very different from last year, which counted 783 errors in PHP 8.3: this one is now counting 1731. This is due to an upgrade in the counting application, which is covering new calls to error functions. The teachings are still the same, though.

New error messages

PHP 8.4 adds 311 new error messages, and removes 123 of them, for a net increase of 188. A good number of the error messages are checking for incoming values in parameters, and look like must be a bitmask of IMAP_GC_TEXTS, IMAP_GC_ELT, and IMAP_GC_ENV, or Cannot clone unconstructed MessageFormatter. We will set them aside, for the sake of avoiding repetitive remarks.

%s(): Implicitly marking parameter $%s as nullable is deprecated, the explicit nullable type

A nullable parameter accepts null as value and, at least, another type. It is visible with the ? or null in the type specification of the parameter.

 
<?php

function foo(?string $s) : void { 
    var_dump($s); 
}

function bar(string $s = null) : void {
     var_dump($s); 
}

?> 

Yet, it is also possible to define a null default value, and not make the type nullable, by simply using = null in the parameter definition. Here, the nullable is not explicitly part of the type, but the method does accept it nonetheless.

Implicit nullable is not possible with properties, or return values. They yield errors at compile or execution time, respectively.

Since PHP 8.4, the implicit nullable parameters are reported by PHP, as a deprecated feature. They will disappear in PHP 9, or in a future version. It is time to make them explicit, or clean up the type specification.

This has chances to be the most popular PHP 8.4 error message.

=> Implicitly marking parameter $%s as nullable is deprecated, the explicit nullable type must be used instead

Cannot unset hooked property %s::$%s

Hooked properties are a new features of PHP 8.4 : a property may have a get and/or a set hook, defined with the property, that serve as accessors.

Normal non-static properties, as in without a hook, may be unset at any time. They are reset to the uninitialized state, and should be be used except for writing or checking with isset.

On the other hand, hooked properties never can be removed.

 
<?php

class x { 
   public int $p = 3;
   public int $q  = 5 { 
      get => $this->q;
   }
}

$x = new x; 
unset($x->p); 
//Cannot unset hooked property x::$q 

unset($x->q);
//Typed property x::$p must not be accessed before initialization 
print $x->p;
?> 

=> Cannot unset hooked property %s::$%s

Must not use parent::$%s::%s() outside a property hook

Property hooks are defining methods, at the property level. These methods behave like other methods, and should be able to access the parent method, when the visibility is not private. This means that the child class may use a parent’s property hook, and doesn’t have to redefine them every time.

This is also an example of using the :: famous operator twice in a row: one to access the parent property, and the second to reach its hook.

Now, that syntax is not allowed outside a property hook, even within the same class: no other method shall call it, or, another property.

 
<?php

class x { 
     public string $q { 
         get => 'in parent'; 
     } 
}

class y extends x { 
   public string $q { 
      get => parent::$q::get(); 
   }

   function foo(): string {
      // Must not use parent::$q::get() outside a property hook
    return parent::$q::get();
   }
}

$y = new y; echo $y->q;
?>

=> Must not use parent::$%s::%s() outside a property hook

Interfaces may only include hooked properties

This error message replaces the previous Interfaces may not include properties. Until PHP 8.4, interfaces could not define properties: only methods and constants.

Now, with hooked properties being properties tied to methods (get and set), it is possible to put these hooks in an interface, without a body.

 
<?php

interface x { 
   public string $q { get; }
   public string $r
}

?> 

Since it is always possible to implement the obvious get => $this->q property hook, it is now trivial to include properties in PHP interfaces.

=> Interfaces may only include hooked properties

contains CR character that is not allowed in the header

There is a family of characters that are now reported when used in a mail header. CR (carriage return), LF (line feed), CRLF (both previous ones), and NULL (aka, \0). They are linked with possible hack that introduces extra email addresses or even full mail headers in the message.

 
<?php

// example from ./ext/standard/tests/mail/gh13415.phpt 
try { 
   mail('to@example.com', 
        'Test Subject', 
        'A Message', 
        ['Reply-To' => "foo@example.com \nCc: hacker@example.com"]); 
} catch (Throwable $e) { 
   echo $e->getMessage()."\n\n"; 
}

?> 

Basically, if certain mail headers are not sanitized enough, PHP has your back.

From the tests, I found Reply-to and custom foo headers are reported.

Power of base 0 and negative exponent is deprecated

It is now explicitly forbidden to take a negative exponent over 0. A negative exponent of $x is the same a taking the positive exponent of 1/$x: the latter makes no mathematical sense.

 <?php

//Deprecated: Power of base 0 and negative exponent is deprecated 
echo 0 ** -1;

//Uncaught DivisionByZeroError: Division by zero 
echo 1 / 0;

?> 

It used to make PHP sense, and be INF, unlike the division by zero, which emitted a saner DivisionByZeroError.

=> Power of base 0 and negative exponent is deprecated

A never-returning function must not return

Using the never return type means that the function will end the script, with die, or throwan exception. In both cases, the return clause would not be reached, and it is actually wrong to include one in such a method.

In fact, even is the return command is unreachable, PHP will complain about it.

 
<?php

function foo() : never { 
   if (0) { return 1; }

   die('the end');
}
?> 

%s(): never-returning function must not implicitly return

The previous error message is emitted at compile time: with an explicit return command, it is easy to spot a potential problem. Yet, PHP doesn’t need the return command: when omitted, it will simply return null.

When the return is implicit, the error will happen at execution time.

 <?php

// all OK 
function foo() : never {}

// error in the previous definition 
foo();

?> 

=> A never-returning function must not return

A function with return type must return a value

Finally, with returned types, PHP now reports attempts at returning nothing, while typing the method. return with no explicit value means return null;.

Although, return and return null do not return the same null: the second definition below also yields the error: the null value must be explicit.

 
<?php

// emits the error 
function foo() : int { return ; }

// also emits the error 
function foo() : null { return ; }

// no error at linting time 
// emits the error upon execution 
function foo() : int { return 'a'; }

?> 

Finally, a typed method, with a returned literal value, is only checked at execution time. Hence, the last code compiles, but will fail at execution time.

=> A never-returning function must not return

Array and string offset access syntax with curly braces is no longer supported

This error message is actually removed from PHP. So, believe it or not, PHP 8.4 is totally dropping the support of curly braces on arrays and strings!

 
<?php 
  $array = ['a', 'b', 'c']; 
  echo $array{2}; 
  // echo c

  $string = 'ABC'; 
  echo $string[1]; 
  // echo B

?> 

Until PHP 8.3, it was a deprecated feature, and in PHP 8.4, it is now a generic syntax error. No need to wait for PHP 9.0!

=> Array and string offset access syntax with curly braces is no longer supported

Unknown errors

Here are some errors which are in the source code of PHP, but we could not reproduce. They are often niche error, deep inside a PHP extension, and are lacking context to be well understood. In fact, they are just fun to read, out of context.

  • Not yet implemented
  • individual body cannot be empty
  • Unknown and uncaught modification type.
  • CPU doesn't support SSE2
  • Cannot use variable $%s twice
    • Actually, this happens when the same variable is used twice (or more) in the use expression of a closure

Previous error message reviews

Get ready for PHP 8.4

Hopefully, this quick tour in the new error messages of PHP 8.4 will help preparing the code for its own migration to PHP 8.4.

Since some of the checks are only available at execution time, it is worth running static analysis on it to spot potential errors. Exakat include the Compatibility PHP 8.4 ruleset, with rules dedicated to PHP 8.4. They can run on PHP 8.3, so as to be ready for November 2024.

Happy auditing.