Last features in PHP 7.1

Last features in PHP 7.1

This is the third part of our series about last features in PHP 7.1. See ‘Upcoming features in PHP 7.1 (part a)‘ and ‘More upcoming features in PHP 7.1 (part b)‘.

PHP 7.1 is now RC1 since our last article, so better dive into the code fast. We are now presenting the last batch of new features and modernizations that PHP 7.1 simply piles on top of PHP 7.0.

Removed every $this usages

$this is a special variable, used inside classes to designate the current object. Outside this scope, it shouldn’t be used. PHP 7.0 started guarding against using $this outside classes, and 7.1 finished the job. $this can’t be used anymore as parameter in a function, as static or global variable, as catch variable or via references, variable variables nor extract() and co. It can’t be unset anymore, and it will also throw exceptions when not used in an object context.

<?php

function foo($this) { /**/ }
// Fatal error: Cannot use $this as parameter
?>

Delete character is not possible anymore in source code

From reading the docs, PHP source code has to be validated with this regex : /[_a-zA-Z\x7F-\xFF][_0-9a-zA-Z\x7F-\xFF]*/. Andrea Faulds (https://ajf.me/) noted recently that this regex accepts the delete char (0x7f), which creates an unreadable PHP code, as editors won’t understand it. So do I.

See the related bug report.

Some functions like func_get_args() can’t be called dynamically anymore

The following functions cannot be used anymore with dynamical call, such as call_user_func(), $function_name or as a callback in ad hoc function (array_map, etc..).

  • extract()
  • compact()
  • get_defined_vars()
  • func_get_args()
  • func_get_arg()
  • func_num_args()
  • parse_str() with one argument
  • mb_parse_str() with one argument
  • assert() with a string argument

In the related RFC, Nikita explains that using them as such leads to unclear behavior and optimisation problems that are better left aside. This means that those functions have to be hardcoded to be used, and can’t be set in a string.

Destructor() were called even for incompletely constructed objects

At instantiation time, a class constructor is called. If an exception is thrown during this call, the object is supposed to be incompletely set up, and the destructor shouldn’t be called. The destructor will only be called on a completely constructed object.

An old bug of PHP 5.0 was reopened concerning this situation. PHP used to intercept the Exception when directly thrown from the constructor, but wouldn’t catch it when it was thrown from another context, like a anciliary function.

This has an impact on frameworks or in-house libraries, that centralize error management in a common function. See bug https://bugs.php.net/bug.php?id=29368 for more details. (Note that bug number is 29368, while the current bug number is 73025 (as of Sep. 6, 2016).

<?php

function throwme($arg) {
   throw new Exception;
}

class foo {
  function __construct() {
    echo "Inside constructor\n";
    throwme($this);
  }

  function __destruct() {
    echo "Inside destructor\n";
  }
}

try {
  $bar = new foo;
} catch(Exception $exc) {
  echo "Caught exception!\n";
}
?>

call_user_func() always refuses references

call_user_func() and call_user_func_array() will now always throw errors when trying to call a method whose signature has references. Previously this sometimes worked if call_user_func() was used outside a namespace.

Rand() is now an alias of mt_rand()

rand() and srand() are now aliases of mt_rand() and mt_srand(). This means that the behavior of rand() changes. In particular, when srand()/rand() were used to create a reproductible yet random serie of numbers. See this code :

<?php
  srand(10);
  print rand()."\n";
  print rand()."\n";
  print rand()."\n";
?>

srand() is used to ‘seed’ rand(), which is another name for initialize. The random numbers are generated starting from a first number (here, 10), but the process that generate the random number is repeatable : calling the same script several times in a row produces always the same random numbers :

1215069295
1311962008
1086128678

When switching to PHP 7.1, rand() has the same behavior, but not the same values anymore :
1656398468
641584702
44564466

In fact, mt_rand() itself changed behavior, so preparing your code by switching now to mt_rand() leads to the same effect :

PHP 7.0 results when using mt_rand() instead of rand()
502355954
641584702
2112621188

It is not possible to prepare one’s code to the change, but PHP 7.1 code may behave like PHP 7.0 when passing the MT_RAND_PHP constant.

All environnement variables

It is now possible to omit getenv()’s argument, and get the whole list of environnement variables, instead of just one. This is now the equivalent to using $_ENV, though a bit slower.

PostGresSQL notices handling

pg_last_notice() fetchs the notice that were emit during the previous query requests. It is now possible to manage this list using new arguments : the default, and previous behavior, is handled with PGSQL_NOTICE_LAST ; PGSQL_NOTICE_ALL, gets all stored notices in an array, and PGSQL_NOTICE_CLEAR remove them all without reading. If nothing is available, pg_last_notice() returns false, as usual.

parse_url() is more restrictive

parse_url() supports now RFC3986 and is more restrictive when handling login and pass. In particular, @ is not allowed anymore in the login or the pass (that would lead to http://user@pass@host/path).

Unicode email filter and IMAP mail size

FILTER_FLAG_EMAIL_UNICODE is available to check for unusual email addresses, that includes symbols or unicode characters : ‘üser@[IPv6:2001:db8:1ff::a0b:dbd0]’. Read more here  about email addresses.

Only related by topic, the IMAP extension has been upgraded : an email address longer than 16385 bytes will throw an instance of Error instead of resulting in a fatal error.

Custom session handlers are catchable

Custom session handlers must return a string when generating a session ID. Failure to do so, such as returning a NULL or an array now throws an Error, instead of an fatal error. It is still very important to catch such error, to avoid sending an unsafe session ID to the users.

Try/catch should be added for session_create_id(), session_regenerate_id() and session_id().

<?php
try{
  session_regenerate_id () ;
} catch (Error $e) {
// Log error
// deal with missing session ID
}
?>

jpeg_ignore_warning default to 0

When manipulating JPEG images, PHP used to report warning from jpeg2wbmp() and imagecreatefromjpeg(). By default, this is now set to 1, where warning are ignored. If your application feels like having less erroneous images than before, this is probably because the warning are hidden : go back to php.ini and activate it again.

OpenSSL sslv2 has been dropped

When OpenSSL is active, several Stream socket transports are added to the PHP binary, including sslv2 and sslv3. Sslv2 has been found insecure and shouldn’t be used. SSLv3 is also on the way out, after google released the POODLE exploitation technic. The current protocol is TLS. TLS 1.0 is close to sslv3, so the current best choice are TLS 1.1 and 1.2. See http://disablessl3.com/ for more.

Session ID may outgrow storage

Session ID are now generated with CSPNG. Besides, for speed reasons, the hash that protect the raw value of the Session ID is dropped, saving a MD5 or SHA1 call on each sessions. Since this would expose the internal value of sessions, the Session ID are padded up to session.sid_length with an extra session.sid_bits_per_character characters (default 4).

Then, for backward compatibility, the size of the session ID may the size of a MD5, to which a minimum of 6 extra random chars are added, leading to a session id size of 22. On the other hand, the session ID may grow up to 256 char.

Generally speaking, this shouldn’t be a problem, until the session ID storage has some size optimization. Joomla ! Has already detected such needed upgrade (see https://issues.joomla.org/tracker/joomla-cms/11761).

Creating closures from callable

PHP 7.0 lacks a way to turn a callable into a closure. Callable are all the ways that PHP has to refer to a function or a method. They are used with array_map, array_filter, etc. to be called at certain point in the processing of the arrays.

On the other hand, Closure are a versatile version of callable, and have complete API. Converting some of the old code to closure has been made possible with a new static method from the Closure class : fromCallable()

See this example from the RFC: https://wiki.php.net/rfc/closurefromcallable : getValidatorCallback returns different callable, built with its method, may they be private or not.

<?php class Validator {   
   public function getValidatorCallback($validationType) {   
      if ($validationType == 'email') { 
         return Closure::fromCallable([$this, 'emailValidation']); 
      }   
      return Closure::fromCallable([$this, 'genericValidation']); 
   }   

    private function emailValidation($userData)  { /**/ }   
    private function genericValidation($userData) { /**/ } 
}   

$validator = new Validator(); 
$callback = $validator->getValidatorCallback('email');
$callback($userData);

?>

Go PHP 7.1 !

This is the end of our three parts series about PHP 7.1. As you may see, there is a huge amount of work from all the 243 contributors to PHP 7.1. Fixes, modernization, and cleaning are all presents in this new version, so better start moving soon.

Most of the features and incompatibilities may be spotted in the code with PHP static analysis, such as Exakat. Even as PHP 7.1 is not ready, it is possible to review automatically PHP sources to find any potential issues and get prepared. Download it and use it on your code to learn more.

Tweet about this on TwitterShare on RedditShare on LinkedInEmail this to someone