Query.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\ORM;
  16. use ArrayObject;
  17. use Cake\Database\ExpressionInterface;
  18. use Cake\Database\Query as DatabaseQuery;
  19. use Cake\Database\ValueBinder;
  20. use Cake\Datasource\QueryInterface;
  21. use Cake\Datasource\QueryTrait;
  22. use JsonSerializable;
  23. use RuntimeException;
  24. /**
  25. * Extends the base Query class to provide new methods related to association
  26. * loading, automatic fields selection, automatic type casting and to wrap results
  27. * into a specific iterator that will be responsible for hydrating results if
  28. * required.
  29. *
  30. */
  31. class Query extends DatabaseQuery implements JsonSerializable, QueryInterface
  32. {
  33. use QueryTrait {
  34. cache as private _cache;
  35. all as private _all;
  36. _decorateResults as private _applyDecorators;
  37. __call as private _call;
  38. }
  39. /**
  40. * Indicates that the operation should append to the list
  41. *
  42. * @var int
  43. */
  44. const APPEND = 0;
  45. /**
  46. * Indicates that the operation should prepend to the list
  47. *
  48. * @var int
  49. */
  50. const PREPEND = 1;
  51. /**
  52. * Indicates that the operation should overwrite the list
  53. *
  54. * @var bool
  55. */
  56. const OVERWRITE = true;
  57. /**
  58. * Whether the user select any fields before being executed, this is used
  59. * to determined if any fields should be automatically be selected.
  60. *
  61. * @var bool
  62. */
  63. protected $_hasFields;
  64. /**
  65. * Tracks whether or not the original query should include
  66. * fields from the top level table.
  67. *
  68. * @var bool
  69. */
  70. protected $_autoFields;
  71. /**
  72. * Whether to hydrate results into entity objects
  73. *
  74. * @var bool
  75. */
  76. protected $_hydrate = true;
  77. /**
  78. * A callable function that can be used to calculate the total amount of
  79. * records this query will match when not using `limit`
  80. *
  81. * @var callable
  82. */
  83. protected $_counter;
  84. /**
  85. * Instance of a class responsible for storing association containments and
  86. * for eager loading them when this query is executed
  87. *
  88. * @var \Cake\ORM\EagerLoader
  89. */
  90. protected $_eagerLoader;
  91. /**
  92. * True if the beforeFind event has already been triggered for this query
  93. *
  94. * @var bool
  95. */
  96. protected $_beforeFindFired = false;
  97. /**
  98. * The COUNT(*) for the query.
  99. *
  100. * When set, count query execution will be bypassed.
  101. *
  102. * @var int
  103. */
  104. protected $_resultsCount;
  105. /**
  106. * Constructor
  107. *
  108. * @param \Cake\Database\Connection $connection The connection object
  109. * @param \Cake\ORM\Table $table The table this query is starting on
  110. */
  111. public function __construct($connection, $table)
  112. {
  113. parent::__construct($connection);
  114. $this->repository($table);
  115. if ($this->_repository) {
  116. $this->addDefaultTypes($this->_repository);
  117. }
  118. }
  119. /**
  120. * {@inheritDoc}
  121. *
  122. * If you pass an instance of a `Cake\ORM\Table` or `Cake\ORM\Association` class,
  123. * all the fields in the schema of the table or the association will be added to
  124. * the select clause.
  125. *
  126. * @param array|ExpressionInterface|string|\Cake\ORM\Table|\Cake\ORM\Association $fields fields
  127. * to be added to the list.
  128. * @param bool $overwrite whether to reset fields with passed list or not
  129. */
  130. public function select($fields = [], $overwrite = false)
  131. {
  132. if ($fields instanceof Association) {
  133. $fields = $fields->target();
  134. }
  135. if ($fields instanceof Table) {
  136. $fields = $this->aliasFields($fields->schema()->columns(), $fields->alias());
  137. }
  138. return parent::select($fields, $overwrite);
  139. }
  140. /**
  141. * Hints this object to associate the correct types when casting conditions
  142. * for the database. This is done by extracting the field types from the schema
  143. * associated to the passed table object. This prevents the user from repeating
  144. * himself when specifying conditions.
  145. *
  146. * This method returns the same query object for chaining.
  147. *
  148. * @param \Cake\ORM\Table $table The table to pull types from
  149. * @return $this
  150. */
  151. public function addDefaultTypes(Table $table)
  152. {
  153. $alias = $table->alias();
  154. $map = $table->schema()->typeMap();
  155. $fields = [];
  156. foreach ($map as $f => $type) {
  157. $fields[$f] = $fields[$alias . '.' . $f] = $type;
  158. }
  159. $this->typeMap()->addDefaults($fields);
  160. return $this;
  161. }
  162. /**
  163. * Sets the instance of the eager loader class to use for loading associations
  164. * and storing containments. If called with no arguments, it will return the
  165. * currently configured instance.
  166. *
  167. * @param \Cake\ORM\EagerLoader $instance The eager loader to use. Pass null
  168. * to get the current eagerloader.
  169. * @return \Cake\ORM\EagerLoader|$this
  170. */
  171. public function eagerLoader(EagerLoader $instance = null)
  172. {
  173. if ($instance === null) {
  174. if ($this->_eagerLoader === null) {
  175. $this->_eagerLoader = new EagerLoader;
  176. }
  177. return $this->_eagerLoader;
  178. }
  179. $this->_eagerLoader = $instance;
  180. return $this;
  181. }
  182. /**
  183. * Sets the list of associations that should be eagerly loaded along with this
  184. * query. The list of associated tables passed must have been previously set as
  185. * associations using the Table API.
  186. *
  187. * ### Example:
  188. *
  189. * ```
  190. * // Bring articles' author information
  191. * $query->contain('Author');
  192. *
  193. * // Also bring the category and tags associated to each article
  194. * $query->contain(['Category', 'Tag']);
  195. * ```
  196. *
  197. * Associations can be arbitrarily nested using dot notation or nested arrays,
  198. * this allows this object to calculate joins or any additional queries that
  199. * must be executed to bring the required associated data.
  200. *
  201. * ### Example:
  202. *
  203. * ```
  204. * // Eager load the product info, and for each product load other 2 associations
  205. * $query->contain(['Product' => ['Manufacturer', 'Distributor']);
  206. *
  207. * // Which is equivalent to calling
  208. * $query->contain(['Products.Manufactures', 'Products.Distributors']);
  209. *
  210. * // For an author query, load his region, state and country
  211. * $query->contain('Regions.States.Countries');
  212. * ```
  213. *
  214. * It is possible to control the conditions and fields selected for each of the
  215. * contained associations:
  216. *
  217. * ### Example:
  218. *
  219. * ```
  220. * $query->contain(['Tags' => function ($q) {
  221. * return $q->where(['Tags.is_popular' => true]);
  222. * }]);
  223. *
  224. * $query->contain(['Products.Manufactures' => function ($q) {
  225. * return $q->select(['name'])->where(['Manufactures.active' => true]);
  226. * }]);
  227. * ```
  228. *
  229. * Each association might define special options when eager loaded, the allowed
  230. * options that can be set per association are:
  231. *
  232. * - foreignKey: Used to set a different field to match both tables, if set to false
  233. * no join conditions will be generated automatically. `false` can only be used on
  234. * joinable associations and cannot be used with hasMany or belongsToMany associations.
  235. * - fields: An array with the fields that should be fetched from the association
  236. * - queryBuilder: Equivalent to passing a callable instead of an options array
  237. *
  238. * ### Example:
  239. *
  240. * ```
  241. * // Set options for the hasMany articles that will be eagerly loaded for an author
  242. * $query->contain([
  243. * 'Articles' => [
  244. * 'fields' => ['title', 'author_id']
  245. * ]
  246. * ]);
  247. * ```
  248. *
  249. * When containing associations, it is important to include foreign key columns.
  250. * Failing to do so will trigger exceptions.
  251. *
  252. * ```
  253. * // Use special join conditions for getting an Articles's belongsTo 'authors'
  254. * $query->contain([
  255. * 'Authors' => [
  256. * 'foreignKey' => false,
  257. * 'queryBuilder' => function ($q) {
  258. * return $q->where(...); // Add full filtering conditions
  259. * }
  260. * ]
  261. * ]);
  262. * ```
  263. *
  264. * If called with no arguments, this function will return an array with
  265. * with the list of previously configured associations to be contained in the
  266. * result.
  267. *
  268. * If called with an empty first argument and $override is set to true, the
  269. * previous list will be emptied.
  270. *
  271. * @param array|string $associations list of table aliases to be queried
  272. * @param bool $override whether override previous list with the one passed
  273. * defaults to merging previous list with the new one.
  274. * @return array|$this
  275. */
  276. public function contain($associations = null, $override = false)
  277. {
  278. $loader = $this->eagerLoader();
  279. if ($override === true) {
  280. $loader->clearContain();
  281. $this->_dirty();
  282. }
  283. $result = $loader->contain($associations);
  284. if ($associations === null) {
  285. return $result;
  286. }
  287. $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result);
  288. return $this;
  289. }
  290. /**
  291. * Used to recursively add contained association column types to
  292. * the query.
  293. *
  294. * @param \Cake\ORM\Table $table The table instance to pluck associations from.
  295. * @param \Cake\Database\TypeMap $typeMap The typemap to check for columns in.
  296. * This typemap is indirectly mutated via Cake\ORM\Query::addDefaultTypes()
  297. * @param array $associations The nested tree of associations to walk.
  298. * @return void
  299. */
  300. protected function _addAssociationsToTypeMap($table, $typeMap, $associations)
  301. {
  302. $typeMap = $this->typeMap();
  303. foreach ($associations as $name => $nested) {
  304. $association = $table->association($name);
  305. if (!$association) {
  306. continue;
  307. }
  308. $target = $association->target();
  309. $primary = (array)$target->primaryKey();
  310. if (empty($primary) || $typeMap->type($target->aliasField($primary[0])) === null) {
  311. $this->addDefaultTypes($target);
  312. }
  313. if (!empty($nested)) {
  314. $this->_addAssociationsToTypeMap($target, $typeMap, $nested);
  315. }
  316. }
  317. }
  318. /**
  319. * Adds filtering conditions to this query to only bring rows that have a relation
  320. * to another from an associated table, based on conditions in the associated table.
  321. *
  322. * This function will add entries in the `contain` graph.
  323. *
  324. * ### Example:
  325. *
  326. * ```
  327. * // Bring only articles that were tagged with 'cake'
  328. * $query->matching('Tags', function ($q) {
  329. * return $q->where(['name' => 'cake']);
  330. * );
  331. * ```
  332. *
  333. * It is possible to filter by deep associations by using dot notation:
  334. *
  335. * ### Example:
  336. *
  337. * ```
  338. * // Bring only articles that were commented by 'markstory'
  339. * $query->matching('Comments.Users', function ($q) {
  340. * return $q->where(['username' => 'markstory']);
  341. * );
  342. * ```
  343. *
  344. * As this function will create `INNER JOIN`, you might want to consider
  345. * calling `distinct` on this query as you might get duplicate rows if
  346. * your conditions don't filter them already. This might be the case, for example,
  347. * of the same user commenting more than once in the same article.
  348. *
  349. * ### Example:
  350. *
  351. * ```
  352. * // Bring unique articles that were commented by 'markstory'
  353. * $query->distinct(['Articles.id'])
  354. * ->matching('Comments.Users', function ($q) {
  355. * return $q->where(['username' => 'markstory']);
  356. * );
  357. * ```
  358. *
  359. * Please note that the query passed to the closure will only accept calling
  360. * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to
  361. * add more complex clauses you can do it directly in the main query.
  362. *
  363. * @param string $assoc The association to filter by
  364. * @param callable $builder a function that will receive a pre-made query object
  365. * that can be used to add custom conditions or selecting some fields
  366. * @return $this
  367. */
  368. public function matching($assoc, callable $builder = null)
  369. {
  370. $this->eagerLoader()->matching($assoc, $builder);
  371. $this->_dirty();
  372. return $this;
  373. }
  374. /**
  375. * Creates a LEFT JOIN with the passed association table while preserving
  376. * the foreign key matching and the custom conditions that were originally set
  377. * for it.
  378. *
  379. * This function will add entries in the `contain` graph.
  380. *
  381. * ### Example:
  382. *
  383. * ```
  384. * // Get the count of articles per user
  385. * $usersQuery
  386. * ->select(['total_articles' => $query->func()->count('Articles.id')])
  387. * ->leftJoinWith('Articles')
  388. * ->group(['Users.id'])
  389. * ->autoFields(true);
  390. * ```
  391. *
  392. * You can also customize the conditions passed to the LEFT JOIN:
  393. *
  394. * ```
  395. * // Get the count of articles per user with at least 5 votes
  396. * $usersQuery
  397. * ->select(['total_articles' => $query->func()->count('Articles.id')])
  398. * ->leftJoinWith('Articles', function ($q) {
  399. * return $q->where(['Articles.votes >=' => 5]);
  400. * })
  401. * ->group(['Users.id'])
  402. * ->autoFields(true);
  403. * ```
  404. *
  405. * This will create the following SQL:
  406. *
  407. * ```
  408. * SELECT COUNT(Articles.id) AS total_articles, Users.*
  409. * FROM users Users
  410. * LEFT JOIN articles Articles ON Articles.user_id = Users.id AND Articles.votes >= 5
  411. * GROUP BY USers.id
  412. * ```
  413. *
  414. * It is possible to left join deep associations by using dot notation
  415. *
  416. * ### Example:
  417. *
  418. * ```
  419. * // Total comments in articles by 'markstory'
  420. * $query
  421. * ->select(['total_comments' => $query->func()->count('Comments.id')])
  422. * ->leftJoinWith('Comments.Users', function ($q) {
  423. * return $q->where(['username' => 'markstory']);
  424. * )
  425. * ->group(['Users.id']);
  426. * ```
  427. *
  428. * Please note that the query passed to the closure will only accept calling
  429. * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to
  430. * add more complex clauses you can do it directly in the main query.
  431. *
  432. * @param string $assoc The association to join with
  433. * @param callable $builder a function that will receive a pre-made query object
  434. * that can be used to add custom conditions or selecting some fields
  435. * @return $this
  436. */
  437. public function leftJoinWith($assoc, callable $builder = null)
  438. {
  439. $this->eagerLoader()->matching($assoc, $builder, [
  440. 'joinType' => 'LEFT',
  441. 'fields' => false
  442. ]);
  443. $this->_dirty();
  444. return $this;
  445. }
  446. /**
  447. * Creates an INNER JOIN with the passed association table while preserving
  448. * the foreign key matching and the custom conditions that were originally set
  449. * for it.
  450. *
  451. * This function will add entries in the `contain` graph.
  452. *
  453. * ### Example:
  454. *
  455. * ```
  456. * // Bring only articles that were tagged with 'cake'
  457. * $query->innerJoinWith('Tags', function ($q) {
  458. * return $q->where(['name' => 'cake']);
  459. * );
  460. * ```
  461. *
  462. * This will create the following SQL:
  463. *
  464. * ```
  465. * SELECT Articles.*
  466. * FROM articles Articles
  467. * INNER JOIN tags Tags ON Tags.name = 'cake'
  468. * INNER JOIN articles_tags ArticlesTags ON ArticlesTags.tag_id = Tags.id
  469. * AND ArticlesTags.articles_id = Articles.id
  470. * ```
  471. *
  472. * This function works the same as `matching()` with the difference that it
  473. * will select no fields from the association.
  474. *
  475. * @param string $assoc The association to join with
  476. * @param callable $builder a function that will receive a pre-made query object
  477. * that can be used to add custom conditions or selecting some fields
  478. * @return $this
  479. * @see \Cake\ORM\Query::matching()
  480. */
  481. public function innerJoinWith($assoc, callable $builder = null)
  482. {
  483. $this->eagerLoader()->matching($assoc, $builder, [
  484. 'joinType' => 'INNER',
  485. 'fields' => false
  486. ]);
  487. $this->_dirty();
  488. return $this;
  489. }
  490. /**
  491. * Adds filtering conditions to this query to only bring rows that have no match
  492. * to another from an associated table, based on conditions in the associated table.
  493. *
  494. * This function will add entries in the `contain` graph.
  495. *
  496. * ### Example:
  497. *
  498. * ```
  499. * // Bring only articles that were not tagged with 'cake'
  500. * $query->notMatching('Tags', function ($q) {
  501. * return $q->where(['name' => 'cake']);
  502. * );
  503. * ```
  504. *
  505. * It is possible to filter by deep associations by using dot notation:
  506. *
  507. * ### Example:
  508. *
  509. * ```
  510. * // Bring only articles that weren't commented by 'markstory'
  511. * $query->notMatching('Comments.Users', function ($q) {
  512. * return $q->where(['username' => 'markstory']);
  513. * );
  514. * ```
  515. *
  516. * As this function will create a `LEFT JOIN`, you might want to consider
  517. * calling `distinct` on this query as you might get duplicate rows if
  518. * your conditions don't filter them already. This might be the case, for example,
  519. * of the same article having multiple comments.
  520. *
  521. * ### Example:
  522. *
  523. * ```
  524. * // Bring unique articles that were commented by 'markstory'
  525. * $query->distinct(['Articles.id'])
  526. * ->notMatching('Comments.Users', function ($q) {
  527. * return $q->where(['username' => 'markstory']);
  528. * );
  529. * ```
  530. *
  531. * Please note that the query passed to the closure will only accept calling
  532. * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to
  533. * add more complex clauses you can do it directly in the main query.
  534. *
  535. * @param string $assoc The association to filter by
  536. * @param callable $builder a function that will receive a pre-made query object
  537. * that can be used to add custom conditions or selecting some fields
  538. * @return $this
  539. */
  540. public function notMatching($assoc, callable $builder = null)
  541. {
  542. $this->eagerLoader()->matching($assoc, $builder, [
  543. 'joinType' => 'LEFT',
  544. 'fields' => false,
  545. 'negateMatch' => true
  546. ]);
  547. $this->_dirty();
  548. return $this;
  549. }
  550. /**
  551. * {@inheritDoc}
  552. *
  553. * Populates or adds parts to current query clauses using an array.
  554. * This is handy for passing all query clauses at once. The option array accepts:
  555. *
  556. * - fields: Maps to the select method
  557. * - conditions: Maps to the where method
  558. * - limit: Maps to the limit method
  559. * - order: Maps to the order method
  560. * - offset: Maps to the offset method
  561. * - group: Maps to the group method
  562. * - having: Maps to the having method
  563. * - contain: Maps to the contain options for eager loading
  564. * - join: Maps to the join method
  565. * - page: Maps to the page method
  566. *
  567. * ### Example:
  568. *
  569. * ```
  570. * $query->applyOptions([
  571. * 'fields' => ['id', 'name'],
  572. * 'conditions' => [
  573. * 'created >=' => '2013-01-01'
  574. * ],
  575. * 'limit' => 10
  576. * ]);
  577. * ```
  578. *
  579. * Is equivalent to:
  580. *
  581. * ```
  582. * $query
  583. * ->select(['id', 'name'])
  584. * ->where(['created >=' => '2013-01-01'])
  585. * ->limit(10)
  586. * ```
  587. */
  588. public function applyOptions(array $options)
  589. {
  590. $valid = [
  591. 'fields' => 'select',
  592. 'conditions' => 'where',
  593. 'join' => 'join',
  594. 'order' => 'order',
  595. 'limit' => 'limit',
  596. 'offset' => 'offset',
  597. 'group' => 'group',
  598. 'having' => 'having',
  599. 'contain' => 'contain',
  600. 'page' => 'page',
  601. ];
  602. ksort($options);
  603. foreach ($options as $option => $values) {
  604. if (isset($valid[$option], $values)) {
  605. $this->{$valid[$option]}($values);
  606. } else {
  607. $this->_options[$option] = $values;
  608. }
  609. }
  610. return $this;
  611. }
  612. /**
  613. * Creates a copy of this current query, triggers beforeFind and resets some state.
  614. *
  615. * The following state will be cleared:
  616. *
  617. * - autoFields
  618. * - limit
  619. * - offset
  620. * - map/reduce functions
  621. * - result formatters
  622. * - order
  623. * - containments
  624. *
  625. * This method creates query clones that are useful when working with subqueries.
  626. *
  627. * @return \Cake\ORM\Query
  628. */
  629. public function cleanCopy()
  630. {
  631. $clone = clone $this;
  632. $clone->triggerBeforeFind();
  633. $clone->autoFields(false);
  634. $clone->limit(null);
  635. $clone->order([], true);
  636. $clone->offset(null);
  637. $clone->mapReduce(null, null, true);
  638. $clone->formatResults(null, true);
  639. return $clone;
  640. }
  641. /**
  642. * Object clone hook.
  643. *
  644. * Destroys the clones inner iterator and clones the value binder, and eagerloader instances.
  645. *
  646. * @return void
  647. */
  648. public function __clone()
  649. {
  650. parent::__clone();
  651. if ($this->_eagerLoader) {
  652. $this->_eagerLoader = clone $this->_eagerLoader;
  653. }
  654. }
  655. /**
  656. * {@inheritDoc}
  657. *
  658. * Returns the COUNT(*) for the query. If the query has not been
  659. * modified, and the count has already been performed the cached
  660. * value is returned
  661. */
  662. public function count()
  663. {
  664. if ($this->_resultsCount === null) {
  665. $this->_resultsCount = $this->_performCount();
  666. }
  667. return $this->_resultsCount;
  668. }
  669. /**
  670. * Performs and returns the COUNT(*) for the query.
  671. *
  672. * @return int
  673. */
  674. protected function _performCount()
  675. {
  676. $query = $this->cleanCopy();
  677. $counter = $this->_counter;
  678. if ($counter) {
  679. $query->counter(null);
  680. return (int)$counter($query);
  681. }
  682. $complex = (
  683. $query->clause('distinct') ||
  684. count($query->clause('group')) ||
  685. count($query->clause('union')) ||
  686. $query->clause('having')
  687. );
  688. if (!$complex) {
  689. // Expression fields could have bound parameters.
  690. foreach ($query->clause('select') as $field) {
  691. if ($field instanceof ExpressionInterface) {
  692. $complex = true;
  693. break;
  694. }
  695. }
  696. }
  697. $count = ['count' => $query->func()->count('*')];
  698. if (!$complex) {
  699. $query->eagerLoader()->autoFields(false);
  700. $statement = $query
  701. ->select($count, true)
  702. ->autoFields(false)
  703. ->execute();
  704. } else {
  705. $statement = $this->connection()->newQuery()
  706. ->select($count)
  707. ->from(['count_source' => $query])
  708. ->execute();
  709. }
  710. $result = $statement->fetch('assoc')['count'];
  711. $statement->closeCursor();
  712. return (int)$result;
  713. }
  714. /**
  715. * Registers a callable function that will be executed when the `count` method in
  716. * this query is called. The return value for the function will be set as the
  717. * return value of the `count` method.
  718. *
  719. * This is particularly useful when you need to optimize a query for returning the
  720. * count, for example removing unnecessary joins, removing group by or just return
  721. * an estimated number of rows.
  722. *
  723. * The callback will receive as first argument a clone of this query and not this
  724. * query itself.
  725. *
  726. * @param callable $counter The counter value
  727. * @return $this
  728. */
  729. public function counter($counter)
  730. {
  731. $this->_counter = $counter;
  732. return $this;
  733. }
  734. /**
  735. * Toggle hydrating entities.
  736. *
  737. * If set to false array results will be returned
  738. *
  739. * @param bool|null $enable Use a boolean to set the hydration mode.
  740. * Null will fetch the current hydration mode.
  741. * @return bool|$this A boolean when reading, and $this when setting the mode.
  742. */
  743. public function hydrate($enable = null)
  744. {
  745. if ($enable === null) {
  746. return $this->_hydrate;
  747. }
  748. $this->_dirty();
  749. $this->_hydrate = (bool)$enable;
  750. return $this;
  751. }
  752. /**
  753. * {@inheritDoc}
  754. *
  755. * @return $this
  756. * @throws \RuntimeException When you attempt to cache a non-select query.
  757. */
  758. public function cache($key, $config = 'default')
  759. {
  760. if ($this->_type !== 'select' && $this->_type !== null) {
  761. throw new RuntimeException('You cannot cache the results of non-select queries.');
  762. }
  763. return $this->_cache($key, $config);
  764. }
  765. /**
  766. * {@inheritDoc}
  767. *
  768. * @throws \RuntimeException if this method is called on a non-select Query.
  769. */
  770. public function all()
  771. {
  772. if ($this->_type !== 'select' && $this->_type !== null) {
  773. throw new RuntimeException(
  774. 'You cannot call all() on a non-select query. Use execute() instead.'
  775. );
  776. }
  777. return $this->_all();
  778. }
  779. /**
  780. * Trigger the beforeFind event on the query's repository object.
  781. *
  782. * Will not trigger more than once, and only for select queries.
  783. *
  784. * @return void
  785. */
  786. public function triggerBeforeFind()
  787. {
  788. if (!$this->_beforeFindFired && $this->_type === 'select') {
  789. $table = $this->repository();
  790. $this->_beforeFindFired = true;
  791. $table->dispatchEvent('Model.beforeFind', [
  792. $this,
  793. new ArrayObject($this->_options),
  794. !$this->eagerLoaded()
  795. ]);
  796. }
  797. }
  798. /**
  799. * {@inheritDoc}
  800. */
  801. public function sql(ValueBinder $binder = null)
  802. {
  803. $this->triggerBeforeFind();
  804. $this->_transformQuery();
  805. $sql = parent::sql($binder);
  806. return $sql;
  807. }
  808. /**
  809. * Executes this query and returns a ResultSet object containing the results.
  810. * This will also setup the correct statement class in order to eager load deep
  811. * associations.
  812. *
  813. * @return \Cake\ORM\ResultSet
  814. */
  815. protected function _execute()
  816. {
  817. $this->triggerBeforeFind();
  818. if ($this->_results) {
  819. $decorator = $this->_decoratorClass();
  820. return new $decorator($this->_results);
  821. }
  822. $statement = $this->eagerLoader()->loadExternal($this, $this->execute());
  823. return new ResultSet($this, $statement);
  824. }
  825. /**
  826. * Applies some defaults to the query object before it is executed.
  827. *
  828. * Specifically add the FROM clause, adds default table fields if none are
  829. * specified and applies the joins required to eager load associations defined
  830. * using `contain`
  831. *
  832. * @see \Cake\Database\Query::execute()
  833. * @return void
  834. */
  835. protected function _transformQuery()
  836. {
  837. if (!$this->_dirty) {
  838. return;
  839. }
  840. if ($this->_type === 'select') {
  841. if (empty($this->_parts['from'])) {
  842. $this->from([$this->_repository->alias() => $this->_repository->table()]);
  843. }
  844. $this->_addDefaultFields();
  845. $this->eagerLoader()->attachAssociations($this, $this->_repository, !$this->_hasFields);
  846. }
  847. }
  848. /**
  849. * Inspects if there are any set fields for selecting, otherwise adds all
  850. * the fields for the default table.
  851. *
  852. * @return void
  853. */
  854. protected function _addDefaultFields()
  855. {
  856. $select = $this->clause('select');
  857. $this->_hasFields = true;
  858. if (!count($select) || $this->_autoFields === true) {
  859. $this->_hasFields = false;
  860. $this->select($this->repository()->schema()->columns());
  861. $select = $this->clause('select');
  862. }
  863. $aliased = $this->aliasFields($select, $this->repository()->alias());
  864. $this->select($aliased, true);
  865. }
  866. /**
  867. * {@inheritDoc}
  868. *
  869. * @see \Cake\ORM\Table::find()
  870. */
  871. public function find($finder, array $options = [])
  872. {
  873. return $this->repository()->callFinder($finder, $this, $options);
  874. }
  875. /**
  876. * Marks a query as dirty, removing any preprocessed information
  877. * from in memory caching such as previous results
  878. *
  879. * @return void
  880. */
  881. protected function _dirty()
  882. {
  883. $this->_results = null;
  884. $this->_resultsCount = null;
  885. parent::_dirty();
  886. }
  887. /**
  888. * Create an update query.
  889. *
  890. * This changes the query type to be 'update'.
  891. * Can be combined with set() and where() methods to create update queries.
  892. *
  893. * @param string|null $table Unused parameter.
  894. * @return $this
  895. */
  896. public function update($table = null)
  897. {
  898. $table = $table ?: $this->repository()->table();
  899. return parent::update($table);
  900. }
  901. /**
  902. * Create a delete query.
  903. *
  904. * This changes the query type to be 'delete'.
  905. * Can be combined with the where() method to create delete queries.
  906. *
  907. * @param string|null $table Unused parameter.
  908. * @return $this
  909. */
  910. public function delete($table = null)
  911. {
  912. $repo = $this->repository();
  913. $this->from([$repo->alias() => $repo->table()]);
  914. return parent::delete();
  915. }
  916. /**
  917. * Create an insert query.
  918. *
  919. * This changes the query type to be 'insert'.
  920. * Note calling this method will reset any data previously set
  921. * with Query::values()
  922. *
  923. * Can be combined with the where() method to create delete queries.
  924. *
  925. * @param array $columns The columns to insert into.
  926. * @param array $types A map between columns & their datatypes.
  927. * @return $this
  928. */
  929. public function insert(array $columns, array $types = [])
  930. {
  931. $table = $this->repository()->table();
  932. $this->into($table);
  933. return parent::insert($columns, $types);
  934. }
  935. /**
  936. * {@inheritDoc}
  937. *
  938. * @throws \BadMethodCallException if the method is called for a non-select query
  939. */
  940. public function __call($method, $arguments)
  941. {
  942. if ($this->type() === 'select') {
  943. return $this->_call($method, $arguments);
  944. }
  945. throw new \BadMethodCallException(
  946. sprintf('Cannot call method "%s" on a "%s" query', $method, $this->type())
  947. );
  948. }
  949. /**
  950. * {@inheritDoc}
  951. */
  952. public function __debugInfo()
  953. {
  954. $eagerLoader = $this->eagerLoader();
  955. return parent::__debugInfo() + [
  956. 'hydrate' => $this->_hydrate,
  957. 'buffered' => $this->_useBufferedResults,
  958. 'formatters' => count($this->_formatters),
  959. 'mapReducers' => count($this->_mapReduce),
  960. 'contain' => $eagerLoader ? $eagerLoader->contain() : [],
  961. 'matching' => $eagerLoader ? $eagerLoader->matching() : [],
  962. 'extraOptions' => $this->_options,
  963. 'repository' => $this->_repository
  964. ];
  965. }
  966. /**
  967. * Executes the query and converts the result set into JSON.
  968. *
  969. * Part of JsonSerializable interface.
  970. *
  971. * @return \Cake\Datasource\ResultSetInterface The data to convert to JSON.
  972. */
  973. public function jsonSerialize()
  974. {
  975. return $this->all();
  976. }
  977. /**
  978. * Get/Set whether or not the ORM should automatically append fields.
  979. *
  980. * By default calling select() will disable auto-fields. You can re-enable
  981. * auto-fields with this method.
  982. *
  983. * @param bool|null $value The value to set or null to read the current value.
  984. * @return bool|$this Either the current value or the query object.
  985. */
  986. public function autoFields($value = null)
  987. {
  988. if ($value === null) {
  989. return $this->_autoFields;
  990. }
  991. $this->_autoFields = (bool)$value;
  992. return $this;
  993. }
  994. /**
  995. * Decorates the results iterator with MapReduce routines and formatters
  996. *
  997. * @param \Traversable $result Original results
  998. * @return \Cake\Datasource\ResultSetInterface
  999. */
  1000. protected function _decorateResults($result)
  1001. {
  1002. $result = $this->_applyDecorators($result);
  1003. if (!($result instanceof ResultSet) && $this->bufferResults()) {
  1004. $class = $this->_decoratorClass();
  1005. $result = new $class($result->buffered());
  1006. }
  1007. return $result;
  1008. }
  1009. }