Writing PHP Extensions with Zephir
PHP would not be as popular today if it was not for its extension system. Developers for PHP have created extensions that hook into just about everything, and by that token end-users can turn around and use systems that are not natively in PHP.
A good example is the database drivers. Many new developers may not realize it right away, but PHP does not natively support all of its databases in core. Things like Firebird, Oracle, and MSSQL are provided as extensions to the core system.
There’s other things too, like cURL, XSL(T), and Subversion, that are made available to the language via extensions. If you want a full list, feel free to check out http://www.php.net/manual/en/extensions.alphabetical.php.
The downside to extensions is that they cannot be written in PHP. Extensions are generally written in C and require a decent knowledge of how the PHP core language works. I can write C code, and I could probably learn to write extensions without too much trouble, but many people don’t know C. Those developers are generally out of luck and must rely on other developers to make extensions for them.
Why would you need an extension in the first place? The most common reason is that you need access to an existing library. You can create a wrapper around the existing library as a PHP extension, and PHP will then be able to use that library. That is the motivation behind many popular extensions developers install for PHP (like cURL).
The other reason is performance. PHP is a dynamic language and that comes with some pitfalls. Using an extension brings your code down to running at the C level and means that it doesn’t need to go through as much to be compiled and run by the server. If you have lots of computationally expensive instructions you need to run, porting that logic to an extension might be a good way to increase performance.
For those that do not want to learn C, but want to get a taste of building their own extensions, there is a new project from the Phalcon project named Zephir. It was born out of a need to make Phalcon, an extension-based framework, easier to maintain, and it sits in between the C and PHP level as a language. Zephir will take the source code you generate and create a PHP extension out of it that you can install on your server. It does not help expose things like libraries on the system, but it will definitely help move logic into an extension.
Why?
As made evident by projects like Facebook’s HHVM, some developers need the performance that a compiled language provides as well as the structure that a staticly-typed language provides. In PHP when you type $a = 1
, $a
can be used as a string, integer, float, or boolean value. The engine will take care of converting it to the most appropriate type of value and you do not have to worry about doing that yourself.
In staticly typed languages you tell the compiler if something is an integer or string, and if you need to convert it to something else you must do it manually. The dynamic nature of PHP comes at a cost though. Moving the logic to a staticly compiled language will allow better compilation (since that happens outside of PHP) and faster code.
The Zephir Code
Zephir is a system that you install on your development machine, so the first thing to do is actually install Zephir.
Their Github page has instructions for doing this, but I also went ahead and created a vagrant box that will install everything you need for Zephir and will set it up. It is stuck at whatever version of Zephir I put in the repository last, but updating it is fairly easy. You can clone/fork the code at https://github.com/dragonmantank/zephir-vagrant.
Once you have Zephir installed, go to wherever you have the project checked out. If you are using my Vagrant box, make sure to vagrant ssh
in and switch to the /vagrant
directory.
In here you’ll find everything Zephir needs to build extensions for you. If you want to look at some sample code have a peek inside the tests/
folder. In fact, let’s look at tests/arithmetic.zep
and poke around the code a bit.
The first thing you’ll notice is that the code is pretty readable. It is kind of a mashup between C and PHP, so PHP developers should feel quite at home reading this code. We have namespaces, classes, functions, and variables. Not too bad.
namespace Test;
class Arithmetic
{
public function intSum()
{
int a, b, c;
let a = 1,
b = 2,
c = a + b;
return c;
}
// ...
The first main difference we seen is on line 12, where we declare a
, b
, and c
as integers. You can use var
instead and let the compiler handle it, but Zephir has support for static typing. Zephir will allow you to staticly type Integers, Booleans, Logs, and Characters. Things like Strings, arrays, and other Objects are declared using the var
keyword.
On line 14 we see another difference with the keyword let
.
Unlike PHP we have to let Zephir know we’re assigning a value to a variable after it’s been instantiated (though you can assign it a default value when you declare it).
If you want to look at all the differences, check out the official website at http://zephir-lang.com/index.html.
Let’s build an extension
The first thing we need to do is let Zephir know about our new namespace. Everything that Zephir does is namespaced, and the default checkout of Zephir has it set to the Test
namespace. Open up config.json
, and let’s type in a new namespace under the namespace
key. For the sake of the example and to keep it unique, we’ll call it ‘myframework’.
Zephir will use the namespace to figure out which folder the source code is in. Since we set our namespace to ‘myframework’, we need to create a new folder in the project named myframework/
. Inside of here all of our code will live. You can nest folders in here as well, which will just create a deeper namespace. For example, if you have a folder structure of myframework/graphing/
, then the namespace will be Myframework\Graphing
. The folder structure is pretty much PSR-1 standards, if you want to think of it that way.
We are going to build a basic calculator. Inside of our myframework/
folder, create a file named calculator.zep
. Inside there, let’s create our class (Calculator
) and give it a basic addition function:
namespace Myframework;
class Calculator {
public function add(int a, int b) {
return a + b;
}
}
This may be pretty bare bones, but it will be enough for right now. We’re going to have a class named Myframework\Calculator
with an add()
function. It takes two integers and adds them together. Now we need to let Zephir translate this into C code. To do this, run bin/zephir compile
from the root of our project. This will invoke the Zephir compiler and generate the extension files for us. For our basic little project this should take just a moment. The compiled output is stored in the ext/
directory. You can see the actual code that is generated if you look in ext/myframework/calculator.c
.
Now, this doesn’t work automatically with PHP because this extension isn’t compiled as an extension. This is just all translated into what we need to build an extension. The nice thing is that you build this extension just like you do any other extension! We’ll phpize it, configure, and make it.
$ cd ext/
$ phpize
$ ./configure
$ make
$ sudo make install
$ echo "extension=/usr/lib/php5/20100525/myframework.so" | sudo tee -a /etc/php5/cli/conf.d/myframework.ini
The last line may need to be modified for your specific installation, but that should work with our vagrant box. The extension should now be available to your PHP installation. Let’s take a look at it through the CLI interface:
vagrant@precise64:/vagrant$ php -a
Interactive mode enabled
php > $calc = new Myframework\Calculator;
php > var_dump($calc->add(2, 1));
int(3)
Where To Go From Here
Making more complex extensions is just as easy. Create more classes under your namespace, compile for Zephir, and then compile for your PHP installation. Take a look at the documentation and their blog for more information about control structures, using arrays, and more language constructs. Zephir does warn that it is not ready for production (in fact, at the time of this writing it is only at 0.2.0a), but it will be growing more and more mature as they get it ready for use with the Phalcon framework.
Zephir seems like a great way to port complex or domain-specific logic into an extension without having to learn the nitty-gritty details of learning C.
Share your thoughts with @engineyard on Twitter