This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Dibi is a PHP database abstraction library (DBAL) providing a unified interface for multiple database systems (MySQL, PostgreSQL, SQLite, Oracle, Firebird, ODBC). It is a standalone library, not an application.
- PHP Version: 8.2 - 8.5
- Homepage: https://dibi.nette.org
- Author: David Grudl (Nette framework creator)
# Run all tests
composer run tester
# Run specific test file
vendor/bin/tester tests/dibi/DataSource.phpt -s
# Run tests in a directory
vendor/bin/tester tests/dibi/ -s
# Static analysis (PHPStan Level 5)
composer run phpstancomposer installConnection Layer (src/Dibi/Connection.php):
- Main entry point for all database operations
- Manages connection lifecycle, transactions, and query execution
- Event system for profiling/logging
- Lazy connection initialization
Driver Architecture (src/Dibi/Drivers/):
- 27+ driver implementations (MySqli, PDO, PostgreSQL, SQLite3, Oracle, Firebird, ODBC, etc.)
- Each driver implements:
Driver,ResultDriver, andReflectorinterfaces - Handles database-specific SQL escaping, type conversion, and LIMIT/OFFSET injection
- Key files:
MySqliDriver.php,PdoDriver.php,PostgreDriver.php,Sqlite3Driver.php
Query Building (src/Dibi/Fluent.php):
- Fluent interface for building dynamic SQL queries
- Methods:
select(),from(),where(),join(),orderBy(),limit(), etc. - Supports SELECT, INSERT, UPDATE, DELETE operations
- Cloning support for query variants
Parameter Translation (src/Dibi/Translator.php):
- Most complex component - converts parameter modifiers to driver-specific escaping
- Handles modifiers:
%s,%i,%n,%d,%dt,%and,%or,%a,%v, etc. - Smart array expansion for IN clauses
- Conditional SQL blocks with
%if...%end - Expression support via
LiteralandExpression
Result Handling (src/Dibi/Result.php, src/Dibi/Row.php):
- Lazy loading of rows from drivers
- Multiple fetch methods:
fetch(),fetchAll(),fetchAssoc(),fetchPairs(),fetchSingle() - Automatic type detection and conversion
- Iterator implementation for memory-efficient processing
Database Reflection (src/Dibi/Reflection/):
- Schema introspection API for tables, columns, indexes, foreign keys
- Works through
Reflectorinterface implemented by each driver - Key classes:
Database,Table,Column,Index,ForeignKey
Framework Integration (src/Dibi/Bridges/):
Nette/DibiExtension3.php- Nette 3.x DI container integrationTracy/Panel.php- Tracy debugger panel for query visualization
Defined in src/Dibi/interfaces.php:
Driver- Database-specific operations (query, escape, reflection)ResultDriver- Result set handlingReflector- Schema introspectionIConnection- Connection contractIDataSource- Generic data source abstraction
src/Dibi/dibi.php provides a static facade for global access to the default connection instance (backward compatibility layer).
- Strict types required:
declare(strict_types=1)in all files - Tabs for indentation (not spaces)
- Single quotes for strings (except HTML)
- Two empty lines between methods
- PSR-12 compliant with Nette modifications
- Type declarations: All properties, parameters, and return types must be typed
- Return type and opening brace on separate lines:
public function example(
string $param,
array $options,
): void
{
// method body
}- Never duplicate signature information without adding value
- Document array contents:
@return string[] - Use two spaces after type/param:
@return string description - Only document when explaining additional context, limitations, or unusual usage
- For exceptions, describe the problem in active voice without "Exception that is thrown when"
- Avoid abbreviations unless full name is too long
- UPPERCASE for two-letter abbreviations, PascalCase/camelCase for longer ones
- PascalCase for classes, camelCase for methods/properties
- Never use prefixes like
Abstract,Interface, orI
Tests use Nette Tester with .phpt extension:
<?php
declare(strict_types=1);
use Tester\Assert;
use Dibi\Connection;
require __DIR__ . '/bootstrap.php';
$conn = new Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
// Test description as first parameter
Assert::same(3, $conn->dataSource('SELECT * FROM products')->count());
Assert::exception(
fn() => $conn->query('INVALID SQL'),
Dibi\Exception::class,
'SQL error message',
);- Each test file covers a specific class or feature
- Use
Assert::same()for strict comparisons - Use
Assert::match()for pattern matching with SQL - Use
Assert::exception()for exception testing - Tests are located in
tests/dibi/ - Test data SQL files in
tests/dibi/data/
When modifying or adding drivers:
- Implement core interfaces:
Driver,ResultDriver,Reflector - Handle type escaping for: text, binary, identifier, boolean, date, datetime
- SQL injection methods: How to add LIMIT/OFFSET to queries
- Result handling: Create driver-specific
*Resultclass - Schema reflection: Create driver-specific
*Reflectorclass
Modifiers are handled in src/Dibi/Translator.php. The translation logic maps modifiers to driver-specific escaping:
- Check
formatValue()method for value formatting - Update
modifier()method for new modifier types - Test with multiple drivers
Result processing happens in two stages:
- Driver-level:
ResultDriverimplementation returns raw data - Abstraction-level:
Resultclass processes and normalizes data
All exceptions inherit from Dibi\Exception. Specific exceptions in src/Dibi/exceptions.php:
ConstraintViolationExceptionForeignKeyConstraintViolationExceptionNotNullConstraintViolationExceptionUniqueConstraintViolationException
The library supports these database systems (drivers in src/Dibi/Drivers/):
- MySQL/MariaDB (via MySqli or PDO)
- PostgreSQL (native or PDO)
- SQLite 3 (native or PDO)
- MS SQL Server (2012+, PDO or sqlsrv)
- Oracle (OCI8 or PDO)
- Firebird/Interbase
- ODBC
- DummyDriver for testing
Basic value modifiers:
%s- string (NULL if value is null)%sN- string, but '' translates as NULL%bin- binary data%b- boolean%i- integer%iN- integer, but 0 translates as NULL%f- float%d- date (accepts DateTime, string or UNIX timestamp)%dt- datetime (accepts DateTime, string or UNIX timestamp)%n- identifier (table or column name, handles dots for qualified names)%N- identifier, treats period as ordinary character (for aliases, database names)%SQL- direct SQL insertion (alternative:Dibi\Literal)%ex- expands array of SQL expressions%lmt- adds LIMIT clause%ofs- adds OFFSET clause
LIKE modifiers for pattern matching:
%like~- expression starts with string%~like- expression ends with string%~like~- expression contains string%like- expression matches string
When passing arrays as parameters:
%and-key1 = value1 AND key2 = value2 AND ...%or-key1 = value1 OR key2 = value2 OR ...%a(assoc) -key1 = value1, key2 = value2, ...(for UPDATE SET)%lor%in(list) -(val1, val2, ...)(for IN clauses)%v(values) -(key1, key2, ...) VALUES (value1, value2, ...)(for INSERT)%m(multi) - multiple value rows for batch INSERT%by(ordering) -key1 ASC, key2 DESC ...(for ORDER BY)%n(names) -key1, key2 AS alias, ...(for SELECT columns)
Example usage:
// UPDATE with %a
$database->query('UPDATE users SET %a', ['name' => 'Jim', 'year' => 1978]);
// WHERE with %and
$database->query('SELECT * FROM users WHERE %and', ['name' => 'Jim', 'active' => true]);
// INSERT with %v
$database->query('INSERT INTO users %v', ['name' => 'Jim', 'year' => 1978]);
// Multiple INSERT with %m
$database->query('INSERT INTO users %m', [
['name' => 'Jim', 'year' => 1978],
['name' => 'Jack', 'year' => 1987],
]);Modifiers in array keys (for UPDATE):
$database->query('UPDATE table SET', [
'date%SQL' => 'NOW()',
'title' => 'normal value',
]);
// UPDATE table SET `date` = NOW(), `title` = 'normal value'Literal SQL (bypass escaping):
$database->literal('NOW()'); // returns Dibi\Literal objectExpressions (with parameters):
$database::expression('SHA1(?)', 'secret'); // returns Dibi\Expression objectConditional SQL (%if, %else, %end):
$database->query('
SELECT *
FROM table
%if', isset($user), 'WHERE user=%s', $user, '%end
ORDER BY name
');Nested conditions in WHERE (arrays without keys):
$database->query('SELECT * FROM table WHERE %and', [
'number > 10',
'number < 100',
['%or', ['left' => 1, 'top' => 2]],
]);
// WHERE (number > 10) AND (number < 100) AND (`left` = 1 OR `top` = 2)Table/column substitutions (prefixes):
$database->substitute('blog', 'wp_');
$database->query("UPDATE [:blog:items] SET [text]='Hello'");
// UPDATE `wp_items` SET `text`='Hello'Advanced fetchAssoc() - Reshapes flat JOIN results into nested arrays:
// Simple associative array by key
$result->fetchAssoc('id'); // returns [$id => $row, ...]
// Two-level nesting
$result->fetchAssoc('customer_id|order_id');
// returns [$customerId => [$orderId => $row, ...], ...]
// With intermediate row object
$result->fetchAssoc('customer_id->order_id');
// returns [$customerId => $row with ->order_id[$orderId], ...]
// With sequential array for duplicate keys
$result->fetchAssoc('name[]order_id');
// returns [$name => [[$orderId => $row], [$orderId2 => $row2]], ...]The descriptor syntax:
|- creates new array level (associative by key)->- inserts row object as intermediate element[]- creates sequential array for duplicate keys
Four methods for transaction handling:
$database->beginTransaction();
$database->commit();
$database->rollback();
// Or using callback (auto-commits on success, auto-rolls back on exception)
$database->transaction(function () use ($database) {
$database->query('...');
$database->query('...');
});Query inspection without execution:
$database->test('SELECT * FROM users WHERE id = ?', $id);
// Echoes the SQL that would be executedResult dumping:
$result->dump(); // Outputs result as HTML tableQuery statistics (available via static facade):
dibi::$sql; // Latest SQL query executed
dibi::$elapsedTime; // Duration in seconds
dibi::$numOfQueries; // Total number of queries
dibi::$totalTime; // Total execution timeLogging configuration:
$database = new Dibi\Connection([
'driver' => 'mysqli',
// ... connection params
'profiler' => [
'file' => 'queries.log',
],
]);Manual type specification:
$result->setType('id', Dibi\Type::INTEGER);
$result->setType('price', Dibi\Type::FLOAT);
$row = $result->fetch();
// Now $row->id is guaranteed to be intDateTime support:
$database->query('INSERT INTO users', [
'created' => new DateTime,
'expires' => new DateTime('+1 year'),
]);Dibi automatically converts bracket/backtick notation to database-specific format:
$database->query("UPDATE `table` SET [status]='value'");
// MySQL: UPDATE `table` SET `status`='value'
// ODBC: UPDATE [table] SET [status]='value'- Zero production dependencies - Core library is completely standalone
- Lazy connections - Database connections are established only when first query is executed
- Event system - Use
Connection::$onEventfor query profiling and logging - Multi-database testing - Tests run against MySQL, PostgreSQL, and SQLite configurations
- PHPStan Level 5 - All code must pass static analysis at level 5
- SQL injection protection - NEVER concatenate user input into SQL, always use placeholders or modifiers