Alex Alex - 5 days ago 4
PHP Question

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
?

Answer

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;
}
Comments