现在不推荐使用 fetchAll() 和 FetchMode,如何将结果提取到自定义对象中?

poi*_*rot 10 php doctrine doctrine-orm dbal doctrine-dbal

在一些现有代码上,我有以下语句(经过相当长的查询构建练习):

return $statement->fetchAll(
    DBAL\FetchMode::CUSTOM_OBJECT,
    PublishedLead::class
);
Run Code Online (Sandbox Code Playgroud)

该作品(到目前为止),但我现在都可以看到fetchAll(),并FetchMode都因为DBAL 2.11弃用:

// ResultStatement::fetchAll()
/* 
 * @deprecated Use fetchAllNumeric(), fetchAllAssociative()
 * or fetchFirstColumn() instead.
 */
Run Code Online (Sandbox Code Playgroud)
// FetchMode
/* 
 * @deprecated Use one of the fetch- or iterate-related 
 * methods on the Statement
 */
Run Code Online (Sandbox Code Playgroud)

为了保持我的代码尽可能向前兼容,如何编写它以将结果提取到自定义对象中?我是否必须根据结果编写自定义保湿逻辑,或者 DBAL 可以为我做这件事吗?

sce*_*nia 9

DBAL 3

该 API 对 DBAL 3 进行了一些重大更改。特别是对于这个答案,语句不再重新用于存储和访问结果,因此不再可能执行语句然后简单地循环遍历它。与已执行StatementResult对象不同,从$statement->execute()is not返回的对象Traversable,因此必须将下面建议的代码更改为$result->fetchAllAssociative在循环之前(或在 foreach 语句中)显式调用,但在其他方面仍然兼容(此时变量只是具有不同的类型) :

function getDatabaseResult(): Generator { // change return type hint, if applicable
    // rest of your function/method
    $result = $statement->execute(); // or ->execute($values);
    foreach ($result->fetchAllAssociative() as $row) {
        yield PublishedLead::fromArray($row);
    }
}
Run Code Online (Sandbox Code Playgroud)

DBAL 2

据我通过阅读 DBAL 源代码得知,一般不推荐使用提取模式,而应使用提供的辅助方法,这将结果限制为数值或关联数组。

这意味着将结果编组到您自己的类中的过程现在可能应该在 DBAL 之外处理。这可能是促进 Doctrine ORM 使用的战术决定,或者他们可能只想关注名称中的内容(抽象数据库访问)而忽略与该任务无关的事情。无论哪种方式,编写自定义补水逻辑实际上并不那么复杂,您基本上可以只编写一个 Trait,它提供一个静态方法fromArray($data),该方法迭代数组并设置所有对象属性,然后返回对象(请参阅相应问题的答案) . 在要从关联数组构建的所有类中使用此特征。

我假设你在某个时候循环遍历你的对象数组,所以你实际上可以把你的函数变成一个生成器。如果您最终使用foreach迭代结果集,这甚至不需要使用结果对代码进行任何更改。这意味着用以下循环替换您的 return 语句:

foreach ($statement as $row) {
    yield PublishedLead::fromArray($row);
}
Run Code Online (Sandbox Code Playgroud)

如果您不熟悉生成器,这会将您的函数变成一个返回 a 的函数\Generator,它可以像 foreach 中的数组一样使用,但实际上并不占用整个内存空间来保存所有数据。相反,每当需要下一个值时,您的原始函数将继续执行,直到到达下一个 yield 语句,此时返回并立即使用已生成的值。

此外,如果您想知道,该语句实际上确实实现了Traversable,因此您可以foreach在从中获取它后直接通过它,execute而无需实际调用任何 fetch 方法,这就是我在上面的示例中所做的;$row将是一个关联数组,或更准确地说,是从\PDO::FETCH_BOTH默认获取模式中获得的数组。

这是一个完整的原型:

<?php
// newly created
trait FromArrayTrait {
    public static function fromArray(array $data = []): self {
        foreach (get_object_vars($obj = new self) as $property => $default) {
            $obj->$property = $data[$property] ?? $default;
        }
        return $obj;
    }
}

class PublishedLead {
    use FromArrayTrait; // add this line
    // rest of your class
}

function getDatabaseResult(): Generator { // change return type hint, if applicable
    // rest of your function/method
    // the following 3 lines replace 'return $statement->fetchAll(...);'
    foreach ($statement as $row) {
        yield PublishedLead::fromArray($row);
    }
}

// your actual main code, this is unchanged assuming you already use foreach
foreach (getDatabaseResult() as $lead) {
    $lead->doSomething();
}
Run Code Online (Sandbox Code Playgroud)

显然要考虑命名空间并将这些部分放在代码中的任何位置。顺便说一下,我稍微改变了fromArray方法,所以它使用默认值,以防数组值为空。如果您确实希望能够用 null 替换默认值,请恢复到上面链接的原始版本。如果你想设置动态属性,即使它们没有在你的类中显式声明,循环$data而不是get_object_vars()

    public static function fromArrayDynamic(iterable $data = []): self {
        $obj = new self;
        foreach ($data as $property => $value) {
            $obj->$property = $value;
        }
        return $obj;
    }
Run Code Online (Sandbox Code Playgroud)

当然,如果数组中包含空值,则此方法将用空值覆盖默认值。作为奖励,它与iterable输入兼容,因此它不仅可以使用数组,还可以与 Generators 和 Traversables 一起使用。

  • Doctrine API 的改变是一场灾难。它们打破了 PHP API 中长期存在的命名方式,实际收益为零。 (6认同)