Query.php 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264
  1. <?php
  2. /**
  3. * PHP Version 5.4
  4. *
  5. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  6. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  7. *
  8. * Licensed under The MIT License
  9. * For full copyright and license information, please see the LICENSE.txt
  10. * Redistributions of files must retain the above copyright notice.
  11. *
  12. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  13. * @link http://cakephp.org CakePHP(tm) Project
  14. * @since CakePHP(tm) v 3.0.0
  15. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  16. */
  17. namespace Cake\ORM;
  18. use Cake\Collection\Iterator\MapReduce;
  19. use Cake\Database\Query as DatabaseQuery;
  20. use Cake\Database\Statement\BufferedStatement;
  21. use Cake\Database\Statement\CallbackStatement;
  22. use Cake\Event\Event;
  23. use Cake\ORM\QueryCacher;
  24. use Cake\ORM\Table;
  25. /**
  26. * Extends the base Query class to provide new methods related to association
  27. * loading, automatic fields selection, automatic type casting and to wrap results
  28. * into an specific iterator that will be responsible for hydrating results if
  29. * required.
  30. *
  31. */
  32. class Query extends DatabaseQuery {
  33. /**
  34. * Instance of a table object this query is bound to
  35. *
  36. * @var \Cake\ORM\Table
  37. */
  38. protected $_table;
  39. /**
  40. * Nested array describing the association to be fetched
  41. * and the options to apply for each of them, if any
  42. *
  43. * @var \ArrayObject
  44. */
  45. protected $_containments;
  46. /**
  47. * Contains a nested array with the compiled containments tree
  48. * This is a normalized version of the user provided containments array.
  49. *
  50. * @var array
  51. */
  52. protected $_normalizedContainments;
  53. /**
  54. * Whether the user select any fields before being executed, this is used
  55. * to determined if any fields should be automatically be selected.
  56. *
  57. * @var boolean
  58. */
  59. protected $_hasFields;
  60. /**
  61. * A list of associations that should be eagerly loaded
  62. *
  63. * @var array
  64. */
  65. protected $_loadEagerly = [];
  66. /**
  67. * List of options accepted by associations in contain()
  68. * index by key for faster access
  69. *
  70. * @var array
  71. */
  72. protected $_containOptions = [
  73. 'associations' => 1,
  74. 'foreignKey' => 1,
  75. 'conditions' => 1,
  76. 'fields' => 1,
  77. 'sort' => 1,
  78. 'matching' => 1,
  79. 'queryBuilder' => 1
  80. ];
  81. /**
  82. * A ResultSet.
  83. *
  84. * When set, query execution will be bypassed.
  85. *
  86. * @var Cake\ORM\ResultSet
  87. * @see setResult()
  88. */
  89. protected $_results;
  90. /**
  91. * Boolean for tracking whether or not buffered results
  92. * are enabled.
  93. *
  94. * @var boolean
  95. */
  96. protected $_useBufferedResults = true;
  97. /**
  98. * List of map-reduce routines that should be applied over the query
  99. * result
  100. *
  101. * @var array
  102. */
  103. protected $_mapReduce = [];
  104. /**
  105. * List of formatter classes or callbacks that will post-process the
  106. * results when fetched
  107. *
  108. * @var array
  109. */
  110. protected $_formatters = [];
  111. /**
  112. * Holds any custom options passed using applyOptions that could not be processed
  113. * by any method in this class.
  114. *
  115. * @var array
  116. */
  117. protected $_options = [];
  118. /**
  119. * Whether to hydrate results into entity objects
  120. *
  121. * @var boolean
  122. */
  123. protected $_hydrate = true;
  124. /**
  125. * A query cacher instance if this query has caching enabled.
  126. *
  127. * @var Cake\ORM\QueryCacher
  128. */
  129. protected $_cache;
  130. /**
  131. * @param Cake\Database\Connection $connection
  132. * @param Cake\ORM\Table $table
  133. */
  134. public function __construct($connection, $table) {
  135. $this->connection($connection);
  136. $this->repository($table);
  137. }
  138. /**
  139. * Returns the default table object that will be used by this query,
  140. * that is, the table that will appear in the from clause.
  141. *
  142. * When called with a Table argument, the default table object will be set
  143. * and this query object will be returned for chaining.
  144. *
  145. * @param \Cake\ORM\Table $table The default table object to use
  146. * @return \Cake\ORM\Table|Query
  147. */
  148. public function repository(Table $table = null) {
  149. if ($table === null) {
  150. return $this->_table;
  151. }
  152. $this->_table = $table;
  153. $this->addDefaultTypes($table);
  154. return $this;
  155. }
  156. /**
  157. * Hints this object to associate the correct types when casting conditions
  158. * for the database. This is done by extracting the field types from the schema
  159. * associated to the passed table object. This prevents the user from repeating
  160. * himself when specifying conditions.
  161. *
  162. * This method returns the same query object for chaining.
  163. *
  164. * @param \Cake\ORM\Table $table
  165. * @return Query
  166. */
  167. public function addDefaultTypes(Table $table) {
  168. $alias = $table->alias();
  169. $schema = $table->schema();
  170. $fields = [];
  171. foreach ($schema->columns() as $f) {
  172. $fields[$f] = $fields[$alias . '.' . $f] = $schema->columnType($f);
  173. }
  174. $this->defaultTypes($this->defaultTypes() + $fields);
  175. return $this;
  176. }
  177. /**
  178. * Sets the list of associations that should be eagerly loaded along with this
  179. * query. The list of associated tables passed must have been previously set as
  180. * associations using the Table API.
  181. *
  182. * ### Example:
  183. *
  184. * {{{
  185. * // Bring articles' author information
  186. * $query->contain('Author');
  187. *
  188. * // Also bring the category and tags associated to each article
  189. * $query->contain(['Category', 'Tag']);
  190. * }}}
  191. *
  192. * Associations can be arbitrarily nested using dot notation or nested arrays,
  193. * this allows this object to calculate joins or any additional queries that
  194. * must be executed to bring the required associated data.
  195. *
  196. * ### Example:
  197. *
  198. * {{{
  199. * // Eager load the product info, and for each product load other 2 associations
  200. * $query->contain(['Product' => ['Manufacturer', 'Distributor']);
  201. *
  202. * // Which is equivalent to calling
  203. * $query->contain(['Products.Manufactures', 'Products.Distributors']);
  204. *
  205. * // For an author query, load his region, state and country
  206. * $query->contain('Regions.States.Countries');
  207. * }}}
  208. *
  209. * It is possible to control the conditions and fields selected for each of the
  210. * contained associations:
  211. *
  212. * ### Example:
  213. *
  214. * {{{
  215. * $query->contain(['Tags' => function($q) {
  216. * return $q->where(['Tags.is_popular' => true]);
  217. * }]);
  218. *
  219. * $query->contain(['Products.Manufactures' => function($q) {
  220. * return $q->select(['name'])->where(['Manufactures.active' => true]);
  221. * }]);
  222. * }}}
  223. *
  224. * Each association might define special options when eager loaded, the allowed
  225. * options that can be set per association are:
  226. *
  227. * - foreignKey: Used to set a different field to match both tables, if set to false
  228. * no join conditions will be generated automatically
  229. * - fields: An array with the fields that should be fetched from the association
  230. * - queryBuilder: Equivalent to passing a callable instead of an options array
  231. *
  232. * ### Example:
  233. *
  234. * {{{
  235. * // Set options for the articles that will be eagerly loaded for an author
  236. * $query->contain([
  237. * 'Articles' => [
  238. * 'fields' => ['title']
  239. * ]
  240. * ]);
  241. *
  242. * // Use special join conditions for getting an article author's 'likes'
  243. * $query->contain([
  244. * 'Likes' => [
  245. * 'foreignKey' => false,
  246. * 'queryBuilder' => function($q) {
  247. * return $q->where(...); // Add full filtering conditions
  248. * }
  249. * ]
  250. * ]);
  251. *
  252. * If called with no arguments, this function will return an ArrayObject with
  253. * with the list of previously configured associations to be contained in the
  254. * result. This object can be modified directly as the reference is kept inside
  255. * the query.
  256. *
  257. * The resulting ArrayObject will always have association aliases as keys, and
  258. * options as values, if no options are passed, the values will be set to an empty
  259. * array
  260. *
  261. * Please note that when modifying directly the containments array, you are
  262. * required to maintain the structure. That is, association names as keys
  263. * having array values. Failing to do so will result in an error
  264. *
  265. * If called with an empty first argument and $override is set to true, the
  266. * previous list will be emptied.
  267. *
  268. * @param array|string $associations list of table aliases to be queried
  269. * @param boolean $override whether override previous list with the one passed
  270. * defaults to merging previous list with the new one.
  271. * @return \ArrayObject|Query
  272. */
  273. public function contain($associations = null, $override = false) {
  274. if ($this->_containments === null || $override) {
  275. $this->_dirty();
  276. $this->_containments = new \ArrayObject;
  277. }
  278. if ($associations === null) {
  279. return $this->_containments;
  280. }
  281. $associations = (array)$associations;
  282. $current = current($associations);
  283. if (is_array($current) && isset($current['instance'])) {
  284. $this->_containments = $this->_normalizedContainments = $associations;
  285. return $this;
  286. }
  287. $old = $this->_containments->getArrayCopy();
  288. $associations = $this->_reformatContain($associations, $old);
  289. $this->_containments->exchangeArray($associations);
  290. $this->_normalizedContainments = null;
  291. $this->_dirty();
  292. return $this;
  293. }
  294. /**
  295. * Adds filtering conditions to this query to only bring rows that have a relation
  296. * to another from an associated table, based on conditions in the associated table.
  297. *
  298. * This function will add entries in the ``contain`` graph.
  299. *
  300. * ### Example:
  301. *
  302. * {{{
  303. * // Bring only articles that were tagged with 'cake'
  304. * $query->matching('Tags', function($q) {
  305. * return $q->where(['name' => 'cake']);
  306. * );
  307. * }}}
  308. *
  309. * It is possible to filter by deep associations by using dot notation:
  310. *
  311. * ### Example:
  312. *
  313. * {{{
  314. * // Bring only articles that were commented by 'markstory'
  315. * $query->matching('Comments.Users', function($q) {
  316. * return $q->where(['username' => 'markstory']);
  317. * );
  318. * }}}
  319. *
  320. * As this function will create ``INNER JOIN``, you might want to consider
  321. * calling ``distinct`` on this query as you might get duplicate rows if
  322. * your conditions don't filter them already. This might be the case, for example,
  323. * of the same user commenting more than once in the same article.
  324. *
  325. * ### Example:
  326. *
  327. * {{{
  328. * // Bring unique articles that were commented by 'markstory'
  329. * $query->distinct(['Articles.id'])
  330. * ->matching('Comments.Users', function($q) {
  331. * return $q->where(['username' => 'markstory']);
  332. * );
  333. * }}}
  334. *
  335. * Please note that the query passed to the closure will only accept calling
  336. * ``select``, ``where``, ``andWhere`` and ``orWhere`` on it. If you wish to
  337. * add more complex clauses you can do it directly in the main query.
  338. *
  339. * @param string $assoc The association to filter by
  340. * @param callable $builder a function that will receive a pre-made query object
  341. * that can be used to add custom conditions or selecting some fields
  342. * @return Query
  343. */
  344. public function matching($assoc, callable $builder = null) {
  345. $assocs = explode('.', $assoc);
  346. $last = array_pop($assocs);
  347. $containments = [];
  348. $pointer =& $containments;
  349. foreach ($assocs as $name) {
  350. $pointer[$name] = ['matching' => true];
  351. $pointer =& $pointer[$name];
  352. }
  353. $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true];
  354. return $this->contain($containments);
  355. }
  356. /**
  357. * Formats the containments array so that associations are always set as keys
  358. * in the array. This function merges the original associations array with
  359. * the new associations provided
  360. *
  361. * @param array $associations user provided containments array
  362. * @param array $original The original containments array to merge
  363. * with the new one
  364. * @return array
  365. */
  366. protected function _reformatContain($associations, $original) {
  367. $result = $original;
  368. foreach ((array)$associations as $table => $options) {
  369. $pointer =& $result;
  370. if (is_int($table)) {
  371. $table = $options;
  372. $options = [];
  373. }
  374. if (isset($this->_containOptions[$table])) {
  375. $pointer[$table] = $options;
  376. continue;
  377. }
  378. if (strpos($table, '.')) {
  379. $path = explode('.', $table);
  380. $table = array_pop($path);
  381. foreach ($path as $t) {
  382. $pointer += [$t => []];
  383. $pointer =& $pointer[$t];
  384. }
  385. }
  386. if (is_array($options)) {
  387. $options = $this->_reformatContain($options, []);
  388. }
  389. if ($options instanceof \Closure) {
  390. $options = ['queryBuilder' => $options];
  391. }
  392. $pointer += [$table => []];
  393. $pointer[$table] = $options + $pointer[$table];
  394. }
  395. return $result;
  396. }
  397. /**
  398. * Returns the fully normalized array of associations that should be eagerly
  399. * loaded. The normalized array will restructure the original one by sorting
  400. * all associations under one key and special options under another.
  401. *
  402. * Additionally it will set an 'instance' key per association containing the
  403. * association instance from the corresponding source table
  404. *
  405. * @return array
  406. */
  407. public function normalizedContainments() {
  408. if ($this->_normalizedContainments !== null || empty($this->_containments)) {
  409. return $this->_normalizedContainments;
  410. }
  411. $contain = [];
  412. foreach ($this->_containments as $table => $options) {
  413. if (!empty($options['instance'])) {
  414. $contain = (array)$this->_containments;
  415. break;
  416. }
  417. $contain[$table] = $this->_normalizeContain(
  418. $this->_table,
  419. $table,
  420. $options
  421. );
  422. }
  423. return $this->_normalizedContainments = $contain;
  424. }
  425. /**
  426. * Enable/Disable buffered results.
  427. *
  428. * When enabled the ResultSet returned by this Query will be
  429. * buffered. This enables you to iterate a ResultSet multiple times, or
  430. * both cache and iterate the ResultSet.
  431. *
  432. * When disabled it will consume less memory as fetched results are not
  433. * remembered in the ResultSet.
  434. *
  435. * If called with no arguments, it will return whether or not buffering is
  436. * enabled.
  437. *
  438. * @param boolean $enable whether or not to enable buffering
  439. * @return boolean|Query
  440. */
  441. public function bufferResults($enable = null) {
  442. if ($enable === null) {
  443. return $this->_useBufferedResults;
  444. }
  445. $this->_dirty();
  446. $this->_useBufferedResults = (bool)$enable;
  447. return $this;
  448. }
  449. /**
  450. * Set the result set for a query.
  451. *
  452. * Setting the resultset of a query will make execute() a no-op. Instead
  453. * of executing the SQL query and fetching results, the ResultSet provided to this
  454. * method will be returned.
  455. *
  456. * This method is most useful when combined with results stored in a persistent cache.
  457. *
  458. * @param Cake\ORM\ResultSet $results The results this query should return.
  459. * @return Query The query instance.
  460. */
  461. public function setResult($results) {
  462. $this->_results = $results;
  463. return $this;
  464. }
  465. /**
  466. * Enable result caching for this query.
  467. *
  468. * If a query has caching enabled, it will do the following when executed:
  469. *
  470. * - Check the cache for $key. If there are results no SQL will be executed.
  471. * Instead the cached results will be returned.
  472. * - When the cached data is stale/missing the result set will be cached as the query
  473. * is executed.
  474. *
  475. * ## Usage
  476. *
  477. * {{{
  478. * // Simple string key + config
  479. * $query->cache('my_key', 'db_results');
  480. *
  481. * // Function to generate key.
  482. * $query->cache(function($q) {
  483. * $key = serialize($q->clause('select'));
  484. * $key .= serialize($q->clause('where'));
  485. * return md5($key);
  486. * });
  487. *
  488. * // Using a pre-built cache engine.
  489. * $query->cache('my_key', $engine);
  490. *
  491. *
  492. * // Disable caching
  493. * $query->cache(false);
  494. * }}}
  495. *
  496. * @param false|string|Closure $key Either the cache key or a function to generate the cache key.
  497. * When using a function, this query instance will be supplied as an argument.
  498. * @param string|CacheEngine $config Either the name of the cache config to use, or
  499. * a cache config instance.
  500. * @return Query The query instance.
  501. * @throws \RuntimeException When you attempt to cache a non-select query.
  502. */
  503. public function cache($key, $config = 'default') {
  504. if ($this->_type !== 'select' && $this->_type !== null) {
  505. throw new \RuntimeException('You cannot cache the results of non-select queries.');
  506. }
  507. if ($key === false) {
  508. $this->_cache = null;
  509. return $this;
  510. }
  511. $this->_cache = new QueryCacher($key, $config);
  512. return $this;
  513. }
  514. /**
  515. * Executes this query and returns a results iterator. This function is required
  516. * for implementing the IteratorAggregate interface and allows the query to be
  517. * iterated without having to call execute() manually, thus making it look like
  518. * a result set instead of the query itself.
  519. *
  520. * @return Iterator
  521. */
  522. public function getIterator() {
  523. if (empty($this->_iterator) || $this->_dirty) {
  524. $this->_iterator = $this->all();
  525. }
  526. return $this->_iterator;
  527. }
  528. /**
  529. * Fetch the results for this query.
  530. *
  531. * Compiles the SQL representation of this query and executes it using the
  532. * provided connection object. Returns a ResultSet iterator object.
  533. *
  534. * ResultSet is a travesable object that implements the methods found
  535. * on Cake\Collection\Collection.
  536. *
  537. * @return Cake\ORM\ResultCollectionTrait
  538. * @throws RuntimeException if this method is called on a non-select Query.
  539. */
  540. public function all() {
  541. if ($this->_type !== 'select' && $this->_type !== null) {
  542. throw new \RuntimeException(
  543. 'You cannot call all() on a non-select query. Use execute() instead.'
  544. );
  545. }
  546. $table = $this->repository();
  547. $event = new Event('Model.beforeFind', $table, [$this, $this->_options]);
  548. $table->getEventManager()->dispatch($event);
  549. return $this->getResults();
  550. }
  551. /**
  552. * Get the result set for this query.
  553. *
  554. * Will return either the results set through setResult(), or execute the underlying statement
  555. * and return the ResultSet object ready for streaming of results.
  556. *
  557. * @return Cake\ORM\ResultCollectionTrait
  558. */
  559. public function getResults() {
  560. if (isset($this->_results)) {
  561. return $this->_results;
  562. }
  563. if ($this->_cache) {
  564. $results = $this->_cache->fetch($this);
  565. }
  566. if (!isset($results)) {
  567. $results = $this->_decorateResults(
  568. new ResultSet($this, $this->execute())
  569. );
  570. if ($this->_cache) {
  571. $this->_cache->store($this, $results);
  572. }
  573. }
  574. $this->_results = $results;
  575. return $this->_results;
  576. }
  577. /**
  578. * Returns an array representation of the results after executing the query.
  579. *
  580. * @return array
  581. */
  582. public function toArray() {
  583. return $this->all()->toArray();
  584. }
  585. /**
  586. * Returns a key => value array representing a single aliased field
  587. * that can be passed directly to the select() method.
  588. * The key will contain the alias and the value the actual field name.
  589. *
  590. * If the field is already aliased, then it will not be changed.
  591. * If no $alias is passed, the default table for this query will be used.
  592. *
  593. * @param string $field
  594. * @param string $alias the alias used to prefix the field
  595. * @return array
  596. */
  597. public function aliasField($field, $alias = null) {
  598. $namespaced = strpos($field, '.') !== false;
  599. $aliasedField = $field;
  600. if ($namespaced) {
  601. list($alias, $field) = explode('.', $field);
  602. }
  603. if (!$alias) {
  604. $alias = $this->repository()->alias();
  605. }
  606. $key = sprintf('%s__%s', $alias, $field);
  607. if (!$namespaced) {
  608. $aliasedField = $alias . '.' . $field;
  609. }
  610. return [$key => $aliasedField];
  611. }
  612. /**
  613. * Runs `aliasfield()` for each field in the provided list and returns
  614. * the result under a single array.
  615. *
  616. * @param array $fields
  617. * @param string $defaultAlias
  618. * @return array
  619. */
  620. public function aliasFields($fields, $defaultAlias = null) {
  621. $aliased = [];
  622. foreach ($fields as $alias => $field) {
  623. if (is_numeric($alias) && is_string($field)) {
  624. $aliased += $this->aliasField($field, $defaultAlias);
  625. continue;
  626. }
  627. $aliased[$alias] = $field;
  628. }
  629. return $aliased;
  630. }
  631. /**
  632. * Populates or adds parts to current query clauses using an array.
  633. * This is handy for passing all query clauses at once.
  634. *
  635. * ## Example:
  636. *
  637. * {{{
  638. * $query->applyOptions([
  639. * 'fields' => ['id', 'name'],
  640. * 'conditions' => [
  641. * 'created >=' => '2013-01-01'
  642. * ],
  643. * 'limit' => 10
  644. * ]);
  645. * }}}
  646. *
  647. * Is equivalent to:
  648. *
  649. * {{{
  650. * $query
  651. * ->select(['id', 'name'])
  652. * ->where(['created >=' => '2013-01-01'])
  653. * ->limit(10)
  654. * }}}
  655. *
  656. * @param array $options list of query clauses to apply new parts to. Accepts:
  657. *
  658. * - fields: Maps to the select method
  659. * - conditions: Maps to the where method
  660. * - limit: Maps to the limit method
  661. * - order: Maps to the order method
  662. * - offset: Maps to the offset method
  663. * - group: Maps to the group method
  664. * - having: Maps to the having method
  665. * - contain: Maps to the contain options for eager loading
  666. * - join: Maps to the join method
  667. * - join: Maps to the page method
  668. *
  669. * @return Cake\ORM\Query
  670. */
  671. public function applyOptions(array $options) {
  672. $valid = [
  673. 'fields' => 'select',
  674. 'conditions' => 'where',
  675. 'join' => 'join',
  676. 'order' => 'order',
  677. 'limit' => 'limit',
  678. 'offset' => 'offset',
  679. 'group' => 'group',
  680. 'having' => 'having',
  681. 'contain' => 'contain',
  682. 'page' => 'page',
  683. ];
  684. foreach ($options as $option => $values) {
  685. if (isset($valid[$option]) && isset($values)) {
  686. $this->{$valid[$option]}($values);
  687. } else {
  688. $this->_options[$option] = $values;
  689. }
  690. }
  691. return $this;
  692. }
  693. /**
  694. * Returns an array with the custom options that were applied to this query
  695. * and that were not already processed by another method in this class.
  696. *
  697. * ###Example:
  698. *
  699. * {{{
  700. * $query->applyOptions(['doABarrelRoll' => true, 'fields' => ['id', 'name']);
  701. * $query->getOptions(); // Returns ['doABarrelRoll' => true]
  702. * }}}
  703. *
  704. * @see \Cake\ORM\Query::applyOptions() to read about the options that will
  705. * be processed by this class and not returned by this function
  706. * @return array
  707. */
  708. public function getOptions() {
  709. return $this->_options;
  710. }
  711. /**
  712. * Register a new MapReduce routine to be executed on top of the database results
  713. * Both the mapper and caller callable should be invokable objects.
  714. *
  715. * The MapReduce routing will only be run when the query is executed and the first
  716. * result is attempted to be fetched.
  717. *
  718. * If the first argument is set to null, it will return the list of previously
  719. * registered map reduce routines.
  720. *
  721. * If the third argument is set to true, it will erase previous map reducers
  722. * and replace it with the arguments passed.
  723. *
  724. * @param callable $mapper
  725. * @param callable $reducer
  726. * @param boolean $overwrite
  727. * @return Cake\ORM\Query|array
  728. * @see Cake\Collection\Iterator\MapReduce for details on how to use emit data to the map reducer.
  729. */
  730. public function mapReduce(callable $mapper = null, callable $reducer = null, $overwrite = false) {
  731. if ($overwrite) {
  732. $this->_mapReduce = [];
  733. }
  734. if ($mapper === null) {
  735. return $this->_mapReduce;
  736. }
  737. $this->_mapReduce[] = compact('mapper', 'reducer');
  738. return $this;
  739. }
  740. /**
  741. * Registers a new formatter callback function that is to be executed when the results
  742. * are tried to be fetched from the database.
  743. *
  744. * Formatting callbacks will get as first parameter a `ResultSetDecorator` that
  745. * can be traversed and modified at will. As the second parameter, the formatting
  746. * callback will receive this query instance.
  747. *
  748. * Callbacks are required to return an iterator object, which will be used as
  749. * the return value for this query's result. Formatter functions are applied
  750. * after all the `MapReduce` routines for this query have been executed.
  751. *
  752. * If the first argument is set to null, it will return the list of previously
  753. * registered map reduce routines.
  754. *
  755. * If the second argument is set to true, it will erase previous formatters
  756. * and replace them with the passed first argument.
  757. *
  758. * ### Example:
  759. *
  760. * {{{
  761. * //Return all results from the table indexed by id
  762. * $query->select(['id', 'name'])->formatResults(function($results, $query) {
  763. * return $results->indexBy('id');
  764. * });
  765. *
  766. * //Add a new column to the ResultSet
  767. * $query->select(['name', 'bith_date'])->formatResults(function($results, $query) {
  768. * return $results->map(function($row) {
  769. * $row['age'] = $row['birth_date']->diff(new DateTime)->y;
  770. * return $row;
  771. * });
  772. * });
  773. * }}}
  774. *
  775. * @param callable $formatter
  776. * @param boolean $overwrite
  777. * @return Cake\ORM\Query|array
  778. */
  779. public function formatResults(callable $formatter = null, $overwrite = false) {
  780. if ($overwrite) {
  781. $this->_formatters = [];
  782. }
  783. if ($formatter === null) {
  784. return $this->_formatters;
  785. }
  786. $this->_formatters[] = $formatter;
  787. return $this;
  788. }
  789. /**
  790. * Returns the first result out of executing this query, if the query has not been
  791. * executed before, it will set the limit clause to 1 for performance reasons.
  792. *
  793. * ### Example:
  794. *
  795. * `$singleUser = $query->select(['id', 'username'])->first();`
  796. *
  797. * @return mixed the first result from the ResultSet
  798. */
  799. public function first() {
  800. if ($this->_dirty) {
  801. $this->limit(1);
  802. }
  803. $this->_results = $this->all();
  804. return $this->_results->first();
  805. }
  806. /**
  807. * Return the COUNT(*) for for the query.
  808. *
  809. * If the query does not contain GROUP BY or map reduce functions, then
  810. * this method will replace the selected fields with a COUNT(*), and the resulting
  811. * count will be returned.
  812. *
  813. * If the query does contain GROUP BY or map reduce functions, then it
  814. * will be executed, and the number of rows in the ResultSet will be returned.
  815. *
  816. * @return integer
  817. */
  818. public function count() {
  819. $noFormatters = $this->mapReduce() === [] && empty($this->_formatters);
  820. if ($this->clause('group') === [] && $noFormatters) {
  821. $this->select(['count' => $this->func()->count('*')], true)
  822. ->hydrate(false);
  823. return (int)$this->first()['count'];
  824. }
  825. $results = $this->execute();
  826. return count($results);
  827. }
  828. /**
  829. * Toggle hydrating entites.
  830. *
  831. * If set to false array results will be returned
  832. *
  833. * @param boolean|null $enable Use a boolean to set the hydration mode.
  834. * Null will fetch the current hydration mode.
  835. * @return boolean|Query A boolean when reading, and $this when setting the mode.
  836. */
  837. public function hydrate($enable = null) {
  838. if ($enable === null) {
  839. return $this->_hydrate;
  840. }
  841. $this->_dirty();
  842. $this->_hydrate = (bool)$enable;
  843. return $this;
  844. }
  845. /**
  846. * Decorates the ResultSet iterator with MapReduce routines
  847. *
  848. * @param $results Cake\ORM\ResultCollectionTrait original results
  849. * @return Cake\ORM\ResultCollectionTrait
  850. */
  851. protected function _decorateResults($result) {
  852. foreach ($this->_mapReduce as $functions) {
  853. $result = new MapReduce($result, $functions['mapper'], $functions['reducer']);
  854. }
  855. if (!empty($this->_mapReduce)) {
  856. $result = new ResultSetDecorator($result);
  857. }
  858. foreach ($this->_formatters as $formatter) {
  859. $result = $formatter($result, $this);
  860. }
  861. if (!empty($this->_formatters) && !($result instanceof ResultSetDecorator)) {
  862. $result = new ResultSetDecorator($result);
  863. }
  864. return $result;
  865. }
  866. /**
  867. * Auxiliary function used to wrap the original statement from the driver with
  868. * any registered callbacks. This will also setup the correct statement class
  869. * in order to eager load deep associations.
  870. *
  871. * @param Cake\Database\Statement $statement to be decorated
  872. * @return Cake\Database\Statement
  873. */
  874. protected function _decorateStatement($statement) {
  875. $statement = parent::_decorateStatement($statement);
  876. if ($this->_loadEagerly) {
  877. if (!($statement instanceof BufferedStatement)) {
  878. $statement = new BufferedStatement($statement, $this->connection()->driver());
  879. }
  880. $statement = $this->_eagerLoad($statement);
  881. }
  882. return $statement;
  883. }
  884. /**
  885. * Applies some defaults to the query object before it is executed.
  886. *
  887. * Specifically add the FROM clause, adds default table fields if none are
  888. * specified and applies the joins required to eager load associations defined
  889. * using `contain`
  890. *
  891. * @see Cake\Database\Query::execute()
  892. * @return Query
  893. */
  894. protected function _transformQuery() {
  895. if (!$this->_dirty) {
  896. return parent::_transformQuery();
  897. }
  898. if ($this->_type === 'select') {
  899. if (empty($this->_parts['from'])) {
  900. $this->from([$this->_table->alias() => $this->_table->table()]);
  901. }
  902. $this->_addDefaultFields();
  903. $this->_addContainments();
  904. }
  905. return parent::_transformQuery();
  906. }
  907. /**
  908. * Helper function used to add the required joins for associations defined using
  909. * `contain()`
  910. *
  911. * @return void
  912. */
  913. protected function _addContainments() {
  914. $this->_loadEagerly = [];
  915. if (empty($this->_containments)) {
  916. return;
  917. }
  918. $contain = $this->normalizedContainments();
  919. foreach ($contain as $relation => $meta) {
  920. if ($meta['instance'] && !$meta['canBeJoined']) {
  921. $this->_loadEagerly[$relation] = $meta;
  922. }
  923. }
  924. foreach ($this->_resolveJoins($this->_table, $contain) as $options) {
  925. $table = $options['instance']->target();
  926. $alias = $table->alias();
  927. $this->_addJoin($options['instance'], $options['config']);
  928. foreach ($options['associations'] as $relation => $meta) {
  929. if ($meta['instance'] && !$meta['canBeJoined']) {
  930. $this->_loadEagerly[$relation] = $meta;
  931. }
  932. }
  933. }
  934. }
  935. /**
  936. * Auxiliary function responsible for fully normalizing deep associations defined
  937. * using `contain()`
  938. *
  939. * @param Table $parent owning side of the association
  940. * @param string $alias name of the association to be loaded
  941. * @param array $options list of extra options to use for this association
  942. * @return array normalized associations
  943. * @throws \InvalidArgumentException When containments refer to associations that do not exist.
  944. */
  945. protected function _normalizeContain(Table $parent, $alias, $options) {
  946. $defaults = $this->_containOptions;
  947. $instance = $parent->association($alias);
  948. if (!$instance) {
  949. throw new \InvalidArgumentException(
  950. sprintf('%s is not associated with %s', $parent->alias(), $alias)
  951. );
  952. }
  953. $table = $instance->target();
  954. $extra = array_diff_key($options, $defaults);
  955. $config = [
  956. 'associations' => [],
  957. 'instance' => $instance,
  958. 'config' => array_diff_key($options, $extra)
  959. ];
  960. $config['canBeJoined'] = $instance->canBeJoined($config['config']);
  961. foreach ($extra as $t => $assoc) {
  962. $config['associations'][$t] = $this->_normalizeContain($table, $t, $assoc);
  963. }
  964. return $config;
  965. }
  966. /**
  967. * Helper function used to compile a list of all associations that can be
  968. * joined in this query.
  969. *
  970. * @param Table $source the owning side of the association
  971. * @param array $associations list of associations for $source
  972. * @return array
  973. */
  974. protected function _resolveJoins($source, $associations) {
  975. $result = [];
  976. foreach ($associations as $table => $options) {
  977. $associated = $options['instance'];
  978. if ($options['canBeJoined']) {
  979. $result[$table] = $options;
  980. $result += $this->_resolveJoins($associated->target(), $options['associations']);
  981. }
  982. }
  983. return $result;
  984. }
  985. /**
  986. * Adds a join based on a particular association and some custom options
  987. *
  988. * @param Association $association
  989. * @param array $options
  990. * @return void
  991. */
  992. protected function _addJoin($association, $options) {
  993. $association->attachTo($this, $options + ['includeFields' => !$this->_hasFields]);
  994. }
  995. /**
  996. * Helper method that will calculate those associations that cannot be joined
  997. * directly in this query and will setup the required extra queries for fetching
  998. * the extra data.
  999. *
  1000. * @param Statement $statement original query statement
  1001. * @return CallbackStatement $statement modified statement with extra loaders
  1002. */
  1003. protected function _eagerLoad($statement) {
  1004. $keys = $this->_collectKeys($statement);
  1005. foreach ($this->_loadEagerly as $association => $meta) {
  1006. $contain = $meta['associations'];
  1007. $alias = $meta['instance']->source()->alias();
  1008. $keys = isset($keys[$alias]) ? $keys[$alias] : null;
  1009. $f = $meta['instance']->eagerLoader(
  1010. $meta['config'] + ['query' => $this, 'contain' => $contain, 'keys' => $keys]
  1011. );
  1012. $statement = new CallbackStatement($statement, $this->connection()->driver(), $f);
  1013. }
  1014. return $statement;
  1015. }
  1016. /**
  1017. * Helper function used to return the keys from the query records that will be used
  1018. * to eagerly load associations.
  1019. *
  1020. *
  1021. * @param BufferedStatement $statement
  1022. * @return array
  1023. */
  1024. protected function _collectKeys($statement) {
  1025. $collectKeys = [];
  1026. foreach ($this->_loadEagerly as $association => $meta) {
  1027. $source = $meta['instance']->source();
  1028. if ($meta['instance']->requiresKeys($meta['config'])) {
  1029. $alias = $source->alias();
  1030. $pkFields = [];
  1031. foreach ((array)$source->primaryKey() as $key) {
  1032. $pkFields[] = key($this->aliasField($key, $alias));
  1033. }
  1034. $collectKeys[] = [$alias, $pkFields, count($pkFields) === 1];
  1035. }
  1036. }
  1037. $keys = [];
  1038. if (!empty($collectKeys)) {
  1039. while ($result = $statement->fetch('assoc')) {
  1040. foreach ($collectKeys as $parts) {
  1041. if ($parts[2]) {
  1042. $keys[$parts[0]][] = $result[$parts[1][0]];
  1043. continue;
  1044. }
  1045. $collected = [];
  1046. foreach ($parts[1] as $key) {
  1047. $collected[] = $result[$key];
  1048. }
  1049. $keys[$parts[0]][] = $collected;
  1050. }
  1051. }
  1052. $statement->rewind();
  1053. }
  1054. return $keys;
  1055. }
  1056. /**
  1057. * Inspects if there are any set fields for selecting, otherwise adds all
  1058. * the fields for the default table.
  1059. *
  1060. * @return void
  1061. */
  1062. protected function _addDefaultFields() {
  1063. $select = $this->clause('select');
  1064. $this->_hasFields = true;
  1065. if (!count($select)) {
  1066. $this->_hasFields = false;
  1067. $this->select($this->repository()->schema()->columns());
  1068. $select = $this->clause('select');
  1069. }
  1070. $aliased = $this->aliasFields($select, $this->repository()->alias());
  1071. $this->select($aliased, true);
  1072. }
  1073. /**
  1074. * Apply custom finds to against an existing query object.
  1075. *
  1076. * Allows custom find methods to be combined and applied to each other.
  1077. *
  1078. * {{{
  1079. * $table->find('all')->find('recent');
  1080. * }}}
  1081. *
  1082. * The above is an example of stacking multiple finder methods onto
  1083. * a single query.
  1084. *
  1085. * @param string $finder The finder method to use.
  1086. * @param array $options The options for the finder.
  1087. * @return Cake\ORM\Query Returns a modified query.
  1088. * @see Cake\ORM\Table::find()
  1089. */
  1090. public function find($finder, $options = []) {
  1091. return $this->repository()->callFinder($finder, $this, $options);
  1092. }
  1093. /**
  1094. * Marks a query as dirty, removing any preprocessed information
  1095. * from in memory caching such as previous results
  1096. *
  1097. * @return void
  1098. */
  1099. protected function _dirty() {
  1100. $this->_results = null;
  1101. parent::_dirty();
  1102. }
  1103. /**
  1104. * Create an update query.
  1105. *
  1106. * This changes the query type to be 'update'.
  1107. * Can be combined with set() and where() methods to create update queries.
  1108. *
  1109. * @param string $table Unused parameter.
  1110. * @return Query
  1111. */
  1112. public function update($table = null) {
  1113. $table = $this->repository()->table();
  1114. return parent::update($table);
  1115. }
  1116. /**
  1117. * Create a delete query.
  1118. *
  1119. * This changes the query type to be 'delete'.
  1120. * Can be combined with the where() method to create delete queries.
  1121. *
  1122. * @param string $table Unused parameter.
  1123. * @return Query
  1124. */
  1125. public function delete($table = null) {
  1126. $table = $this->repository()->table();
  1127. return parent::delete($table);
  1128. }
  1129. /**
  1130. * Create an insert query.
  1131. *
  1132. * This changes the query type to be 'insert'.
  1133. * Note calling this method will reset any data previously set
  1134. * with Query::values()
  1135. *
  1136. * Can be combined with the where() method to create delete queries.
  1137. *
  1138. * @param array $columns The columns to insert into.
  1139. * @param array $types A map between columns & their datatypes.
  1140. * @return Query
  1141. */
  1142. public function insert($columns, $types = []) {
  1143. $table = $this->repository()->table();
  1144. $this->into($table);
  1145. return parent::insert($columns, $types);
  1146. }
  1147. /**
  1148. * Enables calling methods from the ResultSet as if they were from this class
  1149. *
  1150. * @param string $method the method to call
  1151. * @param array $arguments list of arguments for the method to call
  1152. * @return mixed
  1153. * @throws \BadMethodCallException if no such method exists in ResultSet
  1154. */
  1155. public function __call($method, $arguments) {
  1156. if ($this->type() === 'select') {
  1157. $resultSetClass = __NAMESPACE__ . '\ResultSetDecorator';
  1158. if (in_array($method, get_class_methods($resultSetClass))) {
  1159. $results = $this->all();
  1160. return call_user_func_array([$results, $method], $arguments);
  1161. }
  1162. }
  1163. throw new \BadMethodCallException(
  1164. sprintf('Unknown method "%s"', $method)
  1165. );
  1166. }
  1167. }