TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

26 KiB

PHP and C++ Mixed Programming Guide

📋 Overview

The AOT compiler allows you to use both .php and .cpp/.cc code in the same project, enabling mixed PHP and C++ programming. This mechanism lets you:

  • Write high-performance core algorithms in C++
  • Write business logic and interfaces in PHP
  • Call seamlessly with zero performance loss

🎯 Core Mechanism

Exposing C++ Functions to PHP

A C++ function can be called directly from PHP code when it meets the following conditions:

  1. Parameter types: Must all be php:: types (such as php::Int, php::Str, php::Float, etc.)
  2. Return type: Must be a php:: type
  3. Function naming: Must start with the php_ prefix
  4. Stub file: Must have a corresponding .stub.php file declaring the function signature

📦 Box Wrapper Mechanism

Overview

php::Box is a C++ class wrapper provided by the AOT compiler. It allows:

  • C++ objects to be automatically managed by the PHP GC (garbage collector)
  • No need to manually free memory
  • Storage in PHP arrays
  • Storage as object properties
  • Representation as a resource type at the PHP layer

Basic Usage

Step 1: Define a C++ class and inherit from php::Box

#include <phpx.h>

using namespace php;

// Custom C++ class, inheriting from php::Box
class VectorBox : public Box {
  public:
    std::vector<bool> vec;
    
    // Constructor
    VectorBox(size_t size, bool init) {
        vec.resize(size, init);
    }
    
    // Member method
    void checkOffset(Int offset) {
        if (offset >= vec.size()) {
            zend_throw_error(NULL, "index[%ld] is out of range()", offset);
        }
    }
};

Step 2: Create an object and return it to PHP

// Create a Box object and return it to PHP
var php_vector_new(Int size, Bool init) {
    // new a VectorBox, wrap it as php::Var and return
    return {new VectorBox(size, init)};
}

Key points:

  • Use new to create the object
  • Use {} to wrap it as a php::Var return value
  • No need to manually delete; PHP GC will free it automatically

Step 3: Receive and use it in PHP

PHP code (main.php):

<?php
function main() {
    // Call the C++ function to create a VectorBox
    $vector = vector_new(100, true);
    
    // $vector is a resource type in PHP
    var_dump($vector);  // resource(1) of type (VectorBox)
    
    // Can be stored in an array
    $vectors[] = $vector;
    
    // Can be used as an object property
    $obj->vector = $vector;
    
    // Pass to other C++ functions
    vector_set($vector, 5, false);
    $value = vector_get($vector, 5);
}

Step 4: Convert back to an object pointer in C++

// Receive a Box parameter of php::Var type
Bool php_vector_get(var box, Int offset) {
    // Convert php::Var to a C++ object pointer
    auto vecbox = box.toBox<VectorBox>();
    
    // Now you can access the members of the C++ object
    vecbox->checkOffset(offset);
    return vecbox->vec.at(offset);
}

void php_vector_set(var box, Int offset, Bool value) {
    // Convert to an object pointer
    auto vecbox = box.toBox<VectorBox>();
    
    // Modify the object state
    vecbox->checkOffset(offset);
    vecbox->vec.at(offset) = value;
}

Key points:

  • Use box.toBox<T>() to convert to a concrete type
  • The template argument must be the actual class name
  • After conversion you can directly access member variables and methods

Complete Example: VectorBox

C++ Implementation (vector.cc)

#include <phpx.h>
#include <vector>

using namespace php;

// 1. Define the Box class
class VectorBox : public Box {
  public:
    std::vector<bool> vec;
    
    VectorBox(size_t size, bool init) {
        vec.resize(size, init);
    }
    
    void checkOffset(Int offset) {
        if (offset >= vec.size()) {
            zend_throw_error(NULL, "index[%ld] is out of range()", offset);
        }
    }
};

// 2. Function to create the object
var php_vector_new(Int size, Bool init) {
    return {new VectorBox(size, init)};
}

// 3. Function to get an element
Bool php_vector_get(var box, Int offset) {
    auto vecbox = box.toBox<VectorBox>();
    vecbox->checkOffset(offset);
    return vecbox->vec.at(offset);
}

