gyro/query-table-gateway

基于 Doctrine DBAL 对象映射的表/查询网关

dev-master 2019-11-17 10:22 UTC

This package is auto-updated.

Last update: 2024-09-17 21:15:57 UTC


README

这是一个在 Doctrine DBAL (带有一些 ORM 映射支持) 之上的非常简单的表/查询网关库。它需要 PHP 7.4 的属性类型,以便在 SQL 查询和对象之间进行自动映射。

想法是将 SQL 查询、查询元数据中的列/类型信息和完全类型化的目标类组合成一个易于使用的对象关系映射器。

目前此库仅实现了读取功能,这意味着从数据库到对象的读取方向。

安装

通过 Composer

composer require gyro/query-table-gateway

代码中设置

use Doctrine\DBAL\DriverManager;
use Gyro\TableGateway\DBAL\DBALGateway;

$connection = DriverManager::getConnection($config);
$gateway = new DBALGateway($connection);

请参阅以下示例中的 API。

示例

在这个查询中,用户的组织作为分组联系人字符串被加入到结果中,然后通过 explode(',', $string) 映射到一个数组。

<?php

use Gyro\TableGateway\Gateway;

class UserListItem
{
    public int $id;
    public string $name;
    public array $organizations = [];
}

class UserListItemQuery
{
    private Gateway $gateway;

    public function __construct(Gateway $gateway)
    {
        $this->gateway = $gateway;
    }

    public function findLastSeenUsers()
    {
        $sql = 'SELECT u.id, u.name, GROUP_CONTACT(o.name) as organizations
                  FROM user u
            INNER JOIN organization_members om ON om.member_id = u.id
            INNER JOIN organization o ON om.organization_id = o.id
              ORDER BY u.last_seen DESC LIMIT 10';

        return $this->gateway->findBySql(UserListItem::class, $sql);
    }
}

$userListItemQuery = new UserListItemQuery($gateway);
$users = $userListItemQuery->findLastSeenUsers();

操作

网关类提供了以下两种方法

通过 SQL 获取单个对象

interface Gateway
{
    public function findOneBySql(string $className, string $sql, array $parameters = [], array $types = []) : ?object;
}

$sql = 'SELECT * FROM user WHERE email = ?';
$user = $gateway->findOneBySql(UserView::class, $sql, [$email]);

通过 SQL 查找对象列表

interface Gateway
{
    public function findBySql(string $className, string $sql, array $parameters = [], array $types = []) : array;
}

$categories = $gateway->findBySql(CategoryItem::class, 'SELECT * FROM category');

自动类型映射

在将数据库行映射到对象时,此网关查看属性类型,并使用 Doctrine DBAL 类型系统和实际的 SQL 列类型进行转换

在获取时,网关查看字段的 SQL 类型与 PHP 对象类型的组合,并决定使用哪个 Doctrine 类型。

与 Doctrine ORM 的比较

与 Doctrine ORM 相比,以下功能缺失。

  • 没有代理对象和集合以允许透明地遍历具有延迟加载的对象图。
  • 更新实体时没有 UnitOfWork 和更改集检测。
  • 没有 IdentityMap 检测您之前是否已获取过行,并返回相同的实体。
  • 没有 DQL,也没有许多查询功能:您必须手动编写大部分 SQL。
  • 没有 Flush 操作,它会自动将所有可用的实体存储在单个事务中。您必须处理事务管理、外键顺序以及代码中的单个更新/插入操作。
  • 等等...

因此,我实际上建议根据用例同时使用两者。使用 Doctrine 进行 CRUD 和以实体为中心的业务逻辑。使用网关进行读取层和高性能写入吞吐量。

为什么与 Doctrine 结合使用?

我经常看到 Doctrine ORM 在其最佳功能之外使用,尤其是在只读或视图为中心的应用程序中。我们经常看到以下步骤

  1. 执行复杂的 DQL
  2. 转换为 Doctrine 实体
  3. 序列化以用于 API/模板,包括许多 N+1 获取

在这种情况下,Doctrine 的强大功能实际上都没有被使用

  • 在只读场景中,UnitOfWork 和 Identity Map 都是无用的
  • 实体上的业务逻辑将不会使用,只有获取器
  • 结果可能被手动转换为非 Doctrine 实体表示

并且仍然会做以下不好的事情

  • 复杂的 DQL 执行和恢复非常性能密集。
  • 从数据库中获取的列和数据是过度的,包括实体包含的 所有 内容,即使视图可能只需要这个数据的一个子集。
  • 发生了很多 N+1,这是低效的,因为规范化的实体图通常不适合查询

Doctrine的另一个缺点是在高吞吐量写入场景下的性能问题,在这种情况下,你只是更新单列或少数几行,但每分钟你可能要重复操作几十万次。Doctrine会反复进行整个实体激活和更改集计算算法。通过这种网关模式,你可以编写用于单个UPDATE语句的专用小型对象。