1. Preloading

Another new feature of PHP 7.4 is to allow to preload some classes, which will be usable as internal classes of the language or of an extension.

  • File with class definition: preload-foo.inc
  • Test file checking Fichier de test vérifiant l’existence de la classe: foo.php

Usage:

$ php -dopcache.preload=preload-foo.inc foo.php
Class Remi\Foo exists

So we'll use this feature with FFI.

2. ZSTD compression

Zstandard is a well known and efficient compression algorithm. The libzstd library provides a reference implementation.

zstd for PHP extension already exists, we'll use it to checkthe performance of our FFI solution.

  • Library definition, copy/paste from the library header zstd.h: preload-zstd.h
  • Remi\Zstd class definition which can be preloaded: preload-zstd.inc
  • Test script using this classe and zstd extension for benchmarking: zstd.php

Notice: if the class is not preloaded, it will be included, simple usage:

$ php zstd.php

If only the class is preloaded, headers will be loaded using FFI;load(), usage:

$ php -d opcache.preload=preload-zstd.inc zstd.php

Starting with 7.4.0RC5 (or using RPM from my repository), headers can also be preloaded, and will be used calling FFI:scope(), usage:

$ php d ffi.preload=preload-zstd.h -d opcache.preload=preload-zstd.inc zstd.php

In previous versions, headers preload only works when run as a normal user, so doesn't work with  mod_php or php-fpm started under administrative account (root)

Execution output:

PHP version 7.4.0RC4
Use preloaded class
Using FFI::scope OK

Src length           = 8673632
ZSTD_compress        = 1828461
Src length           = 1828461
ZSTD_decompress      = 8673632
Check                = OK
Using FFI  extension = 0,09"

Src length           = 8673632
ZSTD_compress        = 1828461
Src length           = 1828461
ZSTD_decompress      = 8673632
Check                = OK
Using ZSTD extension = 0,09"

For final user, code using FFI is close the code using the Zstd extension, and performances are identical (no noticeable difference).

3. Redis client

Various implementations of Redis client exist, written in C or PHP, this sample use FFI to access functions of the hiredis library.

  • Library definition, copy/paste from the library headers hiredis/hredis.h et hiredis/read.h: preload-redis.h
  • Remi\Redis class definition to be preloaded: preload-redis.inc
  • Test script using this class: redis.php

Output extract

$ php74 -d ffi.preload=preload-redis.h -d opcache.preload=preload-redis.inc redis.php
...
+ Remi\Redis::__construct(localhost, 6379)
+ Remi\Redis::initFFI()
+ Remi\Redis::del(foo)
int(1)
+ Remi\Redis::get(foo)
NULL
+ Remi\Redis::set(foo, 2019/10/23 12:45:03)
string(2) "OK"
+ Remi\Redis::get(foo)
string(19) "2019/10/23 12:45:03"
+ Remi\Redis::__destruct

This simplistic code, written in a few hours works and fulfill its goal..

4. Liens

  • Complete and really detailed documentation: https://www.php.net/ffi
  • FFIme projet by Anthony Ferrara designed to automate soe part of the work (experimental)
  • Git reporitory with used examples

5. Conclusion

FFI appears to be a new way to develop directly using PHP, and allowing more features without any need to create and maintain extension written in C language.

Its usage stills requires good C skills, to understand the library headers and documentation, and to avoid memory leaks, but should attract more developers / contributors.

The future will tell if FFI keep its promises, for a production usage, and if it will allow to reduce the number of existing extensions which will have to be maintained and adapted for next PHP versions.