The "native" way is to do it like in the PHP source, in C, with the Zend API to spice things up.
The documentation for the Zend API (all C macros you can use to simplify the extension's code), about the Zend Engine internal workings and how to build an extension can be found mostly in these two places:
Note that both resources are WIP in some areas. But if you are serious about building native extensions, you read it all.
Here is a breakdown of links toward the main subjects :
- PHP lifecycle: Book
- Memory Management: PHP manual | Book: here and there
- Zval and working with variables: PHP manual | Book
- Arrays: PHP manual - Hashtable and Arrays | Book
- Strings: Book
- Resources: Book
- Functions: PHP manual | Book
- Classes: Book
- Extension structure: PHP manual | Book - Extension skeleton
See the native_extension.c
file. It contains some examples of how to set-up constants, functions, classes, methods, arguments, properties, etc.. and working with Zvals.
Typically a PHP extension is around 10 times faster than the "same" code in PHP userland.
PHP-CPP is a C++ library that is a nice wrapper around the Zend API. It can do everything the Zend Engine allows to do. It is maintained by a company that used it for their own products but it's free, open-source and has a full documentation.
See the phpcpp_extension.cpp
for a quick cheat sheet of the syntax and features.
From their documentation, we can read that a simple bubble sort algorithm was 10 times faster when written in a PHP extension rather than in the PHP userland.
Zephir is a high level language that compiles into a PHP extension.
The syntax is very similar to PHP's, so I only mention here what is new/different :
Property declaration shorthand :
private myProperty = 10 {
set, get
};
Is the same as declaring the property and both getMyProperty()
and setMyProperty(value)
.
Class constants can only be called via the class name: MyClass::THE_CLASS_CONSTANT
.
Type hint in arguments and returned value : function theFunc(array! input, const int size = 0)
.
The !
after the type forces the argument to be of that type otherwise casting is attempted.
The const
keyword can be used inside functions body and arguments
Possibility to return void
, and/or one or several types: function theFunc() -> string | bool
.
Variable declaration without strict type uses the var
keyword: var a;
.
These can be assigned a value of any of the PHP's types. Var keyword not necessary in arguments (but possible).
Types can also be specified, like in C.
For these variables, their types can not change later (value can still change if not const).
int theNumber = 0;
array theArray = [];
Available types : bool
, char
, string
, int
, long
, float
, double
, array
.
The unsigned
keyword is also available for char
, int
and long
: unsigned long veryBigNumber = 999999999;
.
Short type notation is possible: uchar
, uint
, ulong
.
When assigned to a value of a different type, casting is attempted. Throws a compiler exception when types are incompatible.
When assigning value to a statically typed variable from a source that may not be (var variable, PHP functions, array values), it generally needs to be explicitely casted.
But rules are not super clear, compiler will tell you when he wants that.
For objects, the type can be specified during assignation, not variable creation.
var o = new TheClass(); // or even var o = <TheClass!> new TheClass();
var o2, o3;
o2 = <TheClass> o; // tells the compiler to think that o2 is an instance of TheClass
o3 = <TheClass!> o; // tells the compiler to actually check that o is an instance of TheClass
Same notation can be used for arguments and retuned values: function theFunc(<TheClass> o) -> <TheClass>
.
Parsing of variables inside double quoted strings is not supported, use concatenation instead.
Variable variable name:
float price = 0.0;
string name = "price";
let {name} = 10.2;
Works the same with properties, functions and classes: this->{propName} this->{methodName}() {funcName}() new {className}()
;
MOST IMPORTANTLY, you MUST precede variables with the keyword let
to be able to change their value:
var theVar = 1; // declaration
let theVar = 2; // assignation of another value
theVar = 3; // throws a parse error
// same with properties:
let this->theProperty = "new value";
Parenthesis are optional in control structures.
No standard for(;;)
.
string aString = "aString";
char aChar;
for aChar in aString { }
int key, value;
var _array = [0, 1, 2, 3, 4];
for key, value in _array { }
// reverse keyword reverse the array
for value in reverse _array { }
// "standard" for
for i in range(0, 9) { }
loop { }
// is the same as
while true { }
Syntactic sugar operators:
if isset theVar { } // same as isset()
if empty theVar { } // same as empty()
if typeof theVar == "string" { } // same as gettype()
var theVar;
if fetch theVar, theArray[theKey] {
// if theKey exists in theArray, theVar is populated with the corresponding value
// and fetch returns true (false otherwise and theVar is left alone)
}
Named parameters:
function theFunc(array a, int size) { }
theFunc([], 0); // standard way
theFunc(size: 1, a: [1]);
Shorthand for closures:
needsAClosureAsParam( number => number * number );
// is the same as
needsAClosureAsParam( function(number) { return number * number; } );
The deprecated
method modifier automatically throws a E_DEPRECATED
error if the method is called.
Require a php script from the userland: require "the/path.php";
.
PHP global constants are not accessible, but the Surperglobals are: string queryStr = _SERVER["query_string"];
You can call any existing functions from PHP core, any extensions of even the userland.
Exceptions at runtime allows you to know in which .zep
script the error occurred. PHP errors don't.
At the time of this writing, Zephir still needs PHP v7.0.x (Zend Module Api No: 20151012) and the extensions created with it will only works with this version of PHP.
So you need to make sure the php
command point to the correct PHP version (I had to update its symlink like so: /usr/bin/php > /usr/bin/php7.0
).
First install anywhere the parser: PHP Zephir parser
sudo apt-get install php7.0-dev gcc make re2c autoconf
git clone git://github.com/phalcon/php-zephir-parser.git
cd php-zephir-parser
sudo ./install
This creates a PHP extension, so you need to update the relevant php.ini
. If you want to use it to compile a Zephir project, you need to use the php.ini
for PHP CLI.
[Zephir Parser]
extension=zephir_parser.so
Then install anywhere Zephir itself :
git clone git://github.com/phalcon/zephir.git
cd zephir
sudo ./install
Anywhere on your disk, initiate a Zephir project : zephir init extension_name
Put your .zep
scripts in the extension_name/extension_name
folder, following the namespace's hierarchy.
When ready, compile with zephir build
or zephir compile
If all goes well you just need to update the correct php.ini
(the one used by your site, so not necessarily the same as before). Obviously, you only need to do that the first time.
extension=extension_name.so
If you got a parser error but no indication of the line where the error occurs, then the parser is missing.
If you got an error about missing functions, check which PHP extension the function is part of and make sure that extension is installed (for some reason my installation was missing the PHP-XML extension, for instance).
If you got an error about incompatible Zend Module Api Number, the php
command do not point toward PHP 7.0.
The syntax of PHP and Zephir are close enough that you can automatically go from PHP to Zephir:
For both it is expected to need to pass over the generated code to make sure it is the most optimized.
From various personal benchmarks, an extension created with Zephir will be at least 2 times faster than the same code in the PHP userland. Which is great considering the low amount of time it takes to go from PHP to Zephir, but it's still far slower than native extensions.
Two frameworks have been built with Zephir: Phalcon and Ice. From a benchmark of many PHP frameworks (GitHub and including Ice) we can see that they perform much better than Symphony or Laravel but they are still championed by some lightweight plain-PHP frameworks.