Article: PHP 8 – Functions and Methods

MMS Founder
MMS Deepak Vohra

Article originally posted on InfoQ. Visit InfoQ

Key Takeaways

  • PHP 8.1 simplifies the syntax for creating a callable to AnyCallableExpression(...).
  • PHP 8.0 introduces named function parameters in addition to positional arguments.
  • PHP 8.1 introduces Fibers as interruptible functions to facilitate multi-tasking.
  • PHP 8 adds new standard library functions, including __toString(), and sets new requirements on the use of magic methods.
  • Private methods may be inherited and reimplemented without any restrictions, except for private final constructors that must be kept as such. 

This article is part of the article series “PHP 8.x”. You can subscribe to receive notifications about new articles in this series via RSS.

PHP continues to be one of the most widely used scripting languages on  the web with 77.3% of all the websites whose server-side programming language is known using it according to w3tech. PHP 8 brings many new features and other improvements, which we shall explore in this article series.

PHP 8.0 adds support for several functions- and methods-related features, some of which are an improvement of existing features, while others are completely new features. The enhanced callable syntax in PHP 8.1 can be used to create anonymous functions from a callable. Named function arguments  may be used along with positional arguments with the added benefit that named arguments are not ordered and can convey meaning by their name.  Fibers are interruptible functions that add support for multitasking. 

Inheritance on private methods is redefined

Object inheritance is a programming paradigm that is used by most object-oriented languages including PHP. It makes it possible to override  public and protected methods, and class properties and constants defined in a class from any class that extends it. In PHP, public methods cannot be reimplemented with a more restrictive access such as by making a public method private. To demonstrate this, consider a class B that extends class A and reimplements a public method from A. 

sortArray();

When run, the script generates an error message:

Fatal error: Access level to B::sortArray() must be public (as in class A) 
public method cannot be reimplemented.

On the contrary, private methods defined in a class are not inherited and can be reimplemented in a class that extends it.   As an example, class B extends class A in the following script and reimplements a private method from A.

<?php

class A{
  private function sortArray():string{
   return "Class A method";
  }
}
 
class B extends A{
  private function sortArray(int $a):string{
   return "Class B method";
  }
}

Prior to PHP 8.0, two restrictions applied to private method redeclaration in an extending class: the final and static modifiers were not allowed to be changed. If a private method was declared final, an extending class was not allowed to redeclare the method. If a private method was declared static, it was to be kept static in an extending class. And, if a private method did not have the static modifier, an extending class was not allowed to add a static modifier. Both restrictions have been lifted in PHP 8. The following script runs ok in PHP 8. 

<?php

class A {
  private final static function sortArray():string{
   return "Class A method";
  }
}
 
class B extends A {
  private  function sortArray(int $a):string{
   return "Class B method";
  }
}

The only private method restriction in PHP 8 is to enforce private final constructors, which are sometimes used to disable the constructor when using static factory methods as a substitute.  

<?php

class A {
  private final function __construct(){
         
  }
}
 
class B extends A {
  private final function __construct(){
    
  }

}

The script generates error message:

Fatal error: Cannot override final method A::__construct() 

A variadic argument may replace any number of function arguments

In PHP 8, a single variadic argument may replace any number of function arguments.  Consider the following script in which class B extends class A and replaces the three-argument function sortArray with a single variadic argument. 

  $val) {
                echo "$key = $val ";
             }  
       } elseif ($sortType == "desc") {
             rsort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
       }  
   }
}
class B extends A {

    public function sortArray(...$multiple) {

        $arrayToSort= $multiple[0];
        $sortType=$multiple[1];
    
    if ($sortType == "asc") {
             sort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        } elseif ($sortType == "desc") {
             rsort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        }  
   }
}

The sortArray function in class B  may be called using multiple arguments.

$sortType="asc";
$arrayToSort=array("B", "A", "f", "C");
$arraySize=4;
 
$b=new B();
$b->sortArray($arrayToSort,$sortType,$arraySize);

The output is as follows:

0 = A 1 = B 2 = C 3 = f 

Simplified Callable Syntax

A callable is a PHP expression that can be called, such as an instance method, a static method, or an invocable object. A callable can be used to create a short-form expression for a method call, for example. In PHP 8.1, there is  new callable syntax available:

