Moodle and PHP 7.0 details

Jump to: navigation, search

Moodle and PHP / 7.0 details

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

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...