PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/database/SafeSQL.php

https://gitlab.com/igorbabko/mindk
PHP | 337 lines | 221 code | 23 blank | 93 comment | 36 complexity | 96e8d5a994230195b174c54d9995b947 MD5 | raw file
  1. <?php
  2. /**
  3. * File /framework/database/SafeSql.php contains the SafeSql class
  4. * which provides secure interaction with database.
  5. *
  6. * PHP version 5
  7. *
  8. * @package Framework\Database
  9. * @author Igor Babko <i.i.babko@gmail.com>
  10. */
  11. namespace Framework\Database;
  12. use \PDO;
  13. use \PDOStatement;
  14. use Framework\Exception\SafeSqlException;
  15. /**
  16. * Class SafeSql is used to make safe sql request to database.
  17. * It extends Database class.
  18. * Default implementation of {@link SafeSqlInterface}.
  19. *
  20. * Class uses QueryBuilder::rawQueryString and QueryBuilder::bindParameters to
  21. * to bind parameters instead of placeholders in QueryBuilder::rawQueryString
  22. * filtering them in appropriate way before binding.
  23. *
  24. * Placeholder types and filter methods associated with them:
  25. * - ?i => SafeSql::escapeIdentifier;
  26. * - ?n => SafeSql::escapeNumber;
  27. * - ?s => SafeSql::escapeString.
  28. *
  29. * @package Framework\Database
  30. * @author Igor Babko <i.i.babko@gmail.com>
  31. */
  32. class SafeSql extends Database implements SafeSqlInterface
  33. {
  34. /**
  35. * @var null|PDOStatement $_sqlResultSet PDOStatement object which holds data obtained from database
  36. */
  37. private $_sqlResultSet = null;
  38. /**
  39. * @var null|array $_resultSet Array of data fetched from PDOStatement object
  40. */
  41. private $_resultSet = null;
  42. /**
  43. * @var null|int $_numOfRows Holds number of rows fetched by the last 'SELECT' request to database
  44. */
  45. private $_numOfRows = null;
  46. /**
  47. * @var null|int $_numOfColumns Holds number of columns fetched by the last 'SELECT' request to database
  48. */
  49. private $_numOfColumns = null;
  50. /**
  51. * @var null|integer $_numOfAffectedRows Holds number of rows in database affected by the
  52. * last 'INSERT', 'UPDATE', OR 'DELETE' requests.
  53. */
  54. private $_numOfAffectedRows = null;
  55. /**
  56. * SafeSql constructor establishes connection with database.
  57. *
  58. * @param string $engine Type of database server (e.g. mysql).
  59. * @param string $host Hostname.
  60. * @param string $db Database name.
  61. * @param string $user Username.
  62. * @param string $pass User password.
  63. * @param string $charset Charset.
  64. *
  65. * @return SafeSql SafeSql instance.
  66. */
  67. public function __construct($user, $pass, $db, $engine = "mysql", $host = "localhost", $charset = "utf8")
  68. {
  69. parent::__construct($user, $pass, $db, $engine, $host, $charset);
  70. }
  71. /**
  72. * {@inheritdoc}
  73. */
  74. public function getResultSet()
  75. {
  76. return $this->_resultSet;
  77. }
  78. /**
  79. * {@inheritdoc}
  80. */
  81. public function getSqlResultSet()
  82. {
  83. return $this->_sqlResultSet;
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. public function getNumOfAffectedRows()
  89. {
  90. return $this->_numOfAffectedRows;
  91. }
  92. /**
  93. * {@inheritdoc}
  94. */
  95. public function getNumOfColumns()
  96. {
  97. return $this->_numOfColumns;
  98. }
  99. /**
  100. * {@inheritdoc}
  101. */
  102. public function getNumOfRows()
  103. {
  104. if ($this->_resultSet !== null) {
  105. return count($this->_resultSet);
  106. } else {
  107. throw new SafeSqlException(
  108. 500,
  109. "<strong>Internal server error:</strong> can not get number of rows if SafeSql::_resultSet is undefined"
  110. );
  111. }
  112. }
  113. /**
  114. * {@inheritdoc}
  115. */
  116. public function getOne()
  117. {
  118. if (isset($this->_resultSet)) {
  119. return reset($this->_resultSet[0]);
  120. } else {
  121. throw new SafeSqlException(
  122. 500,
  123. "<strong>Internal server error:</strong> can not get fetched data if SafeSql::_resultSet is undefined"
  124. );
  125. }
  126. }
  127. /**
  128. * {@inheritdoc}
  129. */
  130. public function getRow($rowIndex = 1)
  131. {
  132. if (isset($this->_resultSet)) {
  133. if ($this->_numOfRows > $rowIndex && $rowIndex >= 1) {
  134. return $this->_resultSet[--$rowIndex];
  135. } else {
  136. throw new SafeSqlException(
  137. 500,
  138. "<strong>Internal server error:</strong> specified row index doesn't belong to range [1; SafeSql::numOfRows]"
  139. );
  140. }
  141. } else {
  142. throw new SafeSqlException(
  143. 500,
  144. "<strong>Internal server error:</strong> can not get fetched data if SafeSql::_resultSet is undefined"
  145. );
  146. }
  147. }
  148. /**
  149. * {@inheritdoc}
  150. */
  151. public function getColumn($columnIndex = 1)
  152. {
  153. $column = array();
  154. if (isset($this->_resultSet)) {
  155. if ($this->_numOfColumns >= $columnIndex && $columnIndex >= 1) {
  156. foreach ($this->_resultSet as $row) {
  157. $counter = 0;
  158. foreach ($row as $value) {
  159. $counter++;
  160. if ($columnIndex == $counter) {
  161. $column[] = $value;
  162. break;
  163. }
  164. }
  165. }
  166. return $column;
  167. } else {
  168. throw new SafeSqlException(
  169. 500,
  170. "<strong>Internal server error:</strong> specified column index doesn't belong to range [1; SafeSql::numOfColumns]"
  171. );
  172. }
  173. } else {
  174. throw new SafeSqlException(
  175. 500,
  176. "<strong>Internal server error:</strong> can not get fetched data if SafeSql::_resultSet is undefined"
  177. );
  178. }
  179. }
  180. /**
  181. * {@inheritdoc}
  182. */
  183. public function getAll()
  184. {
  185. if (isset($this->_resultSet)) {
  186. return $this->_resultSet;
  187. } else {
  188. throw new SafeSqlException(
  189. 500,
  190. "<strong>Internal server error:</strong> can not get fetched data if SafeSql::_resultSet is undefined"
  191. );
  192. }
  193. }
  194. /**
  195. * {@inheritdoc}
  196. */
  197. public function safeQuery($rawQueryString, $bindParameters)
  198. {
  199. $queryString = $this->prepareQuery($rawQueryString, $bindParameters);
  200. if (strpos($queryString, "SELECT") !== false) {
  201. $this->_sqlResultSet = $this->query($queryString);
  202. if ($this->_sqlResultSet !== false) {
  203. $this->_resultSet = array();
  204. while ($row = $this->_sqlResultSet->fetch(PDO::FETCH_ASSOC)) {
  205. $this->_resultSet[] = $row;
  206. }
  207. $this->_numOfRows = count($this->_resultSet);
  208. if ($this->_numOfRows === 0) {
  209. $this->_numOfColumns = 0;
  210. } else {
  211. foreach ($this->_resultSet as $row) {
  212. $this->_numOfColumns = count($row);
  213. break;
  214. }
  215. }
  216. return $this->_resultSet;
  217. } else {
  218. throw new SafeSqlException(500, "<strong>Internal server error:</strong> sql request is failed");
  219. }
  220. } else {
  221. $this->_sqlResultSet = null;
  222. $this->_resultSet = null;
  223. $this->_numOfRows = null;
  224. $this->_numOfColumns = null;
  225. $this->_numOfAffectedRows = $this->exec($queryString);
  226. if ($this->_numOfAffectedRows === false) {
  227. $this->_numOfAffectedRows = null;
  228. throw new SafeSqlException(500, "<strong>Internal server error:</strong> sql request is failed");
  229. } else {
  230. return $this->_numOfAffectedRows;
  231. }
  232. }
  233. }
  234. /**
  235. * {@inheritdoc}
  236. */
  237. public function prepareQuery($rawQueryString, $bindParameters)
  238. {
  239. $queryString = '';
  240. $array = preg_split('/(\?[isn])/', $rawQueryString, null, PREG_SPLIT_DELIM_CAPTURE);
  241. foreach ($array as $index => $queryPart) {
  242. if (($index % 2) === 0) {
  243. $queryString .= $queryPart;
  244. continue;
  245. }
  246. switch ($queryPart) {
  247. case '?i':
  248. $queryPart = $this->escapeIdentifier(array_shift($bindParameters));
  249. break;
  250. case '?s':
  251. $queryPart = $this->escapeString(array_shift($bindParameters));
  252. break;
  253. case '?n':
  254. $queryPart = $this->escapeNumber(array_shift($bindParameters));
  255. }
  256. $queryString .= $queryPart;
  257. }
  258. return $queryString;
  259. }
  260. /**
  261. * {@inheritdoc}
  262. */
  263. public function escapeNumber($value = null)
  264. {
  265. if (!isset($value)) {
  266. throw new SafeSqlException(
  267. 500,
  268. "<strong>Internal server error:</strong> empty value for number (?n) placeholder"
  269. );
  270. } elseif (is_numeric($value)) {
  271. if (is_integer($value)) {
  272. return $value;
  273. }
  274. $value = number_format($value, 0, '.', '');
  275. return $value;
  276. } else {
  277. throw new SafeSqlException(
  278. 500,
  279. "<strong>Internal server error:</strong> number (?n) placeholder expects numeric value, ".gettype(
  280. $value
  281. )." given"
  282. );
  283. }
  284. }
  285. /**
  286. * {@inheritdoc}
  287. */
  288. public function escapeString($value)
  289. {
  290. if (isset($value)) {
  291. return $this->quote($value);
  292. } else {
  293. throw new SafeSqlException(
  294. 500,
  295. "<strong>Internal server error:</strong> empty value for string (?s) placeholder"
  296. );
  297. }
  298. }
  299. /**
  300. * {@inheritdoc}
  301. */
  302. public function escapeIdentifier($value = null)
  303. {
  304. if (isset($value)) {
  305. return "`".str_replace("`", "``", $value)."`";
  306. } else {
  307. throw new SafeSqlException(
  308. 500,
  309. "<strong>Internal server error:</strong> empty value for identifier (?i) placeholder"
  310. );
  311. }
  312. }
  313. }