BT

InfoQ Homepage Articles PHP 7 – Standard Library Improvements

PHP 7 – Standard Library Improvements

Leia em Português

Bookmarks

Key Takeaways

  • To create named array constants  at runtime, use the new define() function
  • To bind an object scope to a variable and invoke it, use the new Closure::call()  function
  • To use an expression and/or a custom AssertionError with the traditional assert(), use expectations
  • PHP 7 supports  returning a value from generator functions
  • PHP 7 supports delegating from one generator function to another generator function
  • For integer division, use the new function called intdiv()
  • To override the session configuration settings in php.ini, use the new session_start function
  • To perform a regular expression search and replace using callbacks, use the new function preg_replace_callback_array()
  • PHP 7 is able to generate cryptographically secure integers and bytes
  • PHP 7.1 supports converting Callables to Closures
  • Arrow (=>) functions provide a concise syntax for anonymous functions
     

PHP 7.x brings several improvements and new features that touch all aspects of the language, including better support for object oriented programming, extensions to classes and interfaces, improvements to the type system, error handling, and more. In this series of articles, we discuss new features across the various PHP 7.x versions.

 

In the preceding article in this series on PHP 7, we discussed new features in the PHP type system. In this article we explore improvements to functions in PHP 7.

PHP supports several types of functions including user-defined functions, internal functions, variable functions, and anonymous functions.  

New Function to define Array Constants

A new function called define() has been added to PHP 7.0 to define named array constants at runtime. The define() function has the syntax:

bool define ( string $name , mixed $value [, bool $case_insensitive = FALSE ] )

The function parameters are discussed in Table 1.

Table 1. Function define() Parameters

Parameter

Description

name

The name of the constant. The name could be a reserved name but it is not recommended.

value

The value of the constant.  The value must be a scalar value (integer, float, string, Boolean, or NULL) or an array.

case_insensitive

Whether the constant is case insensitive, the default being case sensitive. Case insensitive constants are deprecated in PHP 7.3.0.

 

Create a PHP script constant.php and define a constant called CONSTANT as follows.

define("CONSTANT", "Hello PHP");

You could also use the const keyword to define a constant:

const CONSTANT_2 = 'Hello php';
const Constant = 'HELLO PHP';

The define() function can be used to define an array constant:

define("Catalog", ['Oracle Magazine','Java Magazine']);

Array constant values may be output using array element access.

echo Catalog[0]
echo Catalog[1]

The constant.php file is listed:

<?php
define("CONSTANT", "Hello PHP");
echo CONSTANT."<br>";  
const CONSTANT_2 = 'Hello php';
echo CONSTANT_2."<br>";
const Constant = 'HELLO PHP';
echo Constant."<br>";
define("Catalog", ['Oracle Magazine','Java Magazine']);
echo Catalog[0]."<br>";
echo Catalog[1]
?>

Run the script to output the constant values defined in it

Hello PHP
Hello php
HELLO PHP
Oracle Magazine
Java Magazine

A globally-defined constant such as TRUE or FALSE cannot be redefined. To demonstrate this, create a script const.php that defines a constant TRUE setting its value to 20:

<?php
define('TRUE', 20);
echo TRUE;
?>

If you run the script, it will output the value 1, which is the globally-defined value for TRUE.

New Function to Bind Object Scope to Closure

A Closure is a class used to represent an anonymous function. PHP 7.0 has introduced a new function Closure::call() as a short-form of temporarily binding an object scope to a closure and invoking it. To demonstrate this, create a script closure.php and copy the following listing:

<?php
class Hello {
private function getMsg() {
                  echo "Hello";
          	}
}
 $getMsg = function() {return $this->getMsg();};
echo $getMsg->call(new Hello);
?>

In the above script, Hello is a class with function getMsg(). The Closure::call()  function is used to create a Hello instance and bind its scope to a closure that invokes the getMsg method. Run the script and the Hello message is output.

Expectations  

Before discussing expectations, let’s review traditional assertions. The traditional assert() method is defined as follows. It will check if  a code expectation (what is called an assertion) is FALSE, and if FALSE  it prints the description message, and aborts the program by default. If an assertionis not FALSEassert() has no effect.

bool assert ( mixed $assertion [, string $description ] )

Assertions are designed to be used for debugging during development and testing and not for runtime operations. The assert() behavior is configured with assert_options() or the .ini file settings.

