A lightweight PHP class that lets you define C-style structs using PHP FFI, enabling memory-efficient, typed data structures for high-performance workloads.
Instead of juggling large PHP arrays with unpredictable memory usage, Struct lets you allocate a fixed-size, type-safe memory block for predictable performance and compact memory layout — just like in C.
This project is inspired by the simplicity of C data structures, adapted for PHP developers who need predictable memory layouts and FFI-level performance without writing C extensions.
- Simulation / physics data storage
- Game development prototypes
- Data streaming and compression
- Machine learning preprocessing in PHP
- Memory-efficient caches or lookup tables
- 💡 Inspiration
- 🧠 Example Use Cases
- 🚀 Features
- 🧰 Requirements
- 🧪 Installation
- 🧱 Example Usage
- 🔍 API Reference
⚠️ Caveats- ⚙️ Performance Benchmark
- 📄 License
- Define custom C structs at runtime using
typedef - Store thousands of records in fixed-size, contiguous memory
- Typed memory fields (
int,float, etc.) - Implements
IteratorandCountable - Provides
sizeof()and total memory usage - Ideal for numerical data, simulation, or high-frequency workloads
- PHP 8.0+
- FFI extension enabled (
--with-ffi) - CLI SAPI (FFI is not enabled by default in web contexts)
Install via Composer once packaged:
composer require rayblair06/php-struct<?php
$count = 100_000;
$typedef = "
typedef struct {
int id;
float value;
} Record;
";
$records = new Struct($typedef, "Record", $count);
// Populate with data
for ($i = 0; $i < $count; $i++) {
$records->set($i, [
'id' => $i,
'value' => (float)$i * 1.5
]);
}
// Retrieve a record at specific index
print_r($records->get(0));
echo 'Struct size: ' . $records->sizeof() . " bytes\n";
echo 'Total memory: ' . $records->totalBytes() . " bytes\n";Create a new struct array of a given type and size.
$records = new Struct($typedef, "Record", 10_000);Set field values for a struct at the given index.
$records->set(0, ['id' => 1, 'value' => 42.0]);Retrieve all field values as an associative array.
$record = $records->get(0);Get total number of structs allocated.
echo count($records);Get the size in bytes of a single struct instance.
Get total allocated memory size (count × sizeof).
Access the underlying raw C buffer (advanced usage).
- Only primitive C types (
int,float,char, etc.) are supported. - The FFI extension must be explicitly enabled in your PHP environment and is only meant for CLI, daemon, or worker processes.
- Structs are stored in native memory — do not exceed memory limits.
- Works best for read-heavy, data-structured workloads (not objects with complex behavior).
By default, FFI::new() allocates memory inside PHP’s managed heap, not raw C memory.
That means the memory used by your struct arrays is automatically freed when the Struct object is destroyed or goes out of scope.
There’s no need to manually call FFI::free() or perform explicit cleanup.
However:
- In long-running scripts (e.g., daemons or workers), memory will only be reclaimed once objects are destroyed.
- To release memory sooner, you can call:
unset($records);
gc_collect_cycles();For most PHP workloads — including large data processing or game loops — you can safely rely on PHP’s automatic memory management.
If you use multithreading (e.g., pthreads, parallel, or ReactPHP workers):
- FFI instances are not thread-safe.
- Each thread/process must create its own FFI context.
This should be fine in CLI single-threaded s.
FFI does not support:
- Flexible array members (
struct { int n; float values[]; }) - Bitfields
- Complex unions or function pointers
Keep your typedefs simple: plain scalar fields or nested structs.
C compilers naturally pad structs to align fields on word boundaries. PHP’s FFI follows your platform’s ABI rules, so:
typedef struct {
char a;
int b;
} Example;may take 8 bytes instead of 5 (due to padding).
Always use $struct->sizeof() to get the true per-item size.
Don’t assume it’s the sum of field sizes.
Storing text requires care:
typedef struct {
char name[32];
} Record;You can assign strings to char[] via:
FFI::memcpy($struct->name, "Hello", 6);But reading them back requires manual null-termination checks. It’s better to avoid strings inside structs unless you know what you’re doing.
- On Windows,
long= 32 bits - On Linux/macOS,
long= 64 bits
So avoid platform-dependent types like long — use int32_t, int64_t, etc., or int, float, double for consistency.
- FFI doesn’t type-check at runtime the way PHP does.
- Assigning an invalid value (e.g., a string to a float field) will be silently truncated or converted.
For production-critical code, consider adding lightweight PHP-side validation before writing to the struct buffer.
Struct data lives in native memory — it’s not serializable via PHP’s normal mechanisms (serialize(), json_encode(), etc.) without converting to arrays.
You can, however, export easily:
$data = array_map(fn($r) => $r, iterator_to_array($records));Because this library uses native C data types via PHP FFI, floating-point values are stored as 32-bit floats, not PHP’s default 64-bit double.
This can lead to small rounding differences when reading values back into PHP — for example:
$record->value = 3.14; // stored as C float
var_dump($record->value); // float(3.140000104904175)This is expected behavior for IEEE 754 32-bit floats and not a bug.
If you need to compare floats in tests or application logic, use approximate comparisons instead of strict equality:
$this->assertEqualsWithDelta(3.14, $record['value'], 0.000001);Or, if you require full double-precision, you can modify your typedefs to use double instead of float:
typedef struct {
int id;
double value;
} Record;To compare Struct memory usage with standard PHP data structures, run the included benchmark script:
php -d memory_limit=512M bin/benchmarks.phpThe script allocates 500,000 records using four different approaches:
| Type | Description | Expected Memory Footprint |
|---|---|---|
| Struct | Fixed-size native memory buffer using FFI | 🔹 Lowest (~a few MB) |
| Array | Standard PHP associative arrays | |
| stdClass | Dynamically assigned object properties | |
| RecordClass | User-defined class instances |
- The Struct implementation consumes ~30–70× less memory than PHP arrays or objects for the same data.
- Performance remains comparable for reads and writes.
- This efficiency scales linearly with the number of records, making it ideal for large datasets or real-time numerical applications.
MIT © 2025 — Crafted for performance and clarity.