-
Notifications
You must be signed in to change notification settings - Fork 0
FAQ
Frequently-asked questions from issues, discussions, and the questions you would have asked second.
All three. The package is a Composer-friendly facade over the InitORM stack:
-
DBAL —
InitPHP\Database\Databasewraps the PDO connection and exposes a CRUD surface. -
Query Builder — chainable
select,where,join, … available on the same object. -
ORM —
InitPHP\Database\ModelandInitPHP\Database\Entitygive you active-record-style models.
Pick the layer that fits the use case; you don't have to commit to one.
So application code can refer to \InitPHP\Database\Database (matching the Composer package name initphp/database) without dragging the InitORM namespace into user-facing code. It also bundles one piece of original code — the DataTables.js helper — that lives in this package only.
PHP 8.1 and later. Tested on 8.1, 8.2, 8.3, 8.4. See Installation.
Anything PDO supports. The package ships per-driver SQL dialects for MySQL, PostgreSQL, SQLite, and a generic fallback. MariaDB works through the MySQL dialect. SQL Server / Oracle / SQL Anywhere work through the generic dialect but are not actively tested.
That is the new 5.0 behaviour — createImmutable() enforces "set exactly once". If you need to swap the shared connection (test fixtures, multi-tenant routing), use DB::replaceImmutable(). See Migration Guide.
Yes — construct a Database directly:
$db = new InitPHP\Database\Database($credentials);
$db->select('*')->from('users')->read();The facade is a convenience over a single shared Database; nothing in the package requires it. See Recipe — Repository Pattern.
DB::connect([...]) returns a fresh Database that does not touch the facade slot. For models, set protected ?array $credentials = [...] on the subclass. See Multiple Connections.
:memory: is per PDO handle. Each call to DB::connect([':memory:']) opens a fresh, empty database. Either share one Database instance throughout the test, or use a file path.
You do not — the package escapes every identifier automatically. See Reserved Keywords.
Known limitation in initorm/query-builder 2.x — parameters bound inside the group() callback do not propagate to the outer builder's bag, so the SQL ships with unbound placeholders. Workaround: build the search clause as a raw fragment with explicit setParameter() calls. See Query Builder and Recipe — Search & Filters.
DB::like('title', $query); // defaults to 'both' — produces %query%
DB::like('title', $query, 'before'); // %query
DB::like('title', $query, 'after'); // query%startLike() / endLike() are friendlier wrappers around the latter two.
Yes:
DB::whereIn('id', DB::subQuery(function ($b) {
$b->select('user_id')->from('orders')->where('total', '>', 1000);
}));DB::enableQueryLog();
// run your code
print_r(DB::getQueryLogs());Each log entry includes the prepared SQL, the bound args, and the elapsed time. See Query Logging.
PDOStatement::rowCount() is unreliable for SELECT statements on SQLite and on unbuffered MySQL connections. Fetch with rows() and count() the array. See CRUD Operations.
The package has no native replace() — the operation is not standard SQL. Use DB::query() with raw SQL; see Recipe — Upsert / REPLACE INTO for MySQL / PostgreSQL / SQLite spellings.
DB::create('posts', ['title' => 'Hello']);
$id = DB::insertId(); // string|false from PDO::lastInsertIdYes. The compiler unions the keys across all rows; missing columns are emitted as NULL:
DB::createBatch('posts', [
['title' => 'A', 'tags' => 'php'],
['title' => 'B'], // tags = NULL
]);Not through the builder. DB::query('INSERT ... RETURNING id', $params) works on databases that support it (PostgreSQL, SQLite, MySQL 8.0).
The conversion is camelCase → snake_case. Posts → posts, BlogPost → blog_post, APIToken → api_token. If you don't like the result, set $schema explicitly.
By default no — update() adds WHERE deleted_at IS NULL automatically. If you need to update a soft-deleted row (e.g. restore it), drop down to the underlying Database:
$model->getDatabase()->update('posts', ['deleted_at' => null], ['id' => 13]);Set $createdField / $updatedField to null on the model, or bypass the model entirely:
$model->getDatabase()->update('posts', ['title' => 'X'], ['id' => 13]);
// no updated_at touchedPDO's FETCH_CLASS writes properties directly on the object, bypassing __set and therefore bypassing your mutator. The package does this on read() for performance reasons. If you need transformation on read, define a get{Column}Attribute() accessor instead — accessors always run.
It bypasses the entity's attribute bag and writes a dynamic property instead. PHP 8.2 deprecated dynamic properties; a future PHP release will make them fatal. Write through $this->setAttribute('column', $value). See Entities.
| Field | Source |
|---|---|
recordsTotal |
Count of all rows the chain produces, without the global search filter. |
recordsFiltered |
Count of rows the chain produces with the global search filter applied. |
When no search is active they are equal. When a search is active recordsFiltered ≤ recordsTotal.
One recordsTotal count, one recordsFiltered count, one page query. Three round-trips is the minimum DataTables needs to render its UI correctly. The protocol does not offer a way to skip any of them.
Yes — the constructor takes any DatabaseInterface (or ModelInterface):
$dt = new Datatables($yourDatabaseInstance);Not currently. The protocol's columns[i].search.value is ignored; only the global search.value is applied. See DataTables — Advanced for the workaround if you need it.
Yes — that's how the package tests itself. tests/Support/SqliteHelper.php shows the pattern.
protected function setUp(): void
{
DB::replaceImmutable(null);
DB::createImmutable($testCredentials);
}The package's own tests/DBTest.php follows this pattern.
testMode: true on transaction() always rolls back, even on the success path. The closure can write freely, run assertions, and the database walks away untouched.
DB::transaction(function ($db) {
$db->create('users', $row);
self::assertSame(1, $db->affectedRows());
}, testMode: true);The database server is unreachable. Check host / port / firewall. See Debugging and Troubleshooting.
The PDO driver extension for your database is not installed. On Debian/Ubuntu: apt install php-mysql / php-pgsql / php-sqlite3. Check with php -m | grep pdo.
Not in a public issue. The org-wide SECURITY.md describes the private disclosure channels.
If your question isn't here, open a Discussion — common questions migrate to this page from there.
initphp/database · MIT License · part of the InitPHP family
Source · Issues · Discussions · Packagist · Contributing · Security Policy
Getting Started
Core API
ORM
Advanced
DataTables Helper
Recipes
- Index
- — Pagination
- — Search & Filters
- — Upsert / REPLACE INTO
- — Audit Log
- — DataTables Bootstrap
- — Repository Pattern
Reference
Migration & Help