gowork/dqo

数据库查询对象


README

简介

DQO 提供了 SQL 数据库表、行和选择查询的对象表示。

特性

  • 每个数据库表都可以描述为 Table
  • 在编写查询时启用 IDE 中的列名自动完成
  • 提供常量作为表列枚举
  • 从 SELECT 查询返回的每一行都可以描述为 Row
  • 可以为相应的 Row 类添加特定的反序列化配方
  • 可以使用 Symfony 控制台命令生成 TableRow 类的代码
  • 提供不可变的 DatabaseSelectBuilder 用于构建 SELECT 查询

DQO 基于 Doctrine DBAL,并使用 Doctrine 类型进行数据反序列化,以及 Doctrine\DBAL\Connection 进行查询执行。

Table 定义

表示特定数据库表的类。它包含表列的枚举常量,并简化了字段别名。可以创建多个实例,使用不同的别名。

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"

TableRow 定义

可以创建的类,用于统一从相应表提取和反序列化数据。

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 构建 SELECT 查询

DatabaseSelectBuilder 简化了使用 Table 对象构建 SELECT 语句。

/** @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);

SELECT 列别名

默认情况下,TableRow 预期 SELECT 部分中使用的表列具有以下别名:table_alias.column_name as table_alias_column_name

有 2 种方法来创建这样的别名

  • 使用 Table 方法创建列别名
    $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());
  • select()table()join() 之后时,简单地使用 $table->column()
    $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());

查询参数

查询参数可以直接在 where/having 方法中指定,也可以稍后提供。

$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']);

可以指定 where() 参数的查询参数类型。

$yesterday = new DateTime('yesterday');
$builder = $builder
    ->from($user)
    ->where("{$user->registered()} > :yesterday", ['yesterday' => $yesterday], ['yesterday' => 'datetime']); 

您还可以定义参数类到正确 Doctrine 类型的映射。

$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

设置

Symfony

将 DatabaseAccessGeneratorBundle 添加到您的应用程序的内核(仅适用于 dev 环境)

<?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

表查询模式

所有查询都应扩展 GW\DQO\Query\AbstractDatabaseQuery

待办事项

  • 为表生成查询
  • 添加命令以更新表/行的新字段

关于

使用于