PHP Driver
High-performance PHP driver for Stoolap. Built as a native PHP extension (C) for minimal overhead.
Installation
Prebuilt Binaries
Download a package matching your PHP version and platform from GitHub Releases. Each archive contains both the PHP extension and the stoolap library.
tar xzf stoolap-v0.3.4-php8.4-linux-x86_64.tar.gz
cd stoolap-v0.3.4-php8.4-linux-x86_64
# Install the shared library
sudo cp libstoolap.so /usr/local/lib/
sudo ldconfig # Linux only
# Install the PHP extension
sudo cp stoolap.so $(php-config --extension-dir)/
Using PIE
Requires the stoolap shared library (libstoolap.so / libstoolap.dylib) installed on your system.
pie install stoolap/stoolap-php
From Source
Requires a C compiler and the stoolap shared library.
cd ext
phpize
./configure --with-stoolap=/path/to/libstoolap
make
sudo make install
Enable the Extension
; php.ini or conf.d/stoolap.ini
extension=stoolap
Or load it per-invocation:
php -d extension=stoolap.so your_script.php
Quick Start
use Stoolap\Database;
$db = Database::open(':memory:');
$db->exec('
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT
)
');
// Insert with positional parameters ($1, $2, ...)
$db->execute(
'INSERT INTO users (id, name, email) VALUES ($1, $2, $3)',
[1, 'Alice', 'alice@example.com']
);
// Insert with named parameters (:key)
$db->execute(
'INSERT INTO users (id, name, email) VALUES (:id, :name, :email)',
['id' => 2, 'name' => 'Bob', 'email' => 'bob@example.com']
);
// Query rows as associative arrays
$users = $db->query('SELECT * FROM users ORDER BY id');
// [['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com'], ...]
// Query single row
$user = $db->queryOne('SELECT * FROM users WHERE id = $1', [1]);
// ['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com']
// Query in raw columnar format (faster, no per-row key creation)
$raw = $db->queryRaw('SELECT id, name FROM users ORDER BY id');
// ['columns' => ['id', 'name'], 'rows' => [[1, 'Alice'], [2, 'Bob']]]
$db->close();
Opening a Database
// In-memory
$db = Database::open(':memory:');
$db = Database::open('');
$db = Database::openInMemory();
// File-based (data persists across restarts)
$db = Database::open('./mydata');
$db = Database::open('file:///absolute/path/to/db');
Database Methods
| Method | Returns | Description |
|---|---|---|
Database::open($dsn) |
Database |
Open a database |
Database::openInMemory() |
Database |
Open an in-memory database |
exec($sql) |
int |
Execute DDL/DML, returns affected rows |
execute($sql, $params) |
int |
Execute parameterized DML, returns affected rows |
executeBatch($sql, $paramsArray) |
int |
Execute with multiple param sets in a transaction |
query($sql, $params?) |
array |
Query rows as associative arrays |
queryOne($sql, $params?) |
?array |
Query first row or null |
queryRaw($sql, $params?) |
array |
Query in columnar format |
prepare($sql) |
Statement |
Create a prepared statement |
begin() |
Transaction |
Begin a read-write transaction |
beginSnapshot() |
Transaction |
Begin a snapshot (read) transaction |
clone() |
Database |
Clone the database handle |
close() |
void |
Close the database |
version() |
string |
Get stoolap engine version |
queryOne() automatically appends LIMIT 1 when the SQL has no LIMIT clause. For prepared statements, add LIMIT 1 yourself since the SQL is fixed at prepare time.
Persistence
File-based databases persist data to disk using WAL (Write-Ahead Logging) and periodic snapshots. Data survives process restarts.
$db = Database::open('./mydata');
$db->exec('CREATE TABLE kv (key TEXT PRIMARY KEY, value TEXT)');
$db->execute('INSERT INTO kv VALUES ($1, $2)', ['hello', 'world']);
$db->close();
// Reopen: data is still there
$db2 = Database::open('./mydata');
$row = $db2->queryOne('SELECT * FROM kv WHERE key = $1', ['hello']);
// ['key' => 'hello', 'value' => 'world']
$db2->close();
Raw Query Format
queryRaw() returns ['columns' => [...], 'rows' => [[...], ...]] instead of an array of associative arrays. Faster when you don’t need named keys.
$raw = $db->queryRaw('SELECT id, name, email FROM users ORDER BY id');
// $raw['columns'] => ['id', 'name', 'email']
// $raw['rows'] => [[1, 'Alice', 'alice@example.com'], [2, 'Bob', 'bob@example.com']]
Batch Execution
Execute the same SQL with multiple parameter sets in a single atomic transaction. SQL is parsed once and reused for every row.
$db->executeBatch(
'INSERT INTO users (id, name, email) VALUES ($1, $2, $3)',
[
[1, 'Alice', 'alice@example.com'],
[2, 'Bob', 'bob@example.com'],
[3, 'Charlie', 'charlie@example.com'],
]
);
// Returns total affected rows (3)
On error, all changes are rolled back (atomic). Also available on transactions via $tx->executeBatch().
Prepared Statements
Prepared statements parse SQL once and reuse the execution plan on every call. No parsing overhead per execution.
$insert = $db->prepare('INSERT INTO users VALUES ($1, $2, $3)');
$insert->execute([1, 'Alice', 'alice@example.com']);
$insert->execute([2, 'Bob', 'bob@example.com']);
$lookup = $db->prepare('SELECT * FROM users WHERE id = $1');
$user = $lookup->queryOne([1]);
// ['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com']
Methods
| Method | Returns | Description |
|---|---|---|
execute($params?) |
int |
Execute DML, returns affected rows |
query($params?) |
array |
Query rows as associative arrays |
queryOne($params?) |
?array |
Query single row or null |
queryRaw($params?) |
array |
Query in columnar format |
sql() |
string |
Get the SQL text |
finalize() |
void |
Release the prepared statement |
Statements are automatically finalized on garbage collection.
Transactions
$tx = $db->begin();
try {
$tx->execute(
'INSERT INTO users VALUES ($1, $2, $3)',
[1, 'Alice', 'alice@example.com']
);
$tx->execute(
'INSERT INTO users VALUES ($1, $2, $3)',
[2, 'Bob', 'bob@example.com']
);
// Read within the transaction (sees uncommitted changes)
$rows = $tx->query('SELECT * FROM users');
$one = $tx->queryOne('SELECT * FROM users WHERE id = $1', [1]);
$raw = $tx->queryRaw('SELECT id, name FROM users');
$tx->commit();
} catch (\Exception $e) {
$tx->rollback();
throw $e;
}
Transactions auto-rollback on garbage collection if not committed.
Batch in Transaction
$tx = $db->begin();
$tx->executeBatch(
'INSERT INTO users VALUES ($1, $2, $3)',
[
[1, 'Alice', 'alice@example.com'],
[2, 'Bob', 'bob@example.com'],
]
);
$tx->commit(); // or $tx->rollback() to undo
Transaction Methods
| Method | Returns | Description |
|---|---|---|
exec($sql) |
int |
Execute DDL/DML without params |
execute($sql, $params) |
int |
Execute parameterized DML |
executeBatch($sql, $paramsArray) |
int |
Execute with multiple param sets |
query($sql, $params?) |
array |
Query rows as associative arrays |
queryOne($sql, $params?) |
?array |
Query single row or null |
queryRaw($sql, $params?) |
array |
Query in columnar format |
commit() |
void |
Commit the transaction |
rollback() |
void |
Rollback the transaction |
Parameters
Both positional and named parameters are supported across all methods:
// Positional ($1, $2, ...)
$db->query('SELECT * FROM users WHERE id = $1 AND name = $2', [1, 'Alice']);
// Named (:key)
$db->query(
'SELECT * FROM users WHERE id = :id AND name = :name',
['id' => 1, 'name' => 'Alice']
);
Error Handling
All methods throw Stoolap\StoolapException (extends \RuntimeException) on errors:
use Stoolap\StoolapException;
try {
$db->execute('INSERT INTO users VALUES ($1, $2)', [1, null]); // NOT NULL violation
} catch (StoolapException $e) {
echo $e->getMessage();
}
Type Mapping
| PHP | Stoolap | Notes |
|---|---|---|
int |
INTEGER |
|
float |
FLOAT |
|
string |
TEXT |
|
bool |
BOOLEAN |
|
null |
NULL |
|
array / object |
JSON |
Auto-encoded/decoded |
DateTimeInterface |
TIMESTAMP |
Converted to nanoseconds |
Building from Source
Requires:
- PHP >= 8.1 with development headers (
php-dev/php-devel) - C compiler (gcc, clang, or MSVC)
- The stoolap shared library (
libstoolap.dylib/libstoolap.so/stoolap.dll), either from Stoolap releases or built from source
git clone https://github.com/stoolap/stoolap-php.git
cd stoolap-php/ext
phpize
./configure --with-stoolap=/path/to/libstoolap
make