Moodle and PHP7

Jump to: navigation, search

PHP7 was released on 3 December 2015 and has significant performance improvement comparing to PHP5. Everybody wants to benefit from it, however the language standards have changed and code written for PHP5 might not work in PHP7. Moodle core and plugins should modify the code so it works the same way on both versions of PHP.

This page contains description of what has been changed in Moodle to make it work on PHP7 and recommendations to plugin developers. Please see also epic issues MDL-50565 (PHP 7.0), MDL-55120 (PHP 7.1) and MDL-60279 (PHP 7.2) that combine all changes that were made in Moodle to ensure PHP7 compatibility. (Note: you must be logged in to tracker to see issues in Epics)

PHP 7.0 can be used with Moodle 3.0.1, Moodle 3.1 and later releases. It is also the minimum supported version for Moodle 3.4

PHP 7.1 can be used with Moodle 3.2 and later releases

Changes in engine

There are multiple small changes that may require action from developers. They include but not limited to:

  • invalid class, interface and trait names (null, int, string, bool, true, false, resource, object, mixed, numeric)
  • handling of indirect variables, properties, and methods (for example,
    $foo->$bar['baz']
    is not going to work the same in php5 and php7)
  • foreach
    no longer changes the internal array pointer

For a complete list see http://php.net/manual/en/migration70.incompatible.php and https://github.com/tpunt/PHP7-Reference#changes

For PHP 7.1 see http://php.net/manual/en/migration71.incompatible.php

https://github.com/sstalle/php7cc is a handy tool for checking your code.

Exception and Throwable

In PHP7 engine errors can be caught using
try {} catch (Throwable $e) {}
. In PHP5 they were either warnings or fatal errors. Throwable is a new interface that Exception implements, which means that these new exceptions are NOT caugh by
try {} catch (Exception $e) {}
in both PHP5 in PHP7. This is great, allows to treat engine errors nicely and usually backward-compatible.

Why is it a separate topic then? Because error handling in Moodle is not standard. Moodle developers have already thought that it would be nice to catch engine errors same way as exceptions and implemented a workaround. This change in PHP7 conflicts with Moodle error handling. Here is an example:

function f(stdClass $a) {}
try {
    f(0);
} catch (Exception $e) {
    echo "Caught exception: ".get_class($e)."\n";
}
echo "hello, world!\n";

Output

  • PHP5 by itself: PHP Catchable fatal error: Argument 1 passed to f() must be an instance of stdClass, integer given, called in - on line 4 and defined in - on line 2
  • PHP5 + moodle: Caught exception: coding_exception. hello, world!
  • PHP7 by itself: Fatal error: Uncaught TypeError: Argument 1 passed to f() must be an instance of stdClass, integer given, called in on line 4 and defined in
  • PHP7 + moodle: Fatal error: Uncaught TypeError: Argument 1 passed to f() must be an instance of stdClass, integer given, called in on line 4 and defined in.

So this change in moodle means that the code after the try-catch that would be executed in PHP5 is skipped in PHP7. This can be quite critical.

Why is it happening? We call in lib/setup.php
set_error_handler('default_error_handler', E_ALL | E_STRICT);
and inside the handler convert the fatal catchable errors into coding_exception. coding_exception is a class extending Exception and it gets caught properly. PHP7 no longer triggers an error, it throws a TypeError which implements Throwable but it does not extend Exception and does not get caught in this example.

The best solution:

function f(stdClass $a) {}
try {
    f(0);
} catch (Exception $e) {
    echo "Caught exception: ".get_class($e)."\n";
} catch (Throwable $e) {
    echo "Caught throwable: ".get_class($e)."\n";
} 
echo "hello, world!\n";

Output:

  • PHP5 + moodle: Caught exception: coding_exception. hello, world!
  • PHP7 + moodle: Caught throwable: TypeError hello, world!
Note here that
catch (Throwable $e)
will be ignored in PHP5 because there is no interface/class with the name Throwable. Using non-existing class name in "catch" does not produce any warnings or errors. If we were writing code for PHP7 only we could remove
catch (Exception $e)
because all exceptions will be caught by Throwable. But we want to make our code work on both PHP5 and PHP7. See also MDL-52284 for related changes that were made in Moodle core. How it can affect plugins? Plugins that call
eval()
inside try/catch may need to change the code similar to how we did in MDL-52333 . Also if your plugin also calls
set_error_handler()
you may step on the same error. Tip: Try to avoid
catch (Exception $e)
at all, instead catch specific exceptions (dml_exception, moodle_exception, etc.)

