Query.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\data\model;
  9. use lithium\util\Set;
  10. use lithium\data\Source;
  11. use lithium\core\ConfigException;
  12. use InvalidArgumentException;
  13. /**
  14. * The `Query` class acts as a container for all information necessary to perform a particular
  15. * database operation. Each `Query` object instance has a type, which is usually one of `'create'`,
  16. * `'read'`, `'update'` or `'delete'`.
  17. *
  18. * Because of this, `Query` objects are the primary method of communication between `Model` classes
  19. * and backend data sources. This helps to keep APIs abstract and flexible, since a model is only
  20. * required to call a single method against its backend. Since the `Query` object simply acts as a
  21. * structured data container, each backend can choose how to operate on the data the `Query`
  22. * contains. See each class method for more details on what data this class supports.
  23. *
  24. * @see lithium\data\Model
  25. * @see lithium\data\Source
  26. */
  27. class Query extends \lithium\core\Object {
  28. /**
  29. * The 'type' of query to be performed. This is either `'create'`, `'read'`, `'update'` or
  30. * `'delete'`, and corresponds to the method to be executed.
  31. *
  32. * @var string
  33. */
  34. protected $_type = null;
  35. /**
  36. * Array containing mappings of relationship and field names, which allow database results to
  37. * be mapped to the correct objects.
  38. *
  39. * @var array
  40. */
  41. protected $_map = array();
  42. /**
  43. * If a `Query` is bound to a `Record` or `Document` object (i.e. for a `'create'` or
  44. * `'update'` query).
  45. *
  46. * @var object
  47. */
  48. protected $_entity = null;
  49. /**
  50. * An array of data used in a write context. Only used if no binding object is present in the
  51. * `$_entity` property.
  52. *
  53. * @var array
  54. */
  55. protected $_data = array();
  56. /**
  57. * A query can be assigned its own custom schema object, using the `schema()` method. If this
  58. * is not assigned, then the model associated with the query will be used to get the schema
  59. * information.
  60. *
  61. * @var object
  62. */
  63. protected $_schema = null;
  64. /**
  65. * Classes used by `Query`.
  66. *
  67. * @var array
  68. */
  69. protected $_classes = array(
  70. 'schema' => 'lithium\data\Schema'
  71. );
  72. /**
  73. * The query's fields
  74. *
  75. * @see lithium\data\model\Query::fields()
  76. *
  77. * @var array
  78. */
  79. protected $_fields = array(0 => array(), 1 => array());
  80. /**
  81. * Count the number of identical models in a query for building
  82. * unique aliases
  83. *
  84. * @see lithium\data\model\Query::alias()
  85. *
  86. * @var array
  87. */
  88. protected $_alias = array();
  89. /**
  90. * Map beetween generated aliases and corresponding relation paths
  91. *
  92. * @see lithium\data\model\Query::alias()
  93. *
  94. * @var array
  95. */
  96. protected $_paths = array();
  97. /**
  98. * Map beetween generated aliases and corresponding models.
  99. *
  100. * @see lithium\data\model\Query::alias()
  101. *
  102. * @var array
  103. */
  104. protected $_models = array();
  105. /**
  106. * Auto configuration properties.
  107. *
  108. * @var array
  109. */
  110. protected $_autoConfig = array('type', 'map');
  111. /**
  112. * Initialization methods on construct
  113. *
  114. * @var array
  115. */
  116. protected $_initializers = array(
  117. 'model', 'entity', 'conditions', 'having', 'group', 'order',
  118. 'limit', 'offset', 'page', 'data', 'calculate', 'schema', 'comment'
  119. );
  120. /**
  121. * Boolean indicate if the query is built or not
  122. *
  123. * @var string
  124. */
  125. protected $_built = false;
  126. /**
  127. * Class constructor, which initializes the default values this object supports.
  128. * Even though only a specific list of configuration parameters is available
  129. * by default, the `Query` object uses the `__call()` method to implement
  130. * automatic getters and setters for any arbitrary piece of data.
  131. *
  132. * This means that any information may be passed into the constructor may be
  133. * used by the backend data source executing the query (or ignored, if support
  134. * is not implemented). This is useful if, for example, you wish to extend a
  135. * core data source and implement custom fucntionality.
  136. *
  137. * @param array $config Config options:
  138. * - `'type'` _string_: The type of the query (`read`, `insert`, `update`, `delete`).
  139. * - `'entity'` _object_: The base entity to query on. If set `'model'` is optionnal.
  140. * - `'model'` _string_: The base model to query on.
  141. * - `'source'` _string_: The name of the table/collection. Unnecessary
  142. * if `model` is set.
  143. * - `'alias'` _string_: Alias for the source. Unnecessary if `model` is set.
  144. * - `'schema'` _object_: A schema model. Unnecessary if `model` is set.
  145. * - `'fields'` _array_: The fields to retreive.
  146. * - `'conditions'` _array_: The conditions of the queries
  147. * - `'having'` _array_: The having conditions of the queries
  148. * - `'group'` _string_: The group by parameter.
  149. * - `'order'` _string_: The order by parameter.
  150. * - `'limit'` _string_: The limit parameter.
  151. * - `'offset'` _string_: The offset of the `limit` options.
  152. * - `'page'` _string_: Convenience parameter for setting the `offset`:
  153. * `offset` = `page` * `limit`.
  154. * - `'with'` _array_: Contain dependencies. Works only if `model` is set.
  155. * - `'joins'` _array_: Contain manual join dependencies.
  156. * - `'data'` _array_: Datas for update queries.
  157. * - `'whitelist'` _array_: Allowed fields for updating queries.
  158. * - `'calculate'` _string_: Alias name of the count.
  159. * - `'comment'` _string_: Comment for the query.
  160. * - `'map'` _object_: Unnecessary if `model` is set.
  161. * - `'relationships'` _array_: Unnecessary if `model` is set.
  162. */
  163. public function __construct(array $config = array()) {
  164. $defaults = array(
  165. 'model' => null,
  166. 'entity' => null,
  167. 'source' => null,
  168. 'alias' => null,
  169. 'fields' => array(),
  170. 'conditions' => array(),
  171. 'having' => array(),
  172. 'group' => null,
  173. 'order' => null,
  174. 'limit' => null,
  175. 'offset' => null,
  176. 'page' => null,
  177. 'with' => array(),
  178. 'joins' => array(),
  179. 'data' => array(),
  180. 'whitelist' => array(),
  181. 'calculate' => null,
  182. 'schema' => null,
  183. 'comment' => null,
  184. 'map' => array(),
  185. 'relationships' => array()
  186. );
  187. parent::__construct($config + $defaults);
  188. }
  189. protected function _init() {
  190. parent::_init();
  191. unset($this->_config['type']);
  192. $keys = array_keys($this->_config);
  193. foreach ($this->_initializers as $key) {
  194. $val = $this->_config[$key];
  195. if ($val !== null) {
  196. $this->_config[$key] = is_array($val) ? array() : null;
  197. $this->{$key}($val);
  198. }
  199. }
  200. if ($list = $this->_config['whitelist']) {
  201. $this->_config['whitelist'] = array_combine($list, $list);
  202. }
  203. if ($this->_entity && !$this->_config['model']) {
  204. $this->model($this->_entity->model());
  205. }
  206. if ($this->_config['with']) {
  207. if (!$model = $this->model()) {
  208. throw new ConfigException("The `'with'` option needs a valid binded model.");
  209. }
  210. $this->_config['with'] = Set::normalize($this->_config['with']);
  211. }
  212. if ($model = $this->model()) {
  213. $this->alias($this->_config['alias'] ?: $model::meta('name'));
  214. }
  215. $this->fields($this->_config['fields']);
  216. unset($this->_config['entity'], $this->_config['init']);
  217. }
  218. /**
  219. * Get method of type, i.e. 'read', 'update', 'create', 'delete'.
  220. *
  221. * @return string
  222. */
  223. public function type() {
  224. return $this->_type;
  225. }
  226. /**
  227. * Generates a schema map of the query's result set, where the keys are aliases, and the values
  228. * are arrays of field names.
  229. *
  230. * @param array $map
  231. * @return array
  232. */
  233. public function map($map = null) {
  234. if ($map !== null) {
  235. $this->_map = $map;
  236. return $this;
  237. }
  238. return $this->_map;
  239. }
  240. /**
  241. * Accessor method for `Query` calculate values.
  242. *
  243. * @param string $calculate Value for calculate config setting.
  244. * @return mixed Current calculate config value.
  245. */
  246. public function calculate($calculate = null) {
  247. if ($calculate) {
  248. $this->_config['calculate'] = $calculate;
  249. return $this;
  250. }
  251. return $this->_config['calculate'];
  252. }
  253. /**
  254. * Set and get method for the model associated with the `Query`.
  255. * Will also set the source table, i.e. `$this->_config['source']`.
  256. *
  257. * @param string $model
  258. * @return string
  259. */
  260. public function model($model = null) {
  261. if (!$model) {
  262. return $this->_config['model'];
  263. }
  264. $this->_config['model'] = $model;
  265. $this->_config['source'] = $this->_config['source'] ?: $model::meta('source');
  266. return $this;
  267. }
  268. /**
  269. * Set and get method for conditions.
  270. *
  271. * If no conditions are set in query, it will ask the bound entity for condition array.
  272. *
  273. * @param mixed $conditions String or array to append to existing conditions.
  274. * @return array Returns an array of all conditions applied to this query.
  275. */
  276. public function conditions($conditions = null) {
  277. if (!$conditions) {
  278. return $this->_config['conditions'] ?: $this->_entityConditions();
  279. }
  280. $conditions = (array) $conditions;
  281. $this->_config['conditions'] = (array) $this->_config['conditions'];
  282. $this->_config['conditions'] = array_merge($this->_config['conditions'], $conditions);
  283. return $this;
  284. }
  285. /**
  286. * Set and get method for havings.
  287. *
  288. * @param mixed $having String or array to append to existing having.
  289. * @return array Returns an array of all having applied to this query.
  290. */
  291. public function having($having = null) {
  292. if (!$having) {
  293. return $this->_config['having'];
  294. }
  295. $having = (array) $having;
  296. $this->_config['having'] = (array) $this->_config['having'];
  297. $this->_config['having'] = array_merge($this->_config['having'], $having);
  298. return $this;
  299. }
  300. /**
  301. * Set, get or reset fields option for query.
  302. *
  303. * Usage:
  304. * {{{
  305. * // to add a field
  306. * $query->fields('created');
  307. * }}}
  308. * {{{
  309. * // to add several fields
  310. * $query->fields(array('title','body','modified'));
  311. * }}}
  312. * {{{
  313. * // to reset fields to none
  314. * $query->fields(false);
  315. * // should be followed by a 2nd call to fields with required fields
  316. * }}}
  317. *
  318. * @param mixed $fields string, array or `false`
  319. * @param boolean $overwrite If `true`, existing fields will be removed before adding `$fields`.
  320. * @return array Returns an array containing all fields added to the query.
  321. */
  322. public function fields($fields = null, $overwrite = false) {
  323. if ($fields === false || $overwrite) {
  324. $this->_fields = array(0 => array(), 1 => array());
  325. }
  326. if ($fields === null) {
  327. return array_merge(array_keys($this->_fields[1]), $this->_fields[0]);
  328. }
  329. if (!$fields) {
  330. return $this;
  331. }
  332. $fields = is_array($fields) ? $fields : array($fields);
  333. foreach ($fields as $key => $field) {
  334. if (is_string($field)) {
  335. $this->_fields[1][$field] = true;
  336. } elseif (is_array($field) && !is_numeric($key)) {
  337. foreach ($field as &$val) {
  338. $val = $key . '.' . $val;
  339. }
  340. $this->fields($field);
  341. } else {
  342. $this->_fields[0][] = $field;
  343. }
  344. }
  345. return $this;
  346. }
  347. /**
  348. * Set and get method for query's limit of amount of records to return
  349. *
  350. * @param integer $limit
  351. * @return integer
  352. */
  353. public function limit($limit = null) {
  354. if ($limit) {
  355. $this->_config['limit'] = intval($limit);
  356. return $this;
  357. }
  358. if ($limit === false) {
  359. $this->_config['limit'] = null;
  360. return $this;
  361. }
  362. return $this->_config['limit'];
  363. }
  364. /**
  365. * Set and get method for query's offset, i.e. which records to get
  366. *
  367. * @param integer $offset
  368. * @return integer
  369. */
  370. public function offset($offset = null) {
  371. if ($offset !== null) {
  372. $this->_config['offset'] = intval($offset);
  373. return $this;
  374. }
  375. return $this->_config['offset'];
  376. }
  377. /**
  378. * Set and get method for page, in relation to limit, of which records to get
  379. *
  380. * @param integer $page
  381. * @return integer
  382. */
  383. public function page($page = null) {
  384. if ($page) {
  385. $this->_config['page'] = $page = (intval($page) ?: 1);
  386. $this->offset(($page - 1) * $this->_config['limit']);
  387. return $this;
  388. }
  389. return $this->_config['page'];
  390. }
  391. /**
  392. * Set and get method for the query's order specification.
  393. *
  394. * @param array|string $order
  395. * @return mixed
  396. */
  397. public function order($order = null) {
  398. if ($order) {
  399. $this->_config['order'] = $order;
  400. return $this;
  401. }
  402. return $this->_config['order'];
  403. }
  404. /**
  405. * Set and get method for the `Query` group config setting.
  406. *
  407. * @param string $group New group config setting.
  408. * @return mixed Current group config setting.
  409. */
  410. public function group($group = null) {
  411. if ($group) {
  412. $this->_config['group'] = $group;
  413. return $this;
  414. }
  415. if ($group === false) {
  416. $this->_config['group'] = null;
  417. return $this;
  418. }
  419. return $this->_config['group'];
  420. }
  421. /**
  422. * Set and get method for current query's comment.
  423. *
  424. * Comment will have no effect on query, but will be passed along so data source can log it.
  425. *
  426. * @param string $comment
  427. * @return string
  428. */
  429. public function comment($comment = null) {
  430. if ($comment) {
  431. $this->_config['comment'] = $comment;
  432. return $this;
  433. }
  434. return $this->_config['comment'];
  435. }
  436. /**
  437. * Set and get method for the query's entity instance.
  438. *
  439. * @param object $entity Reference to the query's current entity object.
  440. * @return object Reference to the query's current entity object.
  441. */
  442. public function &entity(&$entity = null) {
  443. if ($entity) {
  444. $this->_entity = $entity;
  445. return $this;
  446. }
  447. return $this->_entity;
  448. }
  449. /**
  450. * Set and get method for the query's record's data.
  451. *
  452. * @param array $data if set, will set given array.
  453. * @return array Empty array if no data, array of data if the record has it.
  454. */
  455. public function data($data = array()) {
  456. $bind =& $this->_entity;
  457. if ($data) {
  458. $bind ? $bind->set($data) : $this->_data = array_merge($this->_data, $data);
  459. return $this;
  460. }
  461. $data = $bind ? $bind->data() : $this->_data;
  462. return ($list = $this->_config['whitelist']) ? array_intersect_key($data, $list) : $data;
  463. }
  464. /**
  465. * Set and get the relationships.
  466. *
  467. * @param string $relpath A dotted path.
  468. * @param array $config the config array to set.
  469. * @return mixed The relationships array or a relationship array if `$relpath` is set. Returns
  470. * `null` if a join doesn't exist.
  471. * @throws InvalidArgumentException
  472. */
  473. public function relationships($relpath = null, $config = null) {
  474. if ($config) {
  475. if (!$relpath) {
  476. throw new InvalidArgumentException("The relation dotted path is empty.");
  477. }
  478. if (isset($config['model']) && isset($config['alias'])) {
  479. $this->_models[$config['alias']] = $config['model'];
  480. }
  481. $this->_config['relationships'][$relpath] = $config;
  482. return $this;
  483. }
  484. if (!$relpath) {
  485. return $this->_config['relationships'];
  486. }
  487. if (isset($this->_config['relationships'][$relpath])) {
  488. return $this->_config['relationships'][$relpath];
  489. }
  490. }
  491. /**
  492. * Set and get the joins
  493. *
  494. * @param string $name Optional name of join. Unless two parameters are passed, this parameter
  495. * is regonized as `$join`.
  496. * @param object|string $join A single query object or an array of query objects
  497. * @return mixed The joins array or a join array if `$name` is set. Returns `null` if a join
  498. * doesn't exist.
  499. */
  500. public function joins($name = null, $join = null) {
  501. if (is_array($name)) {
  502. $join = $name;
  503. $name = null;
  504. }
  505. if ($join) {
  506. if (!$name) {
  507. $this->_config['joins'][] = $join;
  508. } else {
  509. $this->_config['joins'][$name] = $join;
  510. }
  511. return $this;
  512. }
  513. if (!$name) {
  514. return $this->_config['joins'];
  515. }
  516. if (isset($this->_config['joins'][$name])) {
  517. return $this->_config['joins'][$name];
  518. }
  519. }
  520. /**
  521. * Convert the query's properties to the data sources' syntax and return it as an array.
  522. *
  523. * @param object $source Instance of the data source (`lithium\data\Source`) to use for
  524. * conversion.
  525. * @param array $options Options to use when exporting the data.
  526. * @return array Returns an array containing a data source-specific representation of a query.
  527. */
  528. public function export(Source $source, array $options = array()) {
  529. $defaults = array('keys' => array());
  530. $options += $defaults;
  531. if ($options['keys']) {
  532. $keys = array_flip($options['keys']);
  533. } else {
  534. $keys =& $this->_config;
  535. }
  536. $results = array('type' => $this->_type);
  537. $apply = array_intersect_key($keys, array_flip($source->methods()));
  538. $copy = array_diff_key($keys, $apply);
  539. if (isset($keys['with'])) {
  540. $this->applyStrategy($source);
  541. }
  542. foreach ($apply as $item => $value) {
  543. $results[$item] = $source->{$item}($this->{$item}(), $this);
  544. }
  545. foreach ($copy as $item => $value) {
  546. $results[$item] = $this->_config[$item];
  547. }
  548. if (array_key_exists('data', $keys)) {
  549. $results['data'] = $this->_exportData();
  550. }
  551. if (array_key_exists('source', $keys)) {
  552. $results['source'] = $source->name($results['source']);
  553. }
  554. if (!isset($results['fields'])) {
  555. return $results;
  556. }
  557. $created = array('fields', 'values');
  558. if (is_array($results['fields']) && array_keys($results['fields']) == $created) {
  559. $results = $results['fields'] + $results;
  560. }
  561. return $results;
  562. }
  563. /**
  564. * Helper method used by `export()` which delegate the query generation to the datasource.
  565. *
  566. * @param object $source Instance of the data source (`lithium\data\Source`) to use for
  567. * conversion.
  568. */
  569. public function applyStrategy(Source $source) {
  570. if ($this->_built) {
  571. return;
  572. }
  573. $this->_built = true;
  574. if (!$this->_config['with']) {
  575. return;
  576. }
  577. $options = array();
  578. if (isset($this->_config['strategy'])) {
  579. $options['strategy'] = $this->_config['strategy'];
  580. }
  581. $source->applyStrategy($options, $this);
  582. }
  583. /**
  584. * Helper method used by `export()` to extract the data either from a bound entity, or from
  585. * passed configuration, and filter it through a configured whitelist, if present.
  586. *
  587. * @return array
  588. */
  589. protected function _exportData() {
  590. $data = $this->_entity ? $this->_entity->export() : $this->_data;
  591. if (!$list = $this->_config['whitelist']) {
  592. return $data;
  593. }
  594. $list = array_combine($list, $list);
  595. if (!$this->_entity) {
  596. return array_intersect_key($data, $list);
  597. }
  598. foreach ($data as $type => $values) {
  599. if (!is_array($values)) {
  600. continue;
  601. }
  602. $data[$type] = array_intersect_key($values, $list);
  603. }
  604. return $data;
  605. }
  606. public function schema($field = null) {
  607. if (is_object($field)) {
  608. $this->_schema = $field;
  609. return;
  610. }
  611. if ($schema = $this->_schema) {
  612. return $field ? $schema[$field] : $schema;
  613. }
  614. if ($model = $this->model()) {
  615. return $model::schema($field);
  616. } else {
  617. return $this->_instance('schema');
  618. }
  619. }
  620. /**
  621. * Get or Set a unique alias for the query or a query's relation if `$relpath` is set.
  622. *
  623. * @param mixed $alias The value of the alias to set for the passed `$relpath`. For getting an
  624. * alias value set alias to `true`.
  625. * @param string $relpath A dotted relation name or `null` for identifying the query's model.
  626. * @return string An alias value or `null` for an unexisting `$relpath` alias.
  627. */
  628. public function alias($alias = true, $relpath = null) {
  629. if ($alias === true) {
  630. if (!$relpath) {
  631. return $this->_config['alias'];
  632. }
  633. $return = array_search($relpath, $this->_paths);
  634. return $return ?: null;
  635. }
  636. if ($relpath === null) {
  637. $this->_config['alias'] = $alias;
  638. }
  639. if ($relpath === null && ($model = $this->_config['model'])) {
  640. $this->_models[$alias] = $model;
  641. }
  642. $relpath = (string) $relpath;
  643. unset($this->_paths[array_search($relpath, $this->_paths)]);
  644. if (!$alias && $relpath) {
  645. $last = strrpos($relpath, '.');
  646. $alias = $last ? substr($relpath, $last + 1) : $relpath;
  647. }
  648. if (isset($this->_alias[$alias])) {
  649. $this->_alias[$alias]++;
  650. $alias .= '__' . $this->_alias[$alias];
  651. } else {
  652. $this->_alias[$alias] = 1;
  653. }
  654. $this->_paths[$alias] = $relpath;
  655. return $alias;
  656. }
  657. /**
  658. * Return the generated aliases mapped to their relation path
  659. *
  660. * @param object $source Instance of the data source (`lithium\data\Source`) to use for
  661. * conversion.
  662. * @return array Map between aliases and their corresponding dotted relation paths.
  663. */
  664. public function paths(Source $source = null) {
  665. if ($source) {
  666. $this->applyStrategy($source);
  667. }
  668. return $this->_paths;
  669. }
  670. /**
  671. * Return the generated aliases mapped to their corresponding model
  672. *
  673. * @param object $source Instance of the data source (`lithium\data\Source`) to use for
  674. * conversion.
  675. * @return array Map between aliases and their corresponding fully-namespaced model names.
  676. */
  677. public function models(Source $source = null) {
  678. if ($source) {
  679. $this->applyStrategy($source);
  680. }
  681. return $this->_models;
  682. }
  683. /**
  684. * Gets or sets a custom query field which does not have an accessor method.
  685. *
  686. * @param string $method Query part.
  687. * @param array $params Query parameters.
  688. * @return mixed Returns the value as set in the `Query` object's constructor.
  689. */
  690. public function __call($method, array $params = array()) {
  691. if ($params) {
  692. $this->_config[$method] = current($params);
  693. return $this;
  694. }
  695. return isset($this->_config[$method]) ? $this->_config[$method] : null;
  696. }
  697. /**
  698. * Custom check to determine if our given magic methods can be responded to.
  699. *
  700. * @param string $method Method name.
  701. * @param bool $internal Interal call or not.
  702. * @return bool
  703. */
  704. public function respondsTo($method, $internal = false) {
  705. return isset($this->_config[$method]) || parent::respondsTo($method, $internal);
  706. }
  707. /**
  708. * Will return a find first condition on the associated model if a record is connected.
  709. * Called by conditions when it is called as a get and no condition is set.
  710. *
  711. * @return array Returns an array in the following format:
  712. * `([model's primary key'] => [that key set in the record])`.
  713. */
  714. protected function _entityConditions() {
  715. if (!$this->_entity || !($model = $this->_config['model'])) {
  716. return;
  717. }
  718. $key = $model::key($this->_entity->data());
  719. if (!$key && $this->_type != "create") {
  720. throw new ConfigException('No matching primary key found.');
  721. }
  722. if (is_array($key)) {
  723. return $key;
  724. }
  725. $key = $model::meta('key');
  726. $val = $this->_entity->{$key};
  727. return $val ? array($key => $val) : array();
  728. }
  729. /**
  730. * Get/set sub queries for the query.
  731. * The getter must be called after an export since the sub queries are built
  732. * during the export according the export's `mode` option and the query `with` option.
  733. *
  734. * @see lithium\data\model\Query::export()
  735. *
  736. * @param string $relpath a dotted relation path
  737. * @param string $query a query instance
  738. * @return mixed
  739. */
  740. public function childs($relpath = null, $query = null) {
  741. if (!$model = $this->model()) {
  742. throw new ConfigException("No binded model.");
  743. }
  744. if ($query) {
  745. $this->_childs[$relpath] = $query;
  746. return $this;
  747. }
  748. return $this->_childs;
  749. }
  750. }
  751. ?>