|
|
# 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
|
|
|
|
|
|
```cpp
|
|
|
#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
|
|
|
|
|
|
```cpp
|
|
|
// 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
|
|
|
<?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++
|
|
|
|
|
|
```cpp
|
|
|
// 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`)
|
|
|
|
|
|
```cpp
|
|
|
#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
|
|
|
/**
|
|
|
* 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
|
|
|
<?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
|
|
|
|
|
|
```cpp
|
|
|
// ❌ 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
|
|
|
|
|
|
```cpp
|
|
|
// 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
|
|
|
|
|
|
```cpp
|
|
|
// Simple conversion syntax
|
|
|
auto ptr = box.toBox<MyClass>();
|
|
|
|
|
|
// Directly access members
|
|
|
ptr->method();
|
|
|
ptr->property = value;
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### Notes
|
|
|
|
|
|
#### ⚠️ 1. Must inherit from php::Box
|
|
|
|
|
|
```cpp
|
|
|
// ✅ 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
|
|
|
|
|
|
```cpp
|
|
|
// ✅ 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
|
|
|
|
|
|
```cpp
|
|
|
// ✅ Correct: specify the correct type
|
|
|
auto ptr = box.toBox<VectorBox>();
|
|
|
|
|
|
// ❌ Wrong: type mismatch
|
|
|
auto ptr = box.toBox<WrongType>(); // Runtime error
|
|
|
```
|
|
|
|
|
|
#### ⚠️ 4. Resource validity check
|
|
|
|
|
|
```cpp
|
|
|
// 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
|
|
|
|
|
|
```cpp
|
|
|
// 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
|
|
|
|
|
|
```cpp
|
|
|
// 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
|
|
|
|
|
|
```cpp
|
|
|
// 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`
|
|
|
|
|
|
```cpp
|
|
|
#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
|
|
|
/**
|
|
|
* 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
|
|
|
<?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
|
|
|
|
|
|
```yaml
|
|
|
name: prime
|
|
|
type: bin
|
|
|
sources:
|
|
|
- src/*.cc # C++ source files
|
|
|
- src/*.php # PHP source files
|
|
|
- main.php # Entry file
|
|
|
```
|
|
|
|
|
|
### Build Command
|
|
|
|
|
|
```bash
|
|
|
# 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](CPP_SYMBOL_NAMING.md) for the complete rules.
|
|
|
|
|
|
✅ **Correct**:
|
|
|
```cpp
|
|
|
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**:
|
|
|
```cpp
|
|
|
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**:
|
|
|
```cpp
|
|
|
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**:
|
|
|
```cpp
|
|
|
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
|
|
|
<?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]`:
|
|
|
|
|
|
```php
|
|
|
#[\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
|
|
|
<?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
|
|
|
<?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
|
|
|
|
|
|
```cpp
|
|
|
// 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;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
```php
|
|
|
// main.php
|
|
|
function main() {
|
|
|
// Call the high-performance Fibonacci implemented in C++
|
|
|
echo fibonacci(50) . "\n";
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 2. Use C++ for complex algorithms
|
|
|
|
|
|
```cpp
|
|
|
// 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
|
|
|
|
|
|
```cpp
|
|
|
// 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`):
|
|
|
```cpp
|
|
|
#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`):
|
|
|
```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`):
|
|
|
```cpp
|
|
|
#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`):
|
|
|
```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`):
|
|
|
```cpp
|
|
|
#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`):
|
|
|
```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
|
|
|
|
|
|
```bash
|
|
|
# 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
|
|
|
|
|
|
```cpp
|
|
|
// 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
|
|
|
|
|
|
```bash
|
|
|
# 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:
|
|
|
```cpp
|
|
|
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:
|
|
|
```cpp
|
|
|
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:
|
|
|
```cpp
|
|
|
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](NATIVE_TYPES.md)
|
|
|
- **AOT compiler architecture**: see [Backend-neutral IR](BACKEND_NEUTRAL_IR.md) and [Core refactoring plan](REFACTORING_PLAN.md)
|
|
|
|
|
|
---
|
|
|
|
|
|
**Last updated**: March 18, 2024
|
|
|
**Applicable version**: PHP AOT Compiler v1.x
|
|
|
|