symplely/zend-ffi

Provides the base API for creating extensions, or modifying Zend/PHP internal core with FFI.

Installs: 2 341

Dependents: 1

Suggesters: 0

Security: 0

Stars: 11

Watchers: 2

Forks: 3

Open Issues: 0

Language:C

Type:project

0.12.8 2023-04-14 02:00 UTC

README

zend-ffi tests

Provides the base API for creating extensions, or modifying Zend/PHP internal core with FFI.

  • For PHP 7.4, 8.0, 8.1, 8.2, Windows, Mac, Linux

It allows loading of shared libraries (.dll or .so), calling of C functions and accessing of C type data structures in pure PHP.

This package breaks down the Zend extension API into PHP classes getting direct access to all PHP internal actions. You can actually change PHP default behaviors. You will be able to get the behavior of componere extension without needing it.

Many routines here is a rewrite of Z-Engine library package that use different terminology and setup structure. This package follow/keep PHP C source code style with a skeleton FFi installation process.

There are many extensions on Pecl that hasn't been updated.

Installation

For normal standalone usage.

    composer require symplely/zend-ffi

To setup a skeleton for FFI integration.

    composer create-project symplely/zend-ffi .

Minimum php.ini setting:

extension=ffi
extension=openssl
extension=sockets

zend_extension=opcache

[opcache]
; Determines if Zend OPCache is enabled
opcache.enable=1

; Determines if Zend OPCache is enabled for the CLI version of PHP
opcache.enable_cli=1

[ffi]
; FFI API restriction. Possible values:
; "preload" - enabled in CLI scripts and preloaded files (default)
; "false"   - always disabled
; "true"    - always enabled
ffi.enable="true"

; List of headers files to preload, wildcard patterns allowed. `ffi.preload` has no effect on Windows.
; See headers directory for `your-php-version`, this feature is untested, since not enabled for Windows.
;ffi.preload=path/to/vendor/symplely/zend-ffi/headers/ze(your-php-version).h

;This feature is untested.
;opcache.preload==path/to/vendor/symplely/zend-ffi/preload.php

For a simple FFI integration process create/edit:

  • ffi_extension.json each package/library should list the files to preload, will be process by ffi_preloader.php script.
  • .ignore_autoload.php will be called/executed by composer create-project your_package .cdef/foldername event.
    • This event is only called when your package is installed by composer create-project command.
  • .preload.php for general common FFI functions to be used, change the tag_changeMe skeleton name.
  • .github\workflows\*.yml these GitHub Actions is designed for cross-compiling and committing the binary back to your repo, change some_lib and some_repo skeleton names.
    • The idea of this is to make installation totally self-contained, the necessary third party library binary is bundled in.
    • The CI build Actions is setup for manually runs only.
// Skeleton for `ffi_extension.json` file
{   // The same name to be used in `composer create-project package .cdef/foldername`
    "name": "foldername",
    // Either
    "preload": {
        "files": [
            "path/to/file.php",
            "...",
            "..."
        ],
    // Or
        "directory": [
            "path/to/directory"
        ]
    }
}

Documentation

The functions in preload.php and Functions.php should be used or expanded upon.

See tests folder for examples. Copy/paste the code between --FILE-- and --EXPECT-- blocks in the .phpt files.

For general FFI C data handling see CStruct class.

Functions c_int_type(), c_struct_type(), c_array_type() and c_typedef() are wrappers for any C data typedef turning it into PHP CStruct class instance, with all FFI functions as methods with additional features.

For AST handling:

  • zend_parse_string() will convert PHP source code into native C data zend_ast node held in ZendAst class, use print_ast() to display results.
  • zend_ast_process(function(\ZE\AstProcess $hook){}) to intercept and modify AST after compilation process.

Get the behavior of PHP extensions like nikic/php-ast and sgolemon/astkit that provide low-level bindings to the underlying AST structures, without any addition library.

The whole PHP lifecycle process can be achieved by just extending StandardModule abstract class.

declare(strict_types=1);

require 'vendor/autoload.php';

final class SimpleCountersModule extends \StandardModule
{
    protected string $module_version = '0.4';

    //Represents ZEND_DECLARE_MODULE_GLOBALS macro.
    protected string $global_type = 'unsigned int[10]';

    // Do module startup?
    protected bool $m_startup = true;
    protected bool $r_startup = true;

    // Represents PHP_MINIT_FUNCTION() macro.
    public function module_startup(int $type, int $module_number): int
    {
        echo 'module_startup' . \PHP_EOL;
        return \ZE::SUCCESS;
    }

    // Represents PHP_RINIT_FUNCTION() macro.
    public function request_startup(...$args): int
    {
        echo 'request_startup' . \PHP_EOL;
        $data = $this->get_globals();
        $data[5] = 25;
        return \ZE::SUCCESS;
    }

    // Represents PHP_GINIT_FUNCTION() macro.
    public function global_startup(\FFI\CData $memory): void
    {
        if (\PHP_ZTS) {
            \tsrmls_activate();
        }

        echo 'global_startup' . \PHP_EOL;
        \FFI::memset($this->get_globals(), 0, $this->globals_size());
    }
}

$module = new SimpleCountersModule();
if (!$module->is_registered()) {
    $module->register();
    $module->startup();
}

// Represents ZEND_MODULE_GLOBALS_ACCESSOR() macro.
$data = $module->get_globals();
$module->get_globals('4', 20);
$data[0] = 5;
$data[9] = 15;
var_dump($data);

ob_start();
phpinfo(8);
$value = ob_get_clean();

preg_match('/simple_counters support => enabled/', $value, $matches);
var_dump($matches[0]);

A hack for headers_sent() like errors: PHP Warning: Cannot modify header information - headers already sent by (output started at xxxxxxxx

require 'vendor/autoload.php';

function headers_sent_reset()
{
    zend_sg('headers_sent', 0);
}

echo 'any non-buffered output';
var_dump(headers_sent()); // true

headers_sent_reset();
var_dump(headers_sent()); // false

// This would have otherwise produced warning/errors!
header('Location: http://www.example.com/');

To create proper FFI C library headers from any C ABI library *.h file

Linux: cpp -P -D"__attribute__(ARGS)=" path/to/original/header.h -o ffi_header.h Windows: First download mcpp mcpp -P -D"__attribute__(ARGS)=" path/to/original/header.h -o ffi_header.h

The option -I <directory> might be needed to search/find additional include sources, and the output file will still need editing, mostly 96% proper headers, FFI will complain, just edit/check the 2 lines after the indicated line.

Reference/Credits

Possible Security Risks

The Beginning

Contributing

Contributions are encouraged and welcome; I am always happy to get feedback or pull requests on Github :) Create Github Issues for bugs and new features and comment on the ones you are interested in.

License

The MIT License (MIT). Please see License File for more information.