6

Using PDO::setAttribute, how do I provide the class name when setting PDO::ATTR_DEFAULT_FETCH_MODE to PDO::FETCH_CLASS.

This is the code I am using.. I would like to set it so all of my rows are returned as an instance of DB_Row:

class DB_Row extends ArrayObject {}

$db = new PDO('mysql:dbname=example;host=localhost', 'user', 'pass');
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_CLASS);

$stmt = $db->query("SELECT * FROM `table` WHERE `id` = 1;");

$row = $stmt->fetch(); // I want a DB_Row by default!

The code above results in a PDOException since the DB_Row class name was not assigned.

Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY000]: General    error: No fetch class specified

How would I go about this?

Thanks in advance..

SOLUTION: I used fireeyedboy's answer. It worked the best for my situation as I was already extending PDOStatement for logging purposes...

class DB extends PDO {
    public function __construct($host = null, $user = null, $pass = null, $db = null) {
        try {
            parent::__construct('mysql:dbname=' . $name .';host=' . $host, $user, $pass);
            $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_CLASS);
            $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('DB_Query', array('DB_Row')));
        } catch (PDOException $e) {
            die('Database Error');
        }
    }
}

class DB_Query extends PDOStatement {
    private $class;
    protected function __construct ($class = 'DB_Row') {
        $this->class = $class;
        $this->setFetchMode(PDO::FETCH_CLASS, $this->class);
    }
}

class DB_Row extends ArrayObject {
    public function __set($name, $val) {
        $this[$name] = $val;
    }
    public function __get($name) {
        return $this[$name];
    }
}
McHerbie
  • 2,945
  • 17
  • 20

3 Answers3

5

I think it should return a stdclass instance so this would be a bug - but would have to look it up in the code to verify. will do if there is no accepted answer till then.

What will work is to use PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE and then provide a class name as first column. While this is a hack:

$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE);

$stmt = $db->query("SELECT 'classname', * FROM `table` WHERE `id` = 1;");

EDIT: As promised here the code. The relevant code is here http://lxr.php.net/xref/PHP_5_4/ext/pdo/pdo_stmt.c#940 So setting the for using fetch() is only possible with FETCH_CLASSTYPE. The alternative is using PDOStatement::fetchObject()

johannes
  • 15,807
  • 3
  • 44
  • 57
  • Thanks @johanne! This method does work nicely, though it's cumbersome for me to update all queries used. It would be nice for PDO to allow the class defined outside of the db structure. Specially since in most cases I plan to always use the same class. – McHerbie Oct 03 '11 at 18:21
4

Another kind of hack would be to extend PDOStatement, override its fetch methods and let your PDO instance use that as the default statement class.

As an example I'll just demonstrate overriding fetch()1 and leave fetchAll() and what have you up to you, if you wanna go this route:

class Db_Row
{
}

class PDOStatementWithClass
    extends PDOStatement
{
    private $fetch_class;

    // PHP complained when I tried to make this public
    protected function __construct( $fetch_class = 'StdClass' )
    {
        // internally set the fetch class for later use
        $this->fetch_class = $fetch_class;
    }

    // let $fetch_style default to PDO::FETCH_CLASS in stead of PDO::FETCH_BOTH
    public function fetch( $fetch_style = PDO::FETCH_CLASS, $cursor_orientation = PDO::FETCH_ORI_NEXT, $cursor_offset = 0 )
    {
        // make sure we're really dealing with the correct fetch style
        if( $fetch_style == PDO::FETCH_CLASS )
        {
            // then automatically set the fetch mode of this statement
            parent::setFetchMode( $fetch_style, $this->fetch_class );
        }

        // go ahead and fetch, we should be good now
        return parent::fetch( $fetch_style, $cursor_orientation, $cursor_offset );
    }
}

$db = new PDO( /* etc... */ );
// set default fetch mode to FETCH_CLASS
$db->setAttribute( PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_CLASS );
// override what statement class to use, and provide constructor arguments (found out by trial and error)
$db->setAttribute( PDO::ATTR_STATEMENT_CLASS, array( 'PDOStatementWithClass', array( 'Db_Row' ) ) );

This has the added benefit that you'll only have to define your PDO::FETCH_CLASS once in your application, and not in every query.


1) I'm surprised PHP didn't complain about overriding the signature of the method by the way.
Decent Dabbler
  • 22,532
  • 8
  • 74
  • 106
  • Thanks for that! This is the method that I went about using. The main reason was because I did not want to define the classname in all my queries. Instead of overriding fetch, I just added this to the construct: $this->setFetchMode(PDO::FETCH_CLASS, $this->class); – McHerbie Oct 03 '11 at 17:12
0
public function fetchAll($fetch_style = \PDO::FETCH_CLASS, $fetch_argument = null, $ctor_args = null) {
    if ($fetch_style == \PDO::FETCH_CLASS){
        parent::setFetchMode( $fetch_style, 'App\Core\DBFetch' );
    }
    $fetchedData = call_user_func_array(array('parent', __FUNCTION__), func_get_args());
    return $fetchedData;
}
  • 2
    Hi, please consider adding some explanation on the answer, along with the code snippet. That way the OP can have a better understanding. – DJ22T Oct 18 '18 at 18:47