AVariableCallableExpression(…)

The AVariableCallableExpression represents a variable callable expression. The ellipses … is included in the syntax. 

Why a new callable syntax? Let’s recall what the traditional callable syntax looks like with some examples:

$f1 = 'strlen'(...);
$f2 = [$someobj, 'somemethod'](...);
$f3 = [SomeClass::class, 'somestaticmethod'](...);

 This has two issues:

  1. The syntax involves strings and arrays
  2. The scope is not maintained at the point at which the callable is created.

To demonstrate this, consider the following script for sorting an array in which the getSortArrayMethod() method returns a callable for the sortArray() method with return [$this, 'sortArray']

arrayToSort = $arrayToSort;
        $this->sortType = $sortType;
    }

    public function getSortArrayMethod() {
             return [$this, 'sortArray'];
         
    }

    private function sortArray() {
        if ($this->sortType == "Asc") {
             sort($this->arrayToSort);
             foreach ($this->arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        } elseif ($this->sortType == "Desc") {
             rsort($this->arrayToSort);
             foreach ($this->arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        } else {
              
             shuffle($this->arrayToSort);
             foreach ($this->arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        }
    }
}

$sortType="Asc";
$arrayToSort=array("B", "A", "f", "C");
$sort = new Sort($arrayToSort,$sortType);
$c = $sort->getSortArrayMethod();
$c();

The script generates an error message:

Fatal error: Uncaught Error: Call to private method Sort::sortArray()
 from global scope

Using Closure::fromCallable([$this, 'sortArray']) instead of  [$this, 'sortArray'] would fix the scope issue, but using the Closure::fromCallable method makes the call verbose. The new callable syntax fixes both the scope and syntax verbosity issues. With the new callable syntax, the function becomes:

public function getSortArrayMethod() {

       return $this->sortArray(...);         
}

The array gets sorted with output:

0 = A 1 = B 2 = C 3 = f

The new syntax can be combined with the traditional syntax involving strings and arrays to fix the scope issue. The scope at which the callable is created is kept unchanged.

public  function getSortArrayMethod() {
        return [$this, 'sortArray'](...);       
            
}

The new callable syntax may be used with static methods as well, as demonstrated by the following script that includes a static function. 

arrayToSort = $arrayToSort;
        $this->sortType = $sortType;
    }

    public  function getStaticMethod() {
       
       return Sort::aStaticFunction(...); 
        
    }
    
    private  static  function aStaticFunction() {

    }
}

$sortType="Asc";
$arrayToSort=array("B", "A", "f", "C");
$sort = new Sort($arrayToSort,$sortType);
$cStatic=$sort->getStaticMethod();
$cStatic();

The output is the same as before:

0 = A 1 = B 2 = C 3 = f

The following are equivalent ways for calling a method:

return $this->sortArray(...); 
return Closure::fromCallable([$this, 'sortArray']);
return [$this, 'sortArray'](...); 

The following are equivalent ways for calling a static method:

return Sort::aStaticFunction(...); 
return [Sort::class, 'aStaticFunction'](...); 
return Closure::fromCallable([Sort::class, 'aStaticFunction']);

 The new callable syntax may be used even if a function declares parameters.

