k-kinzal/ztd-query-mysqli-adapter

MySQLi adapter for ZTD Query

Maintainers

Package info

github.com/k-kinzal/ztd-query-mysqli-adapter

pkg:composer/k-kinzal/ztd-query-mysqli-adapter

Statistics

Installs: 16

Dependents: 0

Suggesters: 0

Stars: 0

v0.1.1 2026-03-06 16:09 UTC

This package is auto-updated.

Last update: 2026-03-07 16:55:17 UTC


README

License: MIT PHP Version

MySQLi adapter for ZTD Query PHP. Drop-in replacement for mysqli that transparently applies Zero Table Dependency query transformation.

Overview

This package provides ZtdMysqli and ZtdMysqliStatement, which extend mysqli and mysqli_stmt respectively. They intercept SQL queries and transform them using CTE (Common Table Expression) shadowing, enabling SQL unit testing without modifying physical databases.

  • Drop-in replacement - ZtdMysqli extends mysqli and is type-compatible everywhere mysqli is expected
  • Transparent rewriting - All queries are automatically rewritten at prepare()/query()/execute_query() time
  • Toggle on/off - Enable or disable ZTD mode at runtime with enableZtd()/disableZtd()
  • Wrap existing connections - Use ZtdMysqli::fromMysqli() to wrap an existing mysqli instance without creating a new connection

Requirements

Installation

composer require --dev k-kinzal/ztd-query-mysqli-adapter

Usage

Creating a New Connection

use ZtdQuery\Adapter\Mysqli\ZtdMysqli;

$mysqli = new ZtdMysqli('localhost', 'user', 'password', 'test');

// Define schema and insert fixture data
$mysqli->query('CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255))');
$mysqli->query("INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.com')");
$mysqli->query("INSERT INTO users (id, name, email) VALUES (2, 'Bob', 'bob@example.com')");

// Query against fixture data (no physical table access)
$stmt = $mysqli->prepare('SELECT * FROM users WHERE id = ?');
$stmt->bind_param('i', $id);
$id = 1;
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
// ['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com']

Wrapping an Existing mysqli Instance

use ZtdQuery\Adapter\Mysqli\ZtdMysqli;

$existingMysqli = new mysqli('localhost', 'user', 'password', 'test');
$ztdMysqli = ZtdMysqli::fromMysqli($existingMysqli);

Testing Write Operations

INSERT/UPDATE/DELETE statements are converted to SELECT queries that return the affected rows:

$mysqli->query('CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255))');
$mysqli->query("INSERT INTO users (id, name) VALUES (1, 'Alice')");

// INSERT returns the inserted row data
$result = $mysqli->query("INSERT INTO users (id, name) VALUES (2, 'Bob')");
$row = $result->fetch_assoc();
// ['id' => 2, 'name' => 'Bob']

// UPDATE returns the updated row data
$result = $mysqli->query("UPDATE users SET name = 'Alice Updated' WHERE id = 1");
$row = $result->fetch_assoc();
// ['id' => 1, 'name' => 'Alice Updated']

// DELETE returns the deleted row data
$result = $mysqli->query("DELETE FROM users WHERE id = 1");
$row = $result->fetch_assoc();
// ['id' => 1, 'name' => 'Alice']

Enabling/Disabling ZTD Mode

$mysqli = new ZtdMysqli('localhost', 'user', 'password', 'test');

// Disable ZTD to execute against physical database
$mysqli->disableZtd();
$mysqli->query('CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255))');

// Re-enable ZTD for testing
$mysqli->enableZtd();

// Check current status
$mysqli->isZtdEnabled(); // true

Affected Row Count

Due to PHP's C extension property handler, $mysqli->affected_rows may not work reliably with ZTD operations. Use the dedicated method instead:

$mysqli->query("INSERT INTO users (id, name) VALUES (1, 'Alice')");
$affectedRows = $mysqli->lastAffectedRows();
// 1

Configuration

use ZtdQuery\Adapter\Mysqli\ZtdMysqli;
use ZtdQuery\Config\ZtdConfig;
use ZtdQuery\Config\UnsupportedSqlBehavior;
use ZtdQuery\Config\UnknownSchemaBehavior;

$config = new ZtdConfig(
    unsupportedBehavior: UnsupportedSqlBehavior::Exception,
    unknownSchemaBehavior: UnknownSchemaBehavior::Exception,
    behaviorRules: [
        'BEGIN' => UnsupportedSqlBehavior::Ignore,
        'COMMIT' => UnsupportedSqlBehavior::Ignore,
        'ROLLBACK' => UnsupportedSqlBehavior::Ignore,
    ],
);

$mysqli = new ZtdMysqli('localhost', 'user', 'password', 'test', config: $config);
Option Values Description
unsupportedBehavior Ignore, Notice, Exception Default behavior when unsupported SQL is executed
unknownSchemaBehavior Passthrough, Exception Behavior when unknown table is referenced
behaviorRules array<string, UnsupportedSqlBehavior> Per-pattern behavior overrides (first match wins)

API Reference

ZtdMysqli

Method Description
__construct($hostname, $username, $password, $database, $port, $socket, $config) Create a new ZTD-wrapped mysqli connection
ZtdMysqli::fromMysqli($mysqli, $config) Wrap an existing mysqli instance
enableZtd() Enable ZTD mode
disableZtd() Disable ZTD mode
isZtdEnabled() Check whether ZTD mode is enabled
lastAffectedRows() Get affected row count from the last ZTD or regular operation
prepare($query) Prepare a statement (rewritten if ZTD enabled)
query($query, $resultMode) Execute a query with ZTD processing
real_query($query) Execute a query without fetching results
execute_query($query, $params) Execute a parameterized query (PHP 8.2+)

All other mysqli methods (begin_transaction, commit, rollback, real_escape_string, etc.) are delegated to the inner mysqli instance. Properties are delegated via __get/__isset.

ZtdMysqliStatement

Extends mysqli_stmt with ZTD-aware behavior. execute(), get_result(), and bind_param() work transparently. Use ztdAffectedRows() to get the ZTD-aware affected row count for write operations.

Development

# Run unit tests
composer test:unit

# Run integration tests (requires Docker)
composer test:integration

# Run all tests
composer test

# Run linter (PHP-CS-Fixer + PHPStan level max)
composer lint

# Fix code style
composer format

License

MIT License. See LICENSE for details.