A modest collection of PHP libraries used at SparkFun.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

223 lines
6.1 KiB

<?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();
}
}