Database and migrations.
Use the container-managed connection, parameterized queries, and explicit one-way migrations.
The database service
Configure a driver in Database.yaml and inject Database. Both select() and statement() accept bindings; never interpolate values into SQL.
<?php
use Lemonade\Framework\Database\Database;
final readonly class UserRepository
{
public function __construct(private Database $database) {}
public function find(int $id): ?array
{
$rows = $this->database->select(
'SELECT id, email FROM users WHERE id = :id',
['id' => $id],
);
return $rows[0] ?? null;
}
}A query builder is available for more involved queries. Database::transaction() uses the same container-managed connection stack.
One-way migrations
A migration implements MigrationInterface, has a stable identifier, and defines up(Schema $schema). Register it with MigrationRegistry from a bootable provider.
<?php
use Lemonade\Framework\Database\Migration\MigrationInterface;
use Lemonade\Framework\Database\Schema\Schema;
final class CreateUsersTable implements MigrationInterface
{
public static function identifier(): string
{
return '20261023090000_create_users';
}
public function up(Schema $schema): void
{
$schema->create('users', static function ($table): void {
$table->id();
$table->string('email', 191)->unique();
});
}
}Run vendor/bin/lemonade database:migrate and inspect status with database:migrate:status. Automatic rollback is not supported.