Skip to content

Database

Foundation Database provides a configured Doctrine DBAL connection, convenient application tables, and reliable WordPress transactions. Use Doctrine directly for expressions, joins, parameter binding, results, and advanced operations.

Install the runtime package:

composer require stellarwp/foundation-database

Foundation requires PHP 8.3, mysqli, and Doctrine DBAL 4.4.

Database services borrow the active WordPress connection. Use the primary MySQL or MariaDB database and InnoDB tables for transactional application data.

Set a stable, unique application prefix in your root configuration:

<?php declare(strict_types=1);

return [
	'foundation' => [
		'root'   => __DIR__,
		'prefix' => $_ENV['FOUNDATION_PREFIX'] ?? 'your-plugin',
	],
];

After creating the application container, register the database provider before application providers in src/App.php:

use StellarWP\Foundation\Container\Contracts\Provider;
use StellarWP\Foundation\Database\DatabaseProvider;

/** @var list<class-string<Provider>> */
private const array PROVIDERS = [
	DatabaseProvider::class,
];

DatabaseProvider supplies one shared Doctrine\DBAL\Connection. Tables and repositories resolved from the container participate in that connection’s transactions. Registration is lazy: it creates no database tables. Register an application provider afterward when you need custom service bindings.

Application tables declare their stable name and inherit their infrastructure constructor:

In src/Database/Tables/Reports_Table.php:

<?php declare(strict_types=1);

namespace Plugin\Database\Tables;

use StellarWP\Foundation\Database\Table\Table;

final readonly class Reports_Table extends Table {

	public function unprefixedName(): string {
		return 'your_plugin_reports';
	}
}

Inject Reports_Table into your service and call its operations:

$id = $this->reports->insertGetId( [ 'title' => 'Weekly report' ] );

$report = $this->reports->query()
	->select( 'id', 'title' )
	->where( 'id = :id' )
	->setParameter( 'id', $id )
	->fetchAssociative();

Create the table through a migration, then use the query and transaction guide for results, writes, and failure handling.

Table is a supported base class. Inherit its constructor and implement unprefixedName(); application query methods may live on the subclass or in a composed repository. During 2.x, Foundation keeps its final method set (name, quotedName, query, insert, insertGetId, update, and delete) unchanged. New conveniences must avoid colliding with application subclass methods; they can be offered through separate collaborators.

A shared container may be reused after switch_to_blog() between complete operations. Tables resolve the active prefix when called; a query already built retains its original table name. Build a fresh query after switching sites. Run migrations separately for each site, for example wp --url=https://site.example your-plugin migrate --run.

Transactions and migrations capture their starting scope and reject detected site or connection changes. Never switch sites, even temporarily, during those operations.

Applications can replace DatabaseScope and TableNameResolver when they own a different naming policy. Register replacements after DatabaseProvider, before resolving database services. Replacing the Doctrine connection also requires preserving Foundation’s documented transaction and migration guarantees, including terminal failure tracking and acknowledged commits. When using Foundation Migrations, a replacement must also supply a coherent Database\Contracts\AdvisorySession using the same connection: every execution must check ownership and retain terminal failures. Registering an unrelated lock implementation cannot supply those guarantees. A plain DBAL connection alone does not provide those guarantees.

Database\Contracts\AdvisorySession is the supported integration for running work under a database-session advisory lock. DatabaseProvider binds it to the same session used by the managed DBAL connection. Foundation Migrations uses it automatically; ordinary table and transaction consumers continue using their existing APIs.

Implementations acquire once without waiting, retain the original site and connection across DDL and nested transactions, check ownership before execution and completion, and preserve a caught database failure until the locked operation ends. An escaping callback exception takes precedence over release failure. Connection changes must never reconnect or replay protected work. Contention raises AdvisoryLockContended; failed or uncertain ownership raises AdvisoryLockInterrupted.

Database owns the supported DBAL constraint. When upgrading it, verify the migration schema APIs and recovery tests as well as queries and transactions. Foundation Migrations requires a compatible Database release; applications should update their Composer lock file and run their migration tests before deployment.