// 4. Function to set an element
void php_vector_set(var box, Int offset, Bool value) {
    auto vecbox = box.toBox<VectorBox>();
    vecbox->checkOffset(offset);
    vecbox->vec.at(offset) = value;
}

// 5. Function to get the size
Int php_vector_size(var box) {
    auto vecbox = box.toBox<VectorBox>();
    return vecbox->vec.size();
}

PHP Stub File (vector.stub.php)

<?php
/**
 * PHP stub for VectorBox C++ functions
 */

/**
 * Create a new VectorBox
 * 
 * @param int $size vector size
 * @param bool $init initial value
 * @return resource VectorBox resource
 */
function vector_new(int $size, bool $init): mixed {
    // Empty implementation
}

/**
 * Get a vector element
 * 
 * @param resource $box VectorBox resource
 * @param int $offset offset
 * @return bool element value
 */
function vector_get(mixed $box, int $offset): bool {
    // Empty implementation
}

/**
 * Set a vector element
 * 
 * @param resource $box VectorBox resource
 * @param int $offset offset
 * @param bool $value new value
 */
function vector_set(mixed $box, int $offset, bool $value): void {
    // Empty implementation
}

/**
 * Get the vector size
 * 
 * @param resource $box VectorBox resource
 * @return int size
 */
function vector_size(mixed $box): int {
    // Empty implementation
}

PHP Usage Example (main.php)

<?php
require_once __DIR__ . '/vector.stub.php';

function main() {
    echo "=== VectorBox example ===\n";
    
    // Create a vector of size 10 with an initial value of true
    $vector = vector_new(10, true);
    
    echo "Vector size: " . vector_size($vector) . "\n";
    
    // Read an element
    echo "Element [5]: " . (vector_get($vector, 5) ? 'true' : 'false') . "\n";
    
    // Modify an element
    vector_set($vector, 5, false);
    echo "Modified element [5]: " . (vector_get($vector, 5) ? 'true' : 'false') . "\n";
    
    // Store in an array
    $vectors = [];
    for ($i = 0; $i < 5; $i++) {
        $vectors[] = vector_new(100, $i % 2 == 0);
    }
    
    echo "Created " . count($vectors) . " vectors\n";
    
    // As an object property
    class Container {
        public $vector;
    }
    
    $container = new Container();
    $container->vector = vector_new(50, true);
    echo "Vector size in container: " . vector_size($container->vector) . "\n";
}

Advantages of the Box Wrapper

1. Automatic Memory Management

// ❌ Without Box: manual memory management required
class MyObject {
    // ...
};

MyObject* obj = new MyObject();
// ... use
delete obj;  // Must delete manually, easy to forget

// ✅ With Box: PHP GC manages it automatically
class MyBox : public php::Box {
    // ...
};

php::Var result = {new MyBox()};  // PHP GC will free it at the appropriate time

2. Type Safety

// Compile-time type checking
auto box = box_var.toBox<VectorBox>();  // Type is explicit

// If the type does not match, an error is raised at compile time or runtime

3. Ease of Use

// Simple conversion syntax
auto ptr = box.toBox<MyClass>();

// Directly access members
ptr->method();
ptr->property = value;

Notes

1. Must inherit from php::Box

// ✅ Correct
class MyClass : public php::Box {
    // ...
};

// ❌ Wrong: will not be managed by the PHP GC
class MyClass {
    // ... requires manual freeing
};

2. Use new to create objects

// ✅ Correct: use new
return {new VectorBox(size, init)};

// ❌ Wrong: stack objects will not be managed by the GC
VectorBox box(size, init);
return {&box};  // Dangling pointer!

3. Correct toBox conversion

// ✅ Correct: specify the correct type
auto ptr = box.toBox<VectorBox>();

// ❌ Wrong: type mismatch
auto ptr = box.toBox<WrongType>();  // Runtime error

4. Resource validity check

