PageRenderTime 33ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/src/ORM/Table.php

https://github.com/binondord/cakephp
PHP | 2203 lines | 948 code | 138 blank | 1117 comment | 123 complexity | fc9c12903fa6a5e39ab728381680bbed 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\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($entity, $data);
  1343. } else {
  1344. $success = $this->_update($entity, $data);
  1345. }
  1346. if ($success) {
  1347. $success = $this->_associations->saveChildren(
  1348. $this,
  1349. $entity,
  1350. $options['associated'],
  1351. ['_primary' => false] + $options->getArrayCopy()
  1352. );
  1353. if ($success || !$options['atomic']) {
  1354. $entity->clean();
  1355. $this->dispatchEvent('Model.afterSave', compact('entity', 'options'));
  1356. if (!$options['atomic'] && !$options['_primary']) {
  1357. $entity->isNew(false);
  1358. $entity->source($this->registryAlias());
  1359. }
  1360. $success = true;
  1361. }
  1362. }
  1363. if (!$success && $isNew) {
  1364. $entity->unsetProperty($this->primaryKey());
  1365. $entity->isNew(true);
  1366. }
  1367. if ($success) {
  1368. return $entity;
  1369. }
  1370. return false;
  1371. }
  1372. /**
  1373. * Auxiliary function to handle the insert of an entity's data in the table
  1374. *
  1375. * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted
  1376. * @param array $data The actual data that needs to be saved
  1377. * @return \Cake\Datasource\EntityInterface|bool
  1378. * @throws \RuntimeException if not all the primary keys where supplied or could
  1379. * be generated when the table has composite primary keys. Or when the table has no primary key.
  1380. */
  1381. protected function _insert($entity, $data)
  1382. {
  1383. $primary = (array)$this->primaryKey();
  1384. if (empty($primary)) {
  1385. $msg = sprintf(
  1386. 'Cannot insert row in "%s" table, it has no primary key.',
  1387. $this->table()
  1388. );
  1389. throw new RuntimeException($msg);
  1390. }
  1391. $keys = array_fill(0, count($primary), null);
  1392. $id = (array)$this->_newId($primary) + $keys;
  1393. $primary = array_combine($primary, $id);
  1394. $filteredKeys = array_filter($primary, 'strlen');
  1395. $data = $data + $filteredKeys;
  1396. if (count($primary) > 1) {
  1397. $schema = $this->schema();
  1398. foreach ($primary as $k => $v) {
  1399. if (!isset($data[$k]) && empty($schema->column($k)['autoIncrement'])) {
  1400. $msg = 'Cannot insert row, some of the primary key values are missing. ';
  1401. $msg .= sprintf(
  1402. 'Got (%s), expecting (%s)',
  1403. implode(', ', $filteredKeys + $entity->extract(array_keys($primary))),
  1404. implode(', ', array_keys($primary))
  1405. );
  1406. throw new RuntimeException($msg);
  1407. }
  1408. }
  1409. }
  1410. $success = false;
  1411. if (empty($data)) {
  1412. return $success;
  1413. }
  1414. $statement = $this->query()->insert(array_keys($data))
  1415. ->values($data)
  1416. ->execute();
  1417. if ($statement->rowCount() !== 0) {
  1418. $success = $entity;
  1419. $entity->set($filteredKeys, ['guard' => false]);
  1420. $schema = $this->schema();
  1421. $driver = $this->connection()->driver();
  1422. foreach ($primary as $key => $v) {
  1423. if (!isset($data[$key])) {
  1424. $id = $statement->lastInsertId($this->table(), $key);
  1425. $type = $schema->columnType($key);
  1426. $entity->set($key, Type::build($type)->toPHP($id, $driver));
  1427. break;
  1428. }
  1429. }
  1430. }
  1431. $statement->closeCursor();
  1432. return $success;
  1433. }
  1434. /**
  1435. * Generate a primary key value for a new record.
  1436. *
  1437. * By default, this uses the type system to generate a new primary key
  1438. * value if possible. You can override this method if you have specific requirements
  1439. * for id generation.
  1440. *
  1441. * @param array $primary The primary key columns to get a new ID for.
  1442. * @return mixed Either null or the new primary key value.
  1443. */
  1444. protected function _newId($primary)
  1445. {
  1446. if (!$primary || count((array)$primary) > 1) {
  1447. return null;
  1448. }
  1449. $typeName = $this->schema()->columnType($primary[0]);
  1450. $type = Type::build($typeName);
  1451. return $type->newId();
  1452. }
  1453. /**
  1454. * Auxiliary function to handle the update of an entity's data in the table
  1455. *
  1456. * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted
  1457. * @param array $data The actual data that needs to be saved
  1458. * @return \Cake\Datasource\EntityInterface|bool
  1459. * @throws \InvalidArgumentException When primary key data is missing.
  1460. */
  1461. protected function _update($entity, $data)
  1462. {
  1463. $primaryColumns = (array)$this->primaryKey();
  1464. $primaryKey = $entity->extract($primaryColumns);
  1465. $data = array_diff_key($data, $primaryKey);
  1466. if (empty($data)) {
  1467. return $entity;
  1468. }
  1469. if (!$entity->has($primaryColumns)) {
  1470. $message = 'All primary key value(s) are needed for updating';
  1471. throw new InvalidArgumentException($message);
  1472. }
  1473. $query = $this->query();
  1474. $statement = $query->update()
  1475. ->set($data)
  1476. ->where($primaryKey)
  1477. ->execute();
  1478. $success = false;
  1479. if ($statement->errorCode() === '00000') {
  1480. $success = $entity;
  1481. }
  1482. $statement->closeCursor();
  1483. return $success;
  1484. }
  1485. /**
  1486. * {@inheritDoc}
  1487. *
  1488. * For HasMany and HasOne associations records will be removed based on
  1489. * the dependent option. Join table records in BelongsToMany associations
  1490. * will always be removed. You can use the `cascadeCallbacks` option
  1491. * when defining associations to change how associated data is deleted.
  1492. *
  1493. * ### Options
  1494. *
  1495. * - `atomic` Defaults to true. When true the deletion happens within a transaction.
  1496. * - `checkRules` Defaults to true. Check deletion rules before deleting the record.
  1497. *
  1498. * ### Events
  1499. *
  1500. * - `Model.beforeDelete` Fired before the delete occurs. If stopped the delete
  1501. * will be aborted. Receives the event, entity, and options.
  1502. * - `Model.afterDelete` Fired after the delete has been successful. Receives
  1503. * the event, entity, and options.
  1504. * - `Model.afterDelete` Fired after the delete has been successful. Receives
  1505. * the event, entity, and options.
  1506. * - `Model.afterDeleteCommit` Fired after the transaction is committed for
  1507. * an atomic delete. Receives the event, entity, and options.
  1508. *
  1509. * The options argument will be converted into an \ArrayObject instance
  1510. * for the duration of the callbacks, this allows listeners to modify
  1511. * the options used in the delete operation.
  1512. *
  1513. */
  1514. public function delete(EntityInterface $entity, $options = [])
  1515. {
  1516. $options = new ArrayObject($options + [
  1517. 'atomic' => true,
  1518. 'checkRules' => true,
  1519. '_primary' => true,
  1520. ]);
  1521. $process = function () use ($entity, $options) {
  1522. return $this->_processDelete($entity, $options);
  1523. };
  1524. $connection = $this->connection();
  1525. if ($options['atomic']) {
  1526. $success = $connection->transactional($process);
  1527. } else {
  1528. $success = $process();
  1529. }
  1530. if ($success &&
  1531. !$connection->inTransaction() &&
  1532. ($options['atomic'] || (!$options['atomic'] && $options['_primary']))
  1533. ) {
  1534. $this->dispatchEvent('Model.afterDeleteCommit', [
  1535. 'entity' => $entity,
  1536. 'options' => $options
  1537. ]);
  1538. }
  1539. return $success;
  1540. }
  1541. /**
  1542. * Perform the delete operation.
  1543. *
  1544. * Will delete the entity provided. Will remove rows from any
  1545. * dependent associations, and clear out join tables for BelongsToMany associations.
  1546. *
  1547. * @param \Cake\DataSource\EntityInterface $entity The entity to delete.
  1548. * @param \ArrayObject $options The options for the delete.
  1549. * @throws \InvalidArgumentException if there are no primary key values of the
  1550. * passed entity
  1551. * @return bool success
  1552. */
  1553. protected function _processDelete($entity, $options)
  1554. {
  1555. if ($entity->isNew()) {
  1556. return false;
  1557. }
  1558. $primaryKey = (array)$this->primaryKey();
  1559. if (!$entity->has($primaryKey)) {
  1560. $msg = 'Deleting requires all primary key values.';
  1561. throw new InvalidArgumentException($msg);
  1562. }
  1563. if ($options['checkRules'] && !$this->checkRules($entity, RulesChecker::DELETE, $options)) {
  1564. return false;
  1565. }
  1566. $event = $this->dispatchEvent('Model.beforeDelete', [
  1567. 'entity' => $entity,
  1568. 'options' => $options
  1569. ]);
  1570. if ($event->isStopped()) {
  1571. return $event->result;
  1572. }
  1573. $this->_associations->cascadeDelete(
  1574. $entity,
  1575. ['_primary' => false] + $options->getArrayCopy()
  1576. );
  1577. $query = $this->query();
  1578. $conditions = (array)$entity->extract($primaryKey);
  1579. $statement = $query->delete()
  1580. ->where($conditions)
  1581. ->execute();
  1582. $success = $statement->rowCount() > 0;
  1583. if (!$success) {
  1584. return $success;
  1585. }
  1586. $this->dispatchEvent('Model.afterDelete', [
  1587. 'entity' => $entity,
  1588. 'options' => $options
  1589. ]);
  1590. return $success;
  1591. }
  1592. /**
  1593. * Returns true if the finder exists for the table
  1594. *
  1595. * @param string $type name of finder to check
  1596. *
  1597. * @return bool
  1598. */
  1599. public function hasFinder($type)
  1600. {
  1601. $finder = 'find' . $type;
  1602. return method_exists($this, $finder) || ($this->_behaviors && $this->_behaviors->hasFinder($type));
  1603. }
  1604. /**
  1605. * Calls a finder method directly and applies it to the passed query,
  1606. * if no query is passed a new one will be created and returned
  1607. *
  1608. * @param string $type name of the finder to be called
  1609. * @param \Cake\ORM\Query $query The query object to apply the finder options to
  1610. * @param array $options List of options to pass to the finder
  1611. * @return \Cake\ORM\Query
  1612. * @throws \BadMethodCallException
  1613. */
  1614. public function callFinder($type, Query $query, array $options = [])
  1615. {
  1616. $query->applyOptions($options);
  1617. $options = $query->getOptions();
  1618. $finder = 'find' . $type;
  1619. if (method_exists($this, $finder)) {
  1620. return $this->{$finder}($query, $options);
  1621. }
  1622. if ($this->_behaviors && $this->_behaviors->hasFinder($type)) {
  1623. return $this->_behaviors->callFinder($type, [$query, $options]);
  1624. }
  1625. throw new \BadMethodCallException(
  1626. sprintf('Unknown finder method "%s"', $type)
  1627. );
  1628. }
  1629. /**
  1630. * Provides the dynamic findBy and findByAll methods.
  1631. *
  1632. * @param string $method The method name that was fired.
  1633. * @param array $args List of arguments passed to the function.
  1634. * @return mixed
  1635. * @throws \BadMethodCallException when there are missing arguments, or when
  1636. * and & or are combined.
  1637. */
  1638. protected function _dynamicFinder($method, $args)
  1639. {
  1640. $method = Inflector::underscore($method);
  1641. preg_match('/^find_([\w]+)_by_/', $method, $matches);
  1642. if (empty($matches)) {
  1643. // find_by_ is 8 characters.
  1644. $fields = substr($method, 8);
  1645. $findType = 'all';
  1646. } else {
  1647. $fields = substr($method, strlen($matches[0]));
  1648. $findType = Inflector::variable($matches[1]);
  1649. }
  1650. $hasOr = strpos($fields, '_or_');
  1651. $hasAnd = strpos($fields, '_and_');
  1652. $makeConditions = function ($fields, $args) {
  1653. $conditions = [];
  1654. if (count($args) < count($fields)) {
  1655. throw new BadMethodCallException(sprintf(
  1656. 'Not enough arguments for magic finder. Got %s required %s',
  1657. count($args),
  1658. count($fields)
  1659. ));
  1660. }
  1661. foreach ($fields as $field) {
  1662. $conditions[$this->aliasField($field)] = array_shift($args);
  1663. }
  1664. return $conditions;
  1665. };
  1666. if ($hasOr !== false && $hasAnd !== false) {
  1667. throw new BadMethodCallException(
  1668. 'Cannot mix "and" & "or" in a magic finder. Use find() instead.'
  1669. );
  1670. }
  1671. if ($hasOr === false && $hasAnd === false) {
  1672. $conditions = $makeConditions([$fields], $args);
  1673. } elseif ($hasOr !== false) {
  1674. $fields = explode('_or_', $fields);
  1675. $conditions = [
  1676. 'OR' => $makeConditions($fields, $args)
  1677. ];
  1678. } elseif ($hasAnd !== false) {
  1679. $fields = explode('_and_', $fields);
  1680. $conditions = $makeConditions($fields, $args);
  1681. }
  1682. return $this->find($findType, [
  1683. 'conditions' => $conditions,
  1684. ]);
  1685. }
  1686. /**
  1687. * Handles behavior delegation + dynamic finders.
  1688. *
  1689. * If your Table uses any behaviors you can call them as if
  1690. * they were on the table object.
  1691. *
  1692. * @param string $method name of the method to be invoked
  1693. * @param array $args List of arguments passed to the function
  1694. * @return mixed
  1695. * @throws \BadMethodCallException
  1696. */
  1697. public function __call($method, $args)
  1698. {
  1699. if ($this->_behaviors && $this->_behaviors->hasMethod($method)) {
  1700. return $this->_behaviors->call($method, $args);
  1701. }
  1702. if (preg_match('/^find(?:\w+)?By/', $method) > 0) {
  1703. return $this->_dynamicFinder($method, $args);
  1704. }
  1705. throw new \BadMethodCallException(
  1706. sprintf('Unknown method "%s"', $method)
  1707. );
  1708. }
  1709. /**
  1710. * Returns the association named after the passed value if exists, otherwise
  1711. * throws an exception.
  1712. *
  1713. * @param string $property the association name
  1714. * @return \Cake\ORM\Association
  1715. * @throws \RuntimeException if no association with such name exists
  1716. */
  1717. public function __get($property)
  1718. {
  1719. $association = $this->_associations->get($property);
  1720. if (!$association) {
  1721. throw new RuntimeException(sprintf(
  1722. 'Table "%s" is not associated with "%s"',
  1723. get_class($this),
  1724. $property
  1725. ));
  1726. }
  1727. return $association;
  1728. }
  1729. /**
  1730. * Returns whether an association named after the passed value
  1731. * exists for this table.
  1732. *
  1733. * @param string $property the association name
  1734. * @return bool
  1735. */
  1736. public function __isset($property)
  1737. {
  1738. return $this->_associations->has($property);
  1739. }
  1740. /**
  1741. * Get the object used to marshal/convert array data into objects.
  1742. *
  1743. * Override this method if you want a table object to use custom
  1744. * marshalling logic.
  1745. *
  1746. * @return \Cake\ORM\Marshaller
  1747. * @see \Cake\ORM\Marshaller
  1748. */
  1749. public function marshaller()
  1750. {
  1751. return new Marshaller($this);
  1752. }
  1753. /**
  1754. * {@inheritDoc}
  1755. *
  1756. * By default all the associations on this table will be hydrated. You can
  1757. * limit which associations are built, or include deeper associations
  1758. * using the options parameter:
  1759. *
  1760. * ```
  1761. * $article = $this->Articles->newEntity(
  1762. * $this->request->data(),
  1763. * ['associated' => ['Tags', 'Comments.Users']]
  1764. * );
  1765. * ```
  1766. *
  1767. * You can limit fields that will be present in the constructed entity by
  1768. * passing the `fieldList` option, which is also accepted for associations:
  1769. *
  1770. * ```
  1771. * $article = $this->Articles->newEntity($this->request->data(), [
  1772. * 'fieldList' => ['title', 'body'],
  1773. * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']]
  1774. * ]
  1775. * );
  1776. * ```
  1777. *
  1778. * The `fieldList` option lets remove or restrict input data from ending up in
  1779. * the entity. If you'd like to relax the entity's default accessible fields,
  1780. * you can use the `accessibleFields` option:
  1781. *
  1782. * ```
  1783. * $article = $this->Articles->newEntity(
  1784. * $this->request->data(),
  1785. * ['accessibleFields' => ['protected_field' => true]]
  1786. * );
  1787. * ```
  1788. *
  1789. * By default, the data is validated before being passed to the new entity. In
  1790. * the case of invalid fields, those will not be present in the resulting object.
  1791. * The `validate` option can be used to disable validation on the passed data:
  1792. *
  1793. * ```
  1794. * $article = $this->Articles->newEntity(
  1795. * $this->request->data(),
  1796. * ['validate' => false]
  1797. * );
  1798. * ```
  1799. *
  1800. * You can also pass the name of the validator to use in the `validate` option.
  1801. * If `null` is passed to the first param of this function, no validation will
  1802. * be performed.
  1803. *
  1804. * You can use the `Model.beforeMarshal` event to modify request data
  1805. * before it is converted into entities.
  1806. */
  1807. public function newEntity($data = null, array $options = [])
  1808. {
  1809. if ($data === null) {
  1810. $class = $this->entityClass();
  1811. $entity = new $class([], ['source' => $this->registryAlias()]);
  1812. return $entity;
  1813. }
  1814. if (!isset($options['associated'])) {
  1815. $options['associated'] = $this->_associations->keys();
  1816. }
  1817. $marshaller = $this->marshaller();
  1818. return $marshaller->one($data, $options);
  1819. }
  1820. /**
  1821. * {@inheritDoc}
  1822. *
  1823. * By default all the associations on this table will be hydrated. You can
  1824. * limit which associations are built, or include deeper associations
  1825. * using the options parameter:
  1826. *
  1827. * ```
  1828. * $articles = $this->Articles->newEntities(
  1829. * $this->request->data(),
  1830. * ['associated' => ['Tags', 'Comments.Users']]
  1831. * );
  1832. * ```
  1833. *
  1834. * You can limit fields that will be present in the constructed entities by
  1835. * passing the `fieldList` option, which is also accepted for associations:
  1836. *
  1837. * ```
  1838. * $articles = $this->Articles->newEntities($this->request->data(), [
  1839. * 'fieldList' => ['title', 'body'],
  1840. * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']]
  1841. * ]
  1842. * );
  1843. * ```
  1844. *
  1845. * You can use the `Model.beforeMarshal` event to modify request data
  1846. * before it is converted into entities.
  1847. */
  1848. public function newEntities(array $data, array $options = [])
  1849. {
  1850. if (!isset($options['associated'])) {
  1851. $options['associated'] = $this->_associations->keys();
  1852. }
  1853. $marshaller = $this->marshaller();
  1854. return $marshaller->many($data, $options);
  1855. }
  1856. /**
  1857. * {@inheritDoc}
  1858. *
  1859. * When merging HasMany or BelongsToMany associations, all the entities in the
  1860. * `$data` array will appear, those that can be matched by primary key will get
  1861. * the data merged, but those that cannot, will be discarded.
  1862. *
  1863. * You can limit fields that will be present in the merged entity by
  1864. * passing the `fieldList` option, which is also accepted for associations:
  1865. *
  1866. * ```
  1867. * $article = $this->Articles->patchEntity($article, $this->request->data(), [
  1868. * 'fieldList' => ['title', 'body'],
  1869. * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']]
  1870. * ]
  1871. * );
  1872. * ```
  1873. *
  1874. * By default, the data is validated before being passed to the entity. In
  1875. * the case of invalid fields, those will not be assigned to the entity.
  1876. * The `validate` option can be used to disable validation on the passed data:
  1877. *
  1878. * ```
  1879. * $article = $this->patchEntity($article, $this->request->data(),[
  1880. * 'validate' => false
  1881. * ]);
  1882. * ```
  1883. *
  1884. * You can use the `Model.beforeMarshal` event to modify request data
  1885. * before it is converted into entities.
  1886. */
  1887. public function patchEntity(EntityInterface $entity, array $data, array $options = [])
  1888. {
  1889. if (!isset($options['associated'])) {
  1890. $options['associated'] = $this->_associations->keys();
  1891. }
  1892. $marshaller = $this->marshaller();
  1893. return $marshaller->merge($entity, $data, $options);
  1894. }
  1895. /**
  1896. * {@inheritDoc}
  1897. *
  1898. * Those entries in `$entities` that cannot be matched to any record in
  1899. * `$data` will be discarded. Records in `$data` that could not be matched will
  1900. * be marshalled as a new entity.
  1901. *
  1902. * When merging HasMany or BelongsToMany associations, all the entities in the
  1903. * `$data` array will appear, those that can be matched by primary key will get
  1904. * the data merged, but those that cannot, will be discarded.
  1905. *
  1906. * You can limit fields that will be present in the merged entities by
  1907. * passing the `fieldList` option, which is also accepted for associations:
  1908. *
  1909. * ```
  1910. * $articles = $this->Articles->patchEntities($articles, $this->request->data(), [
  1911. * 'fieldList' => ['title', 'body'],
  1912. * 'associated' => ['Tags', 'Comments.Users' => ['fieldList' => 'username']]
  1913. * ]
  1914. * );
  1915. * ```
  1916. *
  1917. * You can use the `Model.beforeMarshal` event to modify request data
  1918. * before it is converted into entities.
  1919. */
  1920. public function patchEntities($entities, array $data, array $options = [])
  1921. {
  1922. if (!isset($options['associated'])) {
  1923. $options['associated'] = $this->_associations->keys();
  1924. }
  1925. $marshaller = $this->marshaller();
  1926. return $marshaller->mergeMany($entities, $data, $options);
  1927. }
  1928. /**
  1929. * Validator method used to check the uniqueness of a value for a column.
  1930. * This is meant to be used with the validation API and not to be called
  1931. * directly.
  1932. *
  1933. * ### Example:
  1934. *
  1935. * ```
  1936. * $validator->add('email', [
  1937. * 'unique' => ['rule' => 'validateUnique', 'provider' => 'table']
  1938. * ])
  1939. * ```
  1940. *
  1941. * Unique validation can be scoped to the value of another column:
  1942. *
  1943. * ```
  1944. * $validator->add('email', [
  1945. * 'unique' => [
  1946. * 'rule' => ['validateUnique', ['scope' => 'site_id']],
  1947. * 'provider' => 'table'
  1948. * ]
  1949. * ]);
  1950. * ```
  1951. *
  1952. * In the above example, the email uniqueness will be scoped to only rows having
  1953. * the same site_id. Scoping will only be used if the scoping field is present in
  1954. * the data to be validated.
  1955. *
  1956. * @param mixed $value The value of column to be checked for uniqueness
  1957. * @param array $options The options array, optionally containing the 'scope' key.
  1958. * May also be the validation context if there are no options.
  1959. * @param array|null $context Either the validation context or null.
  1960. * @return bool true if the value is unique
  1961. */
  1962. public function validateUnique($value, array $options, array $context = null)
  1963. {
  1964. if ($context === null) {
  1965. $context = $options;
  1966. }
  1967. $entity = new Entity(
  1968. $context['data'],
  1969. [
  1970. 'useSetters' => false,
  1971. 'markNew' => $context['newRecord'],
  1972. 'source' => $this->registryAlias()
  1973. ]
  1974. );
  1975. $fields = array_merge(
  1976. [$context['field']],
  1977. isset($options['scope']) ? (array)$options['scope'] : []
  1978. );
  1979. $rule = new IsUnique($fields);
  1980. return $rule($entity, ['repository' => $this]);
  1981. }
  1982. /**
  1983. * Get the Model callbacks this table is interested in.
  1984. *
  1985. * By implementing the conventional methods a table class is assumed
  1986. * to be interested in the related event.
  1987. *
  1988. * Override this method if you need to add non-conventional event listeners.
  1989. * Or if you want you table to listen to non-standard events.
  1990. *
  1991. * The conventional method map is:
  1992. *
  1993. * - Model.beforeMarshal => beforeMarshal
  1994. * - Model.beforeFind => beforeFind
  1995. * - Model.beforeSave => beforeSave
  1996. * - Model.afterSave => afterSave
  1997. * - Model.afterSaveCommit => afterSaveCommit
  1998. * - Model.beforeDelete => beforeDelete
  1999. * - Model.afterDelete => afterDelete
  2000. * - Model.afterDeleteCommit => afterDeleteCommit
  2001. * - Model.beforeRules => beforeRules
  2002. * - Model.afterRules => afterRules
  2003. *
  2004. * @return array
  2005. */
  2006. public function implementedEvents()
  2007. {
  2008. $eventMap = [
  2009. 'Model.beforeMarshal' => 'beforeMarshal',
  2010. 'Model.beforeFind' => 'beforeFind',
  2011. 'Model.beforeSave' => 'beforeSave',
  2012. 'Model.afterSave' => 'afterSave',
  2013. 'Model.afterSaveCommit' => 'afterSaveCommit',
  2014. 'Model.beforeDelete' => 'beforeDelete',
  2015. 'Model.afterDelete' => 'afterDelete',
  2016. 'Model.afterDeleteCommit' => 'afterDeleteCommit',
  2017. 'Model.beforeRules' => 'beforeRules',
  2018. 'Model.afterRules' => 'afterRules',
  2019. ];
  2020. $events = [];
  2021. foreach ($eventMap as $event => $method) {
  2022. if (!method_exists($this, $method)) {
  2023. continue;
  2024. }
  2025. $events[$event] = $method;
  2026. }
  2027. return $events;
  2028. }
  2029. /**
  2030. * {@inheritDoc}
  2031. *
  2032. * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
  2033. * @return \Cake\ORM\RulesChecker
  2034. */
  2035. public function buildRules(RulesChecker $rules)
  2036. {
  2037. return $rules;
  2038. }
  2039. /**
  2040. * Returns an array that can be used to describe the internal state of this
  2041. * object.
  2042. *
  2043. * @return array
  2044. */
  2045. public function __debugInfo()
  2046. {
  2047. $conn = $this->connection();
  2048. return [
  2049. 'registryAlias' => $this->registryAlias(),
  2050. 'table' => $this->table(),
  2051. 'alias' => $this->alias(),
  2052. 'entityClass' => $this->entityClass(),
  2053. 'associations' => $this->_associations->keys(),
  2054. 'behaviors' => $this->_behaviors->loaded(),
  2055. 'defaultConnection' => $this->defaultConnectionName(),
  2056. 'connectionName' => $conn ? $conn->configName() : null
  2057. ];
  2058. }
  2059. }