On some existing code I have the following statement (after a rather long query building exercise):
return $statement->fetchAll(
DBAL\FetchMode::CUSTOM_OBJECT,
PublishedLead::class
);
This works (so far), but I see now that both fetchAll()
and FetchMode
are deprecated since 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
*/
In the interest of keeping my code as forward-compatible as possible, how can write this to fetch the results hydrating into a custom objects? Do I have to write a custom hydrating logic from the results, or can DBAL do this for me?
DBAL 3
The API had some significant changes with DBAL 3. Notably for this answer, statements are no longer re-used for storing and accessing results, so executing the statement and then simply looping over it is no longer possible. Unlike an executed
Statement
, theResult
object returned from$statement->execute()
is notTraversable
, so the code suggested below must be changed to explicitly call$result->fetchAllAssociative
before looping (or right in the foreach statement), but is otherwise still compatible (the variable just has a different type at that point):DBAL 2
As far as I can tell from reading through the DBAL source code, the use of fetch modes in general is deprecated, and the provided helper methods should be used instead, which limits results to either numeric or associative arrays.
This means the process of marshalling results into your own class is probably supposed to be handled outside of DBAL now. This might be a tactical decision to promote the use of Doctrine ORM or maybe they just want to focus on what's in the name (abstracting database access) and leave out things that don't really relate to that task. Either way, writing custom hydrating logic isn't actually that complex, you can basically just write a Trait that offers a static method
fromArray($data)
which iterates through the array and sets all object properties, then returns the object (See the answer to the corresponding question). Use this trait in all classes that you want to build from an associative array.I assume you're looping through your array of objects at some point, so you can actually turn your function into a generator. If you end up using
foreach
to iterate through your result set, this doesn't even require any changes in the code using the result. This would mean replacing your return statement with the following loop:In case you're unfamiliar with generators, this turns your function into one that returns a
\Generator
, which can be used like an array inside a foreach, but doesn't actually take up the entire memory space to hold all the data. Instead, whenever the next value is needed, execution of your original function resumes until the next yield statement is reached, at which point the yielded value is returned and immediately used.Also, in case you're wondering, the statement does in fact implement
Traversable
, so you can simplyforeach
through it right after getting it fromexecute
without actually calling any of the fetch methods, which is what I'm doing in the example above;$row
will be an associative array or more precisely, an array gained from the\PDO::FETCH_BOTH
default fetch mode.Here's a complete prototype:
Obviously consider namespaces and put those parts wherever they should be in your code. By the way, I've slightly changed the
fromArray
method so it uses defaults in case an array value is null. If you actually want to be able to replace defaults with null, revert to the original version linked above. In case you want to set dynamic properties even if they're not explicitly declared in your class, loop through$data
instead ofget_object_vars()
:Of course, this one will overwrite defaults with null if nulls are included in the array. As a bonus, this one is compatible with
iterable
inputs, so it won't only take arrays, it can also work with Generators and Traversables.