Skip to content

marko/database-pgsql

PostgreSQL driver for the Marko framework database layer. Implements ConnectionInterface, QueryBuilderInterface, IntrospectorInterface, and SqlGeneratorInterface from marko/database using PostgreSQL-native features --- JSONB, UUID, SERIAL types, dollar-sign parameter placeholders, and double-quoted identifiers.

Terminal window
composer require marko/database-pgsql

This automatically installs marko/database (the interface package) as a dependency.

Create a configuration file at config/database.php:

config/database.php
<?php
declare(strict_types=1);
return [
'driver' => 'pgsql',
'host' => $_ENV['DB_HOST'] ?? 'localhost',
'port' => (int) ($_ENV['DB_PORT'] ?? 5432),
'database' => $_ENV['DB_DATABASE'] ?? 'marko',
'username' => $_ENV['DB_USERNAME'] ?? 'postgres',
'password' => $_ENV['DB_PASSWORD'] ?? '',
'schema' => 'public',
];

Set these in your .env file:

DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=your_database
DB_USERNAME=postgres
DB_PASSWORD=your_password

Once configured, the PostgreSQL driver is automatically used when you interact with the database. See marko/database for entity definition and repository usage.

use Marko\Database\Connection\ConnectionInterface;
class MyService
{
public function __construct(
private ConnectionInterface $connection,
) {}
public function doSomething(): void
{
// Connection is automatically PostgreSQL
$result = $this->connection->query('SELECT * FROM users');
}
}

The PostgreSQL query builder uses dollar-sign placeholders ($1, $2, …) and double-quoted identifiers. It supports selects, inserts, updates, deletes, joins, ordering, limits, and raw queries:

use Marko\Database\Query\QueryBuilderInterface;
class PostRepository
{
public function __construct(
private QueryBuilderInterface $queryBuilder,
) {}
public function findPublished(): array
{
return $this->queryBuilder
->table('posts')
->select('id', 'title', 'published_at')
->where('status', '=', 'published')
->orderBy('published_at', 'DESC')
->limit(10)
->get();
}
}

PgSqlConnection implements TransactionInterface, providing beginTransaction(), commit(), rollback(), and a transaction() wrapper that auto-commits on success and rolls back on exception:

use Marko\Database\Connection\ConnectionInterface;
$connection->transaction(function () use ($connection): void {
$connection->execute('INSERT INTO accounts (name) VALUES ($1)', ['Acme']);
$connection->execute('INSERT INTO ledger (account, amount) VALUES ($1, $2)', ['Acme', 100]);
});

Nested transactions are not supported --- calling beginTransaction() while already in a transaction throws TransactionException.

This driver supports PostgreSQL 14+. Older versions may work but are not tested.

The default schema is public. You can specify a different schema in the configuration:

'schema' => 'my_schema',

PostgreSQL has excellent support for advanced data types. Marko leverages these native types:

PHP TypePostgreSQL Type
arrayJSONB
DateTimeImmutableTIMESTAMPTZ
BackedEnumVARCHAR (enum values as strings)

PostgreSQL’s JSONB type is fully supported and recommended over JSON for better indexing and query performance:

#[Column(type: 'jsonb')]
public array $metadata = [];

PostgreSQL has native UUID support. Use the type parameter:

#[Column(primaryKey: true, type: 'uuid', default: 'gen_random_uuid()')]
public string $id;

Implements ConnectionInterface and TransactionInterface. Connects lazily on first query.

MethodDescription
connect(): voidEstablish the PDO connection (called automatically)
disconnect(): voidClose the connection
isConnected(): boolCheck if currently connected
query(string $sql, array $bindings = []): arrayExecute a query and return rows as associative arrays
execute(string $sql, array $bindings = []): intExecute a statement and return the affected row count
prepare(string $sql): StatementInterfacePrepare a statement for repeated execution
lastInsertId(): intGet the last inserted ID
beginTransaction(): voidStart a transaction
commit(): voidCommit the current transaction
rollback(): voidRoll back the current transaction
inTransaction(): boolCheck if a transaction is active
transaction(callable $callback): mixedExecute a callback inside an auto-managed transaction

