PageRenderTime 78ms CodeModel.GetById 1ms RepoModel.GetById 1ms app.codeStats 0ms

/yii/framework/db/schema/CDbCriteria.php

https://github.com/joshuaswarren/weatherhub
PHP | 596 lines | 291 code | 32 blank | 273 comment | 76 complexity | 5b05da5729cb7e39872e52b0a31a6ef8 MD5 | raw file
  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 3238 2011-05-25 19:02:15Z qiang.xue $
  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.
  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, a SQL statement will be executed for each HAS_MANY relation.
  108. * Otherwise, a single SQL statement will be executed for all.
  109. *
  110. * @since 1.1.4
  111. */
  112. public $together;
  113. /**
  114. * @var string the name of the AR attribute whose value should be used as index of the query result array.
  115. * Defaults to null, meaning the result array will be zero-based integers.
  116. * @since 1.1.5
  117. */
  118. public $index;
  119. /**
  120. * @var mixed scopes to apply
  121. *
  122. * This property is effective only when passing criteria to
  123. * the one of the following methods:
  124. * <ul>
  125. * <li>{@link CActiveRecord::find()}</li>
  126. * <li>{@link CActiveRecord::findAll()}</li>
  127. * <li>{@link CActiveRecord::findByPk()}</li>
  128. * <li>{@link CActiveRecord::findAllByPk()}</li>
  129. * <li>{@link CActiveRecord::findByAttributes()}</li>
  130. * <li>{@link CActiveRecord::findAllByAttributes()}</li>
  131. * <li>{@link CActiveRecord::count()}</li>
  132. * </ul>
  133. *
  134. * Can be set to one of the following:
  135. * <ul>
  136. * <li>One scope: $criteria->scopes='scopeName';</li>
  137. * <li>Multiple scopes: $criteria->scopes=array('scopeName1','scopeName1');</li>
  138. * <li>Scope with parameters: $criteria->scopes=array('scopeName'=>array($paramters));</li>
  139. * <li>Multiple scopes with the same name: array(array('scopeName'=>array($paramters1)),array('scopeName'=>array($paramters2)));</li>
  140. * </ul>
  141. * @since 1.1.7
  142. */
  143. public $scopes;
  144. /**
  145. * Constructor.
  146. * @param array $data criteria initial property values (indexed by property name)
  147. */
  148. public function __construct($data=array())
  149. {
  150. foreach($data as $name=>$value)
  151. $this->$name=$value;
  152. }
  153. /**
  154. * Appends a condition to the existing {@link condition}.
  155. * The new condition and the existing condition will be concatenated via the specified operator
  156. * which defaults to 'AND'.
  157. * The new condition can also be an array. In this case, all elements in the array
  158. * will be concatenated together via the operator.
  159. * This method handles the case when the existing condition is empty.
  160. * After calling this method, the {@link condition} property will be modified.
  161. * @param mixed $condition the new condition. It can be either a string or an array of strings.
  162. * @param string $operator the operator to join different conditions. Defaults to 'AND'.
  163. * @return CDbCriteria the criteria object itself
  164. * @since 1.0.9
  165. */
  166. public function addCondition($condition,$operator='AND')
  167. {
  168. if(is_array($condition))
  169. {
  170. if($condition===array())
  171. return $this;
  172. $condition='('.implode(') '.$operator.' (',$condition).')';
  173. }
  174. if($this->condition==='')
  175. $this->condition=$condition;
  176. else
  177. $this->condition='('.$this->condition.') '.$operator.' ('.$condition.')';
  178. return $this;
  179. }
  180. /**
  181. * Appends a search condition to the existing {@link condition}.
  182. * The search condition and the existing condition will be concatenated via the specified operator
  183. * which defaults to 'AND'.
  184. * The search condition is generated using the SQL LIKE operator with the given column name and
  185. * search keyword.
  186. * @param string $column the column name (or a valid SQL expression)
  187. * @param string $keyword the search keyword. This interpretation of the keyword is affected by the next parameter.
  188. * @param boolean $escape whether the keyword should be escaped if it contains characters % or _.
  189. * When this parameter is true (default), the special characters % (matches 0 or more characters)
  190. * and _ (matches a single character) will be escaped, and the keyword will be surrounded with a %
  191. * character on both ends. When this parameter is false, the keyword will be directly used for
  192. * matching without any change.
  193. * @param string $operator the operator used to concatenate the new condition with the existing one.
  194. * Defaults to 'AND'.
  195. * @param string $like the LIKE operator. Defaults to 'LIKE'. You may also set this to be 'NOT LIKE'.
  196. * @return CDbCriteria the criteria object itself
  197. * @since 1.0.10
  198. */
  199. public function addSearchCondition($column,$keyword,$escape=true,$operator='AND',$like='LIKE')
  200. {
  201. if($keyword=='')
  202. return $this;
  203. if($escape)
  204. $keyword='%'.strtr($keyword,array('%'=>'\%', '_'=>'\_', '\\'=>'\\\\')).'%';
  205. $condition=$column." $like ".self::PARAM_PREFIX.self::$paramCount;
  206. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$keyword;
  207. return $this->addCondition($condition, $operator);
  208. }
  209. /**
  210. * Appends an IN condition to the existing {@link condition}.
  211. * The IN condition and the existing condition will be concatenated via the specified operator
  212. * which defaults to 'AND'.
  213. * The IN condition is generated by using the SQL IN operator which requires the specified
  214. * column value to be among the given list of values.
  215. * @param string $column the column name (or a valid SQL expression)
  216. * @param array $values list of values that the column value should be in
  217. * @param string $operator the operator used to concatenate the new condition with the existing one.
  218. * Defaults to 'AND'.
  219. * @return CDbCriteria the criteria object itself
  220. * @since 1.0.10
  221. */
  222. public function addInCondition($column,$values,$operator='AND')
  223. {
  224. if(($n=count($values))<1)
  225. return $this->addCondition('0=1',$operator); // 0=1 is used because in MSSQL value alone can't be used in WHERE
  226. if($n===1)
  227. {
  228. $value=reset($values);
  229. if($value===null)
  230. return $this->addCondition($column.' IS NULL');
  231. $condition=$column.'='.self::PARAM_PREFIX.self::$paramCount;
  232. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$value;
  233. }
  234. else
  235. {
  236. $params=array();
  237. foreach($values as $value)
  238. {
  239. $params[]=self::PARAM_PREFIX.self::$paramCount;
  240. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$value;
  241. }
  242. $condition=$column.' IN ('.implode(', ',$params).')';
  243. }
  244. return $this->addCondition($condition,$operator);
  245. }
  246. /**
  247. * Appends an NOT IN condition to the existing {@link condition}.
  248. * The NOT IN condition and the existing condition will be concatenated via the specified operator
  249. * which defaults to 'AND'.
  250. * The NOT IN condition is generated by using the SQL NOT IN operator which requires the specified
  251. * column value to be among the given list of values.
  252. * @param string $column the column name (or a valid SQL expression)
  253. * @param array $values list of values that the column value should not be in
  254. * @param string $operator the operator used to concatenate the new condition with the existing one.
  255. * Defaults to 'AND'.
  256. * @return CDbCriteria the criteria object itself
  257. * @since 1.1.1
  258. */
  259. public function addNotInCondition($column,$values,$operator='AND')
  260. {
  261. if(($n=count($values))<1)
  262. return $this;
  263. if($n===1)
  264. {
  265. $value=reset($values);
  266. if($value===null)
  267. return $this->addCondition($column.' IS NOT NULL');
  268. $condition=$column.'!='.self::PARAM_PREFIX.self::$paramCount;
  269. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$value;
  270. }
  271. else
  272. {
  273. $params=array();
  274. foreach($values as $value)
  275. {
  276. $params[]=self::PARAM_PREFIX.self::$paramCount;
  277. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$value;
  278. }
  279. $condition=$column.' NOT IN ('.implode(', ',$params).')';
  280. }
  281. return $this->addCondition($condition,$operator);
  282. }
  283. /**
  284. * Appends a condition for matching the given list of column values.
  285. * The generated condition will be concatenated to the existing {@link condition}
  286. * via the specified operator which defaults to 'AND'.
  287. * The condition is generated by matching each column and the corresponding value.
  288. * @param array $columns list of column names and values to be matched (name=>value)
  289. * @param string $columnOperator the operator to concatenate multiple column matching condition. Defaults to 'AND'.
  290. * @param string $operator the operator used to concatenate the new condition with the existing one.
  291. * Defaults to 'AND'.
  292. * @return CDbCriteria the criteria object itself
  293. * @since 1.0.10
  294. */
  295. public function addColumnCondition($columns,$columnOperator='AND',$operator='AND')
  296. {
  297. $params=array();
  298. foreach($columns as $name=>$value)
  299. {
  300. if($value===null)
  301. $params[]=$name.' IS NULL';
  302. else
  303. {
  304. $params[]=$name.'='.self::PARAM_PREFIX.self::$paramCount;
  305. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$value;
  306. }
  307. }
  308. return $this->addCondition(implode(" $columnOperator ",$params), $operator);
  309. }
  310. /**
  311. * Adds a comparison expression to the {@link condition} property.
  312. *
  313. * This method is a helper that appends to the {@link condition} property
  314. * with a new comparison expression. The comparison is done by comparing a column
  315. * with the given value using some comparison operator.
  316. *
  317. * The comparison operator is intelligently determined based on the first few
  318. * characters in the given value. In particular, it recognizes the following operators
  319. * if they appear as the leading characters in the given value:
  320. * <ul>
  321. * <li><code>&lt;</code>: the column must be less than the given value.</li>
  322. * <li><code>&gt;</code>: the column must be greater than the given value.</li>
  323. * <li><code>&lt;=</code>: the column must be less than or equal to the given value.</li>
  324. * <li><code>&gt;=</code>: the column must be greater than or equal to the given value.</li>
  325. * <li><code>&lt;&gt;</code>: the column must not be the same as the given value.
  326. * Note that when $partialMatch is true, this would mean the value must not be a substring
  327. * of the column.</li>
  328. * <li><code>=</code>: the column must be equal to the given value.</li>
  329. * <li>none of the above: the column must be equal to the given value. Note that when $partialMatch
  330. * is true, this would mean the value must be the same as the given value or be a substring of it.</li>
  331. * </ul>
  332. *
  333. * Note that any surrounding white spaces will be removed from the value before comparison.
  334. * When the value is empty, no comparison expression will be added to the search condition.
  335. *
  336. * @param string $column the name of the column to be searched
  337. * @param mixed $value the column value to be compared with. If the value is a string, the aforementioned
  338. * intelligent comparison will be conducted. If the value is an array, the comparison is done
  339. * by exact match of any of the value in the array. If the string or the array is empty,
  340. * the existing search condition will not be modified.
  341. * @param boolean $partialMatch whether the value should consider partial text match (using LIKE and NOT LIKE operators).
  342. * Defaults to false, meaning exact comparison.
  343. * @param string $operator the operator used to concatenate the new condition with the existing one.
  344. * Defaults to 'AND'.
  345. * @param boolean $escape whether the value should be escaped if $partialMatch is true and
  346. * the value contains characters % or _. When this parameter is true (default),
  347. * the special characters % (matches 0 or more characters)
  348. * and _ (matches a single character) will be escaped, and the value will be surrounded with a %
  349. * character on both ends. When this parameter is false, the value will be directly used for
  350. * matching without any change.
  351. * @return CDbCriteria the criteria object itself
  352. * @since 1.1.1
  353. */
  354. public function compare($column, $value, $partialMatch=false, $operator='AND', $escape=true)
  355. {
  356. if(is_array($value))
  357. {
  358. if($value===array())
  359. return $this;
  360. return $this->addInCondition($column,$value,$operator);
  361. }
  362. else
  363. $value="$value";
  364. if(preg_match('/^(?:\s*(<>|<=|>=|<|>|=))?(.*)$/',$value,$matches))
  365. {
  366. $value=$matches[2];
  367. $op=$matches[1];
  368. }
  369. else
  370. $op='';
  371. if($value==='')
  372. return $this;
  373. if($partialMatch)
  374. {
  375. if($op==='')
  376. return $this->addSearchCondition($column,$value,$escape,$operator);
  377. if($op==='<>')
  378. return $this->addSearchCondition($column,$value,$escape,$operator,'NOT LIKE');
  379. }
  380. else if($op==='')
  381. $op='=';
  382. $this->addCondition($column.$op.self::PARAM_PREFIX.self::$paramCount,$operator);
  383. $this->params[self::PARAM_PREFIX.self::$paramCount++]=$value;
  384. return $this;
  385. }
  386. /**
  387. * Adds a between condition to the {@link condition} property.
  388. *
  389. * The new between condition and the existing condition will be concatenated via
  390. * the specified operator which defaults to 'AND'.
  391. * If one or both values are empty then the condition is not added to the existing condition.
  392. * This method handles the case when the existing condition is empty.
  393. * After calling this method, the {@link condition} property will be modified.
  394. * @param string $column the name of the column to search between.
  395. * @param string $valueStart the beginning value to start the between search.
  396. * @param string $valueEnd the ending value to end the between search.
  397. * @param string $operator the operator used to concatenate the new condition with the existing one.
  398. * Defaults to 'AND'.
  399. * @return CDbCriteria the criteria object itself
  400. * @since 1.1.2
  401. */
  402. public function addBetweenCondition($column,$valueStart,$valueEnd,$operator='AND')
  403. {
  404. if($valueStart==='' || $valueEnd==='')
  405. return $this;
  406. $paramStart=self::PARAM_PREFIX.self::$paramCount++;
  407. $paramEnd=self::PARAM_PREFIX.self::$paramCount++;
  408. $this->params[$paramStart]=$valueStart;
  409. $this->params[$paramEnd]=$valueEnd;
  410. $condition="$column BETWEEN $paramStart AND $paramEnd";
  411. if($this->condition==='')
  412. $this->condition=$condition;
  413. else
  414. $this->condition='('.$this->condition.') '.$operator.' ('.$condition.')';
  415. return $this;
  416. }
  417. /**
  418. * Merges with another criteria.
  419. * In general, the merging makes the resulting criteria more restrictive.
  420. * For example, if both criterias have conditions, they will be 'AND' together.
  421. * Also, the criteria passed as the parameter takes precedence in case
  422. * two options cannot be merged (e.g. LIMIT, OFFSET).
  423. * @param CDbCriteria $criteria the criteria to be merged with.
  424. * @param boolean $useAnd whether to use 'AND' to merge condition and having options.
  425. * If false, 'OR' will be used instead. Defaults to 'AND'. This parameter has been
  426. * available since version 1.0.6.
  427. * @since 1.0.5
  428. */
  429. public function mergeWith($criteria,$useAnd=true)
  430. {
  431. $and=$useAnd ? 'AND' : 'OR';
  432. if(is_array($criteria))
  433. $criteria=new self($criteria);
  434. if($this->select!==$criteria->select)
  435. {
  436. if($this->select==='*')
  437. $this->select=$criteria->select;
  438. else if($criteria->select!=='*')
  439. {
  440. $select1=is_string($this->select)?preg_split('/\s*,\s*/',trim($this->select),-1,PREG_SPLIT_NO_EMPTY):$this->select;
  441. $select2=is_string($criteria->select)?preg_split('/\s*,\s*/',trim($criteria->select),-1,PREG_SPLIT_NO_EMPTY):$criteria->select;
  442. $this->select=array_merge($select1,array_diff($select2,$select1));
  443. }
  444. }
  445. if($this->condition!==$criteria->condition)
  446. {
  447. if($this->condition==='')
  448. $this->condition=$criteria->condition;
  449. else if($criteria->condition!=='')
  450. $this->condition="({$this->condition}) $and ({$criteria->condition})";
  451. }
  452. if($this->params!==$criteria->params)
  453. $this->params=array_merge($this->params,$criteria->params);
  454. if($criteria->limit>0)
  455. $this->limit=$criteria->limit;
  456. if($criteria->offset>=0)
  457. $this->offset=$criteria->offset;
  458. if($criteria->alias!==null)
  459. $this->alias=$criteria->alias;
  460. if($this->order!==$criteria->order)
  461. {
  462. if($this->order==='')
  463. $this->order=$criteria->order;
  464. else if($criteria->order!=='')
  465. $this->order=$criteria->order.', '.$this->order;
  466. }
  467. if($this->group!==$criteria->group)
  468. {
  469. if($this->group==='')
  470. $this->group=$criteria->group;
  471. else if($criteria->group!=='')
  472. $this->group.=', '.$criteria->group;
  473. }
  474. if($this->join!==$criteria->join)
  475. {
  476. if($this->join==='')
  477. $this->join=$criteria->join;
  478. else if($criteria->join!=='')
  479. $this->join.=' '.$criteria->join;
  480. }
  481. if($this->having!==$criteria->having)
  482. {
  483. if($this->having==='')
  484. $this->having=$criteria->having;
  485. else if($criteria->having!=='')
  486. $this->having="({$this->having}) $and ({$criteria->having})";
  487. }
  488. if($criteria->distinct>0)
  489. $this->distinct=$criteria->distinct;
  490. if($criteria->together!==null)
  491. $this->together=$criteria->together;
  492. if($criteria->index!==null)
  493. $this->index=$criteria->index;
  494. if(empty($this->scopes))
  495. $this->scopes=$criteria->scopes;
  496. else if(!empty($criteria->scopes))
  497. {
  498. $scopes1=(array)$this->scopes;
  499. $scopes2=(array)$criteria->scopes;
  500. foreach($scopes1 as $k=>$v)
  501. {
  502. if(is_integer($k))
  503. $scopes[]=$v;
  504. else if(isset($scopes2[$k]))
  505. $scopes[]=array($k=>$v);
  506. else
  507. $scopes[$k]=$v;
  508. }
  509. foreach($scopes2 as $k=>$v)
  510. {
  511. if(is_integer($k))
  512. $scopes[]=$v;
  513. else if(isset($scopes1[$k]))
  514. $scopes[]=array($k=>$v);
  515. else
  516. $scopes[$k]=$v;
  517. }
  518. $this->scopes=$scopes;
  519. }
  520. if(empty($this->with))
  521. $this->with=$criteria->with;
  522. else if(!empty($criteria->with))
  523. {
  524. $this->with=(array)$this->with;
  525. foreach((array)$criteria->with as $k=>$v)
  526. {
  527. if(is_integer($k))
  528. $this->with[]=$v;
  529. else if(isset($this->with[$k]))
  530. {
  531. $excludes=array();
  532. foreach(array('joinType','on') as $opt)
  533. {
  534. if(isset($this->with[$k][$opt]))
  535. $excludes[$opt]=$this->with[$k][$opt];
  536. if(isset($v[$opt]))
  537. $excludes[$opt]= ($opt==='on' && isset($excludes[$opt]) && $v[$opt]!==$excludes[$opt]) ?
  538. "($excludes[$opt]) AND $v[$opt]" : $v[$opt];
  539. unset($this->with[$k][$opt]);
  540. unset($v[$opt]);
  541. }
  542. $this->with[$k]=new self($this->with[$k]);
  543. $this->with[$k]->mergeWith($v,$useAnd);
  544. $this->with[$k]=$this->with[$k]->toArray();
  545. if (count($excludes)!==0)
  546. $this->with[$k]=CMap::mergeArray($this->with[$k],$excludes);
  547. }
  548. else
  549. $this->with[$k]=$v;
  550. }
  551. }
  552. }
  553. /**
  554. * @return array the array representation of the criteria
  555. * @since 1.0.6
  556. */
  557. public function toArray()
  558. {
  559. $result=array();
  560. foreach(array('select', 'condition', 'params', 'limit', 'offset', 'order', 'group', 'join', 'having', 'distinct', 'scopes', 'with', 'alias', 'index', 'together') as $name)
  561. $result[$name]=$this->$name;
  562. return $result;
  563. }
  564. }