Statement.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Db
  17. * @subpackage Statement
  18. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /**
  23. * @see Zend_Db
  24. */
  25. require_once 'Zend/Db.php';
  26. /**
  27. * @see Zend_Db_Statement_Interface
  28. */
  29. require_once 'Zend/Db/Statement/Interface.php';
  30. /**
  31. * Abstract class to emulate a PDOStatement for native database adapters.
  32. *
  33. * @category Zend
  34. * @package Zend_Db
  35. * @subpackage Statement
  36. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  37. * @license http://framework.zend.com/license/new-bsd New BSD License
  38. */
  39. abstract class Zend_Db_Statement implements Zend_Db_Statement_Interface
  40. {
  41. /**
  42. * @var resource|object The driver level statement object/resource
  43. */
  44. protected $_stmt = null;
  45. /**
  46. * @var Zend_Db_Adapter_Abstract
  47. */
  48. protected $_adapter = null;
  49. /**
  50. * The current fetch mode.
  51. *
  52. * @var integer
  53. */
  54. protected $_fetchMode = Zend_Db::FETCH_ASSOC;
  55. /**
  56. * Attributes.
  57. *
  58. * @var array
  59. */
  60. protected $_attribute = array();
  61. /**
  62. * Column result bindings.
  63. *
  64. * @var array
  65. */
  66. protected $_bindColumn = array();
  67. /**
  68. * Query parameter bindings; covers bindParam() and bindValue().
  69. *
  70. * @var array
  71. */
  72. protected $_bindParam = array();
  73. /**
  74. * SQL string split into an array at placeholders.
  75. *
  76. * @var array
  77. */
  78. protected $_sqlSplit = array();
  79. /**
  80. * Parameter placeholders in the SQL string by position in the split array.
  81. *
  82. * @var array
  83. */
  84. protected $_sqlParam = array();
  85. /**
  86. * @var Zend_Db_Profiler_Query
  87. */
  88. protected $_queryId = null;
  89. /**
  90. * Constructor for a statement.
  91. *
  92. * @param Zend_Db_Adapter_Abstract $adapter
  93. * @param mixed $sql Either a string or Zend_Db_Select.
  94. */
  95. public function __construct($adapter, $sql)
  96. {
  97. $this->_adapter = $adapter;
  98. if ($sql instanceof Zend_Db_Select) {
  99. $sql = $sql->assemble();
  100. }
  101. $this->_parseParameters($sql);
  102. $this->_prepare($sql);
  103. $this->_queryId = $this->_adapter->getProfiler()->queryStart($sql);
  104. }
  105. /**
  106. * @param string $sql
  107. * @return void
  108. */
  109. protected function _parseParameters($sql)
  110. {
  111. $sql = $this->_stripQuoted($sql);
  112. // split into text and params
  113. $this->_sqlSplit = preg_split('/(\?|\:[a-zA-Z0-9_]+)/',
  114. $sql, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
  115. // map params
  116. $this->_sqlParam = array();
  117. foreach ($this->_sqlSplit as $key => $val) {
  118. if ($val == '?') {
  119. if ($this->_adapter->supportsParameters('positional') === false) {
  120. /**
  121. * @see Zend_Db_Statement_Exception
  122. */
  123. require_once 'Zend/Db/Statement/Exception.php';
  124. throw new Zend_Db_Statement_Exception("Invalid bind-variable position '$val'");
  125. }
  126. } else if ($val[0] == ':') {
  127. if ($this->_adapter->supportsParameters('named') === false) {
  128. /**
  129. * @see Zend_Db_Statement_Exception
  130. */
  131. require_once 'Zend/Db/Statement/Exception.php';
  132. throw new Zend_Db_Statement_Exception("Invalid bind-variable name '$val'");
  133. }
  134. }
  135. $this->_sqlParam[] = $val;
  136. }
  137. // set up for binding
  138. $this->_bindParam = array();
  139. }
  140. /**
  141. * Remove parts of a SQL string that contain quoted strings
  142. * of values or identifiers.
  143. *
  144. * @param string $sql
  145. * @return string
  146. */
  147. protected function _stripQuoted($sql)
  148. {
  149. // get the character for delimited id quotes,
  150. // this is usually " but in MySQL is `
  151. $d = $this->_adapter->quoteIdentifier('a');
  152. $d = $d[0];
  153. // get the value used as an escaped delimited id quote,
  154. // e.g. \" or "" or \`
  155. $de = $this->_adapter->quoteIdentifier($d);
  156. $de = substr($de, 1, 2);
  157. $de = str_replace('\\', '\\\\', $de);
  158. // get the character for value quoting
  159. // this should be '
  160. $q = $this->_adapter->quote('a');
  161. $q = $q[0];
  162. // get the value used as an escaped quote,
  163. // e.g. \' or ''
  164. $qe = $this->_adapter->quote($q);
  165. $qe = substr($qe, 1, 2);
  166. $qe = str_replace('\\', '\\\\', $qe);
  167. // get a version of the SQL statement with all quoted
  168. // values and delimited identifiers stripped out
  169. // remove "foo\"bar"
  170. $sql = preg_replace("/$q($qe|\\\\{2}|[^$q])*$q/", '', $sql);
  171. // remove 'foo\'bar'
  172. if (!empty($q)) {
  173. $sql = preg_replace("/$q($qe|[^$q])*$q/", '', $sql);
  174. }
  175. return $sql;
  176. }
  177. /**
  178. * Bind a column of the statement result set to a PHP variable.
  179. *
  180. * @param string $column Name the column in the result set, either by
  181. * position or by name.
  182. * @param mixed $param Reference to the PHP variable containing the value.
  183. * @param mixed $type OPTIONAL
  184. * @return bool
  185. */
  186. public function bindColumn($column, &$param, $type = null)
  187. {
  188. $this->_bindColumn[$column] =& $param;
  189. return true;
  190. }
  191. /**
  192. * Binds a parameter to the specified variable name.
  193. *
  194. * @param mixed $parameter Name the parameter, either integer or string.
  195. * @param mixed $variable Reference to PHP variable containing the value.
  196. * @param mixed $type OPTIONAL Datatype of SQL parameter.
  197. * @param mixed $length OPTIONAL Length of SQL parameter.
  198. * @param mixed $options OPTIONAL Other options.
  199. * @return bool
  200. */
  201. public function bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
  202. {
  203. if (!is_int($parameter) && !is_string($parameter)) {
  204. /**
  205. * @see Zend_Db_Statement_Exception
  206. */
  207. require_once 'Zend/Db/Statement/Exception.php';
  208. throw new Zend_Db_Statement_Exception('Invalid bind-variable position');
  209. }
  210. $position = null;
  211. if (($intval = (int) $parameter) > 0 && $this->_adapter->supportsParameters('positional')) {
  212. if ($intval >= 1 || $intval <= count($this->_sqlParam)) {
  213. $position = $intval;
  214. }
  215. } else if ($this->_adapter->supportsParameters('named')) {
  216. if ($parameter[0] != ':') {
  217. $parameter = ':' . $parameter;
  218. }
  219. if (in_array($parameter, $this->_sqlParam) !== false) {
  220. $position = $parameter;
  221. }
  222. }
  223. if ($position === null) {
  224. /**
  225. * @see Zend_Db_Statement_Exception
  226. */
  227. require_once 'Zend/Db/Statement/Exception.php';
  228. throw new Zend_Db_Statement_Exception("Invalid bind-variable position '$parameter'");
  229. }
  230. // Finally we are assured that $position is valid
  231. $this->_bindParam[$position] =& $variable;
  232. return $this->_bindParam($position, $variable, $type, $length, $options);
  233. }
  234. /**
  235. * Binds a value to a parameter.
  236. *
  237. * @param mixed $parameter Name the parameter, either integer or string.
  238. * @param mixed $value Scalar value to bind to the parameter.
  239. * @param mixed $type OPTIONAL Datatype of the parameter.
  240. * @return bool
  241. */
  242. public function bindValue($parameter, $value, $type = null)
  243. {
  244. return $this->bindParam($parameter, $value, $type);
  245. }
  246. /**
  247. * Executes a prepared statement.
  248. *
  249. * @param array $params OPTIONAL Values to bind to parameter placeholders.
  250. * @return bool
  251. */
  252. public function execute(array $params = null)
  253. {
  254. /*
  255. * Simple case - no query profiler to manage.
  256. */
  257. if ($this->_queryId === null) {
  258. return $this->_execute($params);
  259. }
  260. /*
  261. * Do the same thing, but with query profiler
  262. * management before and after the execute.
  263. */
  264. $prof = $this->_adapter->getProfiler();
  265. $qp = $prof->getQueryProfile($this->_queryId);
  266. if ($qp->hasEnded()) {
  267. $this->_queryId = $prof->queryClone($qp);
  268. $qp = $prof->getQueryProfile($this->_queryId);
  269. }
  270. if ($params !== null) {
  271. $qp->bindParams($params);
  272. } else {
  273. $qp->bindParams($this->_bindParam);
  274. }
  275. $qp->start($this->_queryId);
  276. $retval = $this->_execute($params);
  277. $prof->queryEnd($this->_queryId);
  278. return $retval;
  279. }
  280. /**
  281. * Returns an array containing all of the result set rows.
  282. *
  283. * @param int $style OPTIONAL Fetch mode.
  284. * @param int $col OPTIONAL Column number, if fetch mode is by column.
  285. * @return array Collection of rows, each in a format by the fetch mode.
  286. */
  287. public function fetchAll($style = null, $col = null)
  288. {
  289. $data = array();
  290. if ($style === Zend_Db::FETCH_COLUMN && $col === null) {
  291. $col = 0;
  292. }
  293. if ($col === null) {
  294. while ($row = $this->fetch($style)) {
  295. $data[] = $row;
  296. }
  297. } else {
  298. while (false !== ($val = $this->fetchColumn($col))) {
  299. $data[] = $val;
  300. }
  301. }
  302. return $data;
  303. }
  304. /**
  305. * Returns a single column from the next row of a result set.
  306. *
  307. * @param int $col OPTIONAL Position of the column to fetch.
  308. * @return string One value from the next row of result set, or false.
  309. */
  310. public function fetchColumn($col = 0)
  311. {
  312. $data = array();
  313. $col = (int) $col;
  314. $row = $this->fetch(Zend_Db::FETCH_NUM);
  315. if (!is_array($row)) {
  316. return false;
  317. }
  318. return $row[$col];
  319. }
  320. /**
  321. * Fetches the next row and returns it as an object.
  322. *
  323. * @param string $class OPTIONAL Name of the class to create.
  324. * @param array $config OPTIONAL Constructor arguments for the class.
  325. * @return mixed One object instance of the specified class, or false.
  326. */
  327. public function fetchObject($class = 'stdClass', array $config = array())
  328. {
  329. $obj = new $class($config);
  330. $row = $this->fetch(Zend_Db::FETCH_ASSOC);
  331. if (!is_array($row)) {
  332. return false;
  333. }
  334. foreach ($row as $key => $val) {
  335. $obj->$key = $val;
  336. }
  337. return $obj;
  338. }
  339. /**
  340. * Retrieve a statement attribute.
  341. *
  342. * @param string $key Attribute name.
  343. * @return mixed Attribute value.
  344. */
  345. public function getAttribute($key)
  346. {
  347. if (array_key_exists($key, $this->_attribute)) {
  348. return $this->_attribute[$key];
  349. }
  350. }
  351. /**
  352. * Set a statement attribute.
  353. *
  354. * @param string $key Attribute name.
  355. * @param mixed $val Attribute value.
  356. * @return bool
  357. */
  358. public function setAttribute($key, $val)
  359. {
  360. $this->_attribute[$key] = $val;
  361. }
  362. /**
  363. * Set the default fetch mode for this statement.
  364. *
  365. * @param int $mode The fetch mode.
  366. * @return bool
  367. * @throws Zend_Db_Statement_Exception
  368. */
  369. public function setFetchMode($mode)
  370. {
  371. switch ($mode) {
  372. case Zend_Db::FETCH_NUM:
  373. case Zend_Db::FETCH_ASSOC:
  374. case Zend_Db::FETCH_BOTH:
  375. case Zend_Db::FETCH_OBJ:
  376. $this->_fetchMode = $mode;
  377. break;
  378. case Zend_Db::FETCH_BOUND:
  379. default:
  380. $this->closeCursor();
  381. /**
  382. * @see Zend_Db_Statement_Exception
  383. */
  384. require_once 'Zend/Db/Statement/Exception.php';
  385. throw new Zend_Db_Statement_Exception('invalid fetch mode');
  386. break;
  387. }
  388. }
  389. /**
  390. * Helper function to map retrieved row
  391. * to bound column variables
  392. *
  393. * @param array $row
  394. * @return bool True
  395. */
  396. public function _fetchBound($row)
  397. {
  398. foreach ($row as $key => $value) {
  399. // bindColumn() takes 1-based integer positions
  400. // but fetch() returns 0-based integer indexes
  401. if (is_int($key)) {
  402. $key++;
  403. }
  404. // set results only to variables that were bound previously
  405. if (isset($this->_bindColumn[$key])) {
  406. $this->_bindColumn[$key] = $value;
  407. }
  408. }
  409. return true;
  410. }
  411. /**
  412. * Gets the Zend_Db_Adapter_Abstract for this
  413. * particular Zend_Db_Statement object.
  414. *
  415. * @return Zend_Db_Adapter_Abstract
  416. */
  417. public function getAdapter()
  418. {
  419. return $this->_adapter;
  420. }
  421. /**
  422. * Gets the resource or object setup by the
  423. * _parse
  424. * @return unknown_type
  425. */
  426. public function getDriverStatement()
  427. {
  428. return $this->_stmt;
  429. }
  430. }