Getting PHP 7.4 and check the launch
I tried FFI on PHP 7.4.
First, get php sources and compile. Needs configure with --with-ffi option.
git clone https://github.com/php/php-src.git
cd php-src
git checkout PHP-7.4
./configure --with-ffi
make
Starting server with FFI needs ffi.enable=1
definition.
php-src/sapi/cli/php -d ffi.enable=1 -S localhost:8000
When the server is up, first copy the example here https://php.net/ffi.examples-basic and make sure there are no errors.
Make shared library by C language for FFI
After launch PHP 7.4, make shared library by c language.
#include <string.h>
#define BUF_SIZE 1024
const char * sample(const char *data, int mod)
{
char buf[BUF_SIZE];
const char * ret = buf;
int i;
for (i = 0; i <= strlen(data) && i < BUF_SIZE; i++){
if (data[i] >= 97 && data[i] <= 122 && i % mod == 0)
buf[i] = data[i] - 32;
else
buf[i] = data[i];
}
return ret;
}
This is the simple program that getting string and int arguments and converting to upper case.
Compile the program to shared library, and load from php program.
gcc -shared -o libsample.so sample.c
<?php
header('Content-Type: text/plain');
$ffi = FFI::cdef("
const char * sample(const char *data, int mod);
", __DIR__ . '/libsample.so');
var_dump($ffi);
var_dump($ffi->sample("sample test test", 3));
var_dump($ffi->sample("sample test test", 4));
var_dump($ffi->sample("sample test test", 1));
/* output:
object(FFI)#1 (0) {
}
string(16) "SamPle teSt TesT"
string(16) "SampLe tEst Test"
string(16) "SAMPLE TEST TEST"
*/
It worked well!
PHP's string
type variables can be received in C as const char *
type variables.
It seems to be required const
.
PHP int type variable is int type variable also in C.
Use a callback function
Currently, the callbacks in the PHP manual are described by zend_write
, but I think it's not so clear.
https://php.net/ffi.examples-callback
So, I wrote a original callback program to make it easy to understand.
#include <string.h>
#define BUF_SIZE 1024
typedef int (*callback_t)(int);
const char * sample(const char *data, callback_t callback)
{
char buf[BUF_SIZE];
const char * ret = buf;
int i;
for (i = 0; i <= strlen(data) && i < BUF_SIZE; i++){
if (data[i] >= 97 && data[i] <= 122 && callback(i))
buf[i] = data[i] - 32;
else
buf[i] = data[i];
}
return ret;
}
Tried the behavior that PHP closure is mapped to C function pointer.
This program uses a simple callback function that receives int argument and returns int.
Here is an example using a structure, callback and reference:
<?php
header('Content-Type: text/plain');
$ffi = FFI::cdef("
typedef int (*callback_t)(int);
const char * sample(const char *data, callback_t callback);
", __DIR__ . '/libsample.so');
var_dump($ffi);
var_dump($ffi->sample("sample test test", fn($i) => $i % 3));
var_dump($ffi->sample("sample test test", fn($i) => $i % 4));
var_dump($ffi->sample("sample test test", fn($_) => true));
/* output:
object(FFI)#1 (0) {
}
string(16) "sAMpLE TEsT tESt"
string(16) "sAMPlE TeST tEST"
string(16) "SAMPLE TEST TEST"
*/
Now that the Arrow Functions function has been merged, can use it.
Callback function cannot return string
type. When try it, raised error that Uncaught FFI\Exception: FFI internal error. Unsupported return type
.
Use struct of C language
Structures can be used by calling FFI :: cdef
with a structure declaration.
#include <string.h>
#define BUF_SIZE 1024
typedef int (*callback_t)(int);
struct cbdata {
callback_t f;
};
const char * sample(const char *data, struct cbdata *cbdata)
{
char buf[BUF_SIZE];
const char * ret = buf;
int i;
for (i = 0; i <= strlen(data) && i < BUF_SIZE; i++){
if (data[i] >= 97 && data[i] <= 122 && cbdata->f(i))
buf[i] = data[i] - 32;
else
buf[i] = data[i];
}
return ret;
}
<?php
header('Content-Type: text/plain');
$ffi = FFI::cdef("
typedef int (*callback_t)(int);
struct cbdata {
callback_t f;
};
const char * sample(const char *data, struct cbdata *cbdata);
", __DIR__ . '/libsample.so');
$cbdata = $ffi->new('struct cbdata');
$n = 3;
$cbdata->f = function($i) use(&$n){
return $i % $n === 0;
};
$pcbdata = FFI::addr($cbdata);
var_dump($ffi->sample("sample test test", $pcbdata));
$n = 4;
var_dump($ffi->sample("sample test test", $pcbdata));
$n = 1;
var_dump($ffi->sample("sample test test", $pcbdata));
/* output:
string(16) "SamPle teSt TesT"
string(16) "SampLe tEst Test"
string(16) "SAMPLE TEST TEST"
*/
From PHP references can be used from callbacks from C functions, it seems possible to execute C functions while changing values at any time.
Use another language
If I want to execute numerical calculation at high speed, it may be better in C language, but I do not want to do string processing in C.
If other languages have FFI, they can communicate through FFI.
{-# LANGUAGE ForeignFunctionInterface #-}
#include "template_operations.h"
module HaskellPhp where
import Foreign
import Foreign.C.String
import Foreign.C.Types
data Tops = Tops { assign::FunPtr (CString -> CString -> IO CInt) }
foreign export ccall hsParse :: CString -> Ptr Tops -> IO CString
foreign import ccall "dynamic" mkFun :: FunPtr (CString -> CString -> IO CInt)
-> (CString -> CString -> IO CInt)
foreign import ccall "assign_value_get" value_get :: IO CString
instance Storable Tops where
sizeOf _ = #size CTOPS
alignment _ = #alignment CTOPS
peek ptr = do
assign' <- (#peek CTOPS, assign) ptr
return Tops { assign=assign' }
poke ptr (Tops assign') = do
(#poke CTOPS, assign) ptr assign'
--
-- Function to parse some string
--
hsParse :: CString -> Ptr Tops -> IO CString
hsParse cs cops = do
ops <- peek cops
let f = mkFun $ assign ops
a <- newCString "foo"
b <- newCString "bar"
r <- f a b
cstr <- value_get
s <- peekCString cstr
newCString $ "hsParse finish: [" ++ show r ++ "]" ++ "[" ++ s ++ "]"
typedef struct template_operations
{
size_t (*assign)(const char * key, const char * value);
} CTOPS;
#include <stdio.h>
#include <HsFFI.h>
#ifdef __GLASGOW_HASKELL__
#include "Callback_stub.h"
#endif
#include "template_operations.h"
int prepare() {
hs_init(0,0);
}
int finish() {
hs_exit();
}
static const char * assign_string = "";
void assign_value_set(const char *value)
{
assign_string = value;
}
const char *assign_value_get()
{
return assign_string;
}
const char* parse(char *data, struct template_operations *tops)
{
return hsParse(data, tops);
}
<?php
header('Content-Type: text/plain');
$ffi = FFI::cdef('
int prepare();
int finish();
typedef struct template_operations
{
size_t (*assign)(const char * key, const char * value);
} CTOPS;
const char* parse(char *data, struct template_operations *tops);
void assign_value_set(const char *value);
const char * assign_value_get();
', __DIR__ . '/libcallback.so');
$ffi->prepare();
$tops = $ffi->new('struct template_operations');
$tops->assign = function($key, $value) use ($ffi){
$value = "$key = $value";
$ffi->assign_value_set($value);
// Notice:
// return string value is not supported
return strlen($value);
};
$data = '<div>$foo</div>';
$ret = $ffi->parse($data, FFI::addr($tops));
var_dump($ffi->assign_value_get());
var_dump($ret);
$ffi->finish();
/* Output
string(9) "foo = bar"
string(30) "hsParse finish: [9][foo = bar]"
*/
Here is an example using Haskell.
It assumes that Haskell will create a templating engine from which to interact with PHP.
Use hsc2hs
to use ffi in haskell.
Since the return value can not be a string type, I made it a global variable of C language.
PHP is not originally thread safe, there is no problem, isn't it? maybe...
If it write up to this point, it can call Haskell directly without using C language.
{-# NOINLINE return_value #-}
return_value :: IORef CString
return_value = unsafePerformIO $ newCString "" >>= newIORef
return_value_set :: CString -> IO ()
return_value_set a = writeIORef return_value a
return_value_get :: IO CString
return_value_get = readIORef return_value
<?php
header('Content-Type: text/plain');
$ffi = FFI::cdef('
int hs_init(int *, char **[]);
int hs_exit();
typedef struct template_operations
{
size_t (*assign)(const char * key, const char * value);
} CTOPS;
const char* parse(char *data, struct template_operations *tops);
void return_value_set(const char *value);
const char * return_value_get();
', __DIR__ . '/libcallback.so');
$argc = FFI::new('int');
$argv = FFI::new('char[0]');
$pargv = FFI::addr($argv);
$ffi->hs_init(FFI::addr($argc), FFI::addr($pargv));
$tops = $ffi->new('struct template_operations');
$tops->assign = function($key, $value) use ($ffi){
$value = "$key = $value";
$ffi->return_value_set($value);
// Notice:
// return string value is not supported
return strlen($value);
};
$data = '<div>$foo</div>';
$ret = $ffi->parse($data, FFI::addr($tops));
var_dump($ffi->return_value_get());
var_dump($ret);
$ffi->hs_exit();
/* Output
string(9) "foo = bar"
string(30) "hsParse finish: [9][foo = bar]"
*/
Conclusion
The sample programs I wrote so far are located here:
PHP FFI Samples
2019/05/18
Compile PHP 7.4
mkdir work
cd work
git clone https://github.com/php/php-src.git
cd php-src
git checkout PHP-7.4
./configure --with-ffi
make
cd ..
Checkout Samples
git clone https://github.com/nishimura/php_ffi_samples.git
Start Server with FFI
cd php_ffi_samples
../php-src/sapi/cli/php -d ffi.enable=1 -S localhost:8000
# and access to
# http://localhost:8080/
# http://localhost:8080/1/
# ...
Create DSO Module
require: c build tools
cd 2
make
# and access to
# http://localhost:8080/2/
Other Samples
- 3: call with closure argument
- 4: c struct, php reference and callback
- 5: FFI bridge
- 6, 7: call haskell functions directly
FFI Bridge to Haskell
require: haskell build tools (ghc)
sample5
PHP => FFI C => FFI Haskell => Callback C => Callback PHP => Set c variable instead of return value
sample6, sample7
PHP => FFI Haskell Callback PHP => Set haskell IORef instead of return value
By using FFI, PHP is dedicated to form manipulation and DB calls, and logic can be written in any language you like, such as Haskell, Go, Rust!
Top comments (0)