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.

379 satır
13KB

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