Traditionally, the first parameter to assert() shall be a string to be evaluated or a boolean condition to be tested as an assertion. If a string is provided, it is evaluated as PHP code.  If a boolean condition is provided, it is converted to a string before being passed to the assertion callback function, if any, defined in assert_options().  

Assertions have been completely overhauled in PHP 7 and are now called expectations and assert() is a language construct in PHP 7. Expectations have been added as an enhancement of assert(), with the following syntax:

bool assert ( mixed $assertion [, Throwable $exception ] )

With expectations, the first assert() parameter may be an expression that returns a value, instead of a string of PHP code to be evaluated or a boolean to be tested. The expression is evaluated and the result used to ascertain if the assertion succeeded.  The use of a string as a first argument is deprecated as of PHP 7. assert_options() are still supported with expectations but are not recommended. Instead, two new configuration php.ini directives should be used, as detailed in table 2.

Table 2. Configuration Directives for Expectations

Configuration Directive

Type

Description

Supported Values

Default Value

zend.assertions

integer

Configures whether assertion code is to be generated and run.

1: Generate and run assertion code (development mode)

0: Generate assertion code but not run it at runtime

-1: Do not generate assertion code (production mode). Use this setting to use expectations. 

1

assert.exception

 

Throws an AssertionError  or Custom exception for failed assertion

1: throw when the assertion fails, either by throwing the object provided as the exception or by throwing a new AssertionError object if no exception was provided. Use this setting to use expectations.

0: use or generate a Throwable as described above, but only generate a warning rather than throwing an exception or AssertionError

0

 

With expectations, the second argument may be a Throwable object instead of a string description. For the Throwable object or exception to be thrown when an assertion fails, the assert.exception directive must be set to 1. 

Expectations have the following advantages over traditional assertions, which are still supported for backward compatibility:

  • An expression may be used for evaluating an assertion, instead of a string or a boolean.
  • Using php.ini settings, the assertion code may be skipped at runtime, even though generated. Or the assertion code may not even be generated, which is recommended for production use.  
  • Custom exceptions may be thrown. A new class AssertionError is added in PHP 7. 

As an example of how to use expectations, create a script expectation.php and copy the following listing to the script. The script sets both configuration directives to 1. The assert language construct has the first argument set to true and the second argument set to a custom AssertionError.

<?php
 
ini_set('assert.exception', 1);
ini_set('zend.assertions', 1);
assert(true, new AssertionError('Assertion failed.'));
?>

If you run the script, no AssertionError is thrown.

Next, set the first arg to false.

assert(false, new AssertionError('Assertion failed.'));

If you run the script again, the expectation fails and throws an AssertonError.

Uncaught AssertionError: Assertion failed

As an example of using expectations with an expression and custom AssertionError in assert(), start with a traditional use of assert() that tests an assertion that a variable has a numeric value. Strings are used both to test the variable and output a message if the assertion fails.

$numValue = '123string';
assert('is_numeric_Value($numValue)' , "Assertion that $numValue is a number failed." );

Next, use an expression as the first argument to assert(). And use an AssertionError object to throw a custom error message. 

$num = 10;
assert($num > 50 , new AssertionError("Assertion that $num is greater than 50 failed.") );

Return Expressions for Generators

Generator functions are functions that return an iterable object, such as foreach.  Generator functions  are simple iterators in that they don’t require the class to implement the Iterator interface. The syntax for generator functions is the same as for a normal function except that they include one or more yield statements with each yield yielding a value when the iterator  object returned by the generator function is iterated over.  A generator function must be invoked as a normal function, providing  any required arguments. 

PHP 7.0  has added a new feature to the generator function, enabling to specify a return statement after all the yield statements. The value returned by a generator function may be accessed with the getReturn() function of the object returned by the generator function. The iterator object returned by a generator function should not be confused with the value returned by a generator function.  The iterator object returned does not include the return value, it only includes the yielded values. Being able to return a value is useful if a generator function needs to perform some computation and return a final value. Generators may only declare a return type of Generator, Iterator, Traversable, or iterable.  

To demonstrate how to use this new feature, create a script script gen_return.php defining a generator function with multiple yield statements  and invoke the generator function passing 1 as  an argument. Use foreach to iterate over its return value and output the yielded values . Finally, output the return value using getReturn(). The gen_return.php script is listed.

<?php
$gen_return = (function($var) {
	$x=$var+2;
	yield $var;
	yield $x;
	$y=$x+2;
	return $y;
})(1);
foreach ($gen_return as $value) {
	echo $value, PHP_EOL;
}
echo $gen_return->getReturn(), PHP_EOL;
?>
If you run the script, you should get the following output:.
1 3 5

