Alex
Alex

Reputation: 3855

How to fetch the first row as object of class? (PDO)

I borrowed the following wrapper class from phpdelusions:

class DB
{
    protected static $instance = null;

    public static function instance() {
        if (self::$instance === null) {
            $opt  = array(
                PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_CLASS,
                PDO::ATTR_EMULATE_PREPARES   => FALSE,
            );
            $dsn = 'mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset='.DB_CHAR;
            self::$instance = new PDO($dsn, DB_USER, DB_PASS, $opt);
        }
        return self::$instance;
    }

    public static function __callStatic($method, $args) {
        return call_user_func_array(array(self::instance(), $method), $args);
    }

    public static function run($sql, $args = []) {
        $stmt = self::instance()->prepare($sql);
        $stmt->execute($args);
        return $stmt;
    }
}

I've also set up a users table with id, name, and password. Now, I am trying to fetch the first row in the table as an object of class User (assume there is a require_once 'User.php' somewhere, which loads the model class):

$stmt = DB::run('SELECT * FROM users LIMIT 1');
$user = $stmt->fetchAll(PDO::FETCH_CLASS, 'User'); // Array ( [0] => User Object ( ... ) )

It works, but

  1. fetchAll always returns an array, and if the query above is somehow corrupted, the result might be a variable number of User objects, whereas the goal is to always get one, i.e. the first object, even if the query returns multiple rows.
  2. Also, (2) despite PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_CLASS in instance() method of DB which sets the default fetch style to CLASS, I always have to specify PDO::FETCH_CLASS in fetchAll, which I feel is redundant.
  3. Lastly, to actually get the first row, I always need to check $first = (is_array($user)) ? $user[0] : null; since you never know if $user ends up to be empty or not. It would be easier to just fetch the first object and either get a legit row (User object) or just null. It doesn't make sense to get back an array, if your goal is to get one object, does it?

So I tried to change fetchAll to fetch with multiple variations, and here is what I got:

$user = $stmt->fetch('User'); // # 1, FETCH_MODE is already CLASS, but no...
// Warning:  PDOStatement::fetch() expects parameter 1 to be integer, string given

$user = $stmt->fetch(PDO::FETCH_CLASS, 'User'); // # 2
// Warning: PDOStatement::fetch() expects parameter 2 to be integer, string given

$user = $stmt->fetch(); // #3
//  Uncaught PDOException: SQLSTATE[HY000]: General error: No fetch class specified
// so how do you specify it then with fetch() ???

Question

Is it possible to fetch the first row as an object of a given class using fetch alone? Or do I have no choice but write $stmt->setFetchMode(PDO::FETCH_CLASS, 'User'); and then run fetch?

Upvotes: 2

Views: 2043

Answers (2)

Your Common Sense
Your Common Sense

Reputation: 157864

As I see, you are using my PDO wrapper. So you were in one click from the right answer, as there is a sister article Fetching objects with PDO :)

So to make it with fetch(), you will need an extra function call

$stmt->setFetchMode(PDO::FETCH_CLASS, 'User');
$user = $stmt->fetch();

But, as it is rightfully said in the other answer, fetchObject() is better in either way:

$user = DB::run('SELECT * FROM users LIMIT 1')->fetchObject('User');

Upvotes: 2

Xorifelse
Xorifelse

Reputation: 7911

Since you're only wanting 1 object, I can perfectly understand why you don't want to use fetchAll(). But since you want one, why not use fetchObject()?

As this is perfectly capable of being used in a while loop as well. Its like the fetch() for objects.

Returns an instance of the required class with property names that correspond to the column names or FALSE on failure.

public mixed PDOStatement::fetchObject ([ string $class_name = "stdClass" [, array $ctor_args ]] )

Example:

class User{
  protected $data = [];

  public function __get(string $key){
    return $this->data[$key];
  }

  public function __set(string $key, $value){
    $this->data[$key] = $value;
  }

  public function __construct($a, $b, $c){
    echo $a, $b, $c;
    print_r($this->data);
  }
}

$stmt = DB::run('SELECT id, name FROM users LIMIT 1');

if($user = $stmt->fetchObject('User', [1, 2, 3])){
  echo $user->id;
}

Upvotes: 2

Related Questions