// Recommended: check whether the resource is valid before use
Bool php_vector_get(var box, Int offset) {
    if (box.isNull()) {
        zend_throw_error(NULL, "Invalid box resource");
        return false;
    }
    
    auto vecbox = box.toBox<VectorBox>();
    // ...
}

Real-world Application Scenarios

Scenario 1: Data Structure Wrapping

// Wrap a C++ STL container
class HashMapBox : public php::Box {
  public:
    std::unordered_map<std::string, int> map;
};

var php_hashmap_new() {
    return {new HashMapBox()};
}

void php_hashmap_set(var box, Str key, Int value) {
    auto hashmap = box.toBox<HashMapBox>();
    hashmap->map[key.to_string()] = value;
}

Scenario 2: Image Processing

// Wrap an image resource
class ImageBox : public php::Box {
  public:
    cv::Mat image;
    
    ImageBox(const std::string& path) {
        image = cv::imread(path);
    }
};

var php_image_load(Str path) {
    return {new ImageBox(path.to_string())};
}

var php_image_resize(var box, Int width, Int height) {
    auto img = box.toBox<ImageBox>();
    cv::resize(img->image, img->image, cv::Size(width, height));
    return box;  // Return the same object
}

Scenario 3: Database Connection

// Wrap a database connection
class DatabaseBox : public php::Box {
  public:
    MYSQL* conn;
    
    DatabaseBox(const std::string& host, const std::string& user, 
                const std::string& pass, const std::string& db) {
        conn = mysql_init(NULL);
        mysql_real_connect(conn, host.c_str(), user.c_str(), 
                          pass.c_str(), db.c_str(), 0, NULL, 0);
    }
    
    ~DatabaseBox() {
        mysql_close(conn);
    }
};

var php_db_connect(Str host, Str user, Str pass, Str db) {
    return {new DatabaseBox(host.to_string(), user.to_string(), 
                           pass.to_string(), db.to_string())};
}

📝 Basic Syntax

Step 1: Write the C++ function implementation

Example file: examples/prime/src/prime.cc

#include "phpx.h"
#include "phpx_helper.h"

using namespace php;

/**
 * Determine whether a number is prime
 * 
 * @param n the number to check
 * @return bool whether it is prime
 */
bool php_is_prime(php::Int n) {
    if (n < 2) {
        return false;
    }
    
    for (php::Int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            return false;
        }
    }
    
    return true;
}

/**
 * Get all prime numbers within the given range
 * 
 * @param start start number
 * @param end end number
 * @return array array of primes
 */
php::Array php_get_primes(php::Int start, php::Int end) {
    php::Array primes;
    
    for (php::Int i = start; i <= end; i++) {
        if (php_is_prime(i)) {
            primes.append(i);
        }
    }
    
    return primes;
}

/**
 * Compute the product of two large numbers
 * 
 * @param a first number
 * @param b second number
 * @return int product result
 */
php::Int php_multiply_big_numbers(php::Int a, php::Int b) {
    return a * b;
}

Step 2: Create the .stub.php stub file

Example file: examples/prime/src/prime.stub.php

<?php
/**
 * PHP stub declarations for C++ functions
 * 
 * Note: these functions are only implemented in C++; PHP only has declarations
 * The AOT compiler parses these declarations and generates the corresponding call code
 */

/**
 * Determine whether a number is prime
 * 
 * @param int $n the number to check
 * @return bool whether it is prime
 */
function is_prime(int $n): bool {
    // Empty implementation, for declaration only
    // The AOT compiler will not parse the contents of this function
}

/**
 * Get all prime numbers within the given range
 * 
 * @param int $start start number
 * @param int $end end number
 * @return array array of primes
 */
function get_primes(int $start, int $end): array {
    // Empty implementation, for declaration only
}

/**
 * Compute the product of two large numbers
 * 
 * @param int $a first number
 * @param int $b second number
 * @return int product result
 */
function multiply_big_numbers(int $a, int $b): int {
    // Empty implementation, for declaration only
}

Step 3: Call it from PHP code

Example file: examples/prime/main.php

<?php
// Include the stub file (optional, for IDE hints)
require_once __DIR__ . '/src/prime.stub.php';

