Question

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!

Was it helpful?

Solution

Here you go

<?php
class Name {
    public function __construct($data) {
        foreach($data as $key=>$val){
            $this->{$key}=$val;
        }

    }
}

OTHER TIPS

Personally, I would never use the constructor to set the properties of an object. The reason is encapsulation is far more than Getters and Setters. Doing encapsulation like this:

class Person {
    private $birthDate;
    private $parents;

    public function getBirthDate(){
        return $this->birthDate;
    }

    public function setBirthDate($birthDate){
        $this->birthDate = $birthDate;
    }

    public function getParents(){
        return $this->parents;
    }

    public function setParents($parents){
        $this->parents = $parents;
    }
}

is even worse than not doing encapsulation at all, because you're avoiding the main purpose of encapsulation itself: Integrity of your data. In the example above, a birthdate could be easily something that is not a date, or some date in the future. And your parents could not be even an array of mother and father (or father and father or mother and mother....). There is something you should take care of.

In your example, in the constructor you're just checking is not null, but you're not checking anything else. That could make some data that is invalid in your object and you ever noticed until the moment you are going to use it. That's something that, when you debug, could easily take you hours to find the problem.

The correct way to me is to create a static method that returns an instance of an object mapped with the proper getters and setters. Like this:

class Person {
    private $birthDate;
    private $parents;

    public function getBirthDate(){
        if (isset($this->birthDate) {
            return $this->birthDate;
        else {
            return new DateTime('NOW');
        }
    }

    public function setBirthDate($birthDate){
        if ($birthDate instanceof DateTime){
            $this->birthDate = $birthDate;
        } else {
            throw new InvalidArgumentException("Person::birthDate should be an instance of DateTime");
        }
    }

    public function getParents(){
        if (isset($this->parents) {
            return $this->parents;
        else {
            return new array("father" => null, "mother" => null);
        }
    }

    public function setParents($parents){
        if (is_array($parents) && array_keys($parents) == array("father", "mother")){
           if ($parents["father"] instanceof Person && $parents["mother"] instanceof Person){
               $this->parents = $parents;
           } else {
               throw new InvalidArgumentException("Father and Mother should be instances of Person class.");
           }
        } else {
            throw new Invalid ArgumentException("Person::parents should be an array and in must contain only ['father'] and ['mother'] keys.");
        }
    }

   public static function createPersonFromPDO($pdoArray){
       // Check here if $pdoArray is an array and it is a valid array.

       $person = new Person();

       foreach($pdoArray as $key => $value){
            switch($key){
                case "birthDate":
                    $person->setBirthDate($value);
                break;
                case "parents":
                    $person->setParents($value);
                break;
            }
       }

       return $person;
   } 
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top