Extension API

Nishant Gupta edited this page May 21, 2016 · 20 revisions
Clone this wiki locally

HHVM provides a set of APIs for adding built-in functionality to the runtime either by way of pure PHP code, or a combination of PHP and C++ (HNI), or by C++ code and a JSON IDL file.

The IDL method for providing extensions is deprecated. The new HNI method should be used instead.

PHP-only extensions

For the simplest implementations, you can just write pure PHP code which will be built into your HHVM binary (as a block called SystemLib) and loaded persistently for all requests. One example of this is the Redis extension which is written entirely in PHP, primarily in hphp/system/php/redis/Redis.php. As you can see, this looks just like any other script you'd write as part of an application, but because it is part of SystemLib, it is loaded and available for every request.

Think of it like an auto_prepend_file, but handled in a persistent manner, so that the script isn't being reloaded each time.

To add your own function or class to SystemLib, place the implementation under hphp/system/php, and reference it from hphp/system/php.txt. From here, the next time you rerun cmake and compile HHVM, it will be included in the binary as part of the runtime.

PHP and C++ extensions

Sometimes PHP just isn't enough and you need to duck into C++, perhaps to call a library function. For this, HHVM provides HNI (HHVM-Native Interface). An example of HNI can be seen in hphp/runtime/ext/fileinfo/ which makes up the fileinfo extension. Right away, you'll notice functions in ext_fileinfo.php with no implementation body, prefixed by <<__Native>>.

function finfo_buffer(resource $finfo,
                      ?string $string = NULL,
                      int $options = FILEINFO_NONE,
                      ?resource $context = NULL): string;

These are HNI stubs. The <<__Native>> attribute tells the compiler that rather than looking for a block of PHP code to be compiled, it should look for a symbol being exported by the C++ runtime named "finfo_buffer". Inside ext_fileinfo.cpp, we see the function being registered within moduleInit() as HHVM_FE(finfo_buffer);. We also see the mini-systemlib being loaded with loadSystemlib();.

static class fileinfoExtension : public Extension {
  fileinfoExtension() : Extension("fileinfo") {}
  void moduleInit() override {
    /* etc... */
    /* etc... */
} s_finfo_extension;

This, in turn, points at the actual implementation further up in the file:

static String HHVM_FUNCTION(finfo_buffer, const Resource& finfo,
                                          const Variant& string,
                                          int64_t options,
                                          const Variant& context) {

As you can see, the parameter and return types have a 1:1 mapping between the PHP and C++ implementations. It is IMPORTANT that your internal types match your external types exactly. Failure will lead to undefined behavior (most likely a crash). See the table below for the types supported by HNI:

PHP Type C++ Parameter Type C++ Return Type Comment
void N/A void Return type for constructors only
bool bool bool Boolean (true/false)
int int64_t int64_t 64-bit signed integer
float double double Double precision floating point
string const String& String HHVM String, similar to std::string
array const Array& Array HHVM Array, similar to std::map/std::vector
resource const Resource& Resource Resource data type (e.g. file stream, db handle, etc...)
object const Object& Object Object data type
mixed const Variant& Variant Able to represent any data type

In addition, a specific class type may be used (like a normal type hint), and Object arg/return types will apply as expected. Nullable (?type) and Soft (@type) typehints may be used, however they will fall back to Variant representation in the implementation's signature.

To pass an arg by reference, declare it in the PHP file as mixed type with an ampersand as usual (e.g. mixed &$foo) and use VRefParam as the internal type.

Dynamically Loadable Objects

In addition to built-in extensions, third-party developers may create an externally buildable DSO as is demonstrated by the hhvm/example extension. Here, you'll find a standalone mini-systemlib called example.php which performs the same introduction-to-the-compiler job as we saw in hash.php above. The matching implementations can, as expected, be found in example.cpp. The magic comes from config.cmake which instructs the build system on which files to build for which purpose. Please note that HHVM_DEFINE_EXTENSION is only for use in builtin extensions. It will not work for a DSO.

HHVM_EXTENSION(example example.cpp)
HHVM_SYSTEMLIB(example example.php)

First, we define the extension binary we're building with HHVM_EXTENSION(${extension_name} ${filestobuild}). In this example we're only building one source file, but we could list any number of source files here.

Next, we select the PHP implementation to embed in this DSO as a mini-systemlib. Unlike the CPP sources, only one file may be embedded as a systemlib. If you want to split your project up into multiple systemlib sources, you'll need to compost them before compilation yourself.

Since this file is CMake, we can also link in other libraries with normal CMake commands such as the following:

find_package(LibFoo REQUIRED)

HHVM_EXTENSION(foo foo1.cpp foo2.cpp foo3.c)
target_link_libraries(foo ${LIBFOO_LIBRARIES})

HHVM_SYSTEMLIB(foo foo.php)

To build an HHVM extension, you'll follow a similar process to PHP extensions:

example$ hphpize
** hphpize complete, now run `cmake . && make` to build
example$ cmake .
[ lots of output ]
example$ make
[ lots more output ]
** Built target example

The extension may now be loaded by adding the following to your .ini file:

hhvm.dynamic_extension_path = /path/to/foo
hhvm.dynamic_extensions[foo] = foo.so

NOTE: when trying to load Zend extension, hhvm.enable_zend_compat ini setting needs to be enabled.