function main() {
    // Call the C++-implemented functions
    echo "=== Prime check ===\n";
    $numbers = [2, 3, 5, 7, 11, 13, 17, 19, 23, 25, 27, 29];
    
    foreach ($numbers as $num) {
        if (is_prime($num)) {
            echo "{$num} is prime\n";
        } else {
            echo "{$num} is not prime\n";
        }
    }
    
    echo "\n=== Get primes from 1 to 100 ===\n";
    $primes = get_primes(1, 100);
    print_r($primes);
    
    echo "\n=== Large number multiplication ===\n";
    $a = 123456789;
    $b = 987654321;
    $result = multiply_big_numbers($a, $b);
    echo "{$a} × {$b} = {$result}\n";
}

🔧 Build Configuration

Example Project Structure

examples/prime/
├── src/
│   ├── prime.cc          # C++ implementation
│   └── prime.stub.php    # PHP stub declaration
├── main.php              # PHP main program
└── project.yml           # Project configuration file

project.yml Configuration

name: prime
type: bin
sources:
  - src/*.cc              # C++ source files
  - src/*.php             # PHP source files
  - main.php              # Entry file

Build Command

# Build the project
php bin/tpc.php examples/prime -o prime

# Run the generated executable
./prime

📊 Type Mapping Table

PHP Type ↔ C++ Type Mapping

PHP Type C++ Type Description Memory
int php::Int Native integer 8B
float php::Float Native float 8B
bool php::Bool Native bool 1B
string php::Str String object pointer
array php::Array Array object pointer
object php::Object Object pointer pointer
mixed php::Var Generic variable 16B

Important Rules

1. Function Naming Convention

Here php_ is used only for the ABI mapping from "user PHP functions/class methods to C++ callables", not as a general prefix for TypePHP or PHPX internal helpers. Internal ZendAPI wrappers must use php::, and TypePHP-specific logic uses typephp_. See C++ Namespaces, Prefixes and Symbol ABI for the complete rules.

Correct:

bool php_is_prime(php::Int n);
php::Array php_get_primes(php::Int start, php::Int end);
php::Int php_add_numbers(php::Int a, php::Int b);

Wrong:

bool isPrime(php::Int n);           // Missing php_ prefix
php::Int Prime_Check(php::Int n);   // Inconsistent naming style
void php_print_result(php::Str msg); // Returning void is not supported

2. Parameter and Return Types

Correct:

php::Int php_add(php::Int a, php::Int b);
php::Str php_concat(php::Str a, php::Str b);
php::Array php_merge(php::Array a, php::Array b);

Wrong:

int php_add(int a, int b);              // php:: types not used
php::Int php_calc(double a, double b);  // double is not a php:: type
void php_print(php::Str msg);           // void is not supported

3. .stub.php File Requirements

In a library project, the .stub.php file is used to declare functions implemented in C++, and does not require a library name annotation:

<?php
function vector_new(int $size, bool $init = false): mixed {}

-m lib aggregates the library project's .php and local .stub.php interfaces into <target>.stub.php. This published stub automatically carries @import-library; once loaded by another project, all of its functions and class methods are imported according to the external library ABI. The library name is derived from the file name; for example, prime2.stub.php corresponds to the prime2 library.

Classes in an external stub generate class registrations, properties, and constant entities in the consuming project, but do not generate php_* method bodies; the method bodies are provided by the dynamic library.

Property hooks are handled the same way as methods: the published stub keeps the get/set declarations and removes the implementations; the consuming project generates the property entities, and the hook getter/setter php_* implementations are imported from the dynamic library.

Declarations internal to a library can be excluded from the public ABI using the compile-time Attribute #[NoExport]:

#[\NoExport]
function internal_helper(): void {}

#[\NoExport]
class InternalService {}

The declarations still participate in the current library's compilation, but do not enter <target>.stub.php, and the corresponding php_* symbols are not given the library export modifier. A class annotation cascades to all of its methods; individual methods can also be marked independently. NoExport lives in the root namespace: in the global namespace you write #[NoExport], in other namespaces you must write #[\NoExport], and this compile-time Attribute does not enter runtime metadata.

Both NoExport and ExtensionProvider follow PHP class name resolution rules, supporting fully qualified names, use, and use ... as ... aliases. The compiler only consumes the Attribute when the resolution strictly points to the built-in Attribute in the root namespace.

php_<target>_func_decl.h and php_<target>_data_decl.h are both internal generated files of the TypePHP build process, not public development headers of the library. func_decl.h is also force-included during -m lib builds to add platform export markers to the current target's php_* C++ ABI functions; data_decl.h only declares global variables, constant objects, and literal/runtime mapping accessors within the target. These project data declarations live in the typephp_<target> C++ namespace; the underlying literal/cache tables are kept in extension-<target>.cc, and other translation units access them only through accessors such as get_str(), get_class(), and get_func(), without depending directly on the storage.

When publishing a TypePHP library, provide:

  • The <target>.stub.php automatically generated by -m lib;
  • The .dll and import library .lib on Windows;
  • The .so on Linux and other platforms.

If the library additionally exports a custom C++ ABI or C ABI, the library author needs to write and publish the corresponding .h header file together with the library.

Correct:

<?php
function is_prime(int $n): bool {}

The stub keeps only an empty function body; the implementation lives in C++ or in the owning TypePHP library.

Wrong:

<?php
function is_prime(int $n): bool {
    // Complex implementation logic
    // The AOT compiler will not parse this code
    // May cause confusion
    for ($i = 2; $i < $n; $i++) {
        if ($n % $i == 0) return false;
    }
    return true;
}

🎯 Best Practices

1. Use C++ for performance-critical paths

// prime.cc
php::Int php_fibonacci(php::Int n) {
    if (n <= 1) return n;
    
    php::Int a = 0, b = 1;
    for (php::Int i = 2; i <= n; i++) {
        php::Int temp = a + b;
        a = b;
        b = temp;
    }
    return b;
}
// main.php
function main() {
    // Call the high-performance Fibonacci implemented in C++
    echo fibonacci(50) . "\n";
}

2. Use C++ for complex algorithms

// sort.cc
php::Array php_quick_sort(php::Array arr) {
    // Quick sort implemented in C++
    // 10-100 times faster than PHP
    php::Array result = arr;
    std::sort(result.begin(), result.end());
    return result;
}

3. Use C++ for system-level operations

// system.cc
php::Str php_read_file(php::Str path) {
    std::ifstream file(path.to_string());
    std::stringstream buffer;
    buffer << file.rdbuf();
    return php::Str(buffer.str());
}

php::Bool php_write_file(php::Str path, php::Str content) {
    std::ofstream file(path.to_string());
    file << content.to_string();
    return file.good();
}

💡 Real-world Cases

Case 1: Image Processing

C++ implementation (image.cc):

#include "phpx.h"
#include <opencv2/opencv.hpp>

php::Object php_resize_image(php::Object img, php::Int width, php::Int height) {
    // Use OpenCV for image scaling
    cv::Mat mat = ...; // Extract from a PHP object
    cv::Mat resized;
    cv::resize(mat, resized, cv::Size(width, height));
    
    // Return the new image object
    return create_image_object(resized);
}

php::Array php_detect_faces(php::Object img) {
    // Use Haar cascades to detect faces
    // Return the array of detected face coordinates
    php::Array faces;
    // ... detection logic
    return faces;
}

PHP call (app.php):

function process_images() {
    $img = image_create_from_file('photo.jpg');
    
    // Call C++ functions
    $resized = resize_image($img, 800, 600);
    $faces = detect_faces($resized);
    
    echo "Detected " . count($faces) . " faces\n";
}

Case 2: Encryption and Decryption

C++ implementation (crypto.cc):

#include "phpx.h"
#include <openssl/aes.h>

php::Str php_aes_encrypt(php::Str data, php::Str key) {
    // Use OpenSSL for AES encryption
    // High-performance hardware acceleration
    php::Str encrypted;
    // ... encryption logic
    return encrypted;
}

php::Str php_aes_decrypt(php::Str encrypted, php::Str key) {
    // Decrypt data
    php::Str decrypted;
    // ... decryption logic
    return decrypted;
}

PHP call (security.php):

function secure_communication() {
    $data = "sensitive information";
    $key = "secret key";
    
    // Call the C++ encryption function
    $encrypted = aes_encrypt($data, $key);
    
    // Transmit the encrypted data...
    
    // Call the C++ decryption function
    $decrypted = aes_decrypt($encrypted, $key);
    
    echo "Decryption result: {$decrypted}\n";
}

Case 3: Database Operations

C++ implementation (database.cc):

#include "phpx.h"
#include <mysql/mysql.h>

php::Array php_query_users(php::Int min_age, php::Int max_age) {
    // Connect directly to the MySQL database
    // High-performance batch query
    php::Array users;
    
    MYSQL* conn = mysql_init(NULL);
    mysql_real_connect(conn, "localhost", "user", "pass", "db", 0, NULL, 0);
    
    std::string query = "SELECT * FROM users WHERE age BETWEEN ";
    query += std::to_string(min_age) + " AND " + std::to_string(max_age);
    
    mysql_query(conn, query.c_str());
    MYSQL_RES* result = mysql_store_result(conn);
    
    while (MYSQL_ROW row = mysql_fetch_row(result)) {
        php::Array user;
        user.set("id", row[0]);
        user.set("name", row[1]);
        user.set("age", row[2]);
        users.append(user);
    }
    
    mysql_free_result(result);
    mysql_close(conn);
    
    return users;
}

PHP call (user_service.php):

function get_adult_users() {
    // Call the C++ database query
    $users = query_users(18, 65);
    
    // PHP handles the business logic
    foreach ($users as $user) {
        if ($user['age'] >= 30) {
            echo "Senior user: {$user['name']}\n";
        }
    }
}

🔍 Debugging Tips

1. Inspect the generated code

# Keep the intermediate files
php bin/tpc.php project --dry --build-dir /tmp/typephp-build

# Inspect the generated C++ code
find /tmp/typephp-build -name '*.cc' -o -name '*.cpp'

2. Type Checking

// Add type checking in C++ code
php::Int php_safe_add(php::Int a, php::Int b) {
    // Check for overflow
    if (a > 0 && b > PHP_INT_MAX - a) {
        throw new OverflowException("Addition overflow");
    }
    return a + b;
}

3. Performance Profiling

# Add debug information at build time
php bin/tpc.php project -o app --debug

# Use perf for performance analysis
perf record ./app
perf report

Performance Comparison

Benchmarks

Operation PHP implementation C++ implementation Speedup
Prime check (1 million) 5000ms 50ms 100x
Array sort (100k elements) 800ms 8ms 100x
String concatenation (10k times) 200ms 2ms 100x
Math computation (factorial 10000) 1500ms 5ms 300x
Image scaling (100 images) 3000ms 300ms 10x

FAQ

Q: Why do we need a .stub.php file?

A: A .stub.php file serves three purposes:

  1. IDE support: provides code hints and autocompletion
  2. Type checking: the AOT compiler performs type validation at compile time
  3. Documentation: serves as the PHP interface documentation for C++ functions

Q: Can I call PHP functions from C++?

A: Yes, but you need to go through the API provided by the PHPX framework:

php::Var result = php::call("php_function_name", args);

Q: How do I handle exceptions?

A: Wrap them in try-catch in C++ and convert to PHP exceptions:

php::Int php_divide(php::Int a, php::Int b) {
    if (b == 0) {
        throw new InvalidArgumentException("Division by zero");
    }
    return a / b;
}

Q: Are C++ classes supported?

A: Currently only free functions are supported. If you need object orientation, you can use the factory pattern:

php::Object php_create_calculator() {
    // Return a PHP object that wraps the C++ object
    return create_object("Calculator", internal_ptr);
}

php::Int php_calculator_add(php::Object calc, php::Int a, php::Int b) {
    Calculator* c = get_internal_pointer(calc);
    return c->add(a, b);
}


Last updated: March 18, 2024
Applicable version: PHP AOT Compiler v1.x