Generators Delegation

PHP 7.0 has added support for generator delegation, which implies that one generator may delegate to another generator, Traversable object or array using the yield from keyword. To demonstrate generator delegation, create a script gen_yield_from.php and  define two generator functions gen($var) and gen2($var), where gen($var) delegates to gen2($var) with the following statement:

     yield from gen2($var);

Subsequently iterate over the iterator object returned by gen($var) in a single foreach loop. Script gen_yield_from.php is listed:

<?php
 function gen($var)
{
	yield $var;
	$x=$var+2;
	yield $x;
	yield from gen2($var);
}
 function gen2($var)
{
	$y=$var+1;
	yield $var;
	yield $y;
}
foreach (gen(1) as $val)
{
	echo $val, PHP_EOL;
} 
?>

Run the script to output all the yielded values from both the generator functions:

1 3 1 2

New function  for  Integer Division

PHP 7.0 has added a new function intdiv() for integer division. The function returns the integer quotient of the division between two integers and has the following syntax. 

int intdiv ( int $dividend , int $divisor )

Create a script int_div.php  to use the intdiv() function. Add some examples of integer division. PHP constants such as PHP_INT_MAX  and PHP_INT_MIN may be used as args to the function.

<?php
var_dump(intdiv(4, 2));
var_dump(intdiv(5, 3));
var_dump(intdiv(-4, 2));
var_dump(intdiv(-7, 3));
var_dump(intdiv(4, -2));
var_dump(intdiv(5, -3));
var_dump(intdiv(-4, -2));
var_dump(intdiv(-5, -2));
var_dump(intdiv(PHP_INT_MAX, PHP_INT_MAX));
var_dump(intdiv(PHP_INT_MIN, PHP_INT_MIN));
?>

If you run the script, you will get  the following output:

nt(2) int(1) int(-2) int(-2) int(-2) int(-1) int(2) int(2) int(1) int(1)

ArithmeticErrors, if any, are output in the browser. To  demonstrate this, include the following function call and run the script again.

var_dump(intdiv(PHP_INT_MIN, -1));

In this case, an ArithmeticError is generated indicating that division of PHP_INT_MIN by -1 is not an integer:

Uncaught ArithmeticError: Division of PHP_INT_MIN by -1 is not an integer

Add the following function call to the int_div.php script and rerun the script.

var_dump(intdiv(1, 0));

This time, a DivisionByZeroError is thrown:

Uncaught DivisionByZeroError: Division by zero

New Session Options

The session_start function is used to start a new session or  resume an existing session. PHP 7.0 has added support for a new parameter called options, which is an associative array of options that override the session configuration directives settings in php.ini. These session configuration directives start with “session.” in php.ini but the “session.” prefix should be omitted in the session_start’s options parameter array supplied as a function argument. In addition to session configuration directives, a new option read_and_close has been added, which, if set to TRUE, closes the session after being read, since keeping the session open may be unnecessary. As an example, create a script session_start.php and copy the following listing. The associative array in the example script has a session_start(options) function call that sets some configuration directives:

<?php
session_start([
	'name' => 'PHPSESSID',
    'cache_limiter' => 'private',
	'use_cookies' => '0'
]);

When you run this script, the session configuration options specified in it will  override the session configuration directives defined in php.ini, if any. The script does not generate any output.

With PHP 7.1 session_start() returns FALSE and does not initialize $_SESSION when it fails to start the session.

New Function to perform Regular Expression Search and Replace Using Callbacks

A new function preg_replace_callback_array() has been added in PHP 7.0 to perform a regular expression search and replace using callbacks. The function is similar to the preg_replace_callback() function except that the callbacks are invoked on a per pattern basis. The function  has the following syntax:

mixed preg_replace_callback_array ( array $patterns_and_callbacks , mixed $subject [, int $limit = -1 [, int &$count ]] )

It returns an array of  strings if the $subject  parameter is an array and a single string if $subject  is a string.  The  array or string returned is the new  subject if any matches are found or the unchanged subject if no matches are found. The function parameters are discussed in Table 3.

Table 3. Function Parameters for preg_replace_callback_array

Parameter

Type

Description

$patterns_and_callbacks

array

Specifies an associative array mapping patterns (keys) to callbacks (values)

$subject

mixed

Specifies the string or string array to search and replace

$limit

int

Specifies a limit on maximum replacements for each pattern in each subject string. Defaults to -1 for no limit.

