DQO provides an object representation of SQL database table, row and select query.
- Each database table can be described as
Table
class - Enables column name completion in IDE while writing queries
- Provides a table columns enumeration as constants
- Each row returned from SELECT query can be described as
Row
class - Table specific deserialization recipes can be added to corresponding
Row
class Table
andRow
classes code can be generated with Symfony console command- Provides immutable
DatabaseSelectBuilder
for building SELECT queries
DQO is based on Doctrine DBAL and uses Doctrine Types for data deserialization and Doctrine\DBAL\Connection
for query execution.
Classes representing specific database tables. It contains enumeration of table columns as constants and simplifies field aliasing. Multiple instances can be created with different aliases.
final class UserTable extends GW\DQO\Table
{
public const ID = 'id';
public const EMAIL = 'email';
public const NAME = 'name';
public function id(): string
{
return $this->fieldPath(self::ID);
}
public function email(): string
{
return $this->fieldPath(self::EMAIL);
}
public function name(): string
{
return $this->fieldPath(self::NAME);
}
public function createRow(array $raw): UserRow
{
return new UserRow($raw, $this);
}
}
$userTable = new UserTable('user_alias');
$userTable->table(); // "user"
$userTable->alias(); // "user_alias"
$userTable->id(); // "user_alias.id"
$userTable->selectField(UserTable::ID); // "user_alias.id as user_alias_id"
Classes that can be created to unify data extracting and deserializing from corresponding table.
final class UserRow extends ClientRow
{
public function id(): UserId
{
return $this->getThroughType('UserId', UserTable::ID);
}
public function name(): string
{
return $this->getString(UserTable::NAME);
}
public function email(): Email
{
return Email::fromString($this->getString(UserTable::EMAIL));
}
public function optionalSecondEmail(): ?Email
{
return $this->getThrough([Email::class, 'fromString'], UserTable::OPTIONAL_SECOND_EMAIL);
}
public function about(): ?string
{
return $this->getNullableString(UserTable::NAME);
}
}
$userTable = new UserTable();
$userRow = new UserRow($rowFromQuery, $userTable);
DatabaseSelectBuilder
simplifies construction of SELECT statements using Table
objects.
/** @var Doctrine\DBAL\Connection $connection */
$builder = new GW\DQO\DatabaseSelectBuilder($connection);
$meTable = new UserTable('me');
$friendTable = new UserTable('friend');
$builder
->from($meTable)
->join($friendTable, "{$friendTable->id()} = {$meTable->friendId()}")
->where("{$meTable->username()} = :me", ['me' => 'John Doe'])
->select($friend->name())
->offsetLimit(0, 10);
By default TableRow
expects that table column used in SELECT part has alias as follows: table_alias.column_name as table_alias_column_name
.
There are 2 ways to create such alias:
- Use
Table
methods creating column aliases$table = new UserTable(); $builder = $builder->select(...$table->select(UserTable::ID, UserTable::email)); // or $builder = $builder->select($table->selectField(UserTable::ID), $table->selectField(UserTable::email)); // or $builder = $builder->select(...$table->selectAll());
- Use simply
$table->column()
whenselect()
is aftertable()
orjoin()
$table = new UserTable(); // first add $table to builder so it can recognize `user.id`, `user.email` and create valid aliases... $builder = $builder->from($table); // ...then simply select $builder = $builder->select($table->id(), $table->email());
Query parameters can be specified directly in where/having
method or provided later.
$builder = $builder->from($user)
->where("{$user->name()} = :name", ['name' => 'John Doe'])
->having('orders > :limit', ['limit' => 10]);
// or
$builder = $builder->from($user)
->where("{$user->name()} = :name")
->withParameter('name', 'John Doe');
// or
$builder = $builder->from($user)
->where("{$user->name()} = :name")
->withParameters(['name' => 'John Doe']);
Query parameter types can be specified as where()
argument.
$yesterday = new DateTime('yesterday');
$builder = $builder
->from($user)
->where("{$user->registered()} > :yesterday", ['yesterday' => $yesterday], ['yesterday' => 'datetime']);
You can also define mapping of parameter classes to proper Doctrine type.
$start = new DateTimeImmutable('first day of last month 00:00');
$end = new DateTimeImmutable('last day of last month 23:59');
$builder = $builder
->withTypes([DateTimeImmutable::class => 'datetime_immutable'])
->from($user)
->where("{$user->registered()} BETWEEN :start AND :end", ['start' => $start, 'end' => $end]);
/** @var array<string, mixed>|null $result one result row or null when there are no rows */
$result = $builder->fetch();
/** @var mixed|null $result one column from first result or null when no results */
$result = $builder->fetchColumn();
/** @var array<int, array<string, mixed>> $result fetch all result rows */
$result = $builder->fetchAll();
/**
* @var ArrayValue<array<string, mixed>> $result
* @see https://github.com/gowork/values
*/
$result = $builder->wrapAll();
/** @var int $result */
@result = $builder->count();
composer require gowork/dqo
Add the DatabaseAccessGeneratorBundle to your application's kernel (only on dev
environment):
<?php
public function registerBundles(): array
{
$bundles = [
// ...
];
if ($this->getEnvironment() === 'dev') {
// ...
$bundles[] = new GW\DQO\Symfony\DatabaseAccessGeneratorBundle();
}
...
}
dqo:generate-tables src/Database App/Database table_1 table_2
All queries should extends GW\DQO\Query\AbstractDatabaseQuery
- generate queries for tables
- add command to update table/row with new fields
Used at: