Table.php 77 KB

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