PageRenderTime 63ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/src/ORM/Table.php

https://github.com/ceeram/cakephp
PHP | 2230 lines | 967 code | 146 blank | 1117 comment | 126 complexity | a4a3cba7829a6ef89b207b344c9f7db3 MD5 | raw 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\Schema\Table as Schema;
  20. use Cake\Database\Type;
  21. use Cake\Datasource\EntityInterface;
  22. use Cake\Datasource\Exception\InvalidPrimaryKeyException;
  23. use Cake\Datasource\RepositoryInterface;
  24. use Cake\Event\EventListenerInterface;
  25. use Cake\Event\EventManager;
  26. use Cake\Event\EventManagerTrait;
  27. use Cake\ORM\AssociationCollection;
  28. use Cake\ORM\Association\BelongsTo;
  29. use Cake\ORM\Association\BelongsToMany;
  30. use Cake\ORM\Association\HasMany;
  31. use Cake\ORM\Association\HasOne;
  32. use Cake\ORM\BehaviorRegistry;
  33. use Cake\ORM\Exception\MissingEntityException;
  34. use Cake\ORM\Marshaller;
  35. use Cake\ORM\RulesChecker;
  36. use Cake\ORM\Rule\IsUnique;
  37. use Cake\Utility\Inflector;
  38. use Cake\Validation\Validator;
  39. use RuntimeException;
  40. /**
  41. * Represents a single database table.
  42. *
  43. * Exposes methods for retrieving data out of it, and manages the associations
  44. * this table has to other tables. Multiple instances of this class can be created
  45. * for the same database table with different aliases, this allows you to address
  46. * your database structure in a richer and more expressive way.
  47. *
  48. * ### Retrieving data
  49. *
  50. * The primary way to retrieve data is using Table::find(). See that method
  51. * for more information.
  52. *
  53. * ### Dynamic finders
  54. *
  55. * In addition to the standard find($type) finder methods, CakePHP provides dynamic
  56. * finder methods. These methods allow you to easily set basic conditions up. For example
  57. * to filter users by username you would call
  58. *
  59. * ```
  60. * $query = $users->findByUsername('mark');
  61. * ```
  62. *
  63. * You can also combine conditions on multiple fields using either `Or` or `And`:
  64. *
  65. * ```
  66. * $query = $users->findByUsernameOrEmail('mark', 'mark@example.org');
  67. * ```
  68. *
  69. * ### Bulk updates/deletes
  70. *
  71. * You can use Table::updateAll() and Table::deleteAll() to do bulk updates/deletes.
  72. * You should be aware that events will *not* be fired for bulk updates/deletes.
  73. *
  74. * ### Callbacks/events
  75. *
  76. * Table objects provide a few callbacks/events you can hook into to augment/replace
  77. * find operations. Each event uses the standard event subsystem in CakePHP
  78. *
  79. * - `beforeFind(Event $event, Query $query, ArrayObject $options, boolean $primary)`
  80. * Fired before each find operation. By stopping the event and supplying a
  81. * return value you can bypass the find operation entirely. Any changes done
  82. * to the $query instance will be retained for the rest of the find. The
  83. * $primary parameter indicates whether or not this is the root query,
  84. * or an associated query.
  85. *
  86. * - `buildValidator(Event $event, Validator $validator, string $name)`
  87. * Allows listeners to modify validation rules for the provided named validator.
  88. *
  89. * - `buildRules(Event $event, RulesChecker $rules)`
  90. * Allows listeners to modify the rules checker by adding more rules.
  91. *
  92. * - `beforeRules(Event $event, Entity $entity, ArrayObject $options, string $operation)`
  93. * Fired before an entity is validated using the rules checker. By stopping this event,
  94. * you can return the final value of the rules checking operation.
  95. *
  96. * - `afterRules(Event $event, Entity $entity, ArrayObject $options, bool $result, string $operation)`
  97. * Fired after the rules have been checked on the entity. By stopping this event,
  98. * you can return the final value of the rules checking operation.
  99. *
  100. * - `beforeSave(Event $event, Entity $entity, ArrayObject $options)`
  101. * Fired before each entity is saved. Stopping this event will abort the save
  102. * operation. When the event is stopped the result of the event will be returned.
  103. *
  104. * - `afterSave(Event $event, Entity $entity, ArrayObject $options)`
  105. * Fired after an entity is saved.
  106. *
  107. * - `beforeDelete(Event $event, Entity $entity, ArrayObject $options)`
  108. * Fired before an entity is deleted. By stopping this event you will abort
  109. * the delete operation.
  110. *
  111. * - `afterDelete(Event $event, Entity $entity, ArrayObject $options)`
  112. * Fired after an entity has been deleted.
  113. *
  114. * @see \Cake\Event\EventManager for reference on the events system.
  115. */
  116. class Table implements RepositoryInterface, EventListenerInterface
  117. {
  118. use EventManagerTrait;
  119. /**
  120. * Name of default validation set.
  121. *
  122. * @var string
  123. */
  124. const DEFAULT_VALIDATOR = 'default';
  125. /**
  126. * Name of the table as it can be found in the database
  127. *
  128. * @var string
  129. */
  130. protected $_table;
  131. /**
  132. * Human name giving to this particular instance. Multiple objects representing
  133. * the same database table can exist by using different aliases.
  134. *
  135. * @var string
  136. */
  137. protected $_alias;
  138. /**
  139. * Connection instance
  140. *
  141. * @var \Cake\Database\Connection
  142. */
  143. protected $_connection;
  144. /**
  145. * The schema object containing a description of this table fields
  146. *
  147. * @var \Cake\Database\Schema\Table
  148. */
  149. protected $_schema;
  150. /**
  151. * The name of the field that represents the primary key in the table
  152. *
  153. * @var string|array
  154. */
  155. protected $_primaryKey;
  156. /**
  157. * The name of the field that represents a human readable representation of a row
  158. *
  159. * @var string
  160. */
  161. protected $_displayField;
  162. /**
  163. * The associations container for this Table.
  164. *
  165. * @var \Cake\ORM\AssociationCollection
  166. */
  167. protected $_associations;
  168. /**
  169. * BehaviorRegistry for this table
  170. *
  171. * @var \Cake\ORM\BehaviorRegistry
  172. */
  173. protected $_behaviors;
  174. /**
  175. * The name of the class that represent a single row for this table
  176. *
  177. * @var string
  178. */
  179. protected $_entityClass;
  180. /**
  181. * Registry key used to create this table object
  182. *
  183. * @var string
  184. */
  185. protected $_registryAlias;
  186. /**
  187. * A list of validation objects indexed by name
  188. *
  189. * @var array
  190. */
  191. protected $_validators = [];
  192. /**
  193. * The domain rules to be applied to entities saved by this table
  194. *
  195. * @var \Cake\ORM\RulesChecker
  196. */
  197. protected $_rulesChecker;
  198. /**
  199. * Initializes a new instance
  200. *
  201. * The $config array understands the following keys:
  202. *
  203. * - table: Name of the database table to represent
  204. * - alias: Alias to be assigned to this table (default to table name)
  205. * - connection: The connection instance to use
  206. * - entityClass: The fully namespaced class name of the entity class that will
  207. * represent rows in this table.
  208. * - schema: A \Cake\Database\Schema\Table object or an array that can be
  209. * passed to it.
  210. * - eventManager: An instance of an event manager to use for internal events
  211. * - behaviors: A BehaviorRegistry. Generally not used outside of tests.
  212. * - associations: An AssociationCollection instance.
  213. * - validator: A Validator instance which is assigned as the "default"
  214. * validation set, or an associative array, where key is the name of the
  215. * validation set and value the Validator instance.
  216. *
  217. * @param array $config List of options for this table
  218. */
  219. public function __construct(array $config = [])
  220. {
  221. if (!empty($config['registryAlias'])) {
  222. $this->registryAlias($config['registryAlias']);
  223. }
  224. if (!empty($config['table'])) {
  225. $this->table($config['table']);
  226. }
  227. if (!empty($config['alias'])) {
  228. $this->alias($config['alias']);
  229. }
  230. if (!empty($config['connection'])) {
  231. $this->connection($config['connection']);
  232. }
  233. if (!empty($config['schema'])) {
  234. $this->schema($config['schema']);
  235. }
  236. if (!empty($config['entityClass'])) {
  237. $this->entityClass($config['entityClass']);
  238. }
  239. $eventManager = $behaviors = $associations = null;
  240. if (!empty($config['eventManager'])) {
  241. $eventManager = $config['eventManager'];
  242. }
  243. if (!empty($config['behaviors'])) {
  244. $behaviors = $config['behaviors'];
  245. }
  246. if (!empty($config['associations'])) {
  247. $associations = $config['associations'];
  248. }
  249. if (!empty($config['validator'])) {
  250. if (!is_array($config['validator'])) {
  251. $this->validator(static::DEFAULT_VALIDATOR, $config['validator']);
  252. } else {
  253. foreach ($config['validator'] as $name => $validator) {
  254. $this->validator($name, $validator);
  255. }
  256. }
  257. }
  258. $this->_eventManager = $eventManager ?: new EventManager();
  259. $this->_behaviors = $behaviors ?: new BehaviorRegistry($this);
  260. $this->_associations = $associations ?: new AssociationCollection();
  261. $this->initialize($config);
  262. $this->_eventManager->on($this);
  263. $this->dispatchEvent('Model.initialize');
  264. }
  265. /**
  266. * Get the default connection name.
  267. *
  268. * This method is used to get the fallback connection name if an
  269. * instance is created through the TableRegistry without a connection.
  270. *
  271. * @return string
  272. * @see \Cake\ORM\TableRegistry::get()
  273. */
  274. public static function defaultConnectionName()
  275. {
  276. return 'default';
  277. }
  278. /**
  279. * Initialize a table instance. Called after the constructor.
  280. *
  281. * You can use this method to define associations, attach behaviors
  282. * define validation and do any other initialization logic you need.
  283. *
  284. * ```
  285. * public function initialize(array $config)
  286. * {
  287. * $this->belongsTo('Users');
  288. * $this->belongsToMany('Tagging.Tags');
  289. * $this->primaryKey('something_else');
  290. * }
  291. * ```
  292. *
  293. * @param array $config Configuration options passed to the constructor
  294. * @return void
  295. */
  296. public function initialize(array $config)
  297. {
  298. }
  299. /**
  300. * Returns the database table name or sets a new one
  301. *
  302. * @param string|null $table the new table name
  303. * @return string
  304. */
  305. public function table($table = null)
  306. {
  307. if ($table !== null) {
  308. $this->_table = $table;
  309. }
  310. if ($this->_table === null) {
  311. $table = namespaceSplit(get_class($this));
  312. $table = substr(end($table), 0, -5);
  313. if (empty($table)) {
  314. $table = $this->alias();
  315. }
  316. $this->_table = Inflector::underscore($table);
  317. }
  318. return $this->_table;
  319. }
  320. /**
  321. * Returns the table alias or sets a new one
  322. *
  323. * @param string|null $alias the new table alias
  324. * @return string
  325. */
  326. public function alias($alias = null)
  327. {
  328. if ($alias !== null) {
  329. $this->_alias = $alias;
  330. }
  331. if ($this->_alias === null) {
  332. $alias = namespaceSplit(get_class($this));
  333. $alias = substr(end($alias), 0, -5) ?: $this->_table;
  334. $this->_alias = $alias;
  335. }
  336. return $this->_alias;
  337. }
  338. /**
  339. * Returns the table registry key used to create this table instance
  340. *
  341. * @param string|null $registryAlias the key used to access this object
  342. * @return string
  343. */
  344. public function registryAlias($registryAlias = null)
  345. {
  346. if ($registryAlias !== null) {
  347. $this->_registryAlias = $registryAlias;
  348. }
  349. if ($this->_registryAlias === null) {
  350. $this->_registryAlias = $this->alias();
  351. }
  352. return $this->_registryAlias;
  353. }
  354. /**
  355. * Returns the connection instance or sets a new one
  356. *
  357. * @param \Cake\Database\Connection|null $conn The new connection instance
  358. * @return \Cake\Database\Connection
  359. */
  360. public function connection($conn = null)
  361. {
  362. if ($conn === null) {
  363. return $this->_connection;
  364. }
  365. return $this->_connection = $conn;
  366. }
  367. /**
  368. * Returns the schema table object describing this table's properties.
  369. *
  370. * If an \Cake\Database\Schema\Table is passed, it will be used for this table
  371. * instead of the default one.
  372. *
  373. * If an array is passed, a new \Cake\Database\Schema\Table will be constructed
  374. * out of it and used as the schema for this table.
  375. *
  376. * @param array|\Cake\Database\Schema\Table|null $schema New schema to be used for this table
  377. * @return \Cake\Database\Schema\Table
  378. */
  379. public function schema($schema = null)
  380. {
  381. if ($schema === null) {
  382. if ($this->_schema === null) {
  383. $this->_schema = $this->_initializeSchema(
  384. $this->connection()
  385. ->schemaCollection()
  386. ->describe($this->table())
  387. );
  388. }
  389. return $this->_schema;
  390. }
  391. if (is_array($schema)) {
  392. $constraints = [];
  393. if (isset($schema['_constraints'])) {
  394. $constraints = $schema['_constraints'];
  395. unset($schema['_constraints']);
  396. }
  397. $schema = new Schema($this->table(), $schema);
  398. foreach ($constraints as $name => $value) {
  399. $schema->addConstraint($name, $value);
  400. }
  401. }
  402. return $this->_schema = $schema;
  403. }
  404. /**
  405. * Override this function in order to alter the schema used by this table.
  406. * This function is only called after fetching the schema out of the database.
  407. * If you wish to provide your own schema to this table without touching the
  408. * database, you can override schema() or inject the definitions though that
  409. * method.
  410. *
  411. * ### Example:
  412. *
  413. * ```
  414. * protected function _initializeSchema(\Cake\Database\Schema\Table $table) {
  415. * $table->columnType('preferences', 'json');
  416. * return $table;
  417. * }
  418. * ```
  419. *
  420. * @param \Cake\Database\Schema\Table $table The table definition fetched from database.
  421. * @return \Cake\Database\Schema\Table the altered schema
  422. * @api
  423. */
  424. protected function _initializeSchema(Schema $table)
  425. {
  426. return $table;
  427. }
  428. /**
  429. * Test to see if a Table has a specific field/column.
  430. *
  431. * Delegates to the schema object and checks for column presence
  432. * using the Schema\Table instance.
  433. *
  434. * @param string $field The field to check for.
  435. * @return bool True if the field exists, false if it does not.
  436. */
  437. public function hasField($field)
  438. {
  439. $schema = $this->schema();
  440. return $schema->column($field) !== null;
  441. }
  442. /**
  443. * Returns the primary key field name or sets a new one
  444. *
  445. * @param string|array|null $key sets a new name to be used as primary key
  446. * @return string|array
  447. */
  448. public function primaryKey($key = null)
  449. {
  450. if ($key !== null) {
  451. $this->_primaryKey = $key;
  452. }
  453. if ($this->_primaryKey === null) {
  454. $key = (array)$this->schema()->primaryKey();
  455. if (count($key) === 1) {
  456. $key = $key[0];
  457. }
  458. $this->_primaryKey = $key;
  459. }
  460. return $this->_primaryKey;
  461. }
  462. /**
  463. * Returns the display field or sets a new one
  464. *
  465. * @param string|null $key sets a new name to be used as display field
  466. * @return string
  467. */
  468. public function displayField($key = null)
  469. {
  470. if ($key !== null) {
  471. $this->_displayField = $key;
  472. }
  473. if ($this->_displayField === null) {
  474. $schema = $this->schema();
  475. $primary = (array)$this->primaryKey();
  476. $this->_displayField = array_shift($primary);
  477. if ($schema->column('title')) {
  478. $this->_displayField = 'title';
  479. }
  480. if ($schema->column('name')) {
  481. $this->_displayField = 'name';
  482. }
  483. }
  484. return $this->_displayField;
  485. }
  486. /**
  487. * Returns the class used to hydrate rows for this table or sets
  488. * a new one
  489. *
  490. * @param string|null $name the name of the class to use
  491. * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found
  492. * @return string
  493. */
  494. public function entityClass($name = null)
  495. {
  496. if ($name === null && !$this->_entityClass) {
  497. $default = '\Cake\ORM\Entity';
  498. $self = get_called_class();
  499. $parts = explode('\\', $self);
  500. if ($self === __CLASS__ || count($parts) < 3) {
  501. return $this->_entityClass = $default;
  502. }
  503. $alias = Inflector::singularize(substr(array_pop($parts), 0, -5));
  504. $name = implode('\\', array_slice($parts, 0, -1)) . '\Entity\\' . $alias;
  505. if (!class_exists($name)) {
  506. return $this->_entityClass = $default;
  507. }
  508. }
  509. if ($name !== null) {
  510. $class = App::className($name, 'Model/Entity');
  511. $this->_entityClass = $class;
  512. }
  513. if (!$this->_entityClass) {
  514. throw new MissingEntityException([$name]);
  515. }
  516. return $this->_entityClass;
  517. }
  518. /**
  519. * Add a behavior.
  520. *
  521. * Adds a behavior to this table's behavior collection. Behaviors
  522. * provide an easy way to create horizontally re-usable features
  523. * that can provide trait like functionality, and allow for events
  524. * to be listened to.
  525. *
  526. * Example:
  527. *
  528. * Load a behavior, with some settings.
  529. *
  530. * ```
  531. * $this->addBehavior('Tree', ['parent' => 'parentId']);
  532. * ```
  533. *
  534. * Behaviors are generally loaded during Table::initialize().
  535. *
  536. * @param string $name The name of the behavior. Can be a short class reference.
  537. * @param array $options The options for the behavior to use.
  538. * @return void
  539. * @throws \RuntimeException If a behavior is being reloaded.
  540. * @see \Cake\ORM\Behavior
  541. */
  542. public function addBehavior($name, array $options = [])
  543. {
  544. $this->_behaviors->load($name, $options);
  545. }
  546. /**
  547. * Removes a behavior from this table's behavior registry.
  548. *
  549. * Example:
  550. *
  551. * Remove a behavior from this table.
  552. *
  553. * ```
  554. * $this->removeBehavior('Tree');
  555. * ```
  556. *
  557. * @param string $name The alias that the behavior was added with.
  558. * @return void
  559. * @see \Cake\ORM\Behavior
  560. */
  561. public function removeBehavior($name)
  562. {
  563. $this->_behaviors->unload($name);
  564. }
  565. /**
  566. * Returns the behavior registry for this table.
  567. *
  568. * @return \Cake\ORM\BehaviorRegistry
  569. */
  570. public function behaviors()
  571. {
  572. return $this->_behaviors;
  573. }
  574. /**
  575. * Check if a behavior with the given alias has been loaded.
  576. *
  577. * @param string $name The behavior alias to check.
  578. * @return bool
  579. */
  580. public function hasBehavior($name)
  581. {
  582. return $this->_behaviors->has($name);
  583. }
  584. /**
  585. * Returns an association object configured for the specified alias if any
  586. *
  587. * @param string $name the alias used for the association
  588. * @return \Cake\ORM\Association
  589. */
  590. public function association($name)
  591. {
  592. return $this->_associations->get($name);
  593. }
  594. /**
  595. * Get the associations collection for this table.
  596. *
  597. * @return \Cake\ORM\AssociationCollection
  598. */
  599. public function associations()
  600. {
  601. return $this->_associations;
  602. }
  603. /**
  604. * Setup multiple associations.
  605. *
  606. * It takes an array containing set of table names indexed by association type
  607. * as argument:
  608. *
  609. * ```
  610. * $this->Posts->addAssociations([
  611. * 'belongsTo' => [
  612. * 'Users' => ['className' => 'App\Model\Table\UsersTable']
  613. * ],
  614. * 'hasMany' => ['Comments'],
  615. * 'belongsToMany' => ['Tags']
  616. * ]);
  617. * ```
  618. *
  619. * Each association type accepts multiple associations where the keys
  620. * are the aliases, and the values are association config data. If numeric
  621. * keys are used the values will be treated as association aliases.
  622. *
  623. * @param array $params Set of associations to bind (indexed by association type)
  624. * @return void
  625. * @see \Cake\ORM\Table::belongsTo()
  626. * @see \Cake\ORM\Table::hasOne()
  627. * @see \Cake\ORM\Table::hasMany()
  628. * @see \Cake\ORM\Table::belongsToMany()
  629. */
  630. public function addAssociations(array $params)
  631. {
  632. foreach ($params as $assocType => $tables) {
  633. foreach ($tables as $associated => $options) {
  634. if (is_numeric($associated)) {
  635. $associated = $options;
  636. $options = [];
  637. }
  638. $this->{$assocType}($associated, $options);
  639. }
  640. }
  641. }
  642. /**
  643. * Creates a new BelongsTo association between this table and a target
  644. * table. A "belongs to" association is a N-1 relationship where this table
  645. * is the N side, and where there is a single associated record in the target
  646. * table for each one in this table.
  647. *
  648. * Target table can be inferred by its name, which is provided in the
  649. * first argument, or you can either pass the to be instantiated or
  650. * an instance of it directly.
  651. *
  652. * The options array accept the following keys:
  653. *
  654. * - className: The class name of the target table object
  655. * - targetTable: An instance of a table object to be used as the target table
  656. * - foreignKey: The name of the field to use as foreign key, if false none
  657. * will be used
  658. * - conditions: array with a list of conditions to filter the join with
  659. * - joinType: The type of join to be used (e.g. INNER)
  660. *
  661. * This method will return the association object that was built.
  662. *
  663. * @param string $associated the alias for the target table. This is used to
  664. * uniquely identify the association
  665. * @param array $options list of options to configure the association definition
  666. * @return \Cake\ORM\Association\BelongsTo
  667. */
  668. public function belongsTo($associated, array $options = [])
  669. {
  670. $options += ['sourceTable' => $this];
  671. $association = new BelongsTo($associated, $options);
  672. return $this->_associations->add($association->name(), $association);
  673. }
  674. /**
  675. * Creates a new HasOne association between this table and a target
  676. * table. A "has one" association is a 1-1 relationship.
  677. *
  678. * Target table can be inferred by its name, which is provided in the
  679. * first argument, or you can either pass the class name to be instantiated or
  680. * an instance of it directly.
  681. *
  682. * The options array accept the following keys:
  683. *
  684. * - className: The class name of the target table object
  685. * - targetTable: An instance of a table object to be used as the target table
  686. * - foreignKey: The name of the field to use as foreign key, if false none
  687. * will be used
  688. * - dependent: Set to true if you want CakePHP to cascade deletes to the
  689. * associated table when an entity is removed on this table. Set to false
  690. * if you don't want CakePHP to remove associated data, for when you are using
  691. * database constraints.
  692. * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on
  693. * cascaded deletes. If false the ORM will use deleteAll() to remove data.
  694. * When true records will be loaded and then deleted.
  695. * - conditions: array with a list of conditions to filter the join with
  696. * - joinType: The type of join to be used (e.g. LEFT)
  697. *
  698. * This method will return the association object that was built.
  699. *
  700. * @param string $associated the alias for the target table. This is used to
  701. * uniquely identify the association
  702. * @param array $options list of options to configure the association definition
  703. * @return \Cake\ORM\Association\HasOne
  704. */
  705. public function hasOne($associated, array $options = [])
  706. {
  707. $options += ['sourceTable' => $this];
  708. $association = new HasOne($associated, $options);
  709. return $this->_associations->add($association->name(), $association);
  710. }
  711. /**
  712. * Creates a new HasMany association between this table and a target
  713. * table. A "has many" association is a 1-N relationship.
  714. *
  715. * Target table can be inferred by its name, which is provided in the
  716. * first argument, or you can either pass the class name to be instantiated or
  717. * an instance of it directly.
  718. *
  719. * The options array accept the following keys:
  720. *
  721. * - className: The class name of the target table object
  722. * - targetTable: An instance of a table object to be used as the target table
  723. * - foreignKey: The name of the field to use as foreign key, if false none
  724. * will be used
  725. * - dependent: Set to true if you want CakePHP to cascade deletes to the
  726. * associated table when an entity is removed on this table. Set to false
  727. * if you don't want CakePHP to remove associated data, for when you are using
  728. * database constraints.
  729. * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on
  730. * cascaded deletes. If false the ORM will use deleteAll() to remove data.
  731. * When true records will be loaded and then deleted.
  732. * - conditions: array with a list of conditions to filter the join with
  733. * - sort: The order in which results for this association should be returned
  734. * - strategy: The strategy to be used for selecting results Either 'select'
  735. * or 'subquery'. If subquery is selected the query used to return results
  736. * in the source table will be used as conditions for getting rows in the
  737. * target table.
  738. *
  739. * This method will return the association object that was built.
  740. *
  741. * @param string $associated the alias for the target table. This is used to
  742. * uniquely identify the association
  743. * @param array $options list of options to configure the association definition
  744. * @return \Cake\ORM\Association\HasMany
  745. */
  746. public function hasMany($associated, array $options = [])
  747. {
  748. $options += ['sourceTable' => $this];
  749. $association = new HasMany($associated, $options);
  750. return $this->_associations->add($association->name(), $association);
  751. }
  752. /**
  753. * Creates a new BelongsToMany association between this table and a target
  754. * table. A "belongs to many" association is a M-N relationship.
  755. *
  756. * Target table can be inferred by its name, which is provided in the
  757. * first argument, or you can either pass the class name to be instantiated or
  758. * an instance of it directly.
  759. *
  760. * The options array accept the following keys:
  761. *
  762. * - className: The class name of the target table object.
  763. * - targetTable: An instance of a table object to be used as the target table.
  764. * - foreignKey: The name of the field to use as foreign key.
  765. * - targetForeignKey: The name of the field to use as the target foreign key.
  766. * - joinTable: The name of the table representing the link between the two
  767. * - through: If you choose to use an already instantiated link table, set this
  768. * key to a configured Table instance containing associations to both the source
  769. * and target tables in this association.
  770. * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on
  771. * cascaded deletes. If false the ORM will use deleteAll() to remove data.
  772. * When true join/junction table records will be loaded and then deleted.
  773. * - conditions: array with a list of conditions to filter the join with.
  774. * - sort: The order in which results for this association should be returned.
  775. * - strategy: The strategy to be used for selecting results Either 'select'
  776. * or 'subquery'. If subquery is selected the query used to return results
  777. * in the source table will be used as conditions for getting rows in the
  778. * target table.
  779. * - saveStrategy: Either 'append' or 'replace'. Indicates the mode to be used
  780. * for saving associated entities. The former will only create new links
  781. * between both side of the relation and the latter will do a wipe and
  782. * replace to create the links between the passed entities when saving.
  783. *
  784. * This method will return the association object that was built.
  785. *
  786. * @param string $associated the alias for the target table. This is used to
  787. * uniquely identify the association
  788. * @param array $options list of options to configure the association definition
  789. * @return \Cake\ORM\Association\BelongsToMany
  790. */
  791. public function belongsToMany($associated, array $options = [])
  792. {
  793. $options += ['sourceTable' => $this];
  794. $association = new BelongsToMany($associated, $options);
  795. return $this->_associations->add($association->name(), $association);
  796. }
  797. /**
  798. * {@inheritDoc}
  799. *
  800. * By default, `$options` will recognize the following keys:
  801. *
  802. * - fields
  803. * - conditions
  804. * - order
  805. * - limit
  806. * - offset
  807. * - page
  808. * - order
  809. * - group
  810. * - having
  811. * - contain
  812. * - join
  813. * @return \Cake\ORM\Query
  814. */
  815. public function find($type = 'all', $options = [])
  816. {
  817. $query = $this->query();
  818. $query->select();
  819. return $this->callFinder($type, $query, $options);
  820. }
  821. /**
  822. * Returns the query as passed
  823. *
  824. * @param \Cake\ORM\Query $query The query to find with
  825. * @param array $options The options to use for the find
  826. * @return \Cake\ORM\Query
  827. */
  828. public function findAll(Query $query, array $options)
  829. {
  830. return $query;
  831. }
  832. /**
  833. * Sets up a query object so results appear as an indexed array, useful for any
  834. * place where you would want a list such as for populating input select boxes.
  835. *
  836. * When calling this finder, the fields passed are used to determine what should
  837. * be used as the array key, value and optionally what to group the results by.
  838. * By default the primary key for the model is used for the key, and the display
  839. * field as value.
  840. *
  841. * The results of this finder will be in the following form:
  842. *
  843. * ```
  844. * [
  845. * 1 => 'value for id 1',
  846. * 2 => 'value for id 2',
  847. * 4 => 'value for id 4'
  848. * ]
  849. * ```
  850. *
  851. * You can specify which property will be used as the key and which as value
  852. * by using the `$options` array, when not specified, it will use the results
  853. * of calling `primaryKey` and `displayField` respectively in this table:
  854. *
  855. * ```
  856. * $table->find('list', [
  857. * 'keyField' => 'name',
  858. * 'valueField' => 'age'
  859. * ]);
  860. * ```
  861. *
  862. * Results can be put together in bigger groups when they share a property, you
  863. * can customize the property to use for grouping by setting `groupField`:
  864. *
  865. * ```
  866. * $table->find('list', [
  867. * 'groupField' => 'category_id',
  868. * ]);
  869. * ```
  870. *
  871. * When using a `groupField` results will be returned in this format:
  872. *
  873. * ```
  874. * [
  875. * 'group_1' => [
  876. * 1 => 'value for id 1',
  877. * 2 => 'value for id 2',
  878. * ]
  879. * 'group_2' => [
  880. * 4 => 'value for id 4'
  881. * ]
  882. * ]
  883. * ```
  884. *
  885. * @param \Cake\ORM\Query $query The query to find with
  886. * @param array $options The options for the find
  887. * @return \Cake\ORM\Query
  888. */
  889. public function findList(Query $query, array $options)
  890. {
  891. $options += [
  892. 'keyField' => $this->primaryKey(),
  893. 'valueField' => $this->displayField(),
  894. 'groupField' => null
  895. ];
  896. if (isset($options['idField'])) {
  897. $options['keyField'] = $options['idField'];
  898. unset($options['idField']);
  899. trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_WARNING);
  900. }
  901. $options = $this->_setFieldMatchers(
  902. $options,
  903. ['keyField', 'valueField', 'groupField']
  904. );
  905. return $query->formatResults(function ($results) use ($options) {
  906. return $results->combine(
  907. $options['keyField'],
  908. $options['valueField'],
  909. $options['groupField']
  910. );
  911. });
  912. }
  913. /**
  914. * Results for this finder will be a nested array, and is appropriate if you want
  915. * to use the parent_id field of your model data to build nested results.
  916. *
  917. * Values belonging to a parent row based on their parent_id value will be
  918. * recursively nested inside the parent row values using the `children` property
  919. *
  920. * You can customize what fields are used for nesting results, by default the
  921. * primary key and the `parent_id` fields are used. If you wish to change
  922. * these defaults you need to provide the keys `keyField` or `parentField` in
  923. * `$options`:
  924. *
  925. * ```
  926. * $table->find('threaded', [
  927. * 'keyField' => 'id',
  928. * 'parentField' => 'ancestor_id'
  929. * ]);
  930. * ```
  931. *
  932. * @param \Cake\ORM\Query $query The query to find with
  933. * @param array $options The options to find with
  934. * @return \Cake\ORM\Query
  935. */
  936. public function findThreaded(Query $query, array $options)
  937. {
  938. $options += [
  939. 'keyField' => $this->primaryKey(),
  940. 'parentField' => 'parent_id',
  941. ];
  942. if (isset($options['idField'])) {
  943. $options['keyField'] = $options['idField'];
  944. unset($options['idField']);
  945. trigger_error('Option "idField" is deprecated, use "keyField" instead.', E_USER_WARNING);
  946. }
  947. $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']);
  948. return $query->formatResults(function ($results) use ($options) {
  949. return $results->nest($options['keyField'], $options['parentField']);
  950. });
  951. }
  952. /**
  953. * Out of an options array, check if the keys described in `$keys` are arrays
  954. * and change the values for closures that will concatenate the each of the
  955. * properties in the value array when passed a row.
  956. *
  957. * This is an auxiliary function used for result formatters that can accept
  958. * composite keys when comparing values.
  959. *
  960. * @param array $options the original options passed to a finder
  961. * @param array $keys the keys to check in $options to build matchers from
  962. * the associated value
  963. * @return array
  964. */
  965. protected function _setFieldMatchers($options, $keys)
  966. {
  967. foreach ($keys as $field) {
  968. if (!is_array($options[$field])) {
  969. continue;
  970. }
  971. if (count($options[$field]) === 1) {
  972. $options[$field] = current($options[$field]);
  973. continue;
  974. }
  975. $fields = $options[$field];
  976. $options[$field] = function ($row) use ($fields) {
  977. $matches = [];
  978. foreach ($fields as $field) {
  979. $matches[] = $row[$field];
  980. }
  981. return implode(';', $matches);
  982. };
  983. }
  984. return $options;
  985. }
  986. /**
  987. * {@inheritDoc}
  988. *
  989. * @throws Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an
  990. * incorrect number of elements.
  991. */
  992. public function get($primaryKey, $options = [])
  993. {
  994. $key = (array)$this->primaryKey();
  995. $alias = $this->alias();
  996. foreach ($key as $index => $keyname) {
  997. $key[$index] = $alias . '.' . $keyname;
  998. }
  999. $primaryKey = (array)$primaryKey;
  1000. if (count($key) !== count($primaryKey)) {
  1001. $primaryKey = $primaryKey ?: [null];
  1002. $primaryKey = array_map(function ($key) {
  1003. return var_export($key, true);
  1004. }, $primaryKey);
  1005. throw new InvalidPrimaryKeyException(sprintf(
  1006. 'Record not found in table "%s" with primary key [%s]',
  1007. $this->table(),
  1008. implode($primaryKey, ', ')
  1009. ));
  1010. }
  1011. $conditions = array_combine($key, $primaryKey);
  1012. $cacheConfig = isset($options['cache']) ? $options['cache'] : false;
  1013. $cacheKey = isset($options['key']) ? $options['key'] : false;
  1014. unset($options['key'], $options['cache']);
  1015. $query = $this->find('all', $options)->where($conditions);
  1016. if ($cacheConfig) {
  1017. if (!$cacheKey) {
  1018. $cacheKey = sprintf(
  1019. "get:%s.%s%s",
  1020. $this->connection()->configName(),
  1021. $this->table(),
  1022. json_encode($primaryKey)
  1023. );
  1024. }
  1025. $query->cache($cacheKey, $cacheConfig);
  1026. }
  1027. return $query->firstOrFail();
  1028. }
  1029. /**
  1030. * Finds an existing record or creates a new one.
  1031. *
  1032. * Using the attributes defined in $search a find() will be done to locate
  1033. * an existing record. If that record exists it will be returned. If it does
  1034. * not exist, a new entity will be created with the $search properties, and
  1035. * the $defaults. When a new entity is created, it will be saved.
  1036. *
  1037. * @param array $search The criteria to find existing records by.
  1038. * @param callable|null $callback A callback that will be invoked for newly
  1039. * created entities. This callback will be called *before* the entity
  1040. * is persisted.
  1041. * @return \Cake\Datasource\EntityInterface An entity.
  1042. */
  1043. public function findOrCreate($search, callable $callback = null)
  1044. {
  1045. $query = $this->find()->where($search);
  1046. $row = $query->first();
  1047. if ($row) {
  1048. return $row;
  1049. }
  1050. $entity = $this->newEntity();
  1051. $entity->set($search, ['guard' => false]);
  1052. if ($callback) {
  1053. $callback($entity);
  1054. }
  1055. return $this->save($entity) ?: $entity;
  1056. }
  1057. /**
  1058. * {@inheritDoc}
  1059. */
  1060. public function query()
  1061. {
  1062. return new Query($this->connection(), $this);
  1063. }
  1064. /**
  1065. * {@inheritDoc}
  1066. */
  1067. public function updateAll($fields, $conditions)
  1068. {
  1069. $query = $this->query();
  1070. $query->update()
  1071. ->set($fields)
  1072. ->where($conditions);
  1073. $statement = $query->execute();
  1074. $statement->closeCursor();
  1075. return $statement->rowCount();
  1076. }
  1077. /**
  1078. * Returns the validation rules tagged with $name. It is possible to have
  1079. * multiple different named validation sets, this is useful when you need
  1080. * to use varying rules when saving from different routines in your system.
  1081. *
  1082. * There are two different ways of creating and naming validation sets: by
  1083. * creating a new method inside your own Table subclass, or by building
  1084. * the validator object yourself and storing it using this method.
  1085. *
  1086. * For example, if you wish to create a validation set called 'forSubscription',
  1087. * you will need to create a method in your Table subclass as follows:
  1088. *
  1089. * ```
  1090. * public function validationForSubscription($validator)
  1091. * {
  1092. * return $validator
  1093. * ->add('email', 'valid-email', ['rule' => 'email'])
  1094. * ->add('password', 'valid', ['rule' => 'notEmpty'])
  1095. * ->requirePresence('username');
  1096. * }
  1097. * ```
  1098. *
  1099. * Otherwise, you can build the object by yourself and store it in the Table object:
  1100. *
  1101. * ```
  1102. * $validator = new \Cake\Validation\Validator($table);
  1103. * $validator
  1104. * ->add('email', 'valid-email', ['rule' => 'email'])
  1105. * ->add('password', 'valid', ['rule' => 'notEmpty'])
  1106. * ->allowEmpty('bio');
  1107. * $table->validator('forSubscription', $validator);
  1108. * ```
  1109. *
  1110. * You can implement the method in `validationDefault` in your Table subclass
  1111. * should you wish to have a validation set that applies in cases where no other
  1112. * set is specified.
  1113. *
  1114. * @param string $name the name of the validation set to return
  1115. * @param \Cake\Validation\Validator|null $validator The validator instance to store,
  1116. * use null to get a validator.
  1117. * @return \Cake\Validation\Validator
  1118. */
  1119. public function validator($name = self::DEFAULT_VALIDATOR, Validator $validator = null)
  1120. {
  1121. if ($validator === null && isset($this->_validators[$name])) {
  1122. return $this->_validators[$name];
  1123. }
  1124. if ($validator === null) {
  1125. $validator = new Validator();
  1126. $validator = $this->{'validation' . ucfirst($name)}($validator);
  1127. $this->dispatchEvent('Model.buildValidator', compact('validator', 'name'));
  1128. }
  1129. $validator->provider('table', $this);
  1130. return $this->_validators[$name] = $validator;
  1131. }
  1132. /**
  1133. * Returns the default validator object. Subclasses can override this function
  1134. * to add a default validation set to the validator object.
  1135. *
  1136. * @param \Cake\Validation\Validator $validator The validator that can be modified to
  1137. * add some rules to it.
  1138. * @return \Cake\Validation\Validator
  1139. */
  1140. public function validationDefault(Validator $validator)
  1141. {
  1142. return $validator;
  1143. }
  1144. /**
  1145. * {@inheritDoc}
  1146. */
  1147. public function deleteAll($conditions)
  1148. {
  1149. $query = $this->query()
  1150. ->delete()
  1151. ->where($conditions);
  1152. $statement = $query->execute();
  1153. $statement->closeCursor();
  1154. return $statement->rowCount();
  1155. }
  1156. /**
  1157. * {@inheritDoc}
  1158. */
  1159. public function exists($conditions)
  1160. {
  1161. return (bool)count(
  1162. $this->find('all')
  1163. ->select(['existing' => 1])
  1164. ->where($conditions)
  1165. ->limit(1)
  1166. ->hydrate(false)
  1167. ->toArray()
  1168. );
  1169. }
  1170. /**
  1171. * {@inheritDoc}
  1172. *
  1173. * ### Options
  1174. *
  1175. * The options array can receive the following keys:
  1176. *
  1177. * - atomic: Whether to execute the save and callbacks inside a database
  1178. * transaction (default: true)
  1179. * - checkRules: Whether or not to check the rules on entity before saving, if the checking
  1180. * fails, it will abort the save operation. (default:true)
  1181. * - associated: If true it will save all associated entities as they are found
  1182. * in the passed `$entity` whenever the property defined for the association
  1183. * is marked as dirty. Associated records are saved recursively unless told
  1184. * otherwise. If an array, it will be interpreted as the list of associations
  1185. * to be saved. It is possible to provide different options for saving on associated
  1186. * table objects using this key by making the custom options the array value.
  1187. * If false no associated records will be saved. (default: true)
  1188. * - checkExisting: Whether or not to check if the entity already exists, assuming that the
  1189. * entity is marked as not new, and the primary key has been set.
  1190. *
  1191. * ### Events
  1192. *
  1193. * When saving, this method will trigger four events:
  1194. *
  1195. * - Model.beforeRules: Will be triggered right before any rule checking is done
  1196. * for the passed entity if the `checkRules` key in $options is not set to false.
  1197. * Listeners will receive as arguments the entity, options array and the operation type.
  1198. * If the event is stopped the checking result will be set to the result of the event itself.
  1199. * - Model.afterRules: Will be triggered right after the `checkRules()` method is
  1200. * called for the entity. Listeners will receive as arguments the entity,
  1201. * options array, the result of checking the rules and the operation type.
  1202. * If the event is stopped the checking result will be set to the result of
  1203. * the event itself.
  1204. * - Model.beforeSave: Will be triggered just before the list of fields to be
  1205. * persisted is calculated. It receives both the entity and the options as
  1206. * arguments. The options array is passed as an ArrayObject, so any changes in
  1207. * it will be reflected in every listener and remembered at the end of the event
  1208. * so it can be used for the rest of the save operation. Returning false in any
  1209. * of the listeners will abort the saving process. If the event is stopped
  1210. * using the event API, the event object's `result` property will be returned.
  1211. * This can be useful when having your own saving strategy implemented inside a
  1212. * listener.
  1213. * - Model.afterSave: Will be triggered after a successful insert or save,
  1214. * listeners will receive the entity and the options array as arguments. The type
  1215. * of operation performed (insert or update) can be determined by checking the
  1216. * entity's method `isNew`, true meaning an insert and false an update.
  1217. * - Model.afterSaveCommit: Will be triggered after the transaction is commited
  1218. * for atomic save, listeners will receive the entity and the options array
  1219. * as arguments.
  1220. *
  1221. * This method will determine whether the passed entity needs to be
  1222. * inserted or updated in the database. It does that by checking the `isNew`
  1223. * method on the entity. If the entity to be saved returns a non-empty value from
  1224. * its `errors()` method, it will not be saved.
  1225. *
  1226. * ### Saving on associated tables
  1227. *
  1228. * This method will by default persist entities belonging to associated tables,
  1229. * whenever a dirty property matching the name of the property name set for an
  1230. * association in this table. It is possible to control what associations will
  1231. * be saved and to pass additional option for saving them.
  1232. *
  1233. * ```
  1234. * // Only save the comments association
  1235. * $articles->save($entity, ['associated' => ['Comments']);
  1236. *
  1237. * // Save the company, the employees and related addresses for each of them.
  1238. * // For employees do not check the entity rules
  1239. * $companies->save($entity, [
  1240. * 'associated' => [
  1241. * 'Employees' => [
  1242. * 'associated' => ['Addresses'],
  1243. * 'checkRules' => false
  1244. * ]
  1245. * ]
  1246. * ]);
  1247. *
  1248. * // Save no associations
  1249. * $articles->save($entity, ['associated' => false]);
  1250. * ```
  1251. *
  1252. */
  1253. public function save(EntityInterface $entity, $options = [])
  1254. {
  1255. $options = new ArrayObject($options + [
  1256. 'atomic' => true,
  1257. 'associated' => true,
  1258. 'checkRules' => true,
  1259. 'checkExisting' => true,
  1260. '_primary' => true
  1261. ]);
  1262. if ($entity->errors()) {
  1263. return false;
  1264. }
  1265. if ($entity->isNew() === false && !$entity->dirty()) {
  1266. return $entity;
  1267. }
  1268. $connection = $this->connection();
  1269. if ($options['atomic']) {
  1270. $success = $connection->transactional(function () use ($entity, $options) {
  1271. return $this->_processSave($entity, $options);
  1272. });
  1273. } else {
  1274. $success = $this->_processSave($entity, $options);
  1275. }
  1276. if ($success) {
  1277. if (!$connection->inTransaction() &&
  1278. ($options['atomic'] || (!$options['atomic'] && $options['_primary']))
  1279. ) {
  1280. $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options'));
  1281. }
  1282. if ($options['atomic'] || $options['_primary']) {
  1283. $entity->isNew(false);
  1284. $entity->source($this->registryAlias());
  1285. }
  1286. }
  1287. return $success;
  1288. }
  1289. /**
  1290. * Performs the actual saving of an entity based on the passed options.
  1291. *
  1292. * @param \Cake\Datasource\EntityInterface $entity the entity to be saved
  1293. * @param \ArrayObject $options the options to use for the save operation
  1294. * @return \Cake\Datasource\EntityInterface|bool
  1295. * @throws \RuntimeException When an entity is missing some of the primary keys.
  1296. */
  1297. protected function _processSave($entity, $options)
  1298. {
  1299. $primaryColumns = (array)$this->primaryKey();
  1300. if ($options['checkExisting'] && $primaryColumns && $entity->isNew() && $entity->has($primaryColumns)) {
  1301. $alias = $this->alias();
  1302. $conditions = [];
  1303. foreach ($entity->extract($primaryColumns) as $k => $v) {
  1304. $conditions["$alias.$k"] = $v;
  1305. }
  1306. $entity->isNew(!$this->exists($conditions));
  1307. }
  1308. $mode = $entity->isNew() ? RulesChecker::CREATE : RulesChecker::UPDATE;
  1309. if ($options['checkRules'] && !$this->checkRules($entity, $mode, $options)) {
  1310. return false;
  1311. }
  1312. $options['associated'] = $this->_associations->normalizeKeys($options['associated']);
  1313. $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options'));
  1314. if ($event->isStopped()) {
  1315. return $event->result;
  1316. }
  1317. $saved = $this->_associations->saveParents(
  1318. $this,
  1319. $entity,
  1320. $options['associated'],
  1321. ['_primary' => false] + $options->getArrayCopy()
  1322. );
  1323. if (!$saved && $options['atomic']) {
  1324. return false;
  1325. }
  1326. $data = $entity->extract($this->schema()->columns(), true);
  1327. $isNew = $entity->isNew();
  1328. if ($isNew) {
  1329. $success = $this->_insert($entity, $data);
  1330. } else {
  1331. $success = $this->_update($entity, $data);
  1332. }
  1333. if ($success) {
  1334. $success = $this->_associations->saveChildren(
  1335. $this,
  1336. $entity,
  1337. $options['associated'],
  1338. ['_primary' => false] + $options->getArrayCopy()
  1339. );
  1340. if ($success || !$options['atomic']) {
  1341. $entity->clean();
  1342. $this->dispatchEvent('Model.afterSave', compact('entity', 'options'));
  1343. if (!$options['atomic'] && !$options['_primary']) {
  1344. $entity->isNew(false);
  1345. $entity->source($this->registryAlias());
  1346. }
  1347. $success = true;
  1348. }
  1349. }
  1350. if (!$success && $isNew) {
  1351. $entity->unsetProperty($this->primaryKey());
  1352. $entity->isNew(true);
  1353. }
  1354. if ($success) {
  1355. return $entity;
  1356. }
  1357. return false;
  1358. }
  1359. /**
  1360. * Auxiliary function to handle the insert of an entity's data in the table
  1361. *
  1362. * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted
  1363. * @param array $data The actual data that needs to be saved
  1364. * @return \Cake\Datasource\EntityInterface|bool
  1365. * @throws \RuntimeException if not all the primary keys where supplied or could
  1366. * be generated when the table has composite primary keys. Or when the table has no primary key.
  1367. */
  1368. protected function _insert($entity, $data)
  1369. {
  1370. $primary = (array)$this->primaryKey();
  1371. if (empty($primary)) {
  1372. $msg = sprintf(
  1373. 'Cannot insert row in "%s" table, it has no primary key.',
  1374. $this->table()
  1375. );
  1376. throw new \RuntimeException($msg);
  1377. }
  1378. $keys = array_fill(0, count($primary), null);
  1379. $id = (array)$this->_newId($primary) + $keys;
  1380. $primary = array_combine($primary, $id);
  1381. $filteredKeys = array_filter($primary, 'strlen');
  1382. $data = $filteredKeys + $data;
  1383. if (count($primary) > 1) {
  1384. foreach ($primary as $k => $v) {
  1385. if (!isset($data[$k])) {
  1386. $msg = 'Cannot insert row, some of the primary key values are missing. ';
  1387. $msg .= sprintf(
  1388. 'Got (%s), expecting (%s)',
  1389. implode(', ', $filteredKeys + $entity->extract(array_keys($primary))),
  1390. implode(', ', array_keys($primary))
  1391. );
  1392. throw new \RuntimeException($msg);
  1393. }
  1394. }
  1395. }
  1396. $success = false;
  1397. if (empty($data)) {
  1398. return $success;
  1399. }
  1400. $statement = $this->query()->insert(array_keys($data))
  1401. ->values($data)
  1402. ->execute();
  1403. if ($statement->rowCount() !== 0) {
  1404. $success = $entity;
  1405. $entity->set($filteredKeys, ['guard' => false]);
  1406. foreach ($primary as $key => $v) {
  1407. if (!isset($data[$key])) {
  1408. $id = $statement->lastInsertId($this->table(), $key);
  1409. $entity->set($key, $id);
  1410. break;
  1411. }
  1412. }
  1413. }
  1414. $statement->closeCursor();
  1415. return $success;
  1416. }
  1417. /**
  1418. * Generate a primary key value for a new record.
  1419. *
  1420. * By default, this uses the type system to generate a new primary key
  1421. * value if possible. You can override this method if you have specific requirements
  1422. * for id generation.
  1423. *
  1424. * @param array $primary The primary key columns to get a new ID for.
  1425. * @return mixed Either null or the new primary key value.
  1426. */
  1427. protected function _newId($primary)
  1428. {
  1429. if (!$primary || count((array)$primary) > 1) {
  1430. return null;
  1431. }
  1432. $typeName = $this->schema()->columnType($primary[0]);
  1433. $type = Type::build($typeName);
  1434. return $type->newId();
  1435. }
  1436. /**
  1437. * Auxiliary function to handle the update of an entity's data in the table
  1438. *
  1439. * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted
  1440. * @param array $data The actual data that needs to be saved
  1441. * @return \Cake\Datasource\EntityInterface|bool
  1442. * @throws \InvalidArgumentException When primary key data is missing.
  1443. */
  1444. protected function _update($entity, $data)
  1445. {
  1446. $primaryColumns = (array)$this->primaryKey();
  1447. $primaryKey = $entity->extract($primaryColumns);
  1448. $data = array_diff_key($data, $primaryKey);
  1449. if (empty($data)) {
  1450. return $entity;
  1451. }
  1452. if (!$entity->has($primaryColumns)) {
  1453. $message = 'All primary key value(s) are needed for updating';
  1454. throw new \InvalidArgumentException($message);
  1455. }
  1456. $query = $this->query();
  1457. $statement = $query->update()
  1458. ->set($data)
  1459. ->where($primaryKey)
  1460. ->execute();
  1461. $success = false;
  1462. if ($statement->errorCode() === '00000') {
  1463. $success = $entity;
  1464. }
  1465. $statement->closeCursor();
  1466. return $success;
  1467. }
  1468. /**
  1469. * {@inheritDoc}
  1470. *
  1471. * For HasMany and HasOne associations records will be removed based on
  1472. * the dependent option. Join table records in BelongsToMany associations
  1473. * will always be removed. You can use the `cascadeCallbacks` option
  1474. * when defining associations to change how associated data is deleted.
  1475. *
  1476. * ### Options
  1477. *
  1478. * - `atomic` Defaults to true. When true the deletion happens within a transaction.
  1479. * - `checkRules` Defaults to true. Check deletion rules before deleting the record.
  1480. *
  1481. * ### Events
  1482. *
  1483. * - `Model.beforeDelete` Fired before the delete occurs. If stopped the delete
  1484. * will be aborted. Receives the event, entity, and options.
  1485. * - `Model.afterDelete` Fired after the delete has been successful. Receives
  1486. * the event, entity, and options.
  1487. * - `Model.afterDelete` Fired after the delete has been successful. Receives
  1488. * the event, entity, and options.
  1489. * - `Model.afterDeleteCommit` Fired after the transaction is committed for
  1490. * an atomic delete. Receives the event, entity, and options.
  1491. *
  1492. * The options argument will be converted into an \ArrayObject instance
  1493. * for the duration of the callbacks, this allows listeners to modify
  1494. * the options used in the delete operation.
  1495. *
  1496. */
  1497. public function delete(EntityInterface $entity, $options = [])
  1498. {
  1499. $options = new ArrayObject($options + [
  1500. 'atomic' => true,
  1501. 'checkRules' => true,
  1502. '_primary' => true,
  1503. ]);
  1504. $process = function () use ($entity, $options) {
  1505. return $this->_processDelete($entity, $options);
  1506. };
  1507. $connection = $this->connection();
  1508. if ($options['atomic']) {
  1509. $success = $connection->transactional($process);
  1510. } else {
  1511. $success = $process();
  1512. }
  1513. if ($success &&
  1514. !$connection->inTransaction() &&
  1515. ($options['atomic'] || (!$options['atomic'] && $options['_primary']))
  1516. ) {
  1517. $this->dispatchEvent('Model.afterDeleteCommit', [
  1518. 'entity' => $entity,
  1519. 'options' => $options
  1520. ]);
  1521. }
  1522. return $success;
  1523. }
  1524. /**
  1525. * Perform the delete operation.
  1526. *
  1527. * Will delete the entity provided. Will remove rows from any
  1528. * dependent associations, and clear out join tables for BelongsToMany associations.
  1529. *
  1530. * @param \Cake\DataSource\EntityInterface $entity The entity to delete.
  1531. * @param \ArrayObject $options The options for the delete.
  1532. * @throws \InvalidArgumentException if there are no primary key values of the
  1533. * passed entity
  1534. * @return bool success
  1535. */
  1536. protected function _processDelete($entity, $options)
  1537. {
  1538. if ($entity->isNew()) {
  1539. return false;
  1540. }
  1541. $primaryKey = (array)$this->primaryKey();
  1542. if (!$entity->has($primaryKey)) {
  1543. $msg = 'Deleting requires all primary key values.';
  1544. throw new \InvalidArgumentException($msg);
  1545. }
  1546. if ($options['checkRules'] && !$this->checkRules($entity, RulesChecker::DELETE, $options)) {
  1547. return false;
  1548. }
  1549. $event = $this->dispatchEvent('Model.beforeDelete', [
  1550. 'entity' => $entity,
  1551. 'options' => $options
  1552. ]);
  1553. if ($event->isStopped()) {
  1554. return $event->result;
  1555. }
  1556. $this->_associations->cascadeDelete(
  1557. $entity,
  1558. ['_primary' => false] + $options->getArrayCopy()
  1559. );
  1560. $query = $this->query();
  1561. $conditions = (array)$entity->extract($primaryKey);
  1562. $statement = $query->delete()
  1563. ->where($conditions)
  1564. ->execute();
  1565. $success = $statement->rowCount() > 0;
  1566. if (!$success) {
  1567. return $success;
  1568. }
  1569. $this->dispatchEvent('Model.afterDelete', [
  1570. 'entity' => $entity,
  1571. 'options' => $options
  1572. ]);
  1573. return $success;
  1574. }
  1575. /**
  1576. * Returns true if the finder exists for the table
  1577. *
  1578. * @param string $type name of finder to check
  1579. *
  1580. * @return bool
  1581. */
  1582. public function hasFinder($type)
  1583. {
  1584. $finder = 'find' . $type;
  1585. return method_exists($this, $finder) || ($this->_behaviors && $this->_behaviors->hasFinder($type));
  1586. }
  1587. /**
  1588. * Calls a finder method directly and applies it to the passed query,
  1589. * if no query is passed a new one will be created and returned
  1590. *
  1591. * @param string $type name of the finder to be called
  1592. * @param \Cake\ORM\Query $query The query object to apply the finder options to
  1593. * @param array $options List of options to pass to the finder
  1594. * @return \Cake\ORM\Query
  1595. * @throws \BadMethodCallException
  1596. */
  1597. public function callFinder($type, Query $query, array $options = [])
  1598. {
  1599. $query->applyOptions($options);
  1600. $options = $query->getOptions();
  1601. $finder = 'find' . $type;
  1602. if (method_exists($this, $finder)) {
  1603. return $this->{$finder}($query, $options);
  1604. }
  1605. if ($this->_behaviors && $this->_behaviors->hasFinder($type)) {
  1606. return $this->_behaviors->callFinder($type, [$query, $options]);
  1607. }
  1608. throw new \BadMethodCallException(
  1609. sprintf('Unknown finder method "%s"', $type)
  1610. );
  1611. }
  1612. /**
  1613. * Provides the dynamic findBy and findByAll methods.
  1614. *
  1615. * @param string $method The method name that was fired.
  1616. * @param array $args List of arguments passed to the function.
  1617. * @return mixed
  1618. * @throws \BadMethodCallException when there are missing arguments, or when
  1619. * and & or are combined.
  1620. */
  1621. protected function _dynamicFinder($method, $args)
  1622. {
  1623. $method = Inflector::underscore($method);
  1624. preg_match('/^find_([\w]+)_by_/', $method, $matches);
  1625. if (empty($matches)) {
  1626. // find_by_ is 8 characters.
  1627. $fields = substr($method, 8);
  1628. $findType = 'all';
  1629. } else {
  1630. $fields = substr($method, strlen($matches[0]));
  1631. $findType = Inflector::variable($matches[1]);
  1632. }
  1633. $hasOr = strpos($fields, '_or_');
  1634. $hasAnd = strpos($fields, '_and_');
  1635. $makeConditions = function ($fields, $args) {
  1636. $conditions = [];
  1637. if (count($args) < count($fields)) {
  1638. throw new BadMethodCallException(sprintf(
  1639. 'Not enough arguments for magic finder. Got %s required %s',
  1640. count($args),
  1641. count($fields)
  1642. ));
  1643. }
  1644. foreach ($fields as $field) {
  1645. $conditions[$field] = array_shift($args);
  1646. }
  1647. return $conditions;
  1648. };
  1649. if ($hasOr !== false && $hasAnd !== false) {
  1650. throw new BadMethodCallException(
  1651. 'Cannot mix "and" & "or" in a magic finder. Use find() instead.'
  1652. );
  1653. }
  1654. if ($hasOr === false && $hasAnd === false) {
  1655. $conditions = $makeConditions([$fields], $args);
  1656. } elseif ($hasOr !== false) {
  1657. $fields = explode('_or_', $fields);
  1658. $conditions = [
  1659. 'OR' => $makeConditions($fields, $args)
  1660. ];
  1661. } elseif ($hasAnd !== false) {
  1662. $fields = explode('_and_', $fields);
  1663. $conditions = $makeConditions($fields, $args);
  1664. }
  1665. return $this->find($findType, [
  1666. 'conditions' => $conditions,
  1667. ]);
  1668. }
  1669. /**
  1670. * Handles behavior delegation + dynamic finders.
  1671. *
  1672. * If your Table uses any behaviors you can call them as if
  1673. * they were on the table object.
  1674. *
  1675. * @param string $method name of the method to be invoked
  1676. * @param array $args List of arguments passed to the function
  1677. * @return mixed
  1678. * @throws \BadMethodCallException
  1679. */
  1680. public function __call($method, $args)
  1681. {
  1682. if ($this->_behaviors && $this->_behaviors->hasMethod($method)) {
  1683. return $this->_behaviors->call($method, $args);
  1684. }
  1685. if (preg_match('/^find(?:\w+)?By/', $method) > 0) {
  1686. return $this->_dynamicFinder($method, $args);
  1687. }
  1688. throw new \BadMethodCallException(
  1689. sprintf('Unknown method "%s"', $method)
  1690. );
  1691. }
  1692. /**
  1693. * Returns the association named after the passed value if exists, otherwise
  1694. * throws an exception.
  1695. *
  1696. * @param string $property the association name
  1697. * @return \Cake\ORM\Association
  1698. * @throws \RuntimeException if no association with such name exists
  1699. */
  1700. public function __get($property)
  1701. {
  1702. $association = $this->_associations->get($property);
  1703. if (!$association) {
  1704. throw new \RuntimeException(sprintf(
  1705. 'Table "%s" is not associated with "%s"',
  1706. get_class($this),
  1707. $property
  1708. ));
  1709. }
  1710. return $association;
  1711. }
  1712. /**
  1713. * Returns whether an association named after the passed value
  1714. * exists for this table.
  1715. *
  1716. * @param string $property the association name
  1717. * @return bool
  1718. */
  1719. public function __isset($property)
  1720. {
  1721. return $this->_associations->has($property);
  1722. }
  1723. /**
  1724. * Get the object used to marshal/convert array data into objects.
  1725. *
  1726. * Override this method if you want a table object to use custom
  1727. * marshalling logic.
  1728. *
  1729. * @return \Cake\ORM\Marshaller
  1730. * @see \Cake\ORM\Marshaller
  1731. */
  1732. public function marshaller()
  1733. {
  1734. return new Marshaller($this);
  1735. }
  1736. /**
  1737. * {@inheritDoc}
  1738. *
  1739. * By default all the associations on this table will be hydrated. You can
  1740. * limit which associations are built, or include deeper associations
  1741. * using the options parameter:
  1742. *
  1743. * ```
  1744. * $article = $this->Articles->newEntity(
  1745. * $this->request->data(),
  1746. * ['associated' => ['Tags', 'Comments.Users']]
  1747. * );
  1748. * ```
  1749. *
  1750. * You can limit fields that will be present in the constructed entity by
  1751. * passing the `fieldList` option, which is also accepted for associations:
  1752. *
  1753. * ```
  1754. * $article = $this->Articles->newEntity($this->request->data(), [
  1755. * 'fieldList' => ['title', 'body'],
  1756. * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']]
  1757. * ]
  1758. * );
  1759. * ```
  1760. *
  1761. * The `fieldList` option lets remove or restrict input data from ending up in
  1762. * the entity. If you'd like to relax the entity's default accessible fields,
  1763. * you can use the `accessibleFields` option:
  1764. *
  1765. * ```
  1766. * $article = $this->Articles->newEntity(
  1767. * $this->request->data(),
  1768. * ['accessibleFields' => ['protected_field' => true]]
  1769. * );
  1770. * ```
  1771. *
  1772. * By default, the data is validated before being passed to the new entity. In
  1773. * the case of invalid fields, those will not be present in the resulting object.
  1774. * The `validate` option can be used to disable validation on the passed data:
  1775. *
  1776. * ```
  1777. * $article = $this->Articles->newEntity(
  1778. * $this->request->data(),
  1779. * ['validate' => false]
  1780. * );
  1781. * ```
  1782. *
  1783. * You can also pass the name of the validator to use in the `validate` option.
  1784. * If `null` is passed to the first param of this function, no validation will
  1785. * be performed.
  1786. *
  1787. * You can use the `Model.beforeMarshal` event to modify request data
  1788. * before it is converted into entities.
  1789. */
  1790. public function newEntity($data = null, array $options = [])
  1791. {
  1792. if ($data === null) {
  1793. $class = $this->entityClass();
  1794. $entity = new $class([], ['source' => $this->registryAlias()]);
  1795. return $entity;
  1796. }
  1797. if (!isset($options['associated'])) {
  1798. $options['associated'] = $this->_associations->keys();
  1799. }
  1800. $marshaller = $this->marshaller();
  1801. return $marshaller->one($data, $options);
  1802. }
  1803. /**
  1804. * {@inheritDoc}
  1805. *
  1806. * By default all the associations on this table will be hydrated. You can
  1807. * limit which associations are built, or include deeper associations
  1808. * using the options parameter:
  1809. *
  1810. * ```
  1811. * $articles = $this->Articles->newEntities(
  1812. * $this->request->data(),
  1813. * ['associated' => ['Tags', 'Comments.Users']]
  1814. * );
  1815. * ```
  1816. *
  1817. * You can limit fields that will be present in the constructed entities by
  1818. * passing the `fieldList` option, which is also accepted for associations:
  1819. *
  1820. * ```
  1821. * $articles = $this->Articles->newEntities($this->request->data(), [
  1822. * 'fieldList' => ['title', 'body'],
  1823. * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']]
  1824. * ]
  1825. * );
  1826. * ```
  1827. *
  1828. * You can use the `Model.beforeMarshal` event to modify request data
  1829. * before it is converted into entities.
  1830. */
  1831. public function newEntities(array $data, array $options = [])
  1832. {
  1833. if (!isset($options['associated'])) {
  1834. $options['associated'] = $this->_associations->keys();
  1835. }
  1836. $marshaller = $this->marshaller();
  1837. return $marshaller->many($data, $options);
  1838. }
  1839. /**
  1840. * {@inheritDoc}
  1841. *
  1842. * When merging HasMany or BelongsToMany associations, all the entities in the
  1843. * `$data` array will appear, those that can be matched by primary key will get
  1844. * the data merged, but those that cannot, will be discarded.
  1845. *
  1846. * You can limit fields that will be present in the merged entity by
  1847. * passing the `fieldList` option, which is also accepted for associations:
  1848. *
  1849. * ```
  1850. * $article = $this->Articles->patchEntity($article, $this->request->data(), [
  1851. * 'fieldList' => ['title', 'body'],
  1852. * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']]
  1853. * ]
  1854. * );
  1855. * ```
  1856. *
  1857. * By default, the data is validated before being passed to the entity. In
  1858. * the case of invalid fields, those will not be assigned to the entity.
  1859. * The `validate` option can be used to disable validation on the passed data:
  1860. *
  1861. * ```
  1862. * $article = $this->patchEntity($article, $this->request->data(),[
  1863. * 'validate' => false
  1864. * ]);
  1865. * ```
  1866. *
  1867. * You can use the `Model.beforeMarshal` event to modify request data
  1868. * before it is converted into entities.
  1869. */
  1870. public function patchEntity(EntityInterface $entity, array $data, array $options = [])
  1871. {
  1872. if (!isset($options['associated'])) {
  1873. $options['associated'] = $this->_associations->keys();
  1874. }
  1875. $marshaller = $this->marshaller();
  1876. return $marshaller->merge($entity, $data, $options);
  1877. }
  1878. /**
  1879. * {@inheritDoc}
  1880. *
  1881. * Those entries in `$entities` that cannot be matched to any record in
  1882. * `$data` will be discarded. Records in `$data` that could not be matched will
  1883. * be marshalled as a new entity.
  1884. *
  1885. * When merging HasMany or BelongsToMany associations, all the entities in the
  1886. * `$data` array will appear, those that can be matched by primary key will get
  1887. * the data merged, but those that cannot, will be discarded.
  1888. *
  1889. * You can limit fields that will be present in the merged entities by
  1890. * passing the `fieldList` option, which is also accepted for associations:
  1891. *
  1892. * ```
  1893. * $articles = $this->Articles->patchEntities($articles, $this->request->data(), [
  1894. * 'fieldList' => ['title', 'body'],
  1895. * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']]
  1896. * ]
  1897. * );
  1898. * ```
  1899. *
  1900. * You can use the `Model.beforeMarshal` event to modify request data
  1901. * before it is converted into entities.
  1902. */
  1903. public function patchEntities($entities, array $data, array $options = [])
  1904. {
  1905. if (!isset($options['associated'])) {
  1906. $options['associated'] = $this->_associations->keys();
  1907. }
  1908. $marshaller = $this->marshaller();
  1909. return $marshaller->mergeMany($entities, $data, $options);
  1910. }
  1911. /**
  1912. * Validator method used to check the uniqueness of a value for a column.
  1913. * This is meant to be used with the validation API and not to be called
  1914. * directly.
  1915. *
  1916. * ### Example:
  1917. *
  1918. * ```
  1919. * $validator->add('email', [
  1920. * 'unique' => ['rule' => 'validateUnique', 'provider' => 'table']
  1921. * ])
  1922. * ```
  1923. *
  1924. * Unique validation can be scoped to the value of another column:
  1925. *
  1926. * ```
  1927. * $validator->add('email', [
  1928. * 'unique' => [
  1929. * 'rule' => ['validateUnique', ['scope' => 'site_id']],
  1930. * 'provider' => 'table'
  1931. * ]
  1932. * ]);
  1933. * ```
  1934. *
  1935. * In the above example, the email uniqueness will be scoped to only rows having
  1936. * the same site_id. Scoping will only be used if the scoping field is present in
  1937. * the data to be validated.
  1938. *
  1939. * @param mixed $value The value of column to be checked for uniqueness
  1940. * @param array $options The options array, optionally containing the 'scope' key.
  1941. * May also be the validation context if there are no options.
  1942. * @param array|null $context Either the validation context or null.
  1943. * @return bool true if the value is unique
  1944. */
  1945. public function validateUnique($value, array $options, array $context = null)
  1946. {
  1947. if ($context === null) {
  1948. $context = $options;
  1949. }
  1950. $entity = new Entity(
  1951. $context['data'],
  1952. [
  1953. 'useSetters' => false,
  1954. 'markNew' => $context['newRecord'],
  1955. 'source' => $this->registryAlias()
  1956. ]
  1957. );
  1958. $fields = array_merge(
  1959. [$context['field']],
  1960. isset($options['scope']) ? (array)$options['scope'] : []
  1961. );
  1962. $rule = new IsUnique($fields);
  1963. return $rule($entity, ['repository' => $this]);
  1964. }
  1965. /**
  1966. * Returns whether or not the passed entity complies with all the rules stored in
  1967. * the rules checker.
  1968. *
  1969. * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
  1970. * @param string $operation The operation being run. Either 'create', 'update' or 'delete'.
  1971. * @param \ArrayObject|array $options The options To be passed to the rules.
  1972. * @return bool
  1973. */
  1974. public function checkRules(EntityInterface $entity, $operation = RulesChecker::CREATE, $options = null)
  1975. {
  1976. $rules = $this->rulesChecker();
  1977. $options = $options ?: new ArrayObject;
  1978. $options = is_array($options) ? new ArrayObject($options) : $options;
  1979. $event = $this->dispatchEvent(
  1980. 'Model.beforeRules',
  1981. compact('entity', 'options', 'operation')
  1982. );
  1983. if ($event->isStopped()) {
  1984. return $event->result;
  1985. }
  1986. $result = $rules->check($entity, $operation, $options->getArrayCopy());
  1987. $event = $this->dispatchEvent(
  1988. 'Model.afterRules',
  1989. compact('entity', 'options', 'result', 'operation')
  1990. );
  1991. if ($event->isStopped()) {
  1992. return $event->result;
  1993. }
  1994. return $result;
  1995. }
  1996. /**
  1997. * Returns the rule checker for this table. A rules checker object is used to
  1998. * test an entity for validity on rules that may involve complex logic or data that
  1999. * needs to be fetched from the database or other sources.
  2000. *
  2001. * @return \Cake\ORM\RulesChecker
  2002. */
  2003. public function rulesChecker()
  2004. {
  2005. if ($this->_rulesChecker !== null) {
  2006. return $this->_rulesChecker;
  2007. }
  2008. $this->_rulesChecker = $this->buildRules(new RulesChecker(['repository' => $this]));
  2009. $this->dispatchEvent('Model.buildRules', ['rules' => $this->_rulesChecker]);
  2010. return $this->_rulesChecker;
  2011. }
  2012. /**
  2013. * Returns rules checker object after modifying the one that was passed. Subclasses
  2014. * can override this method in order to initialize the rules to be applied to
  2015. * entities saved by this table.
  2016. *
  2017. * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
  2018. * @return \Cake\ORM\RulesChecker
  2019. */
  2020. public function buildRules(RulesChecker $rules)
  2021. {
  2022. return $rules;
  2023. }
  2024. /**
  2025. * Get the Model callbacks this table is interested in.
  2026. *
  2027. * By implementing the conventional methods a table class is assumed
  2028. * to be interested in the related event.
  2029. *
  2030. * Override this method if you need to add non-conventional event listeners.
  2031. * Or if you want you table to listen to non-standard events.
  2032. *
  2033. * @return array
  2034. */
  2035. public function implementedEvents()
  2036. {
  2037. $eventMap = [
  2038. 'Model.beforeMarshal' => 'beforeMarshal',
  2039. 'Model.beforeFind' => 'beforeFind',
  2040. 'Model.beforeSave' => 'beforeSave',
  2041. 'Model.afterSave' => 'afterSave',
  2042. 'Model.afterSaveCommit' => 'afterSaveCommit',
  2043. 'Model.beforeDelete' => 'beforeDelete',
  2044. 'Model.afterDelete' => 'afterDelete',
  2045. 'Model.afterDeleteCommit' => 'afterDeleteCommit',
  2046. 'Model.beforeRules' => 'beforeRules',
  2047. 'Model.afterRules' => 'afterRules',
  2048. ];
  2049. $events = [];
  2050. foreach ($eventMap as $event => $method) {
  2051. if (!method_exists($this, $method)) {
  2052. continue;
  2053. }
  2054. $events[$event] = $method;
  2055. }
  2056. return $events;
  2057. }
  2058. /**
  2059. * Returns an array that can be used to describe the internal state of this
  2060. * object.
  2061. *
  2062. * @return array
  2063. */
  2064. public function __debugInfo()
  2065. {
  2066. $conn = $this->connection();
  2067. return [
  2068. 'registryAlias' => $this->registryAlias(),
  2069. 'table' => $this->table(),
  2070. 'alias' => $this->alias(),
  2071. 'entityClass' => $this->entityClass(),
  2072. 'associations' => $this->_associations->keys(),
  2073. 'behaviors' => $this->_behaviors->loaded(),
  2074. 'defaultConnection' => $this->defaultConnectionName(),
  2075. 'connectionName' => $conn ? $conn->configName() : null
  2076. ];
  2077. }
  2078. }