&$count

int

The number of replacements completed is stored in the $count variable.

 

To demonstrate this new functionality, create an example script prereg.php and copy the following listing to it. The subject or the example string to search is set to 'AAaaaaa Bbbbb'. The patterns_and_callbacks arg is set to find the number of matches for ‘A’ and ‘b’.

<?php
$subject = 'AAaaaaa Bbbbb';
preg_replace_callback_array(
	[
    	'~[A]+~i' => function ($match) {
        	echo strlen($match[0]), ' matches for "A" found', PHP_EOL;
    	},
    	'~[b]+~i' => function ($match) {
        	echo strlen($match[0]), ' matches for "b" found', PHP_EOL;
    	}
	],
	$subject
);
?>

If you run the script, the number of matches for ‘A’ and ‘b’ is printed out:

7 matches for "A" found 5 matches for "b" found

New Functions to generate Cryptographically Secure Integers and Bytes

Two new functions have been added to generate cryptographically secure integers and bytes. These functions are discussed in Table 4.

Table 4. New Cryptographic Functions 

Function

Syntax

Parameter/s

Return Value

Description

random_bytes()

string random_bytes ( int $length )

$length, of type int, is the length of the random string to be returned as bytes.

Returns a string containing the requested number of cryptographically secure random bytes.

Generates and returns an arbitrary string containing cryptographic random bytes.

random_int()

int random_int ( int $min , int $max )

The $min parameter specifies the lower limit for the value  to be returned, which must be PHP_INT_MIN or higher. The $max parameter specifies the upper limit for the value to be returned, which must be PHP_INT_MAX or lower.

A cryptographically secure integer in between $min and $max.

Generates and returns cryptographic random integers.

 

As an example create a script random_int.php to generate cryptographic random integers. First, generate an integer in the range of 1 and 99, then generate another integer in the range of  -100 and 0.

<?php
var_dump(random_int(1, 99));
var_dump(random_int(-100, 0));
?>

If you run the script, two integers will be  printed out.

int(98) int(-84)

The integers generated are random and if the same script is run again it will generate most likely two different integers.

Next, create an example script random_bytes.php to generate cryptographic random bytes with length of 10. Then, use the bin2hex function to convert random bytes to an ASCII string containing hexadecimal representation of the string of bytes returned.  Copy the following listing to the script:

<?php
$bytes = random_bytes(10);
var_dump(bin2hex($bytes));
?>

un the script to generate cryptographic random bytes:

string(20) "ab9ad4234e7c6ceeb70d"

The list() function modifications

The list() function is used to assign a list of variables as if they were an array. PHP 7.0 and 7.1 have brought about several changes to  the list() function. The list() function in PHP 7 cannot unpack strings as earlier versions could and returns NULL if a string is unpacked.  

In PHP 5.x, unpacking a string with list() assigns a variable in list() with a value from a string. We will first demonstrate unpacking a string with list() using PHP 5.x. Create a script list.php and copy the following listing to the script.

<?php
$str = "aString";
 list($elem) = $str;
  var_dump($elem);

The script unpacks a value from a $str to a list element. Run the script with PHP 5.x and the $elem value is output as ‘a’. If you run the same script with PHP 7.0,  you will get NULL. Another example of list()  not being able to unpack a string with PHP 7 is  the following:

<?php
list($catalog) = "Oracle Magazine";
var_dump($catalog);
?>

If you run the script, you will get an output value of NULL, as with the previous script. 

In fact, in PHP 7.x, if a list() is to be assigned values from a string, the str_split function must be used:

<?php
$str = "aString";
list($elem) = str_split($str);
var_dump($elem);

If you run the preceding example,the list element is assigned a value of ‘a’, as expected.   With PHP 7.0 a list() expression cannot be completely empty. As an example run the following listing list.php.

<?php
$info = array('Oracle Magazine', 'January-February 2018', 'Oracle Publishing');
list(, , ) = $info;
echo " \n";

This time, an error message is output indicating that “Cannot use empty list..”. A list could have some of its elements empty.  In the following listing one of the list() elements is empty:

<?php
$info = array('Oracle Magazine', 'January-February 2018', 'Oracle Publishing');
list($name, $edition,) = $info;
echo "$name  latest edition is $edition.\n";
?>

If you run the script, no error is generated and a list with 2 non-null elements and one empty element  is created.

Oracle Magazine latest edition is January-February 2018.

