/src/ORM/Table.php

https://github.com/binondord/cakephp · PHP · 2203 lines · 918 code · 144 blank · 1141 comment · 126 complexity · fc9c12903fa6a5e39ab728381680bbed MD5 · raw file

Large files are truncated click here to view the full file

  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 BadMethodCallException;
  18. use Cake\Core\App;
  19. use Cake\Database\Connection;
  20. use Cake\Database\Schema\Table as Schema;
  21. use Cake\Database\Type;
  22. use Cake\Datasource\EntityInterface;
  23. use Cake\Datasource\Exception\InvalidPrimaryKeyException;
  24. use Cake\Datasource\RepositoryInterface;
  25. use Cake\Datasource\RulesAwareTrait;
  26. use Cake\Event\EventDispatcherInterface;
  27. use Cake\Event\EventListenerInterface;
  28. use Cake\Event\EventManager;
  29. use Cake\Event\EventManagerTrait;
  30. use Cake\ORM\AssociationCollection;
  31. use Cake\ORM\Association\BelongsTo;
  32. use Cake\ORM\Association\BelongsToMany;
  33. use Cake\ORM\Association\HasMany;
  34. use Cake\ORM\Association\HasOne;
  35. use Cake\ORM\BehaviorRegistry;
  36. use Cake\ORM\Exception\MissingEntityException;
  37. use Cake\ORM\Marshaller;
  38. use Cake\ORM\RulesChecker;
  39. use Cake\ORM\Rule\IsUnique;
  40. use Cake\Utility\Inflector;
  41. use Cake\Validation\ValidatorAwareTrait;
  42. use InvalidArgumentException;
  43. use RuntimeException;
  44. /**
  45. * Represents a single database table.
  46. *
  47. * Exposes methods for retrieving data out of it, and manages the associations
  48. * this table has to other tables. Multiple instances of this class can be created
  49. * for the same database table with different aliases, this allows you to address
  50. * your database structure in a richer and more expressive way.
  51. *
  52. * ### Retrieving data
  53. *
  54. * The primary way to retrieve data is using Table::find(). See that method
  55. * for more information.
  56. *
  57. * ### Dynamic finders
  58. *
  59. * In addition to the standard find($type) finder methods, CakePHP provides dynamic
  60. * finder methods. These methods allow you to easily set basic conditions up. For example
  61. * to filter users by username you would call
  62. *
  63. * ```
  64. * $query = $users->findByUsername('mark');
  65. * ```
  66. *
  67. * You can also combine conditions on multiple fields using either `Or` or `And`:
  68. *
  69. * ```
  70. * $query = $users->findByUsernameOrEmail('mark', 'mark@example.org');
  71. * ```
  72. *
  73. * ### Bulk updates/deletes
  74. *
  75. * You can use Table::updateAll() and Table::deleteAll() to do bulk updates/deletes.
  76. * You should be aware that events will *not* be fired for bulk updates/deletes.
  77. *
  78. * ### Callbacks/events
  79. *
  80. * Table objects provide a few callbacks/events you can hook into to augment/replace
  81. * find operations. Each event uses the standard event subsystem in CakePHP
  82. *
  83. * - `beforeFind(Event $event, Query $query, ArrayObject $options, boolean $primary)`
  84. * Fired before each find operation. By stopping the event and supplying a
  85. * return value you can bypass the find operation entirely. Any changes done
  86. * to the $query instance will be retained for the rest of the find. The
  87. * $primary parameter indicates whether or not this is the root query,
  88. * or an associated query.
  89. *
  90. * - `buildValidator(Event $event, Validator $validator, string $name)`
  91. * Allows listeners to modify validation rules for the provided named validator.
  92. *
  93. * - `buildRules(Event $event, RulesChecker $rules)`
  94. * Allows listeners to modify the rules checker by adding more rules.
  95. *
  96. * - `beforeRules(Event $event, Entity $entity, ArrayObject $options, string $operation)`
  97. * Fired before an entity is validated using the rules checker. By stopping this event,
  98. * you can return the final value of the rules checking operation.
  99. *
  100. * - `afterRules(Event $event, Entity $entity, ArrayObject $options, bool $result, string $operation)`
  101. * Fired after the rules have been checked on the entity. By stopping this event,
  102. * you can return the final value of the rules checking operation.
  103. *
  104. * - `beforeSave(Event $event, Entity $entity, ArrayObject $options)`
  105. * Fired before each entity is saved. Stopping this event will abort the save
  106. * operation. When the event is stopped the result of the event will be returned.
  107. *
  108. * - `afterSave(Event $event, Entity $entity, ArrayObject $options)`
  109. * Fired after an entity is saved.
  110. *
  111. * - `beforeDelete(Event $event, Entity $entity, ArrayObject $options)`
  112. * Fired before an entity is deleted. By stopping this event you will abort
  113. * the delete operation.
  114. *
  115. * - `afterDelete(Event $event, Entity $entity, ArrayObject $options)`
  116. * Fired after an entity has been deleted.
  117. *
  118. * @see \Cake\Event\EventManager for reference on the events system.
  119. */
  120. class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface
  121. {
  122. use EventManagerTrait;
  123. use RulesAwareTrait;
  124. use ValidatorAwareTrait;
  125. /**
  126. * Name of default validation set.
  127. *
  128. * @var string
  129. */
  130. const DEFAULT_VALIDATOR = 'default';
  131. /**
  132. * The alias this object is assigned to validators as.
  133. *
  134. * @var string
  135. */
  136. const VALIDATOR_PROVIDER_NAME = 'table';
  137. /**
  138. * The rules class name that is used.
  139. *
  140. * @var string
  141. */
  142. const RULES_CLASS = 'Cake\ORM\RulesChecker';
  143. /**
  144. * Name of the table as it can be found in the database
  145. *
  146. * @var string
  147. */
  148. protected $_table;
  149. /**
  150. * Human name giving to this particular instance. Multiple objects representing
  151. * the same database table can exist by using different aliases.
  152. *
  153. * @var string
  154. */
  155. protected $_alias;
  156. /**
  157. * Connection instance
  158. *
  159. * @var \Cake\Database\Connection
  160. */
  161. protected $_connection;
  162. /**
  163. * The schema object containing a description of this table fields
  164. *
  165. * @var \Cake\Database\Schema\Table
  166. */
  167. protected $_schema;
  168. /**
  169. * The name of the field that represents the primary key in the table
  170. *
  171. * @var string|array
  172. */
  173. protected $_primaryKey;
  174. /**
  175. * The name of the field that represents a human readable representation of a row
  176. *
  177. * @var string
  178. */
  179. protected $_displayField;
  180. /**
  181. * The associations container for this Table.
  182. *
  183. * @var \Cake\ORM\AssociationCollection
  184. */
  185. protected $_associations;
  186. /**
  187. * BehaviorRegistry for this table
  188. *
  189. * @var \Cake\ORM\BehaviorRegistry
  190. */
  191. protected $_behaviors;
  192. /**
  193. * The name of the class that represent a single row for this table
  194. *
  195. * @var string
  196. */
  197. protected $_entityClass;
  198. /**
  199. * Registry key used to create this table object
  200. *
  201. * @var string
  202. */
  203. protected $_registryAlias;
  204. /**
  205. * Initializes a new instance
  206. *
  207. * The $config array understands the following keys:
  208. *
  209. * - table: Name of the database table to represent
  210. * - alias: Alias to be assigned to this table (default to table name)
  211. * - connection: The connection instance to use
  212. * - entityClass: The fully namespaced class name of the entity class that will
  213. * represent rows in this table.
  214. * - schema: A \Cake\Database\Schema\Table object or an array that can be
  215. * passed to it.
  216. * - eventManager: An instance of an event manager to use for internal events
  217. * - behaviors: A BehaviorRegistry. Generally not used outside of tests.
  218. * - associations: An AssociationCollection instance.
  219. * - validator: A Validator instance which is assigned as the "default"
  220. * validation set, or an associative array, where key is the name of the
  221. * validation set and value the Validator instance.
  222. *
  223. * @param array $config List of options for this table
  224. */
  225. public function __construct(array $config = [])
  226. {
  227. if (!empty($config['registryAlias'])) {
  228. $this->registryAlias($config['registryAlias']);
  229. }
  230. if (!empty($config['table'])) {
  231. $this->table($config['table']);
  232. }
  233. if (!empty($config['alias'])) {
  234. $this->alias($config['alias']);
  235. }
  236. if (!empty($config['connection'])) {
  237. $this->connection($config['connection']);
  238. }
  239. if (!empty($config['schema'])) {
  240. $this->schema($config['schema']);
  241. }
  242. if (!empty($config['entityClass'])) {
  243. $this->entityClass($config['entityClass']);
  244. }
  245. $eventManager = $behaviors = $associations = null;
  246. if (!empty($config['eventManager'])) {
  247. $eventManager = $config['eventManager'];
  248. }
  249. if (!empty($config['behaviors'])) {
  250. $behaviors = $config['behaviors'];
  251. }
  252. if (!empty($config['associations'])) {
  253. $associations = $config['associations'];
  254. }
  255. if (!empty($config['validator'])) {
  256. if (!is_array($config['validator'])) {
  257. $this->validator(static::DEFAULT_VALIDATOR, $config['validator']);
  258. } else {
  259. foreach ($config['validator'] as $name => $validator) {
  260. $this->validator($name, $validator);
  261. }
  262. }
  263. }
  264. $this->_eventManager = $eventManager ?: new EventManager();
  265. $this->_behaviors = $behaviors ?: new BehaviorRegistry($this);
  266. $this->_associations = $associations ?: new AssociationCollection();
  267. $this->initialize($config);
  268. $this->_eventManager->on($this);
  269. $this->dispatchEvent('Model.initialize');
  270. }
  271. /**
  272. * Get the default connection name.
  273. *
  274. * This method is used to get the fallback connection name if an
  275. * instance is created through the TableRegistry without a connection.
  276. *
  277. * @return string
  278. * @see \Cake\ORM\TableRegistry::get()
  279. */
  280. public static function defaultConnectionName()
  281. {
  282. return 'default';
  283. }
  284. /**
  285. * Initialize a table instance. Called after the constructor.
  286. *
  287. * You can use this method to define associations, attach behaviors
  288. * define validation and do any other initialization logic you need.
  289. *
  290. * ```
  291. * public function initialize(array $config)
  292. * {
  293. * $this->belongsTo('Users');
  294. * $this->belongsToMany('Tagging.Tags');
  295. * $this->primaryKey('something_else');
  296. * }
  297. * ```
  298. *
  299. * @param array $config Configuration options passed to the constructor
  300. * @return void
  301. */
  302. public function initialize(array $config)
  303. {
  304. }
  305. /**
  306. * Returns the database table name or sets a new one
  307. *
  308. * @param string|null $table the new table name
  309. * @return string
  310. */
  311. public function table($table = null)
  312. {
  313. if ($table !== null) {
  314. $this->_table = $table;
  315. }
  316. if ($this->_table === null) {
  317. $table = namespaceSplit(get_class($this));
  318. $table = substr(end($table), 0, -5);
  319. if (empty($table)) {
  320. $table = $this->alias();
  321. }
  322. $this->_table = Inflector::underscore($table);
  323. }
  324. return $this->_table;
  325. }
  326. /**
  327. * {@inheritDoc}
  328. */
  329. public function alias($alias = null)
  330. {
  331. if ($alias !== null) {
  332. $this->_alias = $alias;
  333. }
  334. if ($this->_alias === null) {
  335. $alias = namespaceSplit(get_class($this));
  336. $alias = substr(end($alias), 0, -5) ?: $this->_table;
  337. $this->_alias = $alias;
  338. }
  339. return $this->_alias;
  340. }
  341. /**
  342. * Alias a field with the table's current alias.
  343. *
  344. * @param string $field The field to alias.
  345. * @return string The field prefixed with the table alias.
  346. */
  347. public function aliasField($field)
  348. {
  349. return $this->alias() . '.' . $field;
  350. }
  351. /**
  352. * Returns the table registry key used to create this table instance
  353. *
  354. * @param string|null $registryAlias the key used to access this object
  355. * @return string
  356. */
  357. public function registryAlias($registryAlias = null)
  358. {
  359. if ($registryAlias !== null) {
  360. $this->_registryAlias = $registryAlias;
  361. }
  362. if ($this->_registryAlias === null) {
  363. $this->_registryAlias = $this->alias();
  364. }
  365. return $this->_registryAlias;
  366. }
  367. /**
  368. * Returns the connection instance or sets a new one
  369. *
  370. * @param \Cake\Database\Connection|null $conn The new connection instance
  371. * @return \Cake\Database\Connection
  372. */
  373. public function connection(Connection $conn = null)
  374. {
  375. if ($conn === null) {
  376. return $this->_connection;
  377. }
  378. return $this->_connection = $conn;
  379. }
  380. /**
  381. * Returns the schema table object describing this table's properties.
  382. *
  383. * If an \Cake\Database\Schema\Table is passed, it will be used for this table
  384. * instead of the default one.
  385. *
  386. * If an array is passed, a new \Cake\Database\Schema\Table will be constructed
  387. * out of it and used as the schema for this table.
  388. *
  389. * @param array|\Cake\Database\Schema\Table|null $schema New schema to be used for this table
  390. * @return \Cake\Database\Schema\Table
  391. */
  392. public function schema($schema = null)
  393. {
  394. if ($schema === null) {
  395. if ($this->_schema === null) {
  396. $this->_schema = $this->_initializeSchema(
  397. $this->connection()
  398. ->schemaCollection()
  399. ->describe($this->table())
  400. );
  401. }
  402. return $this->_schema;
  403. }
  404. if (is_array($schema)) {
  405. $constraints = [];
  406. if (isset($schema['_constraints'])) {
  407. $constraints = $schema['_constraints'];
  408. unset($schema['_constraints']);
  409. }
  410. $schema = new Schema($this->table(), $schema);
  411. foreach ($constraints as $name => $value) {
  412. $schema->addConstraint($name, $value);
  413. }
  414. }
  415. return $this->_schema = $schema;
  416. }
  417. /**
  418. * Override this function in order to alter the schema used by this table.
  419. * This function is only called after fetching the schema out of the database.
  420. * If you wish to provide your own schema to this table without touching the
  421. * database, you can override schema() or inject the definitions though that
  422. * method.
  423. *
  424. * ### Example:
  425. *
  426. * ```
  427. * protected function _initializeSchema(\Cake\Database\Schema\Table $table) {
  428. * $table->columnType('preferences', 'json');
  429. * return $table;
  430. * }
  431. * ```
  432. *
  433. * @param \Cake\Database\Schema\Table $table The table definition fetched from database.
  434. * @return \Cake\Database\Schema\Table the altered schema
  435. * @api
  436. */
  437. protected function _initializeSchema(Schema $table)
  438. {
  439. return $table;
  440. }
  441. /**
  442. * Test to see if a Table has a specific field/column.
  443. *
  444. * Delegates to the schema object and checks for column presence
  445. * using the Schema\Table instance.
  446. *
  447. * @param string $field The field to check for.
  448. * @return bool True if the field exists, false if it does not.
  449. */
  450. public function hasField($field)
  451. {
  452. $schema = $this->schema();
  453. return $schema->column($field) !== null;
  454. }
  455. /**
  456. * Returns the primary key field name or sets a new one
  457. *
  458. * @param string|array|null $key sets a new name to be used as primary key
  459. * @return string|array
  460. */
  461. public function primaryKey($key = null)
  462. {
  463. if ($key !== null) {
  464. $this->_primaryKey = $key;
  465. }
  466. if ($this->_primaryKey === null) {
  467. $key = (array)$this->schema()->primaryKey();
  468. if (count($key) === 1) {
  469. $key = $key[0];
  470. }
  471. $this->_primaryKey = $key;
  472. }
  473. return $this->_primaryKey;
  474. }
  475. /**
  476. * Returns the display field or sets a new one
  477. *
  478. * @param string|null $key sets a new name to be used as display field
  479. * @return string
  480. */
  481. public function displayField($key = null)
  482. {
  483. if ($key !== null) {
  484. $this->_displayField = $key;
  485. }
  486. if ($this->_displayField === null) {
  487. $schema = $this->schema();
  488. $primary = (array)$this->primaryKey();
  489. $this->_displayField = array_shift($primary);
  490. if ($schema->column('title')) {
  491. $this->_displayField = 'title';
  492. }
  493. if ($schema->column('name')) {
  494. $this->_displayField = 'name';
  495. }
  496. }
  497. return $this->_displayField;
  498. }
  499. /**
  500. * Returns the class used to hydrate rows for this table or sets
  501. * a new one
  502. *
  503. * @param string|null $name the name of the class to use
  504. * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found
  505. * @return string
  506. */
  507. public function entityClass($name = null)
  508. {
  509. if ($name === null && !$this->_entityClass) {
  510. $default = '\Cake\ORM\Entity';
  511. $self = get_called_class();
  512. $parts = explode('\\', $self);
  513. if ($self === __CLASS__ || count($parts) < 3) {
  514. return $this->_entityClass = $default;
  515. }
  516. $alias = Inflector::singularize(substr(array_pop($parts), 0, -5));
  517. $name = implode('\\', array_slice($parts, 0, -1)) . '\Entity\\' . $alias;
  518. if (!class_exists($name)) {
  519. return $this->_entityClass = $default;
  520. }
  521. }
  522. if ($name !== null) {
  523. $class = App::className($name, 'Model/Entity');
  524. $this->_entityClass = $class;
  525. }
  526. if (!$this->_entityClass) {
  527. throw new MissingEntityException([$name]);
  528. }
  529. return $this->_entityClass;
  530. }
  531. /**
  532. * Add a behavior.
  533. *
  534. * Adds a behavior to this table's behavior collection. Behaviors
  535. * provide an easy way to create horizontally re-usable features
  536. * that can provide trait like functionality, and allow for events
  537. * to be listened to.
  538. *
  539. * Example:
  540. *
  541. * Load a behavior, with some settings.
  542. *
  543. * ```
  544. * $this->addBehavior('Tree', ['parent' => 'parentId']);
  545. * ```
  546. *
  547. * Behaviors are generally loaded during Table::initialize().
  548. *
  549. * @param string $name The name of the behavior. Can be a short class reference.
  550. * @param array $options The options for the behavior to use.
  551. * @return void
  552. * @throws \RuntimeException If a behavior is being reloaded.
  553. * @see \Cake\ORM\Behavior
  554. */
  555. public function addBehavior($name, array $options = [])
  556. {
  557. $this->_behaviors->load($name, $options);
  558. }
  559. /**
  560. * Removes a behavior from this table's behavior registry.
  561. *
  562. * Example:
  563. *
  564. * Remove a behavior from this table.
  565. *
  566. * ```
  567. * $this->removeBehavior('Tree');
  568. * ```
  569. *
  570. * @param string $name The alias that the behavior was added with.
  571. * @return void
  572. * @see \Cake\ORM\Behavior
  573. */
  574. public function removeBehavior($name)
  575. {
  576. $this->_behaviors->unload($name);
  577. }
  578. /**
  579. * Returns the behavior registry for this table.
  580. *
  581. * @return \Cake\ORM\BehaviorRegistry The BehaviorRegistry instance.
  582. */
  583. public function behaviors()
  584. {
  585. return $this->_behaviors;
  586. }
  587. /**
  588. * Check if a behavior with the given alias has been loaded.
  589. *
  590. * @param string $name The behavior alias to check.
  591. * @return bool Whether or not the behavior exists.
  592. */
  593. public function hasBehavior($name)
  594. {
  595. return $this->_behaviors->has($name);
  596. }
  597. /**
  598. * Returns an association object configured for the specified alias if any
  599. *
  600. * @param string $name the alias used for the association.
  601. * @return \Cake\ORM\Association|null Either the association or null.
  602. */
  603. public function association($name)
  604. {
  605. return $this->_associations->get($name);
  606. }
  607. /**
  608. * Get the associations collection for this table.
  609. *
  610. * @return \Cake\ORM\AssociationCollection The collection of association objects.
  611. */
  612. public function associations()
  613. {
  614. return $this->_associations;
  615. }
  616. /**
  617. * Setup multiple associations.
  618. *
  619. * It takes an array containing set of table names indexed by association type
  620. * as argument:
  621. *
  622. * ```
  623. * $this->Posts->addAssociations([
  624. * 'belongsTo' => [
  625. * 'Users' => ['className' => 'App\Model\Table\UsersTable']
  626. * ],
  627. * 'hasMany' => ['Comments'],
  628. * 'belongsToMany' => ['Tags']
  629. * ]);
  630. * ```
  631. *
  632. * Each association type accepts multiple associations where the keys
  633. * are the aliases, and the values are association config data. If numeric
  634. * keys are used the values will be treated as association aliases.
  635. *
  636. * @param array $params Set of associations to bind (indexed by association type)
  637. * @return void
  638. * @see \Cake\ORM\Table::belongsTo()
  639. * @see \Cake\ORM\Table::hasOne()
  640. * @see \Cake\ORM\Table::hasMany()
  641. * @see \Cake\ORM\Table::belongsToMany()
  642. */
  643. public function addAssociations(array $params)
  644. {
  645. foreach ($params as $assocType => $tables) {
  646. foreach ($tables as $associated => $options) {
  647. if (is_numeric($associated)) {
  648. $associated = $options;
  649. $options = [];
  650. }
  651. $this->{$assocType}($associated, $options);
  652. }
  653. }
  654. }
  655. /**
  656. * Creates a new BelongsTo association between this table and a target
  657. * table. A "belongs to" association is a N-1 relationship where this table
  658. * is the N side, and where there is a single associated record in the target
  659. * table for each one in this table.
  660. *
  661. * Target table can be inferred by its name, which is provided in the
  662. * first argument, or you can either pass the to be instantiated or
  663. * an instance of it directly.
  664. *
  665. * The options array accept the following keys:
  666. *
  667. * - className: The class name of the target table object
  668. * - targetTable: An instance of a table object to be used as the target table
  669. * - foreignKey: The name of the field to use as foreign key, if false none
  670. * will be used
  671. * - conditions: array with a list of conditions to filter the join with
  672. * - joinType: The type of join to be used (e.g. INNER)
  673. *
  674. * This method will return the association object that was built.
  675. *
  676. * @param string $associated the alias for the target table. This is used to
  677. * uniquely identify the association
  678. * @param array $options list of options to configure the association definition
  679. * @return \Cake\ORM\Association\BelongsTo
  680. */
  681. public function belongsTo($associated, array $options = [])
  682. {
  683. $options += ['sourceTable' => $this];
  684. $association = new BelongsTo($associated, $options);
  685. return $this->_associations->add($association->name(), $association);
  686. }
  687. /**
  688. * Creates a new HasOne association between this table and a target
  689. * table. A "has one" association is a 1-1 relationship.
  690. *
  691. * Target table can be inferred by its name, which is provided in the
  692. * first argument, or you can either pass the class name to be instantiated or
  693. * an instance of it directly.
  694. *
  695. * The options array accept the following keys:
  696. *
  697. * - className: The class name of the target table object
  698. * - targetTable: An instance of a table object to be used as the target table
  699. * - foreignKey: The name of the field to use as foreign key, if false none
  700. * will be used
  701. * - dependent: Set to true if you want CakePHP to cascade deletes to the
  702. * associated table when an entity is removed on this table. Set to false
  703. * if you don't want CakePHP to remove associated data, for when you are using
  704. * database constraints.
  705. * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on
  706. * cascaded deletes. If false the ORM will use deleteAll() to remove data.
  707. * When true records will be loaded and then deleted.
  708. * - conditions: array with a list of conditions to filter the join with
  709. * - joinType: The type of join to be used (e.g. LEFT)
  710. *
  711. * This method will return the association object that was built.
  712. *
  713. * @param string $associated the alias for the target table. This is used to
  714. * uniquely identify the association
  715. * @param array $options list of options to configure the association definition
  716. * @return \Cake\ORM\Association\HasOne
  717. */
  718. public function hasOne($associated, array $options = [])
  719. {
  720. $options += ['sourceTable' => $this];
  721. $association = new HasOne($associated, $options);
  722. return $this->_associations->add($association->name(), $association);
  723. }
  724. /**
  725. * Creates a new HasMany association between this table and a target
  726. * table. A "has many" association is a 1-N relationship.
  727. *
  728. * Target table can be inferred by its name, which is provided in the
  729. * first argument, or you can either pass the class name to be instantiated or
  730. * an instance of it directly.
  731. *
  732. * The options array accept the following keys:
  733. *
  734. * - className: The class name of the target table object
  735. * - targetTable: An instance of a table object to be used as the target table
  736. * - foreignKey: The name of the field to use as foreign key, if false none
  737. * will be used
  738. * - dependent: Set to true if you want CakePHP to cascade deletes to the
  739. * associated table when an entity is removed on this table. Set to false
  740. * if you don't want CakePHP to remove associated data, for when you are using
  741. * database constraints.
  742. * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on
  743. * cascaded deletes. If false the ORM will use deleteAll() to remove data.
  744. * When true records will be loaded and then deleted.
  745. * - conditions: array with a list of conditions to filter the join with
  746. * - sort: The order in which results for this association should be returned
  747. * - strategy: The strategy to be used for selecting results Either 'select'
  748. * or 'subquery'. If subquery is selected the query used to return results
  749. * in the source table will be used as conditions for getting rows in the
  750. * target table.
  751. *
  752. * This method will return the association object that was built.
  753. *
  754. * @param string $associated the alias for the target table. This is used to
  755. * uniquely identify the association
  756. * @param array $options list of options to configure the association definition
  757. * @return \Cake\ORM\Association\HasMany
  758. */
  759. public function hasMany($associated, array $options = [])
  760. {
  761. $options += ['sourceTable' => $this];
  762. $association = new HasMany($associated, $options);
  763. return $this->_associations->add($association->name(), $association);
  764. }
  765. /**
  766. * Creates a new BelongsToMany association between this table and a target
  767. * table. A "belongs to many" association is a M-N relationship.
  768. *
  769. * Target table can be inferred by its name, which is provided in the
  770. * first argument, or you can either pass the class name to be instantiated or
  771. * an instance of it directly.
  772. *
  773. * The options array accept the following keys:
  774. *
  775. * - className: The class name of the target table object.
  776. * - targetTable: An instance of a table object to be used as the target table.
  777. * - foreignKey: The name of the field to use as foreign key.
  778. * - targetForeignKey: The name of the field to use as the target foreign key.
  779. * - joinTable: The name of the table representing the link between the two
  780. * - through: If you choose to use an already instantiated link table, set this
  781. * key to a configured Table instance containing associations to both the source
  782. * and target tables in this association.
  783. * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on
  784. * cascaded deletes. If false the ORM will use deleteAll() to remove data.
  785. * When true join/junction table records will be loaded and then deleted.
  786. * - conditions: array with a list of conditions to filter the join with.
  787. * - sort: The order in which results for this association should be returned.
  788. * - strategy: The strategy to be used for selecting results Either 'select'
  789. * or 'subquery'. If subquery is selected the query used to return results
  790. * in the source table will be used as conditions for getting rows in the
  791. * target table.
  792. * - saveStrategy: Either 'append' or 'replace'. Indicates the mode to be used
  793. * for saving associated entities. The former will only create new links
  794. * between both side of the relation and the latter will do a wipe and
  795. * replace to create the links between the passed entities when saving.
  796. *
  797. * This method will return the association object that was built.
  798. *
  799. * @param string $associated the alias for the target table. This is used to
  800. * uniquely identify the association
  801. * @param array $options list of options to configure the association definition
  802. * @return \Cake\ORM\Association\BelongsToMany
  803. */
  804. public function belongsToMany($associated, array $options = [])
  805. {
  806. $options += ['sourceTable' => $this];
  807. $association = new BelongsToMany($associated, $options);
  808. return $this->_associations->add($association->name(), $association);
  809. }
  810. /**
  811. * {@inheritDoc}
  812. *
  813. * ### Model.beforeFind event
  814. *
  815. * Each find() will trigger a `Model.beforeFind` event for all attached
  816. * listeners. Any listener can set a valid result set using $query
  817. *
  818. * By default, `$options` will recognize the following keys:
  819. *
  820. * - fields
  821. * - conditions
  822. * - order
  823. * - limit
  824. * - offset
  825. * - page
  826. * - group
  827. * - having
  828. * - contain
  829. * - join
  830. *
  831. * ### Usage
  832. *
  833. * Using the options array:
  834. *
  835. * ```
  836. * $query = $articles->find('all', [
  837. * 'conditions' => ['published' => 1],
  838. * 'limit' => 10,
  839. * 'contain' => ['Users', 'Comments']
  840. * ]);
  841. * ```
  842. *
  843. * Using the builder interface:
  844. *
  845. * ```
  846. * $query = $articles->find()
  847. * ->where(['published' => 1])
  848. * ->limit(10)
  849. * ->contain(['Users', 'Comments']);
  850. * ```
  851. *
  852. * ### Calling finders
  853. *
  854. * The find() method is the entry point for custom finder methods.
  855. * You can invoke a finder by specifying the type:
  856. *
  857. * ```
  858. * $query = $articles->find('published');
  859. * ```
  860. *
  861. * Would invoke the `findPublished` method.
  862. *
  863. * @return \Cake\ORM\Query The query builder
  864. */
  865. public function find($type = 'all', $options = [])
  866. {
  867. $query = $this->query();
  868. $query->select();
  869. return $this->callFinder($type, $query, $options);
  870. }
  871. /**
  872. * Returns the query as passed.
  873. *
  874. * By default findAll() applies no conditions, you
  875. * can override this method in subclasses to modify how `find('all')` works.
  876. *
  877. * @param \Cake\ORM\Query $query The query to find with
  878. * @param array $options The options to use for the find
  879. * @return \Cake\ORM\Query The query builder
  880. */
  881. public function findAll(Query $query, array $options)
  882. {
  883. return $query;
  884. }
  885. /**
  886. * Sets up a query object so results appear as an indexed array, useful for any
  887. * place where you would want a list such as for populating input select boxes.
  888. *
  889. * When calling this finder, the fields passed are used to determine what should
  890. * be used as the array key, value and optionally what to group the results by.
  891. * By default the primary key for the model is used for the key, and the display
  892. * field as value.
  893. *
  894. * The results of this finder will be in the following form:
  895. *
  896. * ```
  897. * [
  898. * 1 => 'value for id 1',
  899. * 2 => 'value for id 2',
  900. * 4 => 'value for id 4'
  901. * ]
  902. * ```
  903. *
  904. * You can specify which property will be used as the key and which as value
  905. * by using the `$options` array, when not specified, it will use the results
  906. * of calling `primaryKey` and `displayField` respectively in this table:
  907. *
  908. * ```
  909. * $table->find('list', [
  910. * 'keyField' => 'name',
  911. * 'valueField' => 'age'
  912. * ]);
  913. * ```
  914. *
  915. * Results can be put together in bigger groups when they share a property, you
  916. * can customize the property to use for grouping by setting `groupField`:
  917. *
  918. * ```
  919. * $table->find('list', [
  920. * 'groupField' => 'category_id',
  921. * ]);
  922. * ```
  923. *
  924. * When using a `groupField` results will be returned in this format:
  925. *
  926. * ```
  927. * [
  928. * 'group_1' => [
  929. * 1 => 'value for id 1',
  930. * 2 => 'value for id 2',
  931. * ]
  932. * 'group_2' => [
  933. * 4 => 'value for id 4'
  934. * ]
  935. * ]
  936. * ```
  937. *
  938. * @param \Cake\ORM\Query $query The query to find with
  939. * @param array $options The options for the find
  940. * @return \Cake\ORM\Query The query builder
  941. */
  942. public function findList(Query $query, array $options)
  943. {
  944. $options += [
  945. 'keyField' => $this->primaryKey(),
  946. 'valueField' => $this->displayField(),
  947. 'groupField' => null
  948. ];
  949. if (isset($options['idField'])) {
  950. $options['keyField'] = $options['idField'];
  951. unset($options['idField']);
  952. trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_WARNING);
  953. }
  954. if (!$query->clause('select') &&
  955. !is_object($options['keyField']) &&
  956. !is_object($options['valueField']) &&
  957. !is_object($options['groupField'])
  958. ) {
  959. $fields = array_merge(
  960. (array)$options['keyField'],
  961. (array)$options['valueField'],
  962. (array)$options['groupField']
  963. );
  964. $columns = $this->schema()->columns();
  965. if (count($fields) === count(array_intersect($fields, $columns))) {
  966. $query->select($fields);
  967. }
  968. }
  969. $options = $this->_setFieldMatchers(
  970. $options,
  971. ['keyField', 'valueField', 'groupField']
  972. );
  973. return $query->formatResults(function ($results) use ($options) {
  974. return $results->combine(
  975. $options['keyField'],
  976. $options['valueField'],
  977. $options['groupField']
  978. );
  979. });
  980. }
  981. /**
  982. * Results for this finder will be a nested array, and is appropriate if you want
  983. * to use the parent_id field of your model data to build nested results.
  984. *
  985. * Values belonging to a parent row based on their parent_id value will be
  986. * recursively nested inside the parent row values using the `children` property
  987. *
  988. * You can customize what fields are used for nesting results, by default the
  989. * primary key and the `parent_id` fields are used. If you wish to change
  990. * these defaults you need to provide the keys `keyField` or `parentField` in
  991. * `$options`:
  992. *
  993. * ```
  994. * $table->find('threaded', [
  995. * 'keyField' => 'id',
  996. * 'parentField' => 'ancestor_id'
  997. * ]);
  998. * ```
  999. *
  1000. * @param \Cake\ORM\Query $query The query to find with
  1001. * @param array $options The options to find with
  1002. * @return \Cake\ORM\Query The query builder
  1003. */
  1004. public function findThreaded(Query $query, array $options)
  1005. {
  1006. $options += [
  1007. 'keyField' => $this->primaryKey(),
  1008. 'parentField' => 'parent_id',
  1009. ];
  1010. if (isset($options['idField'])) {
  1011. $options['keyField'] = $options['idField'];
  1012. unset($options['idField']);
  1013. trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_WARNING);
  1014. }
  1015. $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']);
  1016. return $query->formatResults(function ($results) use ($options) {
  1017. return $results->nest($options['keyField'], $options['parentField']);
  1018. });
  1019. }
  1020. /**
  1021. * Out of an options array, check if the keys described in `$keys` are arrays
  1022. * and change the values for closures that will concatenate the each of the
  1023. * properties in the value array when passed a row.
  1024. *
  1025. * This is an auxiliary function used for result formatters that can accept
  1026. * composite keys when comparing values.
  1027. *
  1028. * @param array $options the original options passed to a finder
  1029. * @param array $keys the keys to check in $options to build matchers from
  1030. * the associated value
  1031. * @return array
  1032. */
  1033. protected function _setFieldMatchers($options, $keys)
  1034. {
  1035. foreach ($keys as $field) {
  1036. if (!is_array($options[$field])) {
  1037. continue;
  1038. }
  1039. if (count($options[$field]) === 1) {
  1040. $options[$field] = current($options[$field]);
  1041. continue;
  1042. }
  1043. $fields = $options[$field];
  1044. $options[$field] = function ($row) use ($fields) {
  1045. $matches = [];
  1046. foreach ($fields as $field) {
  1047. $matches[] = $row[$field];
  1048. }
  1049. return implode(';', $matches);
  1050. };
  1051. }
  1052. return $options;
  1053. }
  1054. /**
  1055. * {@inheritDoc}
  1056. *
  1057. * ### Usage
  1058. *
  1059. * Get an article and some relationships:
  1060. *
  1061. * ```
  1062. * $article = $articles->get(1, ['contain' => ['Users', 'Comments']]);
  1063. * ```
  1064. *
  1065. * @throws \Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an
  1066. * incorrect number of elements.
  1067. */
  1068. public function get($primaryKey, $options = [])
  1069. {
  1070. $key = (array)$this->primaryKey();
  1071. $alias = $this->alias();
  1072. foreach ($key as $index => $keyname) {
  1073. $key[$index] = $alias . '.' . $keyname;
  1074. }
  1075. $primaryKey = (array)$primaryKey;
  1076. if (count($key) !== count($primaryKey)) {
  1077. $primaryKey = $primaryKey ?: [null];
  1078. $primaryKey = array_map(function ($key) {
  1079. return var_export($key, true);
  1080. }, $primaryKey);
  1081. throw new InvalidPrimaryKeyException(sprintf(
  1082. 'Record not found in table "%s" with primary key [%s]',
  1083. $this->table(),
  1084. implode($primaryKey, ', ')
  1085. ));
  1086. }
  1087. $conditions = array_combine($key, $primaryKey);
  1088. $cacheConfig = isset($options['cache']) ? $options['cache'] : false;
  1089. $cacheKey = isset($options['key']) ? $options['key'] : false;
  1090. unset($options['key'], $options['cache']);
  1091. $query = $this->find('all', $options)->where($conditions);
  1092. if ($cacheConfig) {
  1093. if (!$cacheKey) {
  1094. $cacheKey = sprintf(
  1095. "get:%s.%s%s",
  1096. $this->connection()->configName(),
  1097. $this->table(),
  1098. json_encode($primaryKey)
  1099. );
  1100. }
  1101. $query->cache($cacheKey, $cacheConfig);
  1102. }
  1103. return $query->firstOrFail();
  1104. }
  1105. /**
  1106. * Finds an existing record or creates a new one.
  1107. *
  1108. * Using the attributes defined in $search a find() will be done to locate
  1109. * an existing record. If records matches the conditions, the first record
  1110. * will be returned.
  1111. *
  1112. * If no record can be found, a new entity will be created
  1113. * with the $search properties. If a callback is provided, it will be
  1114. * called allowing you to define additional default values. The new
  1115. * entity will be saved and returned.
  1116. *
  1117. * @param array $search The criteria to find existing records by.
  1118. * @param callable|null $callback A callback that will be invoked for newly
  1119. * created entities. This callback will be called *before* the entity
  1120. * is persisted.
  1121. * @return \Cake\Datasource\EntityInterface An entity.
  1122. */
  1123. public function findOrCreate($search, callable $callback = null)
  1124. {
  1125. $query = $this->find()->where($search);
  1126. $row = $query->first();
  1127. if ($row) {
  1128. return $row;
  1129. }
  1130. $entity = $this->newEntity();
  1131. $entity->set($search, ['guard' => false]);
  1132. if ($callback) {
  1133. $callback($entity);
  1134. }
  1135. return $this->save($entity) ?: $entity;
  1136. }
  1137. /**
  1138. * {@inheritDoc}
  1139. */
  1140. public function query()
  1141. {
  1142. return new Query($this->connection(), $this);
  1143. }
  1144. /**
  1145. * {@inheritDoc}
  1146. */
  1147. public function updateAll($fields, $conditions)
  1148. {
  1149. $query = $this->query();
  1150. $query->update()
  1151. ->set($fields)
  1152. ->where($conditions);
  1153. $statement = $query->execute();
  1154. $statement->closeCursor();
  1155. return $statement->rowCount();
  1156. }
  1157. /**
  1158. * {@inheritDoc}
  1159. */
  1160. public function deleteAll($conditions)
  1161. {
  1162. $query = $this->query()
  1163. ->delete()
  1164. ->where($conditions);
  1165. $statement = $query->execute();
  1166. $statement->closeCursor();
  1167. return $statement->rowCount();
  1168. }
  1169. /**
  1170. * {@inheritDoc}
  1171. */
  1172. public function exists($conditions)
  1173. {
  1174. return (bool)count(
  1175. $this->find('all')
  1176. ->select(['existing' => 1])
  1177. ->where($conditions)
  1178. ->limit(1)
  1179. ->hydrate(false)
  1180. ->toArray()
  1181. );
  1182. }
  1183. /**
  1184. * {@inheritDoc}
  1185. *
  1186. * ### Options
  1187. *
  1188. * The options array accepts the following keys:
  1189. *
  1190. * - atomic: Whether to execute the save and callbacks inside a database
  1191. * transaction (default: true)
  1192. * - checkRules: Whether or not to check the rules on entity before saving, if the checking
  1193. * fails, it will abort the save operation. (default:true)
  1194. * - associated: If true it will save all associated entities as they are found
  1195. * in the passed `$entity` whenever the property defined for the association
  1196. * is marked as dirty. Associated records are saved recursively unless told
  1197. * otherwise. If an array, it will be interpreted as the list of associations
  1198. * to be saved. It is possible to provide different options for saving on associated
  1199. * table objects using this key by making the custom options the array value.
  1200. * If false no associated records will be saved. (default: true)
  1201. * - checkExisting: Whether or not to check if the entity already exists, assuming that the
  1202. * entity is marked as not new, and the primary key has been set.
  1203. *
  1204. * ### Events
  1205. *
  1206. * When saving, this method will trigger four events:
  1207. *
  1208. * - Model.beforeRules: Will be triggered right before any rule checking is done
  1209. * for the passed entity if the `checkRules` key in $options is not set to false.
  1210. * Listeners will receive as arguments the entity, options array and the operation type.
  1211. * If the event is stopped the rules check result will be set to the result of the event itself.
  1212. * - Model.afterRules: Will be triggered right after the `checkRules()` method is
  1213. * called for the entity. Listeners will receive as arguments the entity,
  1214. * options array, the result of checking the rules and the operation type.
  1215. * If the event is stopped the checking result will be set to the result of
  1216. * the event itself.
  1217. * - Model.beforeSave: Will be triggered just before the list of fields to be
  1218. * persisted is calculated. It receives both the entity and the options as
  1219. * arguments. The options array is passed as an ArrayObject, so any changes in
  1220. * it will be reflected in every listener and remembered at the end of the event
  1221. * so it can be used for the rest of the save operation. Returning false in any
  1222. * of the listeners will abort the saving process. If the event is stopped
  1223. * using the event API, the event object's `result` property will be returned.
  1224. * This can be useful when having your own saving strategy implemented inside a
  1225. * listener.
  1226. * - Model.afterSave: Will be triggered after a successful insert or save,
  1227. * listeners will receive the entity and the options array as arguments. The type
  1228. * of operation performed (insert or update) can be determined by checking the
  1229. * entity's method `isNew`, true meaning an insert and false an update.
  1230. * - Model.afterSaveCommit: Will be triggered after the transaction is commited
  1231. * for atomic save, listeners will receive the entity and the options array
  1232. * as arguments.
  1233. *
  1234. * This method will determine whether the passed entity needs to be
  1235. * inserted or updated in the database. It does that by checking the `isNew`
  1236. * method on the entity. If the entity to be saved returns a non-empty value from
  1237. * its `errors()` method, it will not be saved.
  1238. *
  1239. * ### Saving on associated tables
  1240. *
  1241. * This method will by default persist entities belonging to associated tables,
  1242. * whenever a dirty property matching the name of the property name set for an
  1243. * association in this table. It is possible to control what associations will
  1244. * be saved and to pass additional option for saving them.
  1245. *
  1246. * ```
  1247. * // Only save the comments association
  1248. * $articles->save($entity, ['associated' => ['Comments']);
  1249. *
  1250. * // Save the company, the employees and related addresses for each of them.
  1251. * // For employees do not check the entity rules
  1252. * $companies->save($entity, [
  1253. * 'associated' => [
  1254. * 'Employees' => [
  1255. * 'associated' => ['Addresses'],
  1256. * 'checkRules' => false
  1257. * ]
  1258. * ]
  1259. * ]);
  1260. *
  1261. * // Save no associations
  1262. * $articles->save($entity, ['associated' => false]);
  1263. * ```
  1264. *
  1265. */
  1266. public function save(EntityInterface $entity, $options = [])
  1267. {
  1268. $options = new ArrayObject($options + [
  1269. 'atomic' => true,
  1270. 'associated' => true,
  1271. 'checkRules' => true,
  1272. 'checkExisting' => true,
  1273. '_primary' => true
  1274. ]);
  1275. if ($entity->errors()) {
  1276. return false;
  1277. }
  1278. if ($entity->isNew() === false && !$entity->dirty()) {
  1279. return $entity;
  1280. }
  1281. $connection = $this->connection();
  1282. if ($options['atomic']) {
  1283. $success = $connection->transactional(function () use ($entity, $options) {
  1284. return $this->_processSave($entity, $options);
  1285. });
  1286. } else {
  1287. $success = $this->_processSave($entity, $options);
  1288. }
  1289. if ($success) {
  1290. if (!$connection->inTransaction() &&
  1291. ($options['atomic'] || (!$options['atomic'] && $options['_primary']))
  1292. ) {
  1293. $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options'));
  1294. }
  1295. if ($options['atomic'] || $options['_primary']) {
  1296. $entity->isNew(false);
  1297. $entity->source($this->registryAlias());
  1298. }
  1299. }
  1300. return $success;
  1301. }
  1302. /**
  1303. * Performs the actual saving of an entity based on the passed options.
  1304. *
  1305. * @param \Cake\Datasource\EntityInterface $entity the entity to be saved
  1306. * @param \ArrayObject $options the options to use for the save operation
  1307. * @return \Cake\Datasource\EntityInterface|bool
  1308. * @throws \RuntimeException When an entity is missing some of the primary keys.
  1309. */
  1310. protected function _processSave($entity, $options)
  1311. {
  1312. $primaryColumns = (array)$this->primaryKey();
  1313. if ($options['checkExisting'] && $primaryColumns && $entity->isNew() && $entity->has($primaryColumns)) {
  1314. $alias = $this->alias();
  1315. $conditions = [];
  1316. foreach ($entity->extract($primaryColumns) as $k => $v) {
  1317. $conditions["$alias.$k"] = $v;
  1318. }
  1319. $entity->isNew(!$this->exists($conditions));
  1320. }
  1321. $mode = $entity->isNew() ? RulesChecker::CREATE : RulesChecker::UPDATE;
  1322. if ($options['checkRules'] && !$this->checkRules($entity, $mode, $options)) {
  1323. return false;
  1324. }
  1325. $options['associated'] = $this->_associations->normalizeKeys($options['associated']);
  1326. $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options'));
  1327. if ($event->isStopped()) {
  1328. return $event->result;
  1329. }
  1330. $saved = $this->_associations->saveParents(
  1331. $this,
  1332. $entity,
  1333. $options['associated'],
  1334. ['_primary' => false] + $options->getArrayCopy()
  1335. );
  1336. if (!$saved && $options['atomic']) {
  1337. return false;
  1338. }
  1339. $data = $entity->extract($this->schema()->columns(), true);
  1340. $isNew = $entity->isNew();
  1341. if ($isNew) {
  1342. $success = $this->_insert($ent