|2.1. Convert the PHP code|
|2.2. Modify the C code|
|2.2.1. Include the PCS header file|
|2.2.2. Include the phpc code|
|2.2.3. MINIT registration|
|2.2.4. Add a module dependency|
This tutorial explains how to integrate a set of PHP files in a PHP extension. We'll use an existing case : the integration of the MongoDB PHP library in the mongoDB PHP extension.
MongoDB is a perfect use test case because, some time ago, its dev team chose to keep only performance-critical code in C and write all the high-level code in PHP. Benefits, in terms of maintainabilty, are obvious, and it allows to publish two C extensions they name drivers : one for PHP, and one for HHVM (drivers). We'll only deal with the PHP driver here, because HHVM provides another mechanism to integrate C and PHP code.
Separating PHP and C code improved maintainability but required distributing C and PHP code as two different software packages, one managed by Composer, and one distributed on PECL. On the positive side, a two-package architecture allows an easier distribution of new PHP code (it doesn't require a recompilation). On the negative side, it is harder to understand from the user's point of view (increased support), it requires managing two different versioning systems in parallel, and it adds concerns about potential compatibility breaks between both layers. So, without knowing the trade-off that will be chosen by the developers, we'll detail below how the PHP Library can be embedded in the C extension.
Here's a synthetic view of the extension with the embedded library :
Install the required PHP environment :
- Ensure the 'php' CLI interpreter is installed and in your PATH (run 'php -v' to check)
- If it is not already present, install the PCS extension from the PECL repository. Activate it in your php.ini file ('extension=pcs.so') and check it ('php -m | grep pcs').
Then, we need the MongoDB extension and library. Choose a directory we'll reference as <base> and create two subdirectories :
- one named 'ext' where you will clone the PHP extension source code (from https://github.com/mongodb/mongo-php-driver),
- and one named 'lib' where you will clone the library code (from https://github.com/mongodb/mongo-php-library)
Then cd to <base>/ext.
First, the PHP code needs to be converted to a format that can be included by the C compiler. PCs provides a tool for this, which recursively scans a directory, and aggregates the contents of every files it meets to a C include file. Note that we produce a single file for a whole file tree, and that the structure of the file tree will be preserved. Before running the conversion tool, we need to choose :
- the name of the file where the converted PHP code will be stored. This is the file that will be included during the compilation. By convention, such file has a '.phpc' suffix. Here, we choose to generate a file named 'mongo-php-library.phpc'. Note that this file will be distributed as an integral part of the extension source code and needs to be added to your 'package.xml' file.
- We also need to choose a variable name. This C variable will reference the PHP code tree. This variable name is totally hidden from the final user and used only once in the code. So, the only constraint is that it must not exist already in the C code. We choose 'lib' as variable name.
Add this to the 'Makefile.frag' file :
Note : When using this tutorial as a model for an extension whose source tree does not include a file named 'Makefile.frag', create one, fill it with the contents above, and add the following line at the end of your 'config.m4' file :
Note : You may generate several 'phpc' files from several file trees if needed (using different variable names, of course). In this case, just duplicate the registration operations below for each 'phpc' file.
Now, we run the conversion :
Check that your directory contains a new file named 'mongo-php-library.phpc'. If you're curious, you may display its contents as it is a C include file.
Note : Even when it comes from a Composer package, the PHP code we embed won't use the Composer autoloader anymore. It will use the PCS autoloader instead, which is a map-based autoloader. It scans scripts at registration time and detects the symbols they define, in order to autoload them later. So, never keep anything related to autoloading in the code you embed. Other consequences : no more constraint on file paths and each script may contain any number of class, function, and constant definitions.
Now, it's time to modify the C code to take the PHP code into account.
Edit the 'php_phongo.c' file (the main C source file).
At the beginning of the file, add this line to the set of '#include' directives :
This file defines the PCS C API.
Insert the following line :
This line must be inserted :
- after the 'client.h' include directive
- and before the module's MINIT function
I would recommend inserting it just after the '#include' directives.
Now, jump to the module's PHP_MINIT_FUNCTION().
At the end of the function, insert :
As you see, your code must check that registration returned SUCCESS and return FAILURE if it is not the case.
Note : We chose to insert the code tree in the "ext/mongodb/lib" virtual directory. The location might be different. By convention, each extension uses paths under 'ext/<extension-name>'. This path is quite transparent and appears only if you want to explicitely interact with embedded files using the stream wrapper or the C/PHP API.
Note : The last argument is a 'flags' argument. See the 'client.h' include file to see the values it may contain. Unless you have real reasons to set a different value, it is recommended to use the default null value.
This part is needed to reflect the fact that our module now depends on the PCS extension. Even if PCS is included in a future core distribution, this dependency should remain, as it is a good security and ensures a clean diagnostic if PCS is not available for any reason.
Before your zend_module_entry definition, insert these lines :
and change the first line of your zend_module_entry definition, from :
Save the file. Every needed changes are done.
First, check compilation is OK :
Install the extension ('make install') and activate it (using your INI file).
Check everything's OK with the 'php -m' command. This shouldn't display any error and the list should contain 'mongodb' and 'pcs'.
Now, let's check that library classes are accessible. Run :
This should display an array dump containing a 'manager' and an 'uri' key. Here, you have checked that the PHP classes are publicly exposed and that the PHP library communicates with the C layer.
Now, let's display the list of virtual files managed by PCS. Run :
This should display a table:
Signification of table columns :
- Virtual path : the path used to register the file. This will be used when accessing the virtual file using the PCS stream wrapper or when using the PHP/C API,
- Size : the size of the script after stripping useless comments,
- L : the way PCS will load the script :
- A : the script will be autoloaded
- R : the script will be loaded at the beginning of each request
- - : PCS will never load the file. This is used when the script will be handled another way, though the stream wrapper, or via the PCS API.
We may also display a similar table, listing defined symbols instead of files :
Note that, as the 'ext/mongodb/lib/functions.php' file contains functions, which cannot be handled by the autoloader, it will be loaded at RINIT time.
So, you have seen how to include a set of PHP files in a C extension. Other registrations are possible. You can, for instance, register a 'physical' file tree located anywhere on the file system, or mix every mode (embed part of the PHP code only).
You may also interact with PCS from C or PHP code. The C API is explained in the 'client.h' file.