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

/alloy/Plugin/Spot/lib/Spot/Query.php

http://github.com/alloyphp/alloy
PHP | 338 lines | 165 code | 48 blank | 125 comment | 13 complexity | 65350b90150029ec9a72873a006e9962 MD5 | raw file
  1. <?php
  2. namespace Spot;
  3. /**
  4. * Query Object - Used to build adapter-independent queries PHP-style
  5. *
  6. * @package Spot
  7. * @author Vance Lucas <vance@vancelucas.com>
  8. * @link http://spot.os.ly
  9. */
  10. class Query implements \Countable, \IteratorAggregate
  11. {
  12. protected $_mapper;
  13. protected $_entityName;
  14. // Storage for query properties
  15. public $fields = array();
  16. public $datasource;
  17. public $conditions = array();
  18. public $search = array();
  19. public $order = array();
  20. public $group = array();
  21. public $limit;
  22. public $offset;
  23. /**
  24. * Constructor Method
  25. *
  26. * @param Spot_Mapper
  27. * @param string $entityName Name of the entity to query on/for
  28. */
  29. public function __construct(\Spot\Mapper $mapper, $entityName)
  30. {
  31. $this->_mapper = $mapper;
  32. $this->_entityName = $entityName;
  33. }
  34. /**
  35. * Get current adapter object
  36. */
  37. public function mapper()
  38. {
  39. return $this->_mapper;
  40. }
  41. /**
  42. * Get current entity name query is to be performed on
  43. */
  44. public function entityName()
  45. {
  46. return $this->_entityName;
  47. }
  48. /**
  49. * Called from mapper's select() function
  50. *
  51. * @param mixed $fields (optional)
  52. * @param string $source Data source name
  53. * @return string
  54. */
  55. public function select($fields = "*", $datasource = null)
  56. {
  57. $this->fields = (is_string($fields) ? explode(',', $fields) : $fields);
  58. if(null !== $datasource) {
  59. $this->from($datasource);
  60. }
  61. return $this;
  62. }
  63. /**
  64. * From
  65. *
  66. * @param string $datasource Name of the data source to perform a query on
  67. * @todo Support multiple sources/joins
  68. */
  69. public function from($datasource = null)
  70. {
  71. $this->datasource = $datasource;
  72. return $this;
  73. }
  74. /**
  75. * Find records with given conditions
  76. * If all parameters are empty, find all records
  77. *
  78. * @param array $conditions Array of conditions in column => value pairs
  79. */
  80. public function all(array $conditions = array())
  81. {
  82. return $this->where($conditions);
  83. }
  84. /**
  85. * WHERE conditions
  86. *
  87. * @param array $conditions Array of conditions for this clause
  88. * @param string $type Keyword that will separate each condition - "AND", "OR"
  89. * @param string $setType Keyword that will separate the whole set of conditions - "AND", "OR"
  90. */
  91. public function where(array $conditions = array(), $type = "AND", $setType = "AND")
  92. {
  93. // Don't add WHERE clause if array is empty (easy way to support dynamic request options that modify current query)
  94. if($conditions) {
  95. $where = array();
  96. $where['conditions'] = $conditions;
  97. $where['type'] = $type;
  98. $where['setType'] = $setType;
  99. $this->conditions[] = $where;
  100. }
  101. return $this;
  102. }
  103. public function orWhere(array $conditions = array(), $type = "AND")
  104. {
  105. return $this->where($conditions, $type, "OR");
  106. }
  107. public function andWhere(array $conditions = array(), $type = "AND")
  108. {
  109. return $this->where($conditions, $type, "AND");
  110. }
  111. /**
  112. * Search criteria (FULLTEXT, LIKE, or REGEX, depending on storage engine and driver)
  113. *
  114. * @param mixed $fields Single string field or array of field names to use for searching
  115. * @param string $query Search keywords or query
  116. * @param array $options Array of options to pass to db engine
  117. * @return $this
  118. */
  119. public function search($fields, $query, array $options = array())
  120. {
  121. $fields = (array) $fields;
  122. $entityDatasourceOptions = $this->mapper()->entityManager()->datasourceOptions($this->entityName());
  123. $fieldString = '`' . implode('`, `', $fields) . '`';
  124. $fieldTypes = $this->mapper()->fields($this->entityName());
  125. // See if we can use FULLTEXT search
  126. $whereType = ':like';
  127. $connection = $this->mapper()->connection($this->entityName());
  128. // Only on MySQL
  129. if($connection instanceof \Spot\Adapter\Mysql) {
  130. // Only for MyISAM engine
  131. if(isset($entityDatasourceOptions['engine'])) {
  132. $engine = $entityDatasourceOptions['engine'];
  133. if('myisam' == strtolower($engine)) {
  134. $whereType = ':fulltext';
  135. // Only if ALL included columns allow fulltext according to entity definition
  136. if(in_array($fields, array_keys($this->mapper()->fields($this->entityName())))) {
  137. // FULLTEXT
  138. $whereType = ':fulltext';
  139. }
  140. }
  141. }
  142. }
  143. // @todo Normal queries can't search mutliple fields, so make them separate searches instead of stringing them together
  144. // Resolve search criteria
  145. return $this->where(array($fieldString . ' ' . $whereType => $query));
  146. }
  147. /**
  148. * ORDER BY columns
  149. *
  150. * @param array $fields Array of field names to use for sorting
  151. * @return $this
  152. */
  153. public function order($fields = array())
  154. {
  155. $orderBy = array();
  156. $defaultSort = "ASC";
  157. if(is_array($fields)) {
  158. foreach($fields as $field => $sort) {
  159. // Numeric index - field as array entry, not key/value pair
  160. if(is_numeric($field)) {
  161. $field = $sort;
  162. $sort = $defaultSort;
  163. }
  164. $this->order[$field] = strtoupper($sort);
  165. }
  166. } else {
  167. $this->order[$fields] = $defaultSort;
  168. }
  169. return $this;
  170. }
  171. /**
  172. * GROUP BY clause
  173. *
  174. * @param array $fields Array of field names to use for grouping
  175. * @return $this
  176. */
  177. public function group(array $fields = array())
  178. {
  179. foreach($fields as $field) {
  180. $this->group[] = $field;
  181. }
  182. return $this;
  183. }
  184. /**
  185. * Limit executed query to specified amount of records
  186. * Implemented at adapter-level for databases that support it
  187. *
  188. * @param int $limit Number of records to return
  189. * @param int $offset Record to start at for limited result set
  190. */
  191. public function limit($limit = 20, $offset = null)
  192. {
  193. $this->limit = $limit;
  194. $this->offset = $offset;
  195. return $this;
  196. }
  197. /**
  198. * Offset executed query to skip specified amount of records
  199. * Implemented at adapter-level for databases that support it
  200. *
  201. * @param int $offset Record to start at for limited result set
  202. */
  203. public function offset($offset = 0)
  204. {
  205. $this->offset = $offset;
  206. return $this;
  207. }
  208. /**
  209. * Return array of parameters in key => value format
  210. *
  211. * @return array Parameters in key => value format
  212. */
  213. public function params()
  214. {
  215. $params = array();
  216. $ci = 0;
  217. foreach($this->conditions as $i => $data) {
  218. if(isset($data['conditions']) && is_array($data['conditions'])) {
  219. foreach($data['conditions'] as $field => $value) {
  220. // Column name with comparison operator
  221. $colData = explode(' ', $field);
  222. $operator = '=';
  223. if (count($colData) > 2) {
  224. $operator = array_pop($colData);
  225. $colData = array(implode(' ', $colData), $operator);
  226. }
  227. $field = $colData[0];
  228. $params[$field . $ci] = $value;
  229. $ci++;
  230. }
  231. }
  232. }
  233. return $params;
  234. }
  235. // ===================================================================
  236. /**
  237. * SPL Countable function
  238. * Called automatically when attribute is used in a 'count()' function call
  239. *
  240. * @return int
  241. */
  242. public function count()
  243. {
  244. // Execute query
  245. $result = $this->mapper()->connection($this->entityName())->count($this);
  246. //return count
  247. return is_numeric($result) ? $result : 0;
  248. }
  249. /**
  250. * SPL IteratorAggregate function
  251. * Called automatically when attribute is used in a 'foreach' loop
  252. *
  253. * @return Spot_Query_Set
  254. */
  255. public function getIterator()
  256. {
  257. // Execute query and return result set for iteration
  258. $result = $this->execute();
  259. return ($result !== false) ? $result : array();
  260. }
  261. /**
  262. * Convenience function passthrough for Collection
  263. *
  264. * @return array
  265. */
  266. public function toArray($keyColumn = null, $valueColumn = null)
  267. {
  268. $result = $this->execute();
  269. return ($result !== false) ? $result->toArray($keyColumn, $valueColumn) : array();
  270. }
  271. /**
  272. * Return the first entity matched by the query
  273. *
  274. * @return mixed Spot_Entity on success, boolean false on failure
  275. */
  276. public function first()
  277. {
  278. $result = $this->limit(1)->execute();
  279. return ($result !== false) ? $result->first() : false;
  280. }
  281. /**
  282. * Execute and return query as a collection
  283. *
  284. * @return mixed Collection object on success, boolean false on failure
  285. */
  286. public function execute()
  287. {
  288. return $this->mapper()->connection($this->entityName())->read($this);
  289. }
  290. }