-
Notifications
You must be signed in to change notification settings - Fork 3k
Extension API
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 and is slowly being removed from the codebase, in favour of the new HNI method. The IDL method is documented in the code at hphp/doc/extension.development.
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.
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();
.
static 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, 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.
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)
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 .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 Settings) ini setting needs to be enabled.