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

22

我有一段现成的代码,在进行了相当长的查询构建后,我采用了下面的语句:

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个回答

18

DBAL 3

随着DBAL 3的发布,API发生了一些重大变化。特别是对于这个答案来说,语句不再被重复使用以存储和访问结果,因此执行语句,然后简单地循环遍历它已经不再可行。与已执行的Statement不同,从$statement->execute()返回的Result对象不是可迭代的,因此建议下面的代码在循环之前(或直接在foreach语句中)显式调用$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);
    }
}

DBAL 2

根据我从阅读DBAL源代码中了解到的情况,一般来说使用fetch模式已经被弃用,应该使用提供的帮助方法来限制结果为数字或关联数组。

这意味着现在将结果转换为自己的类的过程可能应该在DBAL之外处理。这可能是一种策略性决定,以促进Doctrine ORM的使用,或者他们只想专注于名字中的内容(抽象数据库访问),并留出与该任务没有真正相关的事情。无论哪种方式,编写自定义的数据填充逻辑实际上并不那么复杂,你可以基本上只需编写一个提供静态方法fromArray($data)的Trait,该方法遍历数组并设置所有对象属性,然后返回对象(参见对应问题的答案)。在所有要从关联数组构建的类中使用此trait。

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

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

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

此外,如果您好奇,该语句确实实现了Traversable,因此在从execute获取它后,您可以直接通过foreach进行遍历,而无需调用任何提取方法,这就是我在上面示例中所做的;$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();
}

显然要考虑命名空间,并将这些部分放在代码中应该放置的位置。顺便说一下,我稍微修改了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;
    }

当然,如果数组中包含null,则这个函数将用null覆盖默认值。作为额外的好处,该函数兼容可迭代输入(iterable),因此它不仅可以使用数组,还可以使用生成器和可遍历对象。


4
Doctrine API的改变是一场灾难。它们背离了PHP API中长期存在的命名惯例,却没有实际的好处。 - Acyra
这是一个链接到文档的页面:https://www.doctrine-project.org/2021/03/29/dbal-2.13.html - Mwangi Thiga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接