Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Brennen Bearnes
committed
Sep 26, 2014
1 parent
53a1412
commit badd374
Showing
2 changed files
with
1,198 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
<?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(); | ||
} | ||
|
||
} |
Oops, something went wrong.