Skip to content

Extension API

Sara Golemon edited this page Jan 10, 2014 · 20 revisions

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

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

<<__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();.

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

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

static String HHVM_FUNCTION(finfo_buffer,
    CResRef finfo, CVarRef string,
    int64_t options, CVarRef 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 CArrRef Array HHVM Array, similar to std::map/std::vector
resource CResRef Resource Resource data type (e.g. file stream, db hangle, etc...)
object CObjRef Object Object data type
mixed CVarRef Variant Able to represent any data type

In addition, a specific class type may be used (like a normal type hint), and CObjRef / Object arg/return types will apply as expected. Nullable (?type) and Soft (@type) typehints may be used, however they will fall back to CVarRef/Variant representation in the implementation's signature. To pass an arg by reference, declare it in the PHP file with an ampersand as usual and use VRefParam as the internal type. See API/References for more info on working with references.

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

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)
include_directories(${LIBFOO_INCLUDE_DIR})

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 config.hdf:

DynamicExtensions {
  example = /path/to/example.so
}
Clone this wiki locally