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

  1. <?php
  2. use \SparkLib\DB;
  3. /**
  4. * SparkFinder
  5. *
  6. * A simple iterator to query the database and return SparkRecord objects
  7. * based on the results.
  8. *
  9. * Get one like so:
  10. *
  11. * $things = new SparkFinder('RecordClass', 'SELECT * FROM record_table');
  12. *
  13. * You may then do
  14. *
  15. * while ($record_instance = $things->getNext()) {
  16. * // Do something with $record_instance
  17. * }
  18. *
  19. * which will (in general) safely run until results are exhausted, or iterate
  20. * over the results using foreach(), like so:
  21. *
  22. * foreach ($things as $id => $record_instance) {
  23. * // Do something with $record_instance
  24. * }
  25. *
  26. * ...but don't do that, since foreach() is quite likely to eat all of your
  27. * available memory if you have very many results, as it will try to build
  28. * the entire array before it ever starts iterating. If you find a way around
  29. * this, I'd love to hear about it.
  30. */
  31. class SparkFinder extends \SparkLib\Iterator {
  32. protected $_recordClass;
  33. protected $_table = null;
  34. protected $_tableKey = null;
  35. protected $_params = null;
  36. protected $_resultSet = null;
  37. protected $_query;
  38. // We'll track the iterator's position and store results with these:
  39. protected $_position = 0;
  40. protected $_row = null;
  41. /**
  42. * Get a new finder. Takes a string containing the class of dinosaur
  43. * to instantiate, and a query to find the dinosaurs with. Optionally
  44. * takes an array of parameters for bound queries.
  45. */
  46. public function __construct ($record_class, $querystring = null, $params = null)
  47. {
  48. $this->_recordClass = $record_class;
  49. /* Careful here - this will usually do the right thing, but class_exists()
  50. invokes the autoloader, which is sometimes dumb enough to go after a
  51. file that doesn't exist: */
  52. if (! class_exists($this->_recordClass))
  53. throw new InvalidArgumentException("No such record class: {$this->_recordClass}");
  54. // Pull in some metadata from the record class:
  55. $this->handleClass();
  56. // Try for a sane default query, if we know about a table
  57. // (we might not know about a table if we're dealing with
  58. // something other than a child of SparkRecord, like a SparkRecordGeneric.
  59. if (is_null($querystring) && (! is_null($this->_table)))
  60. $querystring = 'SELECT * FROM ' . $this->_table;
  61. $this->_query = $querystring;
  62. if (is_array($params))
  63. $this->_params = $params;
  64. $this->doQuery();
  65. }
  66. /**
  67. * Start over with a fresh run of the query. Here for compatibility with the
  68. * Iterator interface, and works, but if you're actually using it much there's
  69. * a better-than-even chance you need to rethink your code.
  70. */
  71. public function rewind ()
  72. {
  73. // Reset, if we need to:
  74. if ($this->_position !== 0) {
  75. $this->_position = 0;
  76. $this->doQuery();
  77. }
  78. }
  79. public function key () { return $this->_row[$this->_tableKey]; }
  80. public function recordClass () { return $this->_recordClass; }
  81. public function tableKey () { return $this->_tableKey; }
  82. public function valid () { return is_array($this->_row); }
  83. public function position() { return $this->_position; }
  84. /**
  85. * How many rows in the current statement results?
  86. */
  87. public function count ()
  88. {
  89. if (! $this->_resultSet)
  90. return false;
  91. return $this->_resultSet->rowCount();
  92. }
  93. /**
  94. * Advance to the next record.
  95. */
  96. public function next ()
  97. {
  98. if (! isset($this->_resultSet))
  99. throw new Exception('No current results - something bad happened here.');
  100. ++$this->_position;
  101. // Use the next thing from the resultset:
  102. if ($row = $this->_resultSet->fetch(\PDO::FETCH_ASSOC))
  103. $this->_row = $row;
  104. else
  105. $this->_row = null;
  106. }
  107. /**
  108. * Return an object for the current record.
  109. */
  110. public function current ()
  111. {
  112. // Do we have a record array? Return false if not.
  113. if (! $this->valid())
  114. return false;
  115. // We've got a record array; make it into a dinosaur.
  116. $class = $this->_recordClass;
  117. return new $class($this->_row);
  118. }
  119. /**
  120. * Load the next record and return an object for it.
  121. */
  122. public function getNext ()
  123. {
  124. // Will be a record object or false;
  125. $result = $this->current();
  126. // If we got an object, we'll advance to the next record.
  127. if (false !== $result)
  128. $this->next();
  129. return $result;
  130. }
  131. /**
  132. * @return string current query
  133. */
  134. public function getQuery ()
  135. {
  136. return $this->_query;
  137. }
  138. /**
  139. * Set up some useful local state based on the dinosaur/record class
  140. * we're working with.
  141. */
  142. protected function handleClass ()
  143. {
  144. // Hack our way around some PHP fail:
  145. $rc = $this->_recordClass;
  146. // If these methods exist, we're probably dealing with a SparkRecord.
  147. // Otherwise, it's something else.
  148. if (method_exists($rc, 'getTableKey')) {
  149. $this->_tableKey = $rc::getDefaultTableKey();
  150. $this->_table = $rc::getDefaultTableName();
  151. $this->_tableRecord = $rc::getDefaults();
  152. }
  153. }
  154. /**
  155. * Talk to our friend, the database, and stash results appropriately.
  156. */
  157. protected function doQuery ()
  158. {
  159. if (constant('DB_LOGQUERIES')) {
  160. \SparkLib\Fail::log($this->_query);
  161. if (is_array($this->_params)) {
  162. \SparkLib\Fail::log($this->_params);
  163. }
  164. }
  165. try {
  166. // Get a resultset and stash it:
  167. if (is_array($this->_params)) {
  168. $this->_resultSet = $this->getDBH()->prepare($this->_query);
  169. $this->_resultSet->execute($this->_params);
  170. } else {
  171. // If we have no parameters, there's no real purpose to prepping
  172. // the query, so we may as well spare ourselves the performance hit:
  173. $this->_resultSet = $this->getDBH()->query($this->_query);
  174. }
  175. } catch (\PDOException $e) {
  176. $param_str = '';
  177. if (isset($this->_params)) {
  178. $param_str = "Params: " . implode(', ', $this->_params) . "\n";
  179. }
  180. $message = 'Query failed in SparkFinder: ' . $e->getMessage() . "\n" . $param_str . "\nQuery: " . $this->_query;
  181. throw new Exception($message);
  182. }
  183. // Pre-load our first result:
  184. $this->_row = $this->_resultSet->fetch(\PDO::FETCH_ASSOC);
  185. }
  186. protected function getDBH ()
  187. {
  188. return DB::getInstance();
  189. }
  190. }