If you want to get a single object from the database, PDO is pretty helpful:
$obj = $handle->fetchAll(PDO::FETCH_CLASS, $obj_name);
If you expect multiple class types to be returned in a single row (e.g.: when doing JOINs), PDO is less helpful:
$handle->fetchAll(PDO::FETCH_ASSOC);
...followed by some data mapping on the resulting array. Fine - except every method I have come up with to achieve this looks horrible.
My question is: how can I instantiate multiple objects from the same array in a graceful, robust manner when the contents of that array may reflect all, none, some or more than those required by the classes?
Example:
Given an array that PDO returns from the database $vars = ('a' => 1, 'b' => 2, 'c' => 3, 'x' => 7, 'y' => 8, 'z' => 9)
, construct the objects ABC
(requiring $a
, $b
and $c)
and XYZ
($x
, $y
, $z
) without too much copy-and-pasting.
Option 1
class ABC {
private $a, $b, $c;
function __construct ($a = null, $b = null, $c = null) {
$this->$a = $a ? $a : $this->a;
$this->$b = $b ? $b : $this->b;
...
(Lots of boilerplate code / copy-and-pasting / requires that the instantiating code has already prepared all of the variables in the correct order)
Option 2
function ABCFactory($vars = array()) {
return new ABC($vars['a'], $vars['b']....
(Better, but will complain if the array key does not exist in $vars. Sure, we could use isset() but that makes the boilerplate issue even worse)
Option 3
foreach ($vars AS $key => $value) {
eval("if (isset(\$key) && \$value) \$this->$key = '$value';");
}
(It works, it's neat, it's tidy, it's easily reusable, it will reflect any new class variables I add ... but it's eval(), and that gives me nightmares! :] )
Solution
dockeryZ hinted at it but mleko provided the answer. I had not heard of variable variables before but they were exactly what I was looking for. I finally went for:
$def_vars = get_object_vars($this);
foreach ($vars AS $key => $value) {
if (array_key_exists($key, $def_vars)) $this->{$key}=$value;
}
However, anyone finding this in the future should bear in mind Johan Perez's answer and not use this snippet as a shortcut in the constructor ... especially if the input array comes from the user's GET or POST parameters!