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:
- Parameter types: Must all be
php::types (such asphp::Int,php::Str,php::Float, etc.) - Return type: Must be a
php::type - Function naming: Must start with the
php_prefix - Stub file: Must have a corresponding
.stub.phpfile 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
resourcetype 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
newto create the object - ✅ Use
{}to wrap it as aphp::Varreturn 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.phpautomatically generated by-m lib; - The
.dlland import library.libon Windows; - The
.soon 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:
- IDE support: provides code hints and autocompletion
- Type checking: the AOT compiler performs type validation at compile time
- 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);
}
📚 Related Resources
- Example project:
examples/prime/ - PHPX framework documentation: [link]
- C++ type system: see NATIVE_TYPES.md
- AOT compiler architecture: see Backend-neutral IR and Core refactoring plan
Last updated: March 18, 2024
Applicable version: PHP AOT Compiler v1.x