arrayToSort = $arrayToSort;
        $this->sortType = $sortType;
    }

    public  function getSortArrayMethod() {

       return $this->sortArray(...); 
    }

    private function sortArray(int $a,string $b) {
        if ($this->sortType == "Asc") {
             sort($this->arrayToSort);
             foreach ($this->arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        } elseif ($this->sortType == "Desc") {
             rsort($this->arrayToSort);
             foreach ($this->arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        } else {
              
             shuffle($this->arrayToSort);
             foreach ($this->arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        }
    }
}

A callable must be called with its arguments if the method declares any.

$sortType="Asc";
$arrayToSort=array("B", "A", "f", "C");
$sort = new Sort($arrayToSort,$sortType);
$c = $sort->getSortArrayMethod();
$c(1,"A");

Simplified Syntax can be used with  any PHP Callable expression

The simplified callable syntax may be used with any PHP callable expression. The callable syntax is not supported with the new operator for object creation because the callable syntax AVariableCallableExpression(…) does not have provision to specify constructor args, which could be required. The following is an example that is not supported:

$sort = new Sort(...);

An error message is generated:

Fatal error: Cannot create Closure for new expression

The following script demonstrates the full range of callable expressions that are supported.

arrayToSort = $arrayToSort;
        $this->sortType = $sortType;
    }

    public  function getSortArrayMethod() {

       return $this->sortArray(...); 
    }

    public  function getStaticMethod() {
       
      return Sort::aStaticFunction(...);   
         
    }
    
    public  static  function aStaticFunction() {

    }
     
    public    function sortArray(int $a,string $b) {
        if ($this->sortType == "Asc") {
             sort($this->arrayToSort);
             foreach ($this->arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        } elseif ($this->sortType == "Desc") {
             rsort($this->arrayToSort);
             foreach ($this->arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        } else {
              
             shuffle($this->arrayToSort);
             foreach ($this->arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        }
    }
    public function __invoke() {}
}

$sortType="Asc";
$arrayToSort=array("B", "A", "f", "C");

 
$classStr = 'Sort';
$staticmethodStr = 'aStaticFunction';
$c1 = $classStr::$staticmethodStr(...);
$methodStr = 'sortArray';

$sort = new Sort($arrayToSort,$sortType);
$c2 = strlen(...);
$c3 = $sort(...);  // invokable object
$c4 = $sort->sortArray(...);
$c5 = $sort->$methodStr(...);
$c6 = Sort::aStaticFunction(...);
$c7 = $classStr::$staticmethodStr(...);

// traditional callable using string, array
$c8 = 'strlen'(...);
$c9 = [$sort, 'sortArray'](...);
$c10 = [Sort::class, 'aStaticFunction'](...); 
$c11 = $sort->getSortArrayMethod();
$c11(1,"A");
$cStatic=$sort->getStaticMethod();
$cStatic();
 

Trailing comma and optional/required arguments order 

Another new feature in PHP 8.0 is support for adding a trailing comma  at the end of the list of parameters  to a function to improve readability. Any trailing comma is ignored. A trailing comma may not always be useful, but could be useful if the  parameters  list is long or if parameter names are long, making it suitable to list them vertically. A trailing comma is also supported in closure use lists. 

PHP 8.0 deprecates declaring optional arguments before required arguments. Optional arguments declared before required arguments are implicitly required. 

The following script demonstrates the required parameters implicit order in addition to the use of trailing comma. 


The output is as follows:

Deprecated: Optional parameter $the_third_arg_of_this_function declared before required parameter $the_last_arg_of_this_function is implicitly treated as a required parameter

Nullable parameters are not considered optional parameters and may be declared before required parameters using  the $param = null form or the explicit nullable type, as in the following script:

 

Named Function Parameters and Arguments

PHP 8.0 adds support for named function parameters and arguments besides already supported positional parameters and arguments. Named arguments are passed in a function call with the following syntax:

Argument_name:Argument_value

Some of the benefits of named arguments are the following:

  • Function parameters may be given a meaningful name to make them self-documenting
  • Arguments are order-independent when passed by name
  • Default values may be skipped arbitrarily.

In the following script, the array_hashtable function declares named parameters. The function may be passed argument values with or without argument names. When positional arguments are passed, the function parameters declaration order is used. When named arguments are passed, any arbitrary order may be used.

<?php

function array_hashtable($key1,$key2,$key3,$key4,$key5){ 
  echo $key1.' '.$key2.' '.$key3.' '.$key4.' '.$key5;
  echo "
"; } // Using positional arguments: array_hashtable(0, 10, 50, 20, 25); // Using named arguments: array_hashtable(key2: 0, key5: 25, key1: 10, key4: 50, key3: 20); ?>

The output is:

0 10 50 20 25
10 0 20 50 25

Named arguments and positional arguments may be used in the same function call.  The mixed arguments call is used with the same example function array_hashtable.

<?php

function array_hashtable($key1,$key2,$key3,$key4,$key5){ 
  echo $key1.' '.$key2.' '.$key3.' '.$key4.' '.$key5;
  echo "
"; } // Using mixed arguments: array_hashtable(0, 10, 50, key5: 25, key4: 20); ?>

Output is :

0 10 50 20 25

Notice that named arguments are used only after positional arguments. The following script reverses the order and uses positional  arguments after named arguments:

<?php

function array_hashtable($key1,$key2,$key3,$key4,$key5){ 
  echo $key1.' '.$key2.' '.$key3.' '.$key4.' '.$key5;
  echo "
"; } // Using mixed arguments: array_hashtable(0, 10, key3: 25, 50, key5: 20); ?>

The script generates an error message:

Fatal error: Cannot use positional argument after named argument 

Declaring optional arguments before required arguments is deprecated even with named arguments, as demonstrated by the following script:

<?php

function array_hashtable($key1=0,$key2=10,$key3=20,$key4,$key5){ 
  echo $key1.' '.$key2.' '.$key3.' '.$key4.' '.$key5;
  echo "
"; } // Using mixed arguments: array_hashtable(1,2,key3: 25, key4: 1,key5: 20); ?>

The output includes the deprecation messages:

Deprecated: Optional parameter $key1 declared before required parameter $key5 is implicitly treated as a required parameter 
Deprecated: Optional parameter $key2 declared before required parameter $key5 is implicitly treated as a required parameter 
Deprecated: Optional parameter $key3 declared before required parameter $key5 is implicitly treated as a required parameter 

When using optional named parameters after required named parameters, named arguments may be used to skip over one or more  optional parameters in a function call, as in the script:

<?php

function array_hashtable($key1,$key2,$key3=20,$key4=50,$key5=10){ 
echo $key1.' '.$key2.' '.$key3.' '.$key4.' '.$key5;
echo "
"; } // Using mixed arguments: array_hashtable(key1:1, key2:2,key4: 25); ?>

The output is:

1 2 20 25 10

You may call a function with only a subset of its optional arguments, regardless of their order.

<?php

function array_hashtable($key1=0,$key2=10,$key3=20,$key4=50,$key5=10){ 
  echo $key1.' '.$key2.' '.$key3.' '.$key4.' '.$key5;
  echo "
"; } // Using mixed arguments: array_hashtable(1,2,key4: 25); ?>

Output is as follows:

1 2 20 25 10

Even when calling a function with a subset of its optional arguments, positional arguments cannot be used after named arguments, as demonstrated by script:

<?php

function array_hashtable($key1=0,$key2=10,$key3=20,$key4=50,$key5=10){ 
echo $key1.' '.$key2.' '.$key3.' '.$key4.' '.$key5;
echo "
"; } // Using mixed arguments: array_hashtable(1,2,key4: 25,5); ?>

The following error message is produced:

Fatal error: Cannot use positional argument after named argument

PHP 8.1 improves on the named arguments feature by supporting   named arguments after unpacking the arguments, as in the script:

<?php

function array_hashtable($key1,$key2,$key3=30,$key4=40,$key5=50){ 
  echo $key1.' '.$key2.' '.$key3.' '.$key4.' '.$key5;
  echo "
"; } echo array_hashtable(...[10, 20], key5: 40); echo array_hashtable(...['key2' => 2, 'key1' => 2], key4: 50); ?>

The output is as follows:

10 20 30 40 40
2 2 30 50 50

However, a named argument must not overwrite an earlier argument as demonstrated by script:

<?php

function array_hashtable($key1,$key2,$key3=30,$key4=40,$key5=50){ 
  echo $key1.' '.$key2.' '.$key3.' '.$key4.' '.$key5;
  echo "
"; } echo array_hashtable(...[10, 20], key2: 40); ?>

Output is as follows:

Fatal error: Uncaught Error: Named parameter $key2 overwrites previous argument.

Non-static method cannot be called statically

Prior to PHP 8.0, if you called a non-static method in a static context, or statically, you only got a deprecation message. With 8.0 you now get an error message. Also, $this is undefined in a static context.  To demonstrate this, consider the following script in which a non-static method aNonStaticMethod() is called with static syntax A::aNonStaticMethod()

<?php
class A
{
    function aNonStaticMethod()
    {
    }
}
 
A::aNonStaticMethod();

If you run the script, you would get an error message:

Uncaught Error: Non-static method A::aNonStaticMethod() cannot be called statically

Fibers

PHP 8.1 adds support for multi-tasking with Fibers. A Fiber is an interruptible function with a stack of its own. A Fiber may be suspended from anywhere in the call stack, and resumed later. The new Fiber class is a final class that supports the following public methods:

Method

Description

__construct(callable $callback)

Constructor to create a new Fiber instance. The parameter is the callable to invoke when starting the fiber. Arguments given to Fiber::start() will be provided as arguments to the given callable. The callable need not call Fiber::suspend() at all, or if called need not call directly. The call to Fiber::suspend() may be deeply nested down the call stack.

start(mixed ...$args): mixed

Starts the fiber. The method returns when the fiber suspends or terminates. A variadic list of arguments is provided to the callable used when constructing the fiber. A mixed value is returned from the first suspension point or NULL if the fiber returns. A FiberError is thrown if the fiber has already been started when this method is called.

resume(mixed $value = null): mixed

Resumes the fiber, returning the given mixed value from Fiber::suspend(). Returns when the fiber suspends or terminates. The returned mixed value is actually returned from the next suspension point or NULL if the fiber returns. Throws a FiberError if the fiber has not started, is running, or has terminated.

throw(Throwable $exception): mixed

Throws the given exception into the fiber from Fiber::suspend(). Returns when the fiber suspends or terminates. The param is the Throwable $exception. The returned  mixed Value is actually returned from the next suspension point or NULL if the fiber returns. Throws a FiberError if the fiber has not started, is running, or has terminated.

getReturn(): mixed

Gets the mixed return value of the fiber callback. NULL is returned if the fiber does not have a return statement. Throws a FiberError if the fiber has not terminated or the fiber threw an exception.

isStarted(): bool

Returns bool True if the fiber has been started.

isSuspended(): bool

Returns bool True if the fiber has been suspended.

isRunning(): bool

Returns bool True if the fiber is currently running.

isTerminated(): bool

Returns bool True if the fiber has been terminated.

static suspend(mixed $value = null): mixed

Suspends  the fiber. Returns execution to the call to Fiber->start(), Fiber->resume(), or Fiber->throw(). The fiber may be resumed with Fiber::resume() or Fiber::throw(). Cannot be called from the main thread outside the fiber. The param is a mixed $value to return from Fiber::resume() or Fiber::throw(). The return mixed value is provided to Fiber::resume().

Throws FiberError if not within a fiber (i.e., if called from the main thread).

static getCurrent(): ?Fiber

Returns the currently executing fiber instance or NULL if in main.

A Fiber may be started only once, but may be suspended and resumed multiple times.  The following script demonstrates multitasking by using a Fiber to perform different types of sorts on an array. The Fiber is suspended after each sort, and resumed later to perform a different type of sort. 

 $val) {
                echo "$key = $val ";
             }  
  echo "
"; Fiber::suspend(); rsort($arr); foreach ($arr as $key => $val) { echo "$key = $val "; } echo "
"; Fiber::suspend(); shuffle($arr); foreach ($arr as $key => $val) { echo "$key = $val "; } }); $arrayToSort=array("B", "A", "f", "C"); $value = $fiber->start($arrayToSort); $fiber->resume(); $fiber->resume(); ?>

The output is as follows:

0 = A 1 = B 2 = C 3 = f
0 = f 1 = C 2 = B 3 = A
0 = C 1 = f 2 = A 3 = B

If the Fiber is not resumed after first suspension, only one type of sort is made, which could be implemented by commenting out the two calls to resume().

//$fiber->resume();

//$fiber->resume(); 

Output is the result from the first sort:

0 = A 1 = B 2 = C 3 = f

Stringable interface and __toString()

PHP 8.0 introduces a new interface called Stringable that provides only one method  __toString(). The  __toString() method if provided in a class would implicitly implement the Stringable interface.  Consider the class A that provides a __toString() method.

<?php

class A    {

     public function __toString(): string {
       return " ";
    }
}

echo (new A() instanceof Stringable);

The script returns 1 from the type check for Stringable.

The reverse is however not true. If a class implements the Stringable interface, it must explicitly provide the __toString() method as the method is not added automatically, as in:

<?php

class A implements Stringable  {
public function __toString(): string { }
   
}

New standard library functions

PHP 8 introduces a number of  new functions belonging to its standard library. 

The str_contains function returns a bool to indicate if the string given as the first argument contains the string given as the second argument. The following script returns false:

<?php

if (str_contains('haystack', 'needle')) {
    echo true;
} else {
    echo false;
}

And the following script returns 1, or true:

<?php
if (str_contains('haystack', 'hay')) {
    echo true;
}else {
    echo "false";
}

The str_starts_with function returns a bool to indicate if the string given as the first argument starts with the string given as the second argument. The following script returns false.

<?php

if (str_contains('haystack', 'hay')) {
    echo true;
}else {
    echo "false";
}

And the following script returns 1, or true.

<?php

if (str_starts_with('haystack', 'needle')) {
    echo true;
} else {
    echo false;
}

The str_ends_with function returns a bool to indicate if the string given as the first argument ends with the string given as the second argument. The following script returns false.

<?php


if (str_starts_with('haystack', 'needle')) {
    echo true;
} else {
    echo false;
}

And the following script returns 1, or true.

<?php

if (str_starts_with('haystack', 'hay')) {
    echo true;
} else {
    echo false;
}

The fdiv function divides two numbers and returns a float value, as demonstrated by the script:

<?php

var_dump(fdiv(1.5, 1.3));   
var_dump(fdiv(10, 2));  
var_dump(fdiv(5.0, 0.0));    
var_dump(fdiv(-2.0, 0.0));   
var_dump(fdiv(0.0, 0.0));  
 
var_dump(fdiv(5.0, 1.0));   
var_dump(fdiv(10.0, 2));  

The output is:

float(1.1538461538461537)
float(5) 
float(INF) 
float(-INF)
float(NAN)
float(5)
float(5)

The fdatasync  function, aliased to fsync on Windows, synchronizes data to a stream on a file. To demonstrate its use, create an empty file test.txt in the scripts directory that contains PHP scripts to run.  Run the script:

<?php

$file = 'test.txt';

$stream = fopen($file, 'w');
fwrite($stream, 'first line of data');
fwrite($stream, "rn");
fwrite($stream, 'second line of  data');
fwrite($stream, 'third line of  data');

fdatasync($stream);
fclose($stream);

Subsequently, open the test.txt file to find the text:

first line of data
second line of data
third line of data

The array_is_list function returns a bool to indicate whether a given array is a list. The array must start at 0, the keys must be all consecutive integer keys and in the correct order.  The following script  demonstrates the array_is_list function:

 'a', 'b']);  

echo array_is_list([1 => 'a', 'b']); // false
echo array_is_list([1 => 'a', 0 => 'b']); // false
echo array_is_list([0 => 'a', 'b' => 'b']); // false
echo array_is_list([0 => 'a', 2 => 'b']); // false

The  output is:

1
1
1

Magic methods must have correct signatures  

Magic methods are special methods in PHP to override default actions They include the following methods, of which the constructor method __construct() may be the most familiar:

__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __serialize(), __unserialize(), __toString(), __invoke(), __set_state(), __clone(), and __debugInfo(). 

As of PHP 8.0 the signature of the magic method definitions must be correct, which implies that if type declarations are used in method parameters or  return type, they must be identical to that in the documentation. The new __toString() method must declare string as return type. To demonstrate declare return type as int:

<?php

class A   {

    public function __toString(): int { 
    }
}

An error message is generated:

Fatal error: A::__toString(): Return type must be string when declared

However, functions that don’t declare a return type by definition such as the constructor function must not declare a return type, not even a void return type. The following script is an example:

<?php

class A   {
    public function __construct():void
    {
         
    }
}

The script returns an error message:

Fatal error: Method A::__construct() cannot declare a return type

All magic methods with a few exceptions, e.g. __construct(), must be declared with public visibility. To demonstrate this, declare __callStatic with private visibility.

 <?php

class A   {

    private static function __callStatic(string $name, array $arguments) {}

}

A warning message is output:

Warning: The magic method A::__callStatic() must have public visibility

Even though it is ok to omit mixed return types,  the method signature must be the same. For example, in the following script, class A declares __callStatic without specifying its return type, while the class B defines its first parameter as an int:

<?php

class A   {

    public static function __callStatic(string $name, array $arguments) {}


class B   {

    public static function __callStatic(int $name, array $arguments) {}

}

An error message is output:

Fatal error: B::__callStatic(): Parameter #1 ($name) must be of type string when declared

Return Type Compatibility with Internal Classes 

With PHP 8.1 most internal methods, which are the methods in internal classes, have “tentatively” started to declare a return type. Tentatively implies that while in 8.1 only a Deprecation notice is raised, in version 9.0 an error condition message shall be output. Thus, any extending class must declare a return type that is compatible with the internal class, or a deprecation notice is issued. To demonstrate this, extend the internal class Directory  and redeclare the function read() without a return type:

<?php

class A extends Directory {
    public function read() { }
}

The script generates a deprecation notice:

Deprecated: Return type of A::read() should either be compatible with Directory::read(): string|false, or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice

The following script, however is OK:

<?php

class A extends Directory {
    public function read():string  { return ""; }
}

 Adding the #[ReturnTypeWillChange] attribute attribute suppresses the deprecation notice:

<?php

class A extends Directory {

    #[ReturnTypeWillChange]
    public function read()    {   }
}

The SensitiveParameter attribute

While stack traces for exceptions that include detailed information about method parameters are quite useful for debugging, you may not want to output parameter values for some sensitive parameters such as those associated with passwords and credentials. PHP 8.2 introduces a new attribute called SensitiveParameter so that, if a method parameter is annotated with the SensitiveParameter attribute, the parameter’s value is not output in an exception stack trace.

To demonstrate this, consider the following script in which the function f1 has the $password parameter associated with the SensitiveParameter attribute. 

<?php
 
function f1(
    $param1 = 1,
    #[SensitiveParameter] $password = “s@5f_u7”,
    $param3 = null
) {
    throw new Exception('Error');
}

The function throws an Exception just to demonstrate the SensitiveParameter feature. Call the function:

f1(param3: 'a');

Notice that the exception stack trace does not include the value for the $password parameter, and instead has Object(SensitiveParameterValue) listed.

Stack trace: #0 : f1(1, Object(SensitiveParameterValue), 'a') #1 {main}

Built-in functions deprecation/enhancement

The built-in functions  utf8_encode() and  utf8_decode() have often been misunderstood because their names imply encoding/decoding just about any string. Actually the functions are for encoding/decoding only ISO8859-1, aka “Latin-1”, strings. Additionally, the error messages they generate are not descriptive enough for debugging. PHP 8.2 has deprecated these functions. The following script makes use of them:

<?php
 
$string_to_encode = "x7Ax6AxdB";
$utf8_string = utf8_encode($string_to_encode);
echo bin2hex($utf8_string), "n";
$utf8_string = "x6Ax6BxD3xCB";
$decoded_string = utf8_decode($utf8_string);
echo bin2hex($decoded_string), "n";

With PHP 8.2, a deprecation notice is output:

Deprecated: Function utf8_encode() is deprecated  
Deprecated: Function utf8_decode() is deprecated

In PHP 8.2, the functions iterator_count  and iterator_to_array accept all iterables. The iterator_to_array() function copies the elements of an iterator into an array. The iterator_count() function counts the elements of an array. These functions accept an $iterator as the first argument. In PHP 8.2, the type of the $iterator parameter has been widened from Traversable to Traversable|array so that any arbitrary iterable value is accepted.  

The following script demonstrates their use with both arrays and Traversables.

'one', 'two', 'three', 'four');
$iterator = new ArrayIterator($a);
var_dump(iterator_to_array($iterator, true));
var_dump(iterator_to_array($a, true));

var_dump(iterator_count($iterator));
var_dump(iterator_count($a));

The output is as follows:

array(4) { [1]=> string(3) "one" [2]=> string(3) "two" [3]=> string(5) "three" [4]=> string(4) "four" } 
array(4) { [1]=> string(3) "one" [2]=> string(3) "two" [3]=> string(5) "three" [4]=> string(4) "four" } 
int(4) 
int(4)

Summary

In this article in the PHP 8 series, we discussed the new features related to functions and methods, the most salient being named function parameters/arguments, a simplified callable syntax, and interruptible functions called Fibers. 

In the next article in the series, we will cover new features for PHP’s  type system.

This article is part of the article series “PHP 8.x”. You can subscribe to receive notifications about new articles in this series via RSS.

PHP continues to be one of the most widely used scripting languages on  the web with 77.3% of all the websites whose server-side programming language is known using it according to w3tech. PHP 8 brings many new features and other improvements, which we shall explore in this article series.

About the Author

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.