Extended PDO API

Can you give an example of how you planned on using it? There just doesn’t seem to be any useful public methods for actually initializing the object once you new’ed it. Adding some additional read methods to the class, sure. But it seems like you would really have to go pretty far outside the box to create one of these outside of the PDO object.

Yeah, that’s where I spotted it, but it doesn’t answer why? No other PHP class has a protected constructor nor can they.

It doesn’t even have a __constructor.


php --rc PDOStatement

Its children are allowed to have one.

http://us3.php.net/manual/en/pdo.setattribute.php

Revlevant line "PDO::ATTR_STATEMENT_CLASS: Set user-supplied statement class derived from PDOStatement. Cannot be used with persistent PDO instances. Requires array(string classname, array(mixed constructor_args)). "

The weird thing is this constructor must be protected instead of public like all other constructors in PHP. :\

Certainly. The need grew out of this function which lives in a class called “DataDispatcher”


public function view ( $name, array $params = array()) {
  if (empty($this->views)) {
    $this->setupViews();
  }

  $s = $this->db->prepare("SELECT * FROM {$this->views[$name]['view']}")->match($params)->execute();

  switch ($this->views[$name]['collation']) {
    case 'tree':return $s->fetchTree();
    case 'tier': return $s->fetchCollection();
    default: return $s->fetchIndexed();
  }
}

The object’s collection of views are mysql views which essentially are prestored but repeatedly used queries. Eventually the datadispatcher will cache this information and only execute a database query to build the cache. Fairly low level stuff, but I’m not ready to worry about the caching yet. When a view is selected from we call it by name and display all of its fields (which is why this is one of the only times I use the sql wildcard). I wanted to pass in parameters for the view in case I want to filter the view to a matching set of parameters. That would involve adding a where clause to the statement. But to really do that I think I need to start with a new query. Hence the match function was added to the statement class


/**
 * Append a where condition to the query string where the keys of the array
 * are the field names and the values they hold are what should they should
 * match to.  This function doesn't check for the validity of doing this to
 * the statement.
 * 
 * @param array
 * @return Statement (A new one. The current statement object dies).
 */
public function match( $array ) {
  if (empty($array)) {
    return $this;
  } else {
    $sql = $this->queryString.' WHERE 1';
			
    foreach (array_keys($array) as $value) {
      $sql .= " AND {$value} = :{$value} ";
    }
			
    return $this->db->prepare($sql)->bindArray($array);
  }
}

As can be seen, match needs to create a new statement - to do that it needs a reference to the main pdo db object, something it doesn’t normally have. So that is why it needs to have a constructor.

(Note, I can see that match has drawbacks and problems and I’m not sure yet whether I’m going to keep it for this reason yet or not. For example, what if the user already bound vars to the statement? These will be lost in translation and unless the programmer is familiar with what match really does this might not be obvious. That’s why the comments point out that match creates a new statement object from the query string of the old one, losing anything bound to the old).


protected function __construct( $db ) {
  $this->db = $db;
}

Meanwhile the constructor of the database object itself passes a reference of itself to the statement. Indeed, at the moment constructor redefining is the only thing I’ve done to the root pdo object


class Database extends \\PDO {
  /**
   * CONSTRUCT. Convert the configuration array into a DSN and pass that down to
   * PDO.
   * @param array $config
   */
  public function __construct( array $config ) {
    assert (
      ((isset($config['database']) && $config['database']) || 
        (isset($config['dsn']) && $config['dsn'])) && 
        isset($config['user']) && isset($config['password'])
      );
	
    if (isset($config['dsn'])) {
      // If a DSN is set use it without question.
      parent::__construct($config['dsn'], $config['user'], $config['password']);
    } else {
      // Otherwise hash the vars we were given into a DSN and pass that.
      $params = array(
        'driver' => isset($config['driver']) ? $config['driver'] : 'mysql',
        'database' => $config['database'],
        'user' => $config['user'],
        'password' => $config['password'],
        'server' => isset($config['server']) ? $config['server'] : 'localhost',
        'port' => isset($config['port']) ? $config['port'] : '3306'
      );
		
    // Start underlying PDO library.
      parent::__construct("{$params['driver']}:dbname={$params['database']};host={$params['server']};port={$params['port']}", 
        $params['user'],
        $params['password']
      );
    }
		
    // Now set the standard behaviors of the Gazelle Framework
    $this->setAttribute(self::ATTR_STATEMENT_CLASS, array('Gazelle\\Statement', array($this)));
    $this->setAttribute(self::ATTR_ERRMODE, self::ERRMODE_EXCEPTION);
  }
}

As can be seen, match needs to create a new statement - to do that it needs a reference to the main pdo db object, something it doesn’t normally have. So that is why it needs to have a constructor.

But the code snippet from the manual I posted shows exactly how to pass $db to the derived statement class. Just need to declare the constructor as protected. Clearly pdo does some majic in actually calling creating the statement and calling the constructor but it should still work as advertised.

In any event I suspect you will talk yourself out of putting match() in the statement class.
$statement = $db->prepareWithMatchingParams(“SELECT * FROM $viewName”,$matchParams);

My whole point is that magic creates an inconsistency in the language interface. So again, “What the Hell?”