Implements StatementInterface. Wraps a prepared PDO statement.

MethodDescription
execute(array $bindings = []): boolExecute the prepared statement with bindings
fetchAll(): arrayFetch all rows as associative arrays
fetch(): ?arrayFetch the next row, or null if none
rowCount(): intGet the number of affected rows

Implements QueryBuilderInterface. Fluent builder for PostgreSQL queries.

MethodDescription
table(string $table): staticSet the target table
select(string ...$columns): staticChoose columns (defaults to *)
where(string $column, string $operator, mixed $value): staticAdd a WHERE condition
orWhere(string $column, string $operator, mixed $value): staticAdd an OR WHERE condition
whereIn(string $column, array $values): staticAdd a WHERE IN condition
whereNull(string $column): staticAdd a WHERE IS NULL condition
whereNotNull(string $column): staticAdd a WHERE IS NOT NULL condition
join(string $table, string $first, string $operator, string $second): staticINNER JOIN
leftJoin(string $table, string $first, string $operator, string $second): staticLEFT JOIN
rightJoin(string $table, string $first, string $operator, string $second): staticRIGHT JOIN
orderBy(string $column, string $direction = 'ASC'): staticAdd ORDER BY clause
limit(int $limit): staticSet LIMIT
offset(int $offset): staticSet OFFSET
get(): arrayExecute SELECT and return all rows
first(): ?arrayExecute SELECT with LIMIT 1 and return the row or null
insert(array $data): intInsert a row and return the id via RETURNING
update(array $data): intUpdate matching rows and return affected count
delete(): intDelete matching rows and return affected count
count(): intReturn the count of matching rows
raw(string $sql, array $bindings = []): arrayExecute a raw SQL query

Implements IntrospectorInterface. Reads schema metadata from information_schema and pg_catalog.

MethodDescription
getTables(): arrayList all table names in the configured schema
getTable(string $name): ?TableGet full table metadata (columns, indexes, foreign keys)
tableExists(string $name): boolCheck if a table exists
getColumns(string $table): arrayGet column definitions for a table
getIndexes(string $table): arrayGet non-primary-key indexes for a table
getForeignKeys(string $table): arrayGet foreign key constraints for a table
getPrimaryKey(string $table): arrayGet primary key column names

Implements SqlGeneratorInterface. Generates PostgreSQL DDL for schema migrations --- uses SERIAL/BIGSERIAL for auto-increment, double-quoted identifiers, and PostgreSQL-specific types (JSONB, BYTEA, etc.).

MethodDescription
generateUp(SchemaDiff $diff): arrayGenerate forward-migration SQL statements
generateDown(SchemaDiff $diff): arrayGenerate rollback SQL statements
generateCreateTable(Table $table): stringGenerate a CREATE TABLE statement
generateDropTable(string $tableName): stringGenerate a DROP TABLE statement
generateAddColumn(string $table, Column $column): stringGenerate an ALTER TABLE ADD COLUMN statement
generateDropColumn(string $table, string $columnName): stringGenerate an ALTER TABLE DROP COLUMN statement
generateModifyColumn(string $table, Column $column, Column $oldColumn): stringGenerate ALTER COLUMN type/nullability/default changes
generateAddIndex(string $table, Index $index): stringGenerate a CREATE INDEX statement
generateDropIndex(string $table, string $indexName): stringGenerate a DROP INDEX statement
generateAddForeignKey(string $table, ForeignKey $foreignKey): stringGenerate an ADD CONSTRAINT FOREIGN KEY statement
generateDropForeignKey(string $table, string $keyName): stringGenerate a DROP CONSTRAINT statement

Thrown when a PostgreSQL connection fails. Includes the host, port, and database name in the message, with a suggestion to verify server status and credentials.