Another  modification  is list() now assigns values to variables in the order they are defined. Previously values were assigned to variables in the reverse order in which they were defined. To demonstrate the earlier behaviour, run the following listing as list.php with  PHP 5.x:

<?php
list($a[], $a[], $a[]) = ['A', 2, 3];
var_dump($a);
?>

As you can see inspecting the script output (shown in Figure 1), values are assigned to variables in the reverse order of the one in which they were defined. 

Figure 1. Values are assigned in the reverse order

Run the same script again with PHP 7.0 and you will see list() assigns values to variables in the same order they were  defined:

array(3) { [0]=> string(1) "A" [1]=> int(2) [2]=> int(3) }

PHP 7.1.0 enables the specification of keys in list to indicate the numerical order of assignment. As an example, create a script list.php with the following listing. Notice all the keys are numerical in this example:

<?php
list(0 => $journal, 1=> $publisher, 2 => $edition) = ['Oracle Magazine', 'Oracle Publishing', 'January February 2018'];
echo "$journal, $publisher, $edition. \n";
?>

If you run the script, you will get the following  list of values:

Oracle Magazine, Oracle Publishing, January February 2018.

The numerical order of assignment may be shuffled as shown in the following listing, where index 0 key is listed after index 1.

<?php
list(1 => $journal, 0=> $publisher, 2=>$edition) = ['Oracle Magazine', 'Oracle
Publishing', 'January February 2018'];
echo "$journal, $publisher,$edition \n";
?>

If you run  the script,  output indicates that variables are assigned a value based on the numerical key and not on the order in which the keys are listed.

Oracle Publishing, Oracle Magazine,January February 2018

The key index may be enclosed in single or double quotes as shown here:

<?php
list('1' => $journal, '0'=> $publisher, '2'=>$edition) = ['Oracle Magazine', 'Oracle
Publishing', 'January February 2018'];
echo "$journal, $publisher,$edition \n";
?>

The preceding script generates the same output as the previous. If one element in list() is keyed, then all of its elements must be keyed. For example, create a list assignment with some keyed elements and others not keyed.           

<?php
list(0 => $journal, 1=> $publisher, 2) = ['Oracle Magazine', 'Oracle Publishing', 'January February 2018'];
echo "$journal, $publisher. \n";
?>

If you run the script, you will get an error message indicating that keyed and unkeyed array entries in assignments cannot be mixed:

 Cannot mix keyed and unkeyed array entries in assignments 

Negative String Offsets

Starting from PHP 7.1 string manipulation functions such as strpos and substr support negative offsets, which are handled as offsets  from the end of the string. String indexing with [] and {} also supports negative offsets. For example, “ABC"[-2] would return the letter ‘B’.  Now, create a script str-negative-offset.php and copy the following listing to the script:

<?php
echo "ABCDEF"[-1];
echo "<br/>";
echo strpos("aabbcc", "a", -6);
echo "<br/>";
echo strpos("abcdef", "c", -1);
echo "<br/>";
echo strpos("abcdef", "c", -5);
echo "<br/>";
echo substr("ABCDEF", -1); 
echo "<br/>"; 
echo substr("ABCDEF", -2, 5); 	
echo "<br/>";
echo substr("ABCDEF", -7);
echo "<br/>";
echo substr("ABCDEF", -6);
echo "<br/>";
echo substr("ABCDEF", -5);
echo "<br/>";
echo substr("ABCDEF", 6);
echo "<br/>";
echo substr("abcdef", 1, -3); 
echo "<br/>";
echo substr("abcdef", 3, -2); 
echo "<br/>"; 
echo substr("abcdef", 4, -1);
echo "<br/>";  
echo substr("abcdef", -5, -2); 
?>

The script provides several examples of using negative offsets with different string manipulation functions and [].If you run the script, it will output:

F
0

2
F
EF
ABCDEF
ABCDEF
BCDEF

bc
d
e
bcd

New Function to convert Callables to Closures

Callables are useful to pass functions (user-defined and built-in functions except language constructs) and methods as string variables.  For example, the function hello() could be passed to another function as an argument or returned from a function using the function name as string, i.e., ‘hello’, if the parameter type/return type is callable. Create an example script callable.php and declare function hello() that outputs a ‘hello’ message. Declare another function with parameter type as callable.

function callFunc(callable $callback) {
	$callback();
}

The callFunc(callable) function may invoke hello() using its name as a string.=:

callFunc("hello");

Alternatively, the built-in mixed call_user_func ( callable $callback [, mixed $... ] ), which has a callable as its first parameter, could be used to invoke the hello() function by name:

call_user_func('hello');

The script callable.php is listed below:

<?php
function hello() {
	echo 'hello';
} 
call_user_func('hello');
function callFunc(callable $callback) {
	$callback();
}
echo '<br/>';
callFunc("hello");

If you run the script, = the hello() function is invoked by name supplied as a string.

hello

hello

A closure is an object representation of an anonymous function. Why convert a callable to a closure? For several reasons, one of which is performance. The callable type is relatively slow due to the cost of finding out if a function is a callable.

Another disadvantage of using a callable is that only public functions can be used as callables. Instead, converting to a closure within a class does not require the function to be public, e.g., the function may be declared private. As an example of this, create a script hello.php and declare a class Hello with  a method getCallback() that returns a callback function:

public function getCallback() {
    	return [$this, 'hello_callback_function'];
	}

The callback function is declared public.

public function hello_callback_function($name) { var_dump($name);  }

Create an instance of the class to get and invoke the callback function. The hello.php script is as follows:

<?php
class Hello {
	public function getCallback() {
    	return [$this, 'hello_callback_function'];
	}
	public function hello_callback_function($name) { var_dump($name);  }
}
$hello = new Hello();
$callback = $hello-> getCallback();
$callback('Deepak');

If you run the script, you will get the following output :

string(6) "Deepak"

Next,  use the Closure::fromCallable static method to convert the private callback function to a closure.

<?php
class Hello {
	public function getClosure() {
    	return Closure::fromCallable([$this, 'hello_callback_function']);
	}
	private function hello_callback_function($name) { var_dump($name);  }
}
$hello = new Hello();
$closure = $hello-> getClosure();
$closure('Deepak');

If you run the script, you will get the same output :

string(6) "Deepak"

Another reason for converting to a closure is error detection at an early phase rather than at runtime. Consider the same example as before but this time with the callback function name misspelled:

public function getCallback() {
    	return [$this, 'hello_callback_functio'];
	}

If you run the script,  an error Call to undefined method Hello::hello_callback_functio() is thrown when the callback is actually used on the following line:

$callback('Deepak');

 Instead, if we convert the callable to a closure, the error Failed to create closure from callable: class 'Hello' does not have a method 'hello_callback_function'  is detected on the following line:

return Closure::fromCallable([$this, 'hello_callback_functio']);

Flag JSON_THROW_ON_ERROR

Error handling for JSON functions json_encode() and json_decode() is rather minimal in PHP versions pre-7.3, with the following shortcomings:

  • Json_decode() returns null on error, but null could also be a valid result  value, for example if JSON “null” is decoded. The only method to find if an error occurred is from the global error state with json_last_error() or json_last_error_msg(). Json_encode() does have an  error return value.
  • The program running is not stopped on error and not even a warning is thrown.

PHP 7.3 has added support for the JSON_THROW_ON_ERROR flag in json_encode() and json_decode().  A new subclass of Exception called JsonException has also been added to describe an exception resulting for JSON encoding/decoding. If the JSON_THROW_ON_ERROR flag is supplied to json_encode() and json_decode()  and a JsonException is thrown, the global error state is not modified. To demonstrate the new JSON_THROW_ON_ERROR and the new JsonException create a script json.php, and attempt to use json_decode to decode an array containing an error when using JSON_THROW_ON_ERROR:.

<?php
try {
$json = '{"a":1,"b":2,"c":3,"d":4,"e":5}';
    json_decode("{",false,1,JSON_THROW_ON_ERROR);
}
catch (\JsonException $exception) {
	echo $exception->getMessage(); // echoes "Syntax error"
}
?>

 If you run the script, you will get  the following JsonException

Maximum stack depth exceeded

As an example of using JSON_THROW_ON_ERROR with json_encode(), encode an array with NAN as an element value as in the following script:

<?php
try {
$arr = array('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => NAN);
echo json_encode($arr,JSON_THROW_ON_ERROR);
}
catch (\JsonException $exception) {
	echo $exception->getMessage(); 
}
?>

When the script is run, the following message is output:

Inf and NaN cannot be JSON encoded

New Functions for getting First/Last Key Value from an Array

Getting the first/last key from an array is a common operation and PHP 7.3 has added two new specialised functions:

$key = array_key_first($array);
$key = array_key_last($array);

 An example of using these with an associative array and even an empty array is given in the following script:

<?php
// usage of an associative array
$array = ['a' => 'A', 2 => 'B', 'c' => 'C'];
$firstKey =array_key_first($array);
$lastKey = array_key_last($array);
echo assert($firstKey === 'a');
echo "<br/>";
 echo $firstKey;
echo "<br/>";
 echo $lastKey;
echo "<br/>";
// usage of an empty array
$array = [];
$firstKey = array_key_first($array);
$lastKey = array_key_last($array);
 echo "<br/>";
echo assert($firstKey === null);
echo "<br/>";
echo assert($lastKey === null);
?>

The output from the script is as follows:

1
a
c

1
1

Compact function reports undefined variables

The compact() function has a new feature in PHP 7.3 to report undefined variables. To demonstrate it, run the following script that includes some undefined variables:

<?php
$array1=['a','b','c',];
$var1="var 1";
var_dump(compact($array1,$array2,'var1','var2'));
?>

The following message is output from the script:

Notice: Undefined variable: array2  on line 9
Notice: compact(): Undefined variable: a  on line 9
Notice: compact(): Undefined variable: b  on line 9
Notice: compact(): Undefined variable: c   on line 9
Notice: compact(): Undefined variable: var2  on line 9

Trailing Commas in Function Calls

PHP 7.3 has added support for trailing commas in function calls. Trailing commas are useful in several contexts in which arguments are appended frequently such as in variadic functions (array_merge, compact, sprintf).  Language constructs unset() and isset() also allow trailing commas. An example of using a trailing comma in an unset function call is provided below.

<?php
function funcA($A,$B,$C)
{
    unset($A,$B,$C,);
	echo $A;
	echo $B;
   echo $C;
 	
}
$A = 'variable A';
$B = 'variable B';
$C = 'variable C';
funcA($A,$B,$C,);
?>

When the script is run the output is as follows:

Notice: Undefined variable: A 
Notice: Undefined variable: B 
Notice: Undefined variable: C 

The array_merge() function is another example in which a trailing comma could make it easier to append values. The following script makes use of a trailing comma in a function call to array_merge():

<?php
$array1=[1,2,3];
$array2=['A','B','C'];
$array = array_merge(
	$array1,
	$array2,
	['4', '5'],
);
?>

 Method calls and closures also allow trailing commas. A method is a function within a class. A closure is an object to represent an anonymous function. Trailing commas are only allowed in function calls and not in/as function declarations. Free standing commas, leading commas, and multiple trailing commas are also prohibited by the language.

Math Function bcscale.php

The bcscale function with syntax int bcscale ([ int $scale ]) is used to set the default scale factor for all subsequent  bc math function calls.  BC math functions such as bcadd(), bcdiv(), and bcsqrt() are used with arbitrary precision numbers.  PHP 7.3 has added support to get the current scale factor with bcscale.  Setter bcscale returns the old scale factor.  Previously scale was mandatory and bcscale() always returned TRUE. As an example of this, the following script sets the default scale factor to 3 and subsequently outputs the current scale factor:

<?php
bcscale(3);
echo bcscale();
?>

The output from the preceding script is 3.

New Function is_countable

PHP 7.3 has added support for a new function is_countable that returns true if the function argument is an array type or an instance of Countable.

bool is_countable(mixed $var)

For example example, is_countable() could be used to find if a given argument is an array.

echo is_countable(['A', 'B', 3]);

An instance of ArrayIterator turns out to be countable and is_countable outputs TRUE  since ArrayIterator implements the Countable interface.

echo is_countable(new ArrayIterator());

is_countable could be used with if() to ensure an argument is countable before running any subsequent code. In the following code snippet, an instance of a class A is tested for countability:

class A{}
if (!is_countable(new A())) {
	echo "Not countable";
}

The result from the preceding code snippet is FALSE since class A does not implement Countable. If the argument to is_countable is an array, then it would return TRUE as in the following code snippet shows:

$array=['A', 'B', 3];
if (is_countable($array)) {
    var_dump(count($array)); 
}

All the code snippets in this section are collected into script is_countable.php.

<?php
class A{}
echo is_countable(['A', 'B', 3]);
echo "<br/>";	
echo is_countable(new ArrayIterator());
echo "<br/>"; 
if (!is_countable(new A())) {
	echo "Not countable";
}
echo "<br/>";
$array=['A', 'B', 3];
if (is_countable($array)) {
	var_dump(count($array)); 
}
?>

Run the script and the output is as follows.

1
1
Not countable
int(3)

Arrow Functions

PHP 7.4 introduces arrow functions for a more concise syntax for anonymous functions. Arrow functions have the following form:

fn(parameter_list) => expr

Arrow functions have the lowest precedence, which implies that expression to the right of the arrow => is applied before the arrow function. For example, arrow function fn($x) => $x + $y is equivalent to fn($x) => ($x + $y) and not to (fn($x) => $x) + $y. A variable used in the expression declared in the enclosing scope is implicitly captured by-value. As an example,  consider the following script that declares an arrow function:

$fn1 = fn($msg) => $msg.' '.$name;

The variable $name is captured from the enclosing scope automatically and the arrow function is equivalent to:

$fn2 = function ($msg) use ($name) {
    return $msg.' '.$name;
};

By-value binding for $x is equivalent to performing use($x) for every $x occurrence within an arrow function. The arrow function also declares a  parameter $msg. In the next example,  var_export calls the arrow function with a supplied argument and outputs the value 'Hello John':

<?php
$name = "John";
$fn1 = fn($msg) => $msg.' '.$name;
var_export($fn1("Hello"));//'Hello John'
?>

Arrow functions may be nested, as demonstrated by the following script. The outer arrow function captures variable $name by-value from the enclosing scope and the inner arrow function captures $name from the outer function: 

<?php
$name = "John";
$fn = fn($msg1) => fn($msg2) => $msg1.' '.$msg2.' '.$name;
var_export($fn("Hello")("Hi")); 
?>

When the script is run, output shown in Figure 2 is displayed.

Figure 2. Arrow Functions may be nested

Because arrow functions use by-value variable binding, modifying the value of a variable in an arrow function has no effect on the variable declared in the enclosing scope. To demonstrate this, the arrow function in the following script decrements the value of the variable $x from the enclosing block, but it has no effect on the variable $x value which is still the same value 1

<?php
$x = 1
$fn = fn() => x--; // Has no effect
$fn();
var_export($x);   //Outputs 1
?>

The arrow function supports arbitrary   function signatures that may include parameter and return types, default values, variadics, and by-reference variable passing and returning. The following script demonstrates the use of different forms of arrow function signatures. The signature description and output from the script are shown with comments //.

<?php
$name = "John";
$x = 1; 
//Arrow function includes parameter type, return type and default value
$fn = fn(string $msg='Hi'): string => $msg.' '.$name;
//Arrow function includes a variadic. 
$fn2 = fn(...$x) => $x;
 //The arrow function includes by-reference variable passing
$fn3=fn(&$x) => $x++;
$fn3($x);
echo $x; // 2
var_export($fn("Hello"));//'Hello John'
var_export($fn());//'Hi John'
var_export($fn2(1,2,3)); //array ( 0 => 1, 1 => 2, 2 => 3, ) 
?>

$this may be used in an arrow function in object context. If used with an arrow function prefixed with static, $this cannot be used. To demonstrate this, consider the following script that uses $this in object context and class context. An error message is output when not used in object context. 

<?php
class A {
    public function fn1() {
        $fn = fn() => var_dump($this);
        $fn(); //  object(A)#1 (0) { }
        $fn = static fn() => var_dump($this);
        $fn(); //Uncaught Error: Using $this when not in object context 
   
    }
}
$a=new A();
$a->fn1();

Summary

In this fourth and penultimate article in the series on new features  in PHP 7, we introduced the new features relating to PHP functions. 

In the next and final article in the series we shall discuss some new features relating to arrays, operators, constants, and exception handling. 

About the Author

Deepak Vohra is a Sun Certified Java Programmer and Sun Certified Web Component Developer. Deepak has published Java and Java EE related technical articles in  WebLogic Developer's Journal, XML Journal, ONJava, java.net, IBM developerWorks, Java Developer’s Journal, Oracle Magazine, and devx. Deepak has published five books on Docker and is a Docker Mentor. Deepak has also published several articles on PHP and a book Ruby on Rails for PHP and Java Developers. 

 

PHP 7.x brings several improvements and new features that touch all aspects of the language, including better support for object oriented programming, extensions to classes and interfaces, improvements to the type system, error handling, and more. In this series of articles, we discuss new features across the various PHP 7.x versions.

 

Rate this Article

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT

Is your profile up-to-date? Please take a moment to review and update.

Note: If updating/changing your email, a validation request will be sent

Company name:
Company role:
Company size:
Country/Zone:
State/Province/Region:
You will be sent an email to validate the new email address. This pop-up will close itself in a few moments.