PageRenderTime 43ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/db/schema/CDbCriteria.php

https://bitbucket.org/dinhtrung/yiicorecms/
PHP | 598 lines | 291 code | 32 blank | 275 comment | 76 complexity | ab4285c5962704c244024d0cf5da06b1 MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause, CC0-1.0, BSD-2-Clause, GPL-2.0, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /**
  3. * CDbCriteria class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright Copyright &copy; 2008-2011 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CDbCriteria represents a query criteria, such as conditions, ordering by, limit/offset.
  12. *
  13. * @author Qiang Xue <qiang.xue@gmail.com>
  14. * @version $Id: CDbCriteria.php 3345 2011-07-06 22:16:44Z alexander.makarow $
  15. * @package system.db.schema
  16. * @since 1.0
  17. */
  18. class CDbCriteria extends CComponent
  19. {
  20. const PARAM_PREFIX=':ycp';
  21. /**
  22. * @var integer the global counter for anonymous binding parameters.
  23. * This counter is used for generating the name for the anonymous parameters.
  24. */
  25. public static $paramCount=0;
  26. /**
  27. * @var mixed the columns being selected. This refers to the SELECT clause in an SQL
  28. * statement. The property can be either a string (column names separated by commas)
  29. * or an array of column names. Defaults to '*', meaning all columns.
  30. */
  31. public $select='*';
  32. /**
  33. * @var boolean whether to select distinct rows of data only. If this is set true,
  34. * the SELECT clause would be changed to SELECT DISTINCT.
  35. * @since 1.0.9
  36. */
  37. public $distinct=false;
  38. /**
  39. * @var string query condition. This refers to the WHERE clause in an SQL statement.
  40. * For example, <code>age>31 AND team=1</code>.
  41. */
  42. public $condition='';
  43. /**
  44. * @var array list of query parameter values indexed by parameter placeholders.
  45. * For example, <code>array(':name'=>'Dan', ':age'=>31)</code>.
  46. */
  47. public $params=array();
  48. /**
  49. * @var integer maximum number of records to be returned. If less than 0, it means no limit.
  50. */
  51. public $limit=-1;
  52. /**
  53. * @var integer zero-based offset from where the records are to be returned. If less than 0, it means starting from the beginning.
  54. */
  55. public $offset=-1;
  56. /**
  57. * @var string how to sort the query results. This refers to the ORDER BY clause in an SQL statement.
  58. */
  59. public $order='';
  60. /**
  61. * @var string how to group the query results. This refers to the GROUP BY clause in an SQL statement.
  62. * For example, <code>'projectID, teamID'</code>.
  63. */
  64. public $group='';
  65. /**
  66. * @var string how to join with other tables. This refers to the JOIN clause in an SQL statement.
  67. * For example, <code>'LEFT JOIN users ON users.id=authorID'</code>.
  68. */
  69. public $join='';
  70. /**
  71. * @var string the condition to be applied with GROUP-BY clause.
  72. * For example, <code>'SUM(revenue)<50000'</code>.
  73. * @since 1.0.1
  74. */
  75. public $having='';
  76. /**
  77. * @var mixed the relational query criteria. This is used for fetching related objects in eager loading fashion.
  78. * This property is effective only when the criteria is passed as a parameter to the following methods of CActiveRecord:
  79. * <ul>
  80. * <li>{@link CActiveRecord::find()}</li>
  81. * <li>{@link CActiveRecord::findAll()}</li>
  82. * <li>{@link CActiveRecord::findByPk()}</li>
  83. * <li>{@link CActiveRecord::findAllByPk()}</li>
  84. * <li>{@link CActiveRecord::findByAttributes()}</li>
  85. * <li>{@link CActiveRecord::findAllByAttributes()}</li>
  86. * <li>{@link CActiveRecord::count()}</li>
  87. * </ul>
  88. * The property value will be used as the parameter to the {@link CActiveRecord::with()} method
  89. * to perform the eager loading. Please refer to {@link CActiveRecord::with()} on how to specify this parameter.
  90. * @since 1.1.0
  91. */
  92. public $with;
  93. /**
  94. * @var string the alias name of the table. If not set, it means the alias is 't'.
  95. */
  96. public $alias;
  97. /**
  98. * @var boolean whether the foreign tables should be joined with the primary table in a single SQL.
  99. * This property is only used in relational AR queries for HAS_MANY and MANY_MANY relations.
  100. *
  101. * When this property is set true, only a single SQL will be executed for a relational AR query,
  102. * even if the primary table is limited and the relationship between a foreign table and the primary
  103. * table is many-to-one.
  104. *
  105. * When this property is set false, a SQL statement will be executed for each HAS_MANY relation.
  106. *
  107. * When this property is not set, if the primary table is limited or paginated,
  108. * a SQL statement will be executed for each HAS_MANY relation.
  109. * Otherwise, a single SQL statement will be executed for all.
  110. *
  111. * @since 1.1.4
  112. */
  113. public $together;
  114. /**
  115. * @var string the name of the AR attribute whose value should be used as index of the query result array.
  116. * Defaults to null, meaning the result array will be zero-based integers.
  117. * @since 1.1.5
  118. */
  119. public $index;
  120. /**
  121. * @var mixed scopes to apply
  122. *
  123. * This property is effective only when passing criteria to
  124. * the one of the following methods:
  125. * <ul>
  126. * <li>{@link CActiveRecord::find()}</li>
  127. * <li>{@link CActiveRecord::findAll()}</li>
  128. * <li>{@link CActiveRecord::findByPk()}</li>
  129. * <li>{@link CActiveRecord::findAllByPk()}</li>
  130. * <li>{@link CActiveRecord::findByAttributes()}</li>
  131. * <li>{@link CActiveRecord::findAllByAttributes()}</li>
  132. * <li>{@link CActiveRecord::count()}</li>
  133. * </ul>
  134. *
  135. * Can be set to one of the following:
  136. * <ul>
  137. * <li>One scope: $criteria->scopes='scopeName';</li>
  138. * <li>Multiple scopes: $criteria->scopes=array('scopeName1','scopeName2');</li>
  139. * <li>Scope with parameters: $criteria->scopes=array('scopeName'=>array($params));</li>
  140. * <li>Multiple scopes with parameters: $criteria->scopes=array('scopeName1'=>array($params1),'scopeName2'=>array($params2));</li>
  141. * <li>Multiple scopes with the same name: array(array('scopeName'=>array($params1)),array('scopeName'=>array($params2)));</li>
  142. * </ul>
  143. * @since 1.1.7
  144. */
  145. public $scopes;
  146. /**
  147. * Constructor.
  148. * @param array $data criteria initial property values (indexed by property name)
  149. */
  150. public function __construct($data=array())
  151. {
  152. foreach($data as $name=>$value)
  153. $this->$name=$value;
  154. }
  155. /**
  156. * Appends a condition to the existing {@link condition}.
  157. * The new condition and the existing condition will be concatenated via the specified operator
  158. * which defaults to 'AND'.
  159. * The new condition can also be an array. In this case, all elements in the array
  160. * will be concatenated together via the operator.
  161. * This method handles the case when the existing condition is empty.
  162. * After calling this method, the {@link condition} property will be modified.
  163. * @param mixed $condition the new condition. It can be either a string or an array of strings.
  164. * @param string $operator the operator to join different conditions. Defaults to 'AND'.
  165. * @return CDbCriteria the criteria object itself
  166. * @since 1.0.9
  167. */
  168. public function addCondition($condition,$operator='AND')
  169. {
  170. if(is_array($condition))
  171. {
  172. if($condition===array())
  173. return $this;
  174. $condition='('.implode(') '.$operator.' (',$condition).')';
  175. }
  176. if($this->condition==='')
  177. $this->condition=$condition;
  178. else
  179. $this->condition='('.$this->condition.') '.$operator.' ('.$condition.')';
  180. return $this;
  181. }
  182. /**
  183. * Appends a search condition to the existing {@link condition}.
  184. * The search condition and the existing condition will be concatenated via the specified operator
  185. * which defaults to 'AND'.
  186. * The search condition is generated using the SQL LIKE operator with the given column name and
  187. * search keyword.
  188. * @param string $column the column name (or a valid SQL expression)
  189. * @param string $keyword the search keyword. This interpretation of the keyword is affected by the next parameter.
  190. * @param boolean $escape whether the keyword should be escaped if it contains characters % or _.
  191. * When this parameter is true (default), the special characters % (matches 0 or more characters)
  192. * and _ (matches a single character) will be escaped, and the keyword will be surrounded with a %
  193. * character on both ends. When this parameter is false, the keyword will be directly used for
  194. * matching without any change.
  195. * @param string $operator the operator used to concatenate the new condition with the existing one.
  196. * Defaults to 'AND'.
  197. * @param string $like the LIKE operator. Defaults to 'LIKE'. You may also set this to be 'NOT LIKE'.
  198. * @return CDbCriteria the criteria object itself
  199. * @since 1.0.10
  200. */
  201. public function addSearchCondition($column,$keyword,$escape=true,$operator='AND',$like='LIKE')
  202. {
  203. if($keyword=='')
  204. return $this;
  205. if($escape)
  206. $keyword='%'.strtr($keyword,array('%'=>'\%', '_'=>'\_', '\\'=>'\\\\')).'%';
  207. $condition=$column." $like ".self::PARAM_PREFIX.self::$paramCount;
  208. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$keyword;
  209. return $this->addCondition($condition, $operator);
  210. }
  211. /**
  212. * Appends an IN condition to the existing {@link condition}.
  213. * The IN condition and the existing condition will be concatenated via the specified operator
  214. * which defaults to 'AND'.
  215. * The IN condition is generated by using the SQL IN operator which requires the specified
  216. * column value to be among the given list of values.
  217. * @param string $column the column name (or a valid SQL expression)
  218. * @param array $values list of values that the column value should be in
  219. * @param string $operator the operator used to concatenate the new condition with the existing one.
  220. * Defaults to 'AND'.
  221. * @return CDbCriteria the criteria object itself
  222. * @since 1.0.10
  223. */
  224. public function addInCondition($column,$values,$operator='AND')
  225. {
  226. if(($n=count($values))<1)
  227. return $this->addCondition('0=1',$operator); // 0=1 is used because in MSSQL value alone can't be used in WHERE
  228. if($n===1)
  229. {
  230. $value=reset($values);
  231. if($value===null)
  232. return $this->addCondition($column.' IS NULL');
  233. $condition=$column.'='.self::PARAM_PREFIX.self::$paramCount;
  234. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$value;
  235. }
  236. else
  237. {
  238. $params=array();
  239. foreach($values as $value)
  240. {
  241. $params[]=self::PARAM_PREFIX.self::$paramCount;
  242. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$value;
  243. }
  244. $condition=$column.' IN ('.implode(', ',$params).')';
  245. }
  246. return $this->addCondition($condition,$operator);
  247. }
  248. /**
  249. * Appends an NOT IN condition to the existing {@link condition}.
  250. * The NOT IN condition and the existing condition will be concatenated via the specified operator
  251. * which defaults to 'AND'.
  252. * The NOT IN condition is generated by using the SQL NOT IN operator which requires the specified
  253. * column value to be among the given list of values.
  254. * @param string $column the column name (or a valid SQL expression)
  255. * @param array $values list of values that the column value should not be in
  256. * @param string $operator the operator used to concatenate the new condition with the existing one.
  257. * Defaults to 'AND'.
  258. * @return CDbCriteria the criteria object itself
  259. * @since 1.1.1
  260. */
  261. public function addNotInCondition($column,$values,$operator='AND')
  262. {
  263. if(($n=count($values))<1)
  264. return $this;
  265. if($n===1)
  266. {
  267. $value=reset($values);
  268. if($value===null)
  269. return $this->addCondition($column.' IS NOT NULL');
  270. $condition=$column.'!='.self::PARAM_PREFIX.self::$paramCount;
  271. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$value;
  272. }
  273. else
  274. {
  275. $params=array();
  276. foreach($values as $value)
  277. {
  278. $params[]=self::PARAM_PREFIX.self::$paramCount;
  279. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$value;
  280. }
  281. $condition=$column.' NOT IN ('.implode(', ',$params).')';
  282. }
  283. return $this->addCondition($condition,$operator);
  284. }
  285. /**
  286. * Appends a condition for matching the given list of column values.
  287. * The generated condition will be concatenated to the existing {@link condition}
  288. * via the specified operator which defaults to 'AND'.
  289. * The condition is generated by matching each column and the corresponding value.
  290. * @param array $columns list of column names and values to be matched (name=>value)
  291. * @param string $columnOperator the operator to concatenate multiple column matching condition. Defaults to 'AND'.
  292. * @param string $operator the operator used to concatenate the new condition with the existing one.
  293. * Defaults to 'AND'.
  294. * @return CDbCriteria the criteria object itself
  295. * @since 1.0.10
  296. */
  297. public function addColumnCondition($columns,$columnOperator='AND',$operator='AND')
  298. {
  299. $params=array();
  300. foreach($columns as $name=>$value)
  301. {
  302. if($value===null)
  303. $params[]=$name.' IS NULL';
  304. else
  305. {
  306. $params[]=$name.'='.self::PARAM_PREFIX.self::$paramCount;
  307. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$value;
  308. }
  309. }
  310. return $this->addCondition(implode(" $columnOperator ",$params), $operator);
  311. }
  312. /**
  313. * Adds a comparison expression to the {@link condition} property.
  314. *
  315. * This method is a helper that appends to the {@link condition} property
  316. * with a new comparison expression. The comparison is done by comparing a column
  317. * with the given value using some comparison operator.
  318. *
  319. * The comparison operator is intelligently determined based on the first few
  320. * characters in the given value. In particular, it recognizes the following operators
  321. * if they appear as the leading characters in the given value:
  322. * <ul>
  323. * <li><code>&lt;</code>: the column must be less than the given value.</li>
  324. * <li><code>&gt;</code>: the column must be greater than the given value.</li>
  325. * <li><code>&lt;=</code>: the column must be less than or equal to the given value.</li>
  326. * <li><code>&gt;=</code>: the column must be greater than or equal to the given value.</li>
  327. * <li><code>&lt;&gt;</code>: the column must not be the same as the given value.
  328. * Note that when $partialMatch is true, this would mean the value must not be a substring
  329. * of the column.</li>
  330. * <li><code>=</code>: the column must be equal to the given value.</li>
  331. * <li>none of the above: the column must be equal to the given value. Note that when $partialMatch
  332. * is true, this would mean the value must be the same as the given value or be a substring of it.</li>
  333. * </ul>
  334. *
  335. * Note that any surrounding white spaces will be removed from the value before comparison.
  336. * When the value is empty, no comparison expression will be added to the search condition.
  337. *
  338. * @param string $column the name of the column to be searched
  339. * @param mixed $value the column value to be compared with. If the value is a string, the aforementioned
  340. * intelligent comparison will be conducted. If the value is an array, the comparison is done
  341. * by exact match of any of the value in the array. If the string or the array is empty,
  342. * the existing search condition will not be modified.
  343. * @param boolean $partialMatch whether the value should consider partial text match (using LIKE and NOT LIKE operators).
  344. * Defaults to false, meaning exact comparison.
  345. * @param string $operator the operator used to concatenate the new condition with the existing one.
  346. * Defaults to 'AND'.
  347. * @param boolean $escape whether the value should be escaped if $partialMatch is true and
  348. * the value contains characters % or _. When this parameter is true (default),
  349. * the special characters % (matches 0 or more characters)
  350. * and _ (matches a single character) will be escaped, and the value will be surrounded with a %
  351. * character on both ends. When this parameter is false, the value will be directly used for
  352. * matching without any change.
  353. * @return CDbCriteria the criteria object itself
  354. * @since 1.1.1
  355. */
  356. public function compare($column, $value, $partialMatch=false, $operator='AND', $escape=true)
  357. {
  358. if(is_array($value))
  359. {
  360. if($value===array())
  361. return $this;
  362. return $this->addInCondition($column,$value,$operator);
  363. }
  364. else
  365. $value="$value";
  366. if(preg_match('/^(?:\s*(<>|<=|>=|<|>|=))?(.*)$/',$value,$matches))
  367. {
  368. $value=$matches[2];
  369. $op=$matches[1];
  370. }
  371. else
  372. $op='';
  373. if($value==='')
  374. return $this;
  375. if($partialMatch)
  376. {
  377. if($op==='')
  378. return $this->addSearchCondition($column,$value,$escape,$operator);
  379. if($op==='<>')
  380. return $this->addSearchCondition($column,$value,$escape,$operator,'NOT LIKE');
  381. }
  382. else if($op==='')
  383. $op='=';
  384. $this->addCondition($column.$op.self::PARAM_PREFIX.self::$paramCount,$operator);
  385. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$value;
  386. return $this;
  387. }
  388. /**
  389. * Adds a between condition to the {@link condition} property.
  390. *
  391. * The new between condition and the existing condition will be concatenated via
  392. * the specified operator which defaults to 'AND'.
  393. * If one or both values are empty then the condition is not added to the existing condition.
  394. * This method handles the case when the existing condition is empty.
  395. * After calling this method, the {@link condition} property will be modified.
  396. * @param string $column the name of the column to search between.
  397. * @param string $valueStart the beginning value to start the between search.
  398. * @param string $valueEnd the ending value to end the between search.
  399. * @param string $operator the operator used to concatenate the new condition with the existing one.
  400. * Defaults to 'AND'.
  401. * @return CDbCriteria the criteria object itself
  402. * @since 1.1.2
  403. */
  404. public function addBetweenCondition($column,$valueStart,$valueEnd,$operator='AND')
  405. {
  406. if($valueStart==='' || $valueEnd==='')
  407. return $this;
  408. $paramStart=self::PARAM_PREFIX.self::$paramCount++;
  409. $paramEnd=self::PARAM_PREFIX.self::$paramCount++;
  410. $this->params[$paramStart]=$valueStart;
  411. $this->params[$paramEnd]=$valueEnd;
  412. $condition="$column BETWEEN $paramStart AND $paramEnd";
  413. if($this->condition==='')
  414. $this->condition=$condition;
  415. else
  416. $this->condition='('.$this->condition.') '.$operator.' ('.$condition.')';
  417. return $this;
  418. }
  419. /**
  420. * Merges with another criteria.
  421. * In general, the merging makes the resulting criteria more restrictive.
  422. * For example, if both criterias have conditions, they will be 'AND' together.
  423. * Also, the criteria passed as the parameter takes precedence in case
  424. * two options cannot be merged (e.g. LIMIT, OFFSET).
  425. * @param CDbCriteria $criteria the criteria to be merged with.
  426. * @param boolean $useAnd whether to use 'AND' to merge condition and having options.
  427. * If false, 'OR' will be used instead. Defaults to 'AND'. This parameter has been
  428. * available since version 1.0.6.
  429. * @since 1.0.5
  430. */
  431. public function mergeWith($criteria,$useAnd=true)
  432. {
  433. $and=$useAnd ? 'AND' : 'OR';
  434. if(is_array($criteria))
  435. $criteria=new self($criteria);
  436. if($this->select!==$criteria->select)
  437. {
  438. if($this->select==='*')
  439. $this->select=$criteria->select;
  440. else if($criteria->select!=='*')
  441. {
  442. $select1=is_string($this->select)?preg_split('/\s*,\s*/',trim($this->select),-1,PREG_SPLIT_NO_EMPTY):$this->select;
  443. $select2=is_string($criteria->select)?preg_split('/\s*,\s*/',trim($criteria->select),-1,PREG_SPLIT_NO_EMPTY):$criteria->select;
  444. $this->select=array_merge($select1,array_diff($select2,$select1));
  445. }
  446. }
  447. if($this->condition!==$criteria->condition)
  448. {
  449. if($this->condition==='')
  450. $this->condition=$criteria->condition;
  451. else if($criteria->condition!=='')
  452. $this->condition="({$this->condition}) $and ({$criteria->condition})";
  453. }
  454. if($this->params!==$criteria->params)
  455. $this->params=array_merge($this->params,$criteria->params);
  456. if($criteria->limit>0)
  457. $this->limit=$criteria->limit;
  458. if($criteria->offset>=0)
  459. $this->offset=$criteria->offset;
  460. if($criteria->alias!==null)
  461. $this->alias=$criteria->alias;
  462. if($this->order!==$criteria->order)
  463. {
  464. if($this->order==='')
  465. $this->order=$criteria->order;
  466. else if($criteria->order!=='')
  467. $this->order=$criteria->order.', '.$this->order;
  468. }
  469. if($this->group!==$criteria->group)
  470. {
  471. if($this->group==='')
  472. $this->group=$criteria->group;
  473. else if($criteria->group!=='')
  474. $this->group.=', '.$criteria->group;
  475. }
  476. if($this->join!==$criteria->join)
  477. {
  478. if($this->join==='')
  479. $this->join=$criteria->join;
  480. else if($criteria->join!=='')
  481. $this->join.=' '.$criteria->join;
  482. }
  483. if($this->having!==$criteria->having)
  484. {
  485. if($this->having==='')
  486. $this->having=$criteria->having;
  487. else if($criteria->having!=='')
  488. $this->having="({$this->having}) $and ({$criteria->having})";
  489. }
  490. if($criteria->distinct>0)
  491. $this->distinct=$criteria->distinct;
  492. if($criteria->together!==null)
  493. $this->together=$criteria->together;
  494. if($criteria->index!==null)
  495. $this->index=$criteria->index;
  496. if(empty($this->scopes))
  497. $this->scopes=$criteria->scopes;
  498. else if(!empty($criteria->scopes))
  499. {
  500. $scopes1=(array)$this->scopes;
  501. $scopes2=(array)$criteria->scopes;
  502. foreach($scopes1 as $k=>$v)
  503. {
  504. if(is_integer($k))
  505. $scopes[]=$v;
  506. else if(isset($scopes2[$k]))
  507. $scopes[]=array($k=>$v);
  508. else
  509. $scopes[$k]=$v;
  510. }
  511. foreach($scopes2 as $k=>$v)
  512. {
  513. if(is_integer($k))
  514. $scopes[]=$v;
  515. else if(isset($scopes1[$k]))
  516. $scopes[]=array($k=>$v);
  517. else
  518. $scopes[$k]=$v;
  519. }
  520. $this->scopes=$scopes;
  521. }
  522. if(empty($this->with))
  523. $this->with=$criteria->with;
  524. else if(!empty($criteria->with))
  525. {
  526. $this->with=(array)$this->with;
  527. foreach((array)$criteria->with as $k=>$v)
  528. {
  529. if(is_integer($k))
  530. $this->with[]=$v;
  531. else if(isset($this->with[$k]))
  532. {
  533. $excludes=array();
  534. foreach(array('joinType','on') as $opt)
  535. {
  536. if(isset($this->with[$k][$opt]))
  537. $excludes[$opt]=$this->with[$k][$opt];
  538. if(isset($v[$opt]))
  539. $excludes[$opt]= ($opt==='on' && isset($excludes[$opt]) && $v[$opt]!==$excludes[$opt]) ?
  540. "($excludes[$opt]) AND $v[$opt]" : $v[$opt];
  541. unset($this->with[$k][$opt]);
  542. unset($v[$opt]);
  543. }
  544. $this->with[$k]=new self($this->with[$k]);
  545. $this->with[$k]->mergeWith($v,$useAnd);
  546. $this->with[$k]=$this->with[$k]->toArray();
  547. if (count($excludes)!==0)
  548. $this->with[$k]=CMap::mergeArray($this->with[$k],$excludes);
  549. }
  550. else
  551. $this->with[$k]=$v;
  552. }
  553. }
  554. }
  555. /**
  556. * @return array the array representation of the criteria
  557. * @since 1.0.6
  558. */
  559. public function toArray()
  560. {
  561. $result=array();
  562. foreach(array('select', 'condition', 'params', 'limit', 'offset', 'order', 'group', 'join', 'having', 'distinct', 'scopes', 'with', 'alias', 'index', 'together') as $name)
  563. $result[$name]=$this->$name;
  564. return $result;
  565. }
  566. }