如何将结果提取到自定义对象中,因为 fetchAll() 和 FetchMode 已被弃用?

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

return $statement->fetchAll(
    DBAL\FetchMode::CUSTOM_OBJECT,
    PublishedLead::class
);

这有效(到目前为止),但我现在看到两者都自DBAL 2.11以来已被弃用:fetchAll()FetchMode

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

为了保持我的代码尽可能向前兼容,如何编写此代码来获取结果,并将其解压缩到自定义对象中?我是否必须根据结果编写自定义补水逻辑,或者 DBAL 是否可以为我执行此操作?


答案 1

德巴尔 3

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

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);
    }
}

德巴尔 2

据我所知,从阅读 DBAL 源代码可以看出,通常不推荐使用获取模式,并且应该使用提供的帮助器方法,这会将结果限制为数字或关联数组。

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

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

foreach ($statement as $row) {
    yield PublishedLead::fromArray($row);
}

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

另外,如果您想知道,该语句实际上确实实现了 ,因此您可以在从中获取它后立即通过它,而无需实际调用任何fetch方法,这就是我在上面的示例中所做的; 将是一个关联数组,或者更准确地说,是从默认提取模式获得的数组。Traversableforeachexecute$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();
}

显然,请考虑命名空间,并将这些部分放在代码中应该位于的任何位置。顺便说一句,我稍微改变了方法,因此在数组值为空的情况下,它使用默认值。如果您确实希望能够将默认值替换为 null,请还原到上面链接的原始版本。如果要设置动态属性,即使它们未在类中显式声明,请循环而不是:fromArray$dataget_object_vars()

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

当然,如果数组中包含 null,则此函数将使用 null 覆盖默认值。作为奖励,这个与输入兼容,因此它不仅可以使用数组,还可以与生成器和可遍历器一起使用。iterable


答案 2

推荐