Michaël Gallego

This is my blog. What can you expect here? Well... Zend Framework 2, Amazon AWS...

Twitter

Google+

LinkedIn

Github

Last.fm

A quick introduction to Zephir language

In this article, I’ll make you discover a new language called Zephir. Zephir was created by Phalcon developers (Phalcon is one of the only PHP framework that is entirely in C).

Zephir can be seen as a hybrid language that allows to write code that looks like PHP, but is compiled to native C code, which allows to create an extension from it, and having a very efficient code.

We started to discuss about this in #zftalk.dev, and some of us see Zephir as an exciting way to bring massive performance improvments to critical components of Zend Framework, like the event manager or the service manager. Those components are rather stable and does not change a lot, so it makes them perfect candidates.

The drawback is that we need to maintain two codebase: the pure PHP implementation (for people that cannot install PHP extensions) and the Zephir code. However, it can be pretty easy considering that Zephir is a language that shares a lot of similarities with PHP.

This article is going to show you how to install Zephir and how to create a very minimal event manager.

Note: Zephir is in very early stage. I have not been able to use any SPL classes, and couldn’t create a project outside the predefined “test” folder. Furthermore, it does not seem to support interfaces yet. But it’s indeed a good start!

Installing dependencies

Before using Zephir, we must install two dependencies: json-c and re2c. If you’re an OS X user, here is how you do it:

  1. If not already done, install Homebrew.
  2. Install json-c: brew install json-c
  3. Install re2c: brew install re2c

Done. Let’s go to something more interesting.

Cloning and installing Zephir

Install the latest version of Zephir (either through the official GitHub repository or using this direct link).

Unzip it wherever you want. Go into the folder and install it using the following command: ./install. If everything went smooth, a zephir-parser.sh should have been created in the bin sub-folder.

Zephir structure

Here is the default structure for the Zephir project:

Zephir default structure

Zephir team has already created some examples in the “test” folder. The folder’s name defines the top namespace. In this example, fibonnaci.zep is the “Fibonnaci” class and its PHP fully qualified class name will be Test\Fibonnaci. Like in PSR-0, each subfolder defines a new namespace level. However, as time of writing, class names are all written in lowercase. I hope Zephir team will change that so that we can reuse PSR-0 naming conventions.

Also notice the config.json file at the root of the project:

{"namespace":"test"}

This file defines the top namespace, and must match the name’s folder.

Creating a minimal event manager in Zephir

For this example, I will write a minimalistic event manager.

Note: for now, I have not been able to generate a valid extension if I write my code outside the test folder. Obviously this should be possible but I don’t know how to do it.

Writing the code

Inside the “test” folder, create a file called “eventmanager.zep” (which will give us the Test\EventManager namespace). Copy paste the following code inside:

namespace Test;

class EventManager
{
   protected listeners;

   public function attach(string event, callable)
   {
       if !is_callable(callable) {
           throw new Test\RuntimeException("Second parameter must be a callable");
       }

       // Let's attach the callable to the event
       var listeners;
       let listeners = [];
       let listeners[event] = callable;

       let this->listeners = listeners;
   }

   public function trigger(string event)
   {
       var callable, listeners;
       let listeners = this->listeners;

       if array_key_exists(event, listeners) {
           let callable = listeners[event];
           return call_user_func(callable);
       }
   }
}

Also add a runtimeexception.zep file with the following code:

namespace Test;

class RuntimeException extends \RuntimeException
{
}

Please note that this code is suboptimal. Mainly because I completely new to Zephir so I don’t know the language. For instance, I have not been able to add an element to a class member array. Hence I had to create a new one in attach, and copy it into the class member. Furthermore, I didn’t find any way to append an element in a multi-dimensional array (eg. listeners[event][] = callable).

You can learn more about the Zephir syntax here.

Compiling the Zephir code

The compilation process is a two-steps process:

  1. Transform the Zephir code to C code.
  2. Compile the C code to a PHP extension.

Transforming the Zephir code is easy: ./bin/zephir compile. This will put the C code in the /ext folder.

To generate the extension (.so file), go to the /ext folder and type the following commands:

  1. phpize
  2. ./configure
  3. make
  4. make install

