Как получить результаты в настраиваемый объект теперь, когда fetchAll () и FetchMode устарели?

В некотором существующем коде у меня есть следующий оператор (после довольно длительного упражнения по построению запроса):

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

Это работает (пока), но теперь я вижу, что оба fetchAll() и FetchMode устарели с DBAL 2.11:

// 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 ответ

Решение

Насколько я могу судить из исходного кода DBAL, использование режимов выборки вообще не рекомендуется, и вместо них следует использовать предоставленные вспомогательные методы, что ограничивает результаты либо числовыми, либо ассоциативными массивами.

Это означает, что теперь процесс маршалинга результатов в ваш собственный класс, вероятно, будет обрабатываться вне DBAL. Это может быть тактическое решение для продвижения использования Doctrine ORM, или, может быть, они просто хотят сосредоточиться на том, что находится в названии (абстрагирование доступа к базе данных), и опустить вещи, которые на самом деле не относятся к этой задаче. В любом случае, написание пользовательской логики гидратации на самом деле не так уж и сложно, вы можете просто написать Trait, который предлагает статический метод. fromArray($data)который выполняет итерацию по массиву и устанавливает все свойства объекта, а затем возвращает объект (см. ответ на соответствующий вопрос). Используйте эту черту во всех классах, которые вы хотите построить из ассоциативного массива.

Я предполагаю, что в какой-то момент вы просматриваете свой массив объектов, поэтому вы действительно можете превратить свою функцию в генератор. Если вы в конечном итоге используете foreachчтобы перебрать ваш набор результатов, это даже не требует каких-либо изменений в коде, использующем результат. Это означало бы заменить ваш оператор return следующим циклом:

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

Если вы не знакомы с генераторами, это превращает вашу функцию в функцию, которая возвращает \Generator, который можно использовать как массив внутри foreach, но на самом деле он не занимает все пространство памяти для хранения всех данных. Вместо этого, когда требуется следующее значение, выполнение исходной функции возобновляется до тех пор, пока не будет достигнут следующий оператор yield, после чего полученное значение будет возвращено и немедленно использовано.

Кроме того, если вам интересно, оператор действительно реализует Traversable, поэтому вы можете просто foreach через это сразу после получения от execute без фактического вызова каких-либо методов выборки, что я и делаю в приведенном выше примере; $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(): iterable { // 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 input, поэтому он не только принимает массивы, но также может работать с генераторами и Traversables.

Другие вопросы по тегам