Constructors

PHP4-style constructors (method with the same name as class) became deprecated in PHP7. Moodle has a lot of code that was written before PHP5 and lots of constructors were not modified later because they continued to work fine. There are also lots of 3rd party libraries that were also using the PHP4-style constructor. Quite a few of issues in MDL-50565 epic are related to the constructor changes.

How Moodle addressed it: There are now two methods in each class that used to have PHP4-style constructor - one is called __construct() and another is called the same as the class name and inside it calls
self::__construct(...)
. Starting from Moodle 3.1 the second one gets deprecated and displays debugging message. Why not just replace the method name with __construct()? The reason is that if some plugin extends this class, it may call
parent::parentclassname()
in its constructor. This would end in fatal error if we did not leave the php4-style constructor. Good thing is that having method with the same name as the class name does not show any warnings in PHP7 if the __construct() is also present in the same class. What plugin developers should do? First of all, make sure that all your classes have proper constructors. Second, make sure that you don't call parent constructors with the old style, for example
parent::moodleform()
or
parent::HTML_QuickForm_input()

Good to know: This code will work in ANY moodle version:

class myform extends moodleform {
    public function __construct() {
        parent::__construct(); // In 2.9 where moodleform::__construct did not yet exist this will call moodleform::moodleform() .
    }
}

Remember, even if you don't care about PHP7 support by your plugin you must change the way you are calling parent constructors. Otherwise you will see debugging messages starting from Moodle 3.1

Can I use PHP7 yet?

Test, test, test.

Additional plugins that you are using may or may not yet be updated to work with PHP7. As mentioned above, some engine changes may result in fatal errors and, therefore, loss of functionality.

If you are using external authentication or enrolment plugins, please note that MSSQL driver is not available under php7. You must use sqlsrv driver.

Also, depending of your configuration, some extensions (memcached, redis, mongodb, xmlrpc...) maybe missing or work in progress for your distribution, triple check the exact availability for your OS.

As an alternative, you can use php-pdo and php-odbc (with FreeTDS) for connectivity with MS-SQL database.

Manually installing Memcached

REMI repository now have fresh PHP 7.1 RPMs for php-pecl-memcached

(Was running the following commands on RedHat 7)

php-memcached version 3.0.0b1

libmemcached version 1.0.16

  • git clone https://github.com/php-memcached-dev/php-memcached/
  • git checkout -b php7 origin/php7
  • yum install php70u-devel zlib-devel libmemcached-devel
  • phpize & ./configure & make & make install
  • Added a new /etc/php.d/20-memcached.ini (with the following line: extension=memcached.so)
  • systemctl restart php-fpm
  • yum install memcached
  • systemctl enable memcached
  • systemctl start memcached
  • Now, setup Moodle as you usually would...

(Was running the following commands on Ubuntu 16.04)

  • git clone https://github.com/php-memcached-dev/php-memcached/
  • git checkout -b php7 origin/php7
  • (sudo) apt-get install php7.0-dev zlib1g-dev libmemcached-dev
  • phpize & ./configure & make & make install
  • Added a new "(sudo) vim /etc/php/7.0/mods-available/memcached.ini" (with the following line: extension=memcached.so)
  • Link above to php-fpm relevant modules folders: "(sudo) ln -s /etc/php/7.0/mods-available/memcached.ini /etc/php/7.0/fpm/conf.d/20-memcached.ini"
  • Link above to php-cli relevant modules folders: "(sudo) ln -s /etc/php/7.0/mods-available/memcached.ini /etc/php/7.0/cli/conf.d/20-memcached.ini"
  • (sudo) systemctl restart php7.0-fpm
  • (sudo) apt-get install memcached
  • (sudo) systemctl enable memcached
  • (sudo) systemctl start memcached
  • Now, setup Moodle as you usually would...

Finally, note that PHP7 is evolving quickly (7.0.13 was released in November 2016), so it's important to follow the evolution until the version becomes stable enough for production. The same can be said about the Moodle side, surely there will be some (still hidden) problems to be fixed in our 3.0.x future versions.

Please edit this page if you know any other reasons that can prevent from migration to PHP7.