The last command will install the module in a given folder (in my case: /usr/local/php5/lib/php/extensions/no-debug-non-zts-20100525/). The last step is to add this extension to your php.ini by adding the following line:

extension=/usr/local/php5/lib/php/extensions/no-debug-non-zts-20100525/test.so

Restart Apache, and we’re done.

Executing the code

Now, we can write the following code in our PHP application:

$eventManager = new Test\EventManager();
$eventManager->attach('myEvent', function() {
	echo 'Okey!';
});

$eventManager->trigger('myEvent'); // outputs "Okey!"

The C code

Here is the C code that was generated:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_test.h"
#include "test.h"

#include "Zend/zend_operators.h"
#include "Zend/zend_exceptions.h"
#include "Zend/zend_interfaces.h"

#include "kernel/main.h"
#include "kernel/exception.h"
#include "kernel/memory.h"
#include "kernel/array.h"
#include "kernel/object.h"
#include "kernel/operators.h"
#include "kernel/string_type.h"
#include "kernel/fcall.h"

ZEPHIR_INIT_CLASS(Test_EventManager) {

	ZEPHIR_REGISTER_CLASS(Test, EventManager, eventmanager, test_eventmanager_method_entry, 0);

	zend_declare_property_null(test_eventmanager_ce, SL("listeners"), ZEND_ACC_PUBLIC TSRMLS_CC);

	return SUCCESS;

}

PHP_METHOD(Test_EventManager, attach) {

	zval *event_param = NULL, *callable, *listeners;
	zephir_str *event = NULL;

	ZEPHIR_MM_GROW();
	zephir_fetch_params(1, 2, 0, &event_param, &callable);

		zephir_get_strval(event_param, &event);


	if (!(zephir_is_callable(callable TSRMLS_CC))) {
		ZEPHIR_THROW_EXCEPTION_STR(test_runtimeexception_ce, "Second parameter must be a callable");
		return;
	}
	ZEPHIR_INIT_VAR(listeners);
	array_init(listeners);

	zephir_array_update_string(&listeners, event->str, event->len, &callable, PH_COPY | PH_SEPARATE);

	zephir_update_property_this(this_ptr, SL("listeners"), listeners TSRMLS_CC);

	ZEPHIR_MM_RESTORE();

}

PHP_METHOD(Test_EventManager, trigger) {

	zval *event_param = NULL, *callable, *listeners, _0 = zval_used_for_init, *_1;
	zephir_str *event = NULL;

	ZEPHIR_MM_GROW();
	zephir_fetch_params(1, 1, 0, &event_param);

		zephir_get_strval(event_param, &event);


	ZEPHIR_OBS_VAR(listeners);
	zephir_read_property_this(&listeners, this_ptr, SL("listeners"), PH_NOISY_CC);

	ZEPHIR_SINIT_VAR(_0);
	ZVAL_STRINGL(&_0, event->str, event->len, 0);
	ZEPHIR_INIT_VAR(_1);
	zephir_call_func_p2(_1, "array_key_exists", &_0, listeners);
	if (zend_is_true(_1)) {
		ZEPHIR_OBS_VAR(callable);
		zephir_array_fetch_string(&callable, listeners, event->str, event->len, PH_NOISY);

		zephir_call_func_p1(return_value, "call_user_func", callable);
		RETURN_MM();
	}
	ZEPHIR_MM_RESTORE();

}

There are interesting things to note here. Among others, in the attach method, we can see the following lines:

if (!(zephir_is_callable(callable TSRMLS_CC))) {
	ZEPHIR_THROW_EXCEPTION_STR(test_runtimeexception_ce, "Second parameter must be a callable");
	return;
}

Zephir uses a bunch of optimizations (you can see them in the Library/Optimizers/FunctionCalls folder). For instance, it automatically transforms each is_callable call to a zephir_is_callable call. So we can expect more and more optimizations from the Zephir team that translate common patterns to very efficient C code.

Conclusion

Of course, in this very simple example, doing it in C brings nearly no performance improvment over a pure PHP implementation, but ZF2 event manager is much more complex and may gain a big performance boost.

However, as of today, Zephir is still in early alpha and lacks a lot of features to be able to port such a complex component like Zend\EventManager. But it’s a very interesting path to follow.