<?php
|
|
use \SparkLib\DB;
|
|
|
|
/**
|
|
* SparkFinder
|
|
*
|
|
* A simple iterator to query the database and return SparkRecord objects
|
|
* based on the results.
|
|
*
|
|
* Get one like so:
|
|
*
|
|
* $things = new SparkFinder('RecordClass', 'SELECT * FROM record_table');
|
|
*
|
|
* You may then do
|
|
*
|
|
* while ($record_instance = $things->getNext()) {
|
|
* // Do something with $record_instance
|
|
* }
|
|
*
|
|
* which will (in general) safely run until results are exhausted, or iterate
|
|
* over the results using foreach(), like so:
|
|
*
|
|
* foreach ($things as $id => $record_instance) {
|
|
* // Do something with $record_instance
|
|
* }
|
|
*
|
|
* ...but don't do that, since foreach() is quite likely to eat all of your
|
|
* available memory if you have very many results, as it will try to build
|
|
* the entire array before it ever starts iterating. If you find a way around
|
|
* this, I'd love to hear about it.
|
|
*/
|
|
class SparkFinder extends \SparkLib\Iterator {
|
|
|
|
protected $_recordClass;
|
|
protected $_table = null;
|
|
protected $_tableKey = null;
|
|
protected $_params = null;
|
|
protected $_resultSet = null;
|
|
protected $_query;
|
|
|
|
// We'll track the iterator's position and store results with these:
|
|
protected $_position = 0;
|
|
protected $_row = null;
|
|
|
|
/**
|
|
* Get a new finder. Takes a string containing the class of dinosaur
|
|
* to instantiate, and a query to find the dinosaurs with. Optionally
|
|
* takes an array of parameters for bound queries.
|
|
*/
|
|
public function __construct ($record_class, $querystring = null, $params = null)
|
|
{
|
|
$this->_recordClass = $record_class;
|
|
|
|
/* Careful here - this will usually do the right thing, but class_exists()
|
|
invokes the autoloader, which is sometimes dumb enough to go after a
|
|
file that doesn't exist: */
|
|
|
|
if (! class_exists($this->_recordClass))
|
|
throw new InvalidArgumentException("No such record class: {$this->_recordClass}");
|
|
|
|
// Pull in some metadata from the record class:
|
|
$this->handleClass();
|
|
|
|
// Try for a sane default query, if we know about a table
|
|
// (we might not know about a table if we're dealing with
|
|
// something other than a child of SparkRecord, like a SparkRecordGeneric.
|
|
if (is_null($querystring) && (! is_null($this->_table)))
|
|
$querystring = 'SELECT * FROM ' . $this->_table;
|
|
|
|
$this->_query = $querystring;
|
|
|
|
if (is_array($params))
|
|
$this->_params = $params;
|
|
|
|
$this->doQuery();
|
|
}
|
|
|
|
/**
|
|
* Start over with a fresh run of the query. Here for compatibility with the
|
|
* Iterator interface, and works, but if you're actually using it much there's
|
|
* a better-than-even chance you need to rethink your code.
|
|
*/
|
|
public function rewind ()
|
|
{
|
|
// Reset, if we need to:
|
|
if ($this->_position !== 0) {
|
|
$this->_position = 0;
|
|
$this->doQuery();
|
|
}
|
|
}
|
|
|
|
public function key () { return $this->_row[$this->_tableKey]; }
|
|
public function recordClass () { return $this->_recordClass; }
|
|
public function tableKey () { return $this->_tableKey; }
|
|
public function valid () { return is_array($this->_row); }
|
|
public function position() { return $this->_position; }
|
|
|
|
/**
|
|
* How many rows in the current statement results?
|
|
*/
|
|
public function count ()
|
|
{
|
|
if (! $this->_resultSet)
|
|
return false;
|
|
|
|
return $this->_resultSet->rowCount();
|
|
}
|
|
|
|
/**
|
|
* Advance to the next record.
|
|
*/
|
|
public function next ()
|
|
{
|
|
if (! isset($this->_resultSet))
|
|
throw new Exception('No current results - something bad happened here.');
|
|
|
|
++$this->_position;
|
|
|
|
// Use the next thing from the resultset:
|
|
if ($row = $this->_resultSet->fetch(\PDO::FETCH_ASSOC))
|
|
$this->_row = $row;
|
|
else
|
|
$this->_row = null;
|
|
}
|
|
|
|
/**
|
|
* Return an object for the current record.
|
|
*/
|
|
public function current ()
|
|
{
|
|
// Do we have a record array? Return false if not.
|
|
if (! $this->valid())
|
|
return false;
|
|
|
|
// We've got a record array; make it into a dinosaur.
|
|
$class = $this->_recordClass;
|
|
return new $class($this->_row);
|
|
}
|
|
|
|
/**
|
|
* Load the next record and return an object for it.
|
|
*/
|
|
public function getNext ()
|
|
{
|
|
// Will be a record object or false;
|
|
$result = $this->current();
|
|
|
|
// If we got an object, we'll advance to the next record.
|
|
if (false !== $result)
|
|
$this->next();
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @return string current query
|
|
*/
|
|
public function getQuery ()
|
|
{
|
|
return $this->_query;
|
|
}
|
|
|
|
/**
|
|
* Set up some useful local state based on the dinosaur/record class
|
|
* we're working with.
|
|
*/
|
|
protected function handleClass ()
|
|
{
|
|
// Hack our way around some PHP fail:
|
|
$rc = $this->_recordClass;
|
|
|
|
// If these methods exist, we're probably dealing with a SparkRecord.
|
|
// Otherwise, it's something else.
|
|
if (method_exists($rc, 'getTableKey')) {
|
|
$this->_tableKey = $rc::getDefaultTableKey();
|
|
$this->_table = $rc::getDefaultTableName();
|
|
$this->_tableRecord = $rc::getDefaults();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Talk to our friend, the database, and stash results appropriately.
|
|
*/
|
|
protected function doQuery ()
|
|
{
|
|
if (constant('DB_LOGQUERIES')) {
|
|
\SparkLib\Fail::log($this->_query);
|
|
if (is_array($this->_params)) {
|
|
\SparkLib\Fail::log($this->_params);
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Get a resultset and stash it:
|
|
|
|
if (is_array($this->_params)) {
|
|
$this->_resultSet = $this->getDBH()->prepare($this->_query);
|
|
$this->_resultSet->execute($this->_params);
|
|
} else {
|
|
// If we have no parameters, there's no real purpose to prepping
|
|
// the query, so we may as well spare ourselves the performance hit:
|
|
$this->_resultSet = $this->getDBH()->query($this->_query);
|
|
}
|
|
|
|
} catch (\PDOException $e) {
|
|
$param_str = '';
|
|
if (isset($this->_params)) {
|
|
$param_str = "Params: " . implode(', ', $this->_params) . "\n";
|
|
}
|
|
$message = 'Query failed in SparkFinder: ' . $e->getMessage() . "\n" . $param_str . "\nQuery: " . $this->_query;
|
|
throw new Exception($message);
|
|
}
|
|
|
|
// Pre-load our first result:
|
|
$this->_row = $this->_resultSet->fetch(\PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
protected function getDBH ()
|
|
{
|
|
return DB::getInstance();
|
|
}
|
|
|
|
}
|