PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php

http://github.com/doctrine/dbal
PHP | 434 lines | 241 code | 70 blank | 123 comment | 29 complexity | e2806df80484e976cd766b002a7cc624 MD5 | raw file
Possible License(s): Unlicense
  1. <?php
  2. namespace Doctrine\DBAL\Driver\SQLSrv;
  3. use Doctrine\DBAL\Driver\Statement;
  4. use Doctrine\DBAL\Driver\StatementIterator;
  5. use Doctrine\DBAL\FetchMode;
  6. use Doctrine\DBAL\ParameterType;
  7. use IteratorAggregate;
  8. use PDO;
  9. use function array_key_exists;
  10. use function count;
  11. use function func_get_args;
  12. use function in_array;
  13. use function is_int;
  14. use function is_numeric;
  15. use function sqlsrv_errors;
  16. use function sqlsrv_execute;
  17. use function sqlsrv_fetch;
  18. use function sqlsrv_fetch_array;
  19. use function sqlsrv_fetch_object;
  20. use function sqlsrv_get_field;
  21. use function sqlsrv_next_result;
  22. use function sqlsrv_num_fields;
  23. use function SQLSRV_PHPTYPE_STREAM;
  24. use function SQLSRV_PHPTYPE_STRING;
  25. use function sqlsrv_prepare;
  26. use function sqlsrv_rows_affected;
  27. use function SQLSRV_SQLTYPE_VARBINARY;
  28. use function stripos;
  29. use const SQLSRV_ENC_BINARY;
  30. use const SQLSRV_ERR_ERRORS;
  31. use const SQLSRV_FETCH_ASSOC;
  32. use const SQLSRV_FETCH_BOTH;
  33. use const SQLSRV_FETCH_NUMERIC;
  34. use const SQLSRV_PARAM_IN;
  35. /**
  36. * SQL Server Statement.
  37. */
  38. class SQLSrvStatement implements IteratorAggregate, Statement
  39. {
  40. /**
  41. * The SQLSRV Resource.
  42. *
  43. * @var resource
  44. */
  45. private $conn;
  46. /**
  47. * The SQL statement to execute.
  48. *
  49. * @var string
  50. */
  51. private $sql;
  52. /**
  53. * The SQLSRV statement resource.
  54. *
  55. * @var resource|null
  56. */
  57. private $stmt;
  58. /**
  59. * References to the variables bound as statement parameters.
  60. *
  61. * @var mixed
  62. */
  63. private $variables = [];
  64. /**
  65. * Bound parameter types.
  66. *
  67. * @var int[]
  68. */
  69. private $types = [];
  70. /**
  71. * Translations.
  72. *
  73. * @var int[]
  74. */
  75. private static $fetchMap = [
  76. FetchMode::MIXED => SQLSRV_FETCH_BOTH,
  77. FetchMode::ASSOCIATIVE => SQLSRV_FETCH_ASSOC,
  78. FetchMode::NUMERIC => SQLSRV_FETCH_NUMERIC,
  79. ];
  80. /**
  81. * The name of the default class to instantiate when fetching class instances.
  82. *
  83. * @var string
  84. */
  85. private $defaultFetchClass = '\stdClass';
  86. /**
  87. * The constructor arguments for the default class to instantiate when fetching class instances.
  88. *
  89. * @var mixed[]
  90. */
  91. private $defaultFetchClassCtorArgs = [];
  92. /**
  93. * The fetch style.
  94. *
  95. * @var int
  96. */
  97. private $defaultFetchMode = FetchMode::MIXED;
  98. /**
  99. * The last insert ID.
  100. *
  101. * @var LastInsertId|null
  102. */
  103. private $lastInsertId;
  104. /**
  105. * Indicates whether the statement is in the state when fetching results is possible
  106. *
  107. * @var bool
  108. */
  109. private $result = false;
  110. /**
  111. * Append to any INSERT query to retrieve the last insert id.
  112. *
  113. * @deprecated This constant has been deprecated and will be made private in 3.0
  114. */
  115. public const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;';
  116. /**
  117. * @param resource $conn
  118. * @param string $sql
  119. */
  120. public function __construct($conn, $sql, ?LastInsertId $lastInsertId = null)
  121. {
  122. $this->conn = $conn;
  123. $this->sql = $sql;
  124. if (stripos($sql, 'INSERT INTO ') !== 0) {
  125. return;
  126. }
  127. $this->sql .= self::LAST_INSERT_ID_SQL;
  128. $this->lastInsertId = $lastInsertId;
  129. }
  130. /**
  131. * {@inheritdoc}
  132. */
  133. public function bindValue($param, $value, $type = ParameterType::STRING)
  134. {
  135. if (! is_numeric($param)) {
  136. throw new SQLSrvException(
  137. 'sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.'
  138. );
  139. }
  140. $this->variables[$param] = $value;
  141. $this->types[$param] = $type;
  142. return true;
  143. }
  144. /**
  145. * {@inheritdoc}
  146. */
  147. public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
  148. {
  149. if (! is_numeric($column)) {
  150. throw new SQLSrvException('sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.');
  151. }
  152. $this->variables[$column] =& $variable;
  153. $this->types[$column] = $type;
  154. // unset the statement resource if it exists as the new one will need to be bound to the new variable
  155. $this->stmt = null;
  156. return true;
  157. }
  158. /**
  159. * {@inheritdoc}
  160. */
  161. public function closeCursor()
  162. {
  163. // not having the result means there's nothing to close
  164. if ($this->stmt === null || ! $this->result) {
  165. return true;
  166. }
  167. // emulate it by fetching and discarding rows, similarly to what PDO does in this case
  168. // @link http://php.net/manual/en/pdostatement.closecursor.php
  169. // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075
  170. // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them
  171. while (sqlsrv_fetch($this->stmt)) {
  172. }
  173. $this->result = false;
  174. return true;
  175. }
  176. /**
  177. * {@inheritdoc}
  178. */
  179. public function columnCount()
  180. {
  181. if ($this->stmt === null) {
  182. return 0;
  183. }
  184. return sqlsrv_num_fields($this->stmt) ?: 0;
  185. }
  186. /**
  187. * {@inheritdoc}
  188. */
  189. public function errorCode()
  190. {
  191. $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
  192. if ($errors) {
  193. return $errors[0]['code'];
  194. }
  195. return false;
  196. }
  197. /**
  198. * {@inheritdoc}
  199. */
  200. public function errorInfo()
  201. {
  202. return (array) sqlsrv_errors(SQLSRV_ERR_ERRORS);
  203. }
  204. /**
  205. * {@inheritdoc}
  206. */
  207. public function execute($params = null)
  208. {
  209. if ($params) {
  210. $hasZeroIndex = array_key_exists(0, $params);
  211. foreach ($params as $key => $val) {
  212. if ($hasZeroIndex && is_int($key)) {
  213. $this->bindValue($key + 1, $val);
  214. } else {
  215. $this->bindValue($key, $val);
  216. }
  217. }
  218. }
  219. if (! $this->stmt) {
  220. $this->stmt = $this->prepare();
  221. }
  222. if (! sqlsrv_execute($this->stmt)) {
  223. throw SQLSrvException::fromSqlSrvErrors();
  224. }
  225. if ($this->lastInsertId) {
  226. sqlsrv_next_result($this->stmt);
  227. sqlsrv_fetch($this->stmt);
  228. $this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0));
  229. }
  230. $this->result = true;
  231. return true;
  232. }
  233. /**
  234. * Prepares SQL Server statement resource
  235. *
  236. * @return resource
  237. *
  238. * @throws SQLSrvException
  239. */
  240. private function prepare()
  241. {
  242. $params = [];
  243. foreach ($this->variables as $column => &$variable) {
  244. switch ($this->types[$column]) {
  245. case ParameterType::LARGE_OBJECT:
  246. $params[$column - 1] = [
  247. &$variable,
  248. SQLSRV_PARAM_IN,
  249. SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY),
  250. SQLSRV_SQLTYPE_VARBINARY('max'),
  251. ];
  252. break;
  253. case ParameterType::BINARY:
  254. $params[$column - 1] = [
  255. &$variable,
  256. SQLSRV_PARAM_IN,
  257. SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY),
  258. ];
  259. break;
  260. default:
  261. $params[$column - 1] =& $variable;
  262. break;
  263. }
  264. }
  265. $stmt = sqlsrv_prepare($this->conn, $this->sql, $params);
  266. if (! $stmt) {
  267. throw SQLSrvException::fromSqlSrvErrors();
  268. }
  269. return $stmt;
  270. }
  271. /**
  272. * {@inheritdoc}
  273. */
  274. public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
  275. {
  276. $this->defaultFetchMode = $fetchMode;
  277. $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass;
  278. $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
  279. return true;
  280. }
  281. /**
  282. * {@inheritdoc}
  283. */
  284. public function getIterator()
  285. {
  286. return new StatementIterator($this);
  287. }
  288. /**
  289. * {@inheritdoc}
  290. *
  291. * @throws SQLSrvException
  292. */
  293. public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
  294. {
  295. // do not try fetching from the statement if it's not expected to contain result
  296. // in order to prevent exceptional situation
  297. if ($this->stmt === null || ! $this->result) {
  298. return false;
  299. }
  300. $args = func_get_args();
  301. $fetchMode = $fetchMode ?: $this->defaultFetchMode;
  302. if ($fetchMode === FetchMode::COLUMN) {
  303. return $this->fetchColumn();
  304. }
  305. if (isset(self::$fetchMap[$fetchMode])) {
  306. return sqlsrv_fetch_array($this->stmt, self::$fetchMap[$fetchMode]) ?: false;
  307. }
  308. if (in_array($fetchMode, [FetchMode::STANDARD_OBJECT, FetchMode::CUSTOM_OBJECT], true)) {
  309. $className = $this->defaultFetchClass;
  310. $ctorArgs = $this->defaultFetchClassCtorArgs;
  311. if (count($args) >= 2) {
  312. $className = $args[1];
  313. $ctorArgs = $args[2] ?? [];
  314. }
  315. return sqlsrv_fetch_object($this->stmt, $className, $ctorArgs) ?: false;
  316. }
  317. throw new SQLSrvException('Fetch mode is not supported!');
  318. }
  319. /**
  320. * {@inheritdoc}
  321. */
  322. public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
  323. {
  324. $rows = [];
  325. switch ($fetchMode) {
  326. case FetchMode::CUSTOM_OBJECT:
  327. while (($row = $this->fetch(...func_get_args())) !== false) {
  328. $rows[] = $row;
  329. }
  330. break;
  331. case FetchMode::COLUMN:
  332. while (($row = $this->fetchColumn()) !== false) {
  333. $rows[] = $row;
  334. }
  335. break;
  336. default:
  337. while (($row = $this->fetch($fetchMode)) !== false) {
  338. $rows[] = $row;
  339. }
  340. }
  341. return $rows;
  342. }
  343. /**
  344. * {@inheritdoc}
  345. */
  346. public function fetchColumn($columnIndex = 0)
  347. {
  348. $row = $this->fetch(FetchMode::NUMERIC);
  349. if ($row === false) {
  350. return false;
  351. }
  352. return $row[$columnIndex] ?? null;
  353. }
  354. /**
  355. * {@inheritdoc}
  356. */
  357. public function rowCount()
  358. {
  359. if ($this->stmt === null) {
  360. return 0;
  361. }
  362. return sqlsrv_rows_affected($this->stmt) ?: 0;
  363. }
  364. }