QueryTrait.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\db;
  8. use yii\base\NotSupportedException;
  9. /**
  10. * The BaseQuery trait represents the minimum method set of a database Query.
  11. *
  12. * It is supposed to be used in a class that implements the [[QueryInterface]].
  13. *
  14. * @author Qiang Xue <qiang.xue@gmail.com>
  15. * @author Carsten Brandt <mail@cebe.cc>
  16. * @since 2.0
  17. */
  18. trait QueryTrait
  19. {
  20. /**
  21. * @var string|array|ExpressionInterface query condition. This refers to the WHERE clause in a SQL statement.
  22. * For example, `['age' => 31, 'team' => 1]`.
  23. * @see where() for valid syntax on specifying this value.
  24. */
  25. public $where;
  26. /**
  27. * @var int|ExpressionInterface maximum number of records to be returned. May be an instance of [[ExpressionInterface]].
  28. * If not set or less than 0, it means no limit.
  29. */
  30. public $limit;
  31. /**
  32. * @var int|ExpressionInterface zero-based offset from where the records are to be returned.
  33. * May be an instance of [[ExpressionInterface]]. If not set or less than 0, it means starting from the beginning.
  34. */
  35. public $offset;
  36. /**
  37. * @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement.
  38. * The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
  39. * can be either [SORT_ASC](https://secure.php.net/manual/en/array.constants.php#constant.sort-asc)
  40. * or [SORT_DESC](https://secure.php.net/manual/en/array.constants.php#constant.sort-desc).
  41. * The array may also contain [[ExpressionInterface]] objects. If that is the case, the expressions
  42. * will be converted into strings without any change.
  43. */
  44. public $orderBy;
  45. /**
  46. * @var string|callable the name of the column by which the query results should be indexed by.
  47. * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
  48. * row data. For more details, see [[indexBy()]]. This property is only used by [[QueryInterface::all()|all()]].
  49. */
  50. public $indexBy;
  51. /**
  52. * @var bool whether to emulate the actual query execution, returning empty or false results.
  53. * @see emulateExecution()
  54. * @since 2.0.11
  55. */
  56. public $emulateExecution = false;
  57. /**
  58. * Sets the [[indexBy]] property.
  59. * @param string|callable $column the name of the column by which the query results should be indexed by.
  60. * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
  61. * row data. The signature of the callable should be:
  62. *
  63. * ```php
  64. * function ($row)
  65. * {
  66. * // return the index value corresponding to $row
  67. * }
  68. * ```
  69. *
  70. * @return $this the query object itself
  71. */
  72. public function indexBy($column)
  73. {
  74. $this->indexBy = $column;
  75. return $this;
  76. }
  77. /**
  78. * Sets the WHERE part of the query.
  79. *
  80. * See [[QueryInterface::where()]] for detailed documentation.
  81. *
  82. * @param string|array|ExpressionInterface $condition the conditions that should be put in the WHERE part.
  83. * @return $this the query object itself
  84. * @see andWhere()
  85. * @see orWhere()
  86. */
  87. public function where($condition)
  88. {
  89. $this->where = $condition;
  90. return $this;
  91. }
  92. /**
  93. * Adds an additional WHERE condition to the existing one.
  94. * The new condition and the existing one will be joined using the 'AND' operator.
  95. * @param string|array|ExpressionInterface $condition the new WHERE condition. Please refer to [[where()]]
  96. * on how to specify this parameter.
  97. * @return $this the query object itself
  98. * @see where()
  99. * @see orWhere()
  100. */
  101. public function andWhere($condition)
  102. {
  103. if ($this->where === null) {
  104. $this->where = $condition;
  105. } else {
  106. $this->where = ['and', $this->where, $condition];
  107. }
  108. return $this;
  109. }
  110. /**
  111. * Adds an additional WHERE condition to the existing one.
  112. * The new condition and the existing one will be joined using the 'OR' operator.
  113. * @param string|array|ExpressionInterface $condition the new WHERE condition. Please refer to [[where()]]
  114. * on how to specify this parameter.
  115. * @return $this the query object itself
  116. * @see where()
  117. * @see andWhere()
  118. */
  119. public function orWhere($condition)
  120. {
  121. if ($this->where === null) {
  122. $this->where = $condition;
  123. } else {
  124. $this->where = ['or', $this->where, $condition];
  125. }
  126. return $this;
  127. }
  128. /**
  129. * Sets the WHERE part of the query but ignores [[isEmpty()|empty operands]].
  130. *
  131. * This method is similar to [[where()]]. The main difference is that this method will
  132. * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
  133. * for building query conditions based on filter values entered by users.
  134. *
  135. * The following code shows the difference between this method and [[where()]]:
  136. *
  137. * ```php
  138. * // WHERE `age`=:age
  139. * $query->filterWhere(['name' => null, 'age' => 20]);
  140. * // WHERE `age`=:age
  141. * $query->where(['age' => 20]);
  142. * // WHERE `name` IS NULL AND `age`=:age
  143. * $query->where(['name' => null, 'age' => 20]);
  144. * ```
  145. *
  146. * Note that unlike [[where()]], you cannot pass binding parameters to this method.
  147. *
  148. * @param array $condition the conditions that should be put in the WHERE part.
  149. * See [[where()]] on how to specify this parameter.
  150. * @return $this the query object itself
  151. * @see where()
  152. * @see andFilterWhere()
  153. * @see orFilterWhere()
  154. */
  155. public function filterWhere(array $condition)
  156. {
  157. $condition = $this->filterCondition($condition);
  158. if ($condition !== []) {
  159. $this->where($condition);
  160. }
  161. return $this;
  162. }
  163. /**
  164. * Adds an additional WHERE condition to the existing one but ignores [[isEmpty()|empty operands]].
  165. * The new condition and the existing one will be joined using the 'AND' operator.
  166. *
  167. * This method is similar to [[andWhere()]]. The main difference is that this method will
  168. * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
  169. * for building query conditions based on filter values entered by users.
  170. *
  171. * @param array $condition the new WHERE condition. Please refer to [[where()]]
  172. * on how to specify this parameter.
  173. * @return $this the query object itself
  174. * @see filterWhere()
  175. * @see orFilterWhere()
  176. */
  177. public function andFilterWhere(array $condition)
  178. {
  179. $condition = $this->filterCondition($condition);
  180. if ($condition !== []) {
  181. $this->andWhere($condition);
  182. }
  183. return $this;
  184. }
  185. /**
  186. * Adds an additional WHERE condition to the existing one but ignores [[isEmpty()|empty operands]].
  187. * The new condition and the existing one will be joined using the 'OR' operator.
  188. *
  189. * This method is similar to [[orWhere()]]. The main difference is that this method will
  190. * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
  191. * for building query conditions based on filter values entered by users.
  192. *
  193. * @param array $condition the new WHERE condition. Please refer to [[where()]]
  194. * on how to specify this parameter.
  195. * @return $this the query object itself
  196. * @see filterWhere()
  197. * @see andFilterWhere()
  198. */
  199. public function orFilterWhere(array $condition)
  200. {
  201. $condition = $this->filterCondition($condition);
  202. if ($condition !== []) {
  203. $this->orWhere($condition);
  204. }
  205. return $this;
  206. }
  207. /**
  208. * Removes [[isEmpty()|empty operands]] from the given query condition.
  209. *
  210. * @param array $condition the original condition
  211. * @return array the condition with [[isEmpty()|empty operands]] removed.
  212. * @throws NotSupportedException if the condition operator is not supported
  213. */
  214. protected function filterCondition($condition)
  215. {
  216. if (!is_array($condition)) {
  217. return $condition;
  218. }
  219. if (!isset($condition[0])) {
  220. // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
  221. foreach ($condition as $name => $value) {
  222. if ($this->isEmpty($value)) {
  223. unset($condition[$name]);
  224. }
  225. }
  226. return $condition;
  227. }
  228. // operator format: operator, operand 1, operand 2, ...
  229. $operator = array_shift($condition);
  230. switch (strtoupper($operator)) {
  231. case 'NOT':
  232. case 'AND':
  233. case 'OR':
  234. foreach ($condition as $i => $operand) {
  235. $subCondition = $this->filterCondition($operand);
  236. if ($this->isEmpty($subCondition)) {
  237. unset($condition[$i]);
  238. } else {
  239. $condition[$i] = $subCondition;
  240. }
  241. }
  242. if (empty($condition)) {
  243. return [];
  244. }
  245. break;
  246. case 'BETWEEN':
  247. case 'NOT BETWEEN':
  248. if (array_key_exists(1, $condition) && array_key_exists(2, $condition)) {
  249. if ($this->isEmpty($condition[1]) || $this->isEmpty($condition[2])) {
  250. return [];
  251. }
  252. }
  253. break;
  254. default:
  255. if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) {
  256. return [];
  257. }
  258. }
  259. array_unshift($condition, $operator);
  260. return $condition;
  261. }
  262. /**
  263. * Returns a value indicating whether the give value is "empty".
  264. *
  265. * The value is considered "empty", if one of the following conditions is satisfied:
  266. *
  267. * - it is `null`,
  268. * - an empty string (`''`),
  269. * - a string containing only whitespace characters,
  270. * - or an empty array.
  271. *
  272. * @param mixed $value
  273. * @return bool if the value is empty
  274. */
  275. protected function isEmpty($value)
  276. {
  277. return $value === '' || $value === [] || $value === null || is_string($value) && trim($value) === '';
  278. }
  279. /**
  280. * Sets the ORDER BY part of the query.
  281. * @param string|array|ExpressionInterface $columns the columns (and the directions) to be ordered by.
  282. * Columns can be specified in either a string (e.g. `"id ASC, name DESC"`) or an array
  283. * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
  284. *
  285. * The method will automatically quote the column names unless a column contains some parenthesis
  286. * (which means the column contains a DB expression).
  287. *
  288. * Note that if your order-by is an expression containing commas, you should always use an array
  289. * to represent the order-by information. Otherwise, the method will not be able to correctly determine
  290. * the order-by columns.
  291. *
  292. * Since version 2.0.7, an [[ExpressionInterface]] object can be passed to specify the ORDER BY part explicitly in plain SQL.
  293. * @return $this the query object itself
  294. * @see addOrderBy()
  295. */
  296. public function orderBy($columns)
  297. {
  298. $this->orderBy = $this->normalizeOrderBy($columns);
  299. return $this;
  300. }
  301. /**
  302. * Adds additional ORDER BY columns to the query.
  303. * @param string|array|ExpressionInterface $columns the columns (and the directions) to be ordered by.
  304. * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
  305. * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
  306. *
  307. * The method will automatically quote the column names unless a column contains some parenthesis
  308. * (which means the column contains a DB expression).
  309. *
  310. * Note that if your order-by is an expression containing commas, you should always use an array
  311. * to represent the order-by information. Otherwise, the method will not be able to correctly determine
  312. * the order-by columns.
  313. *
  314. * Since version 2.0.7, an [[ExpressionInterface]] object can be passed to specify the ORDER BY part explicitly in plain SQL.
  315. * @return $this the query object itself
  316. * @see orderBy()
  317. */
  318. public function addOrderBy($columns)
  319. {
  320. $columns = $this->normalizeOrderBy($columns);
  321. if ($this->orderBy === null) {
  322. $this->orderBy = $columns;
  323. } else {
  324. $this->orderBy = array_merge($this->orderBy, $columns);
  325. }
  326. return $this;
  327. }
  328. /**
  329. * Normalizes format of ORDER BY data.
  330. *
  331. * @param array|string|ExpressionInterface $columns the columns value to normalize. See [[orderBy]] and [[addOrderBy]].
  332. * @return array
  333. */
  334. protected function normalizeOrderBy($columns)
  335. {
  336. if ($columns instanceof ExpressionInterface) {
  337. return [$columns];
  338. } elseif (is_array($columns)) {
  339. return $columns;
  340. }
  341. $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
  342. $result = [];
  343. foreach ($columns as $column) {
  344. if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
  345. $result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC;
  346. } else {
  347. $result[$column] = SORT_ASC;
  348. }
  349. }
  350. return $result;
  351. }
  352. /**
  353. * Sets the LIMIT part of the query.
  354. * @param int|ExpressionInterface|null $limit the limit. Use null or negative value to disable limit.
  355. * @return $this the query object itself
  356. */
  357. public function limit($limit)
  358. {
  359. $this->limit = $limit;
  360. return $this;
  361. }
  362. /**
  363. * Sets the OFFSET part of the query.
  364. * @param int|ExpressionInterface|null $offset the offset. Use null or negative value to disable offset.
  365. * @return $this the query object itself
  366. */
  367. public function offset($offset)
  368. {
  369. $this->offset = $offset;
  370. return $this;
  371. }
  372. /**
  373. * Sets whether to emulate query execution, preventing any interaction with data storage.
  374. * After this mode is enabled, methods, returning query results like [[QueryInterface::one()]],
  375. * [[QueryInterface::all()]], [[QueryInterface::exists()]] and so on, will return empty or false values.
  376. * You should use this method in case your program logic indicates query should not return any results, like
  377. * in case you set false where condition like `0=1`.
  378. * @param bool $value whether to prevent query execution.
  379. * @return $this the query object itself.
  380. * @since 2.0.11
  381. */
  382. public function emulateExecution($value = true)
  383. {
  384. $this->emulateExecution = $value;
  385. return $this;
  386. }
  387. }