Table.php 99 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\ORM;
  16. use ArrayObject;
  17. use BadMethodCallException;
  18. use Cake\Core\App;
  19. use Cake\Database\Schema\TableSchema;
  20. use Cake\Database\Type;
  21. use Cake\Datasource\ConnectionInterface;
  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\EventDispatcherTrait;
  28. use Cake\Event\EventListenerInterface;
  29. use Cake\Event\EventManager;
  30. use Cake\ORM\Association\BelongsTo;
  31. use Cake\ORM\Association\BelongsToMany;
  32. use Cake\ORM\Association\HasMany;
  33. use Cake\ORM\Association\HasOne;
  34. use Cake\ORM\Exception\MissingEntityException;
  35. use Cake\ORM\Exception\PersistenceFailedException;
  36. use Cake\ORM\Exception\RolledbackTransactionException;
  37. use Cake\ORM\Rule\IsUnique;
  38. use Cake\Utility\Inflector;
  39. use Cake\Validation\ValidatorAwareInterface;
  40. use Cake\Validation\ValidatorAwareTrait;
  41. use InvalidArgumentException;
  42. use RuntimeException;
  43. /**
  44. * Represents a single database table.
  45. *
  46. * Exposes methods for retrieving data out of it, and manages the associations
  47. * this table has to other tables. Multiple instances of this class can be created
  48. * for the same database table with different aliases, this allows you to address
  49. * your database structure in a richer and more expressive way.
  50. *
  51. * ### Retrieving data
  52. *
  53. * The primary way to retrieve data is using Table::find(). See that method
  54. * for more information.
  55. *
  56. * ### Dynamic finders
  57. *
  58. * In addition to the standard find($type) finder methods, CakePHP provides dynamic
  59. * finder methods. These methods allow you to easily set basic conditions up. For example
  60. * to filter users by username you would call
  61. *
  62. * ```
  63. * $query = $users->findByUsername('mark');
  64. * ```
  65. *
  66. * You can also combine conditions on multiple fields using either `Or` or `And`:
  67. *
  68. * ```
  69. * $query = $users->findByUsernameOrEmail('mark', 'mark@example.org');
  70. * ```
  71. *
  72. * ### Bulk updates/deletes
  73. *
  74. * You can use Table::updateAll() and Table::deleteAll() to do bulk updates/deletes.
  75. * You should be aware that events will *not* be fired for bulk updates/deletes.
  76. *
  77. * ### Callbacks/events
  78. *
  79. * Table objects provide a few callbacks/events you can hook into to augment/replace
  80. * find operations. Each event uses the standard event subsystem in CakePHP
  81. *
  82. * - `beforeFind(Event $event, Query $query, ArrayObject $options, boolean $primary)`
  83. * Fired before each find operation. By stopping the event and supplying a
  84. * return value you can bypass the find operation entirely. Any changes done
  85. * to the $query instance will be retained for the rest of the find. The
  86. * $primary parameter indicates whether or not this is the root query,
  87. * or an associated query.
  88. *
  89. * - `buildValidator(Event $event, Validator $validator, string $name)`
  90. * Allows listeners to modify validation rules for the provided named validator.
  91. *
  92. * - `buildRules(Event $event, RulesChecker $rules)`
  93. * Allows listeners to modify the rules checker by adding more rules.
  94. *
  95. * - `beforeRules(Event $event, EntityInterface $entity, ArrayObject $options, string $operation)`
  96. * Fired before an entity is validated using the rules checker. By stopping this event,
  97. * you can return the final value of the rules checking operation.
  98. *
  99. * - `afterRules(Event $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation)`
  100. * Fired after the rules have been checked on the entity. By stopping this event,
  101. * you can return the final value of the rules checking operation.
  102. *
  103. * - `beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)`
  104. * Fired before each entity is saved. Stopping this event will abort the save
  105. * operation. When the event is stopped the result of the event will be returned.
  106. *
  107. * - `afterSave(Event $event, EntityInterface $entity, ArrayObject $options)`
  108. * Fired after an entity is saved.
  109. *
  110. * - `afterSaveCommit(Event $event, EntityInterface $entity, ArrayObject $options)`
  111. * Fired after the transaction in which the save operation is wrapped has been committed.
  112. * It’s also triggered for non atomic saves where database operations are implicitly committed.
  113. * The event is triggered only for the primary table on which save() is directly called.
  114. * The event is not triggered if a transaction is started before calling save.
  115. *
  116. * - `beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options)`
  117. * Fired before an entity is deleted. By stopping this event you will abort
  118. * the delete operation.
  119. *
  120. * - `afterDelete(Event $event, EntityInterface $entity, ArrayObject $options)`
  121. * Fired after an entity has been deleted.
  122. *
  123. * @see \Cake\Event\EventManager for reference on the events system.
  124. */
  125. class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface
  126. {
  127. use EventDispatcherTrait;
  128. use RulesAwareTrait;
  129. use ValidatorAwareTrait;
  130. /**
  131. * The alias this object is assigned to validators as.
  132. *
  133. * @var string
  134. */
  135. const VALIDATOR_PROVIDER_NAME = 'table';
  136. /**
  137. * The name of the event dispatched when a validator has been built.
  138. *
  139. * @var string
  140. */
  141. const BUILD_VALIDATOR_EVENT = 'Model.buildValidator';
  142. /**
  143. * The rules class name that is used.
  144. *
  145. * @var string
  146. */
  147. const RULES_CLASS = RulesChecker::class;
  148. /**
  149. * The IsUnique class name that is used.
  150. *
  151. * @var string
  152. */
  153. const IS_UNIQUE_CLASS = IsUnique::class;
  154. /**
  155. * Name of the table as it can be found in the database
  156. *
  157. * @var string
  158. */
  159. protected $_table;
  160. /**
  161. * Human name giving to this particular instance. Multiple objects representing
  162. * the same database table can exist by using different aliases.
  163. *
  164. * @var string
  165. */
  166. protected $_alias;
  167. /**
  168. * Connection instance
  169. *
  170. * @var \Cake\Database\Connection
  171. */
  172. protected $_connection;
  173. /**
  174. * The schema object containing a description of this table fields
  175. *
  176. * @var \Cake\Database\Schema\TableSchema
  177. */
  178. protected $_schema;
  179. /**
  180. * The name of the field that represents the primary key in the table
  181. *
  182. * @var string|array
  183. */
  184. protected $_primaryKey;
  185. /**
  186. * The name of the field that represents a human readable representation of a row
  187. *
  188. * @var string
  189. */
  190. protected $_displayField;
  191. /**
  192. * The associations container for this Table.
  193. *
  194. * @var \Cake\ORM\AssociationCollection
  195. */
  196. protected $_associations;
  197. /**
  198. * BehaviorRegistry for this table
  199. *
  200. * @var \Cake\ORM\BehaviorRegistry
  201. */
  202. protected $_behaviors;
  203. /**
  204. * The name of the class that represent a single row for this table
  205. *
  206. * @var string
  207. */
  208. protected $_entityClass;
  209. /**
  210. * Registry key used to create this table object
  211. *
  212. * @var string
  213. */
  214. protected $_registryAlias;
  215. /**
  216. * Initializes a new instance
  217. *
  218. * The $config array understands the following keys:
  219. *
  220. * - table: Name of the database table to represent
  221. * - alias: Alias to be assigned to this table (default to table name)
  222. * - connection: The connection instance to use
  223. * - entityClass: The fully namespaced class name of the entity class that will
  224. * represent rows in this table.
  225. * - schema: A \Cake\Database\Schema\TableSchema object or an array that can be
  226. * passed to it.
  227. * - eventManager: An instance of an event manager to use for internal events
  228. * - behaviors: A BehaviorRegistry. Generally not used outside of tests.
  229. * - associations: An AssociationCollection instance.
  230. * - validator: A Validator instance which is assigned as the "default"
  231. * validation set, or an associative array, where key is the name of the
  232. * validation set and value the Validator instance.
  233. *
  234. * @param array $config List of options for this table
  235. */
  236. public function __construct(array $config = [])
  237. {
  238. if (!empty($config['registryAlias'])) {
  239. $this->setRegistryAlias($config['registryAlias']);
  240. }
  241. if (!empty($config['table'])) {
  242. $this->setTable($config['table']);
  243. }
  244. if (!empty($config['alias'])) {
  245. $this->setAlias($config['alias']);
  246. }
  247. if (!empty($config['connection'])) {
  248. $this->setConnection($config['connection']);
  249. }
  250. if (!empty($config['schema'])) {
  251. $this->setSchema($config['schema']);
  252. }
  253. if (!empty($config['entityClass'])) {
  254. $this->setEntityClass($config['entityClass']);
  255. }
  256. $eventManager = $behaviors = $associations = null;
  257. if (!empty($config['eventManager'])) {
  258. $eventManager = $config['eventManager'];
  259. }
  260. if (!empty($config['behaviors'])) {
  261. $behaviors = $config['behaviors'];
  262. }
  263. if (!empty($config['associations'])) {
  264. $associations = $config['associations'];
  265. }
  266. if (!empty($config['validator'])) {
  267. if (!is_array($config['validator'])) {
  268. $this->setValidator(static::DEFAULT_VALIDATOR, $config['validator']);
  269. } else {
  270. foreach ($config['validator'] as $name => $validator) {
  271. $this->setValidator($name, $validator);
  272. }
  273. }
  274. }
  275. $this->_eventManager = $eventManager ?: new EventManager();
  276. $this->_behaviors = $behaviors ?: new BehaviorRegistry();
  277. $this->_behaviors->setTable($this);
  278. $this->_associations = $associations ?: new AssociationCollection();
  279. $this->initialize($config);
  280. $this->_eventManager->on($this);
  281. $this->dispatchEvent('Model.initialize');
  282. }
  283. /**
  284. * Get the default connection name.
  285. *
  286. * This method is used to get the fallback connection name if an
  287. * instance is created through the TableLocator without a connection.
  288. *
  289. * @return string
  290. * @see \Cake\ORM\Locator\TableLocator::get()
  291. */
  292. public static function defaultConnectionName()
  293. {
  294. return 'default';
  295. }
  296. /**
  297. * Initialize a table instance. Called after the constructor.
  298. *
  299. * You can use this method to define associations, attach behaviors
  300. * define validation and do any other initialization logic you need.
  301. *
  302. * ```
  303. * public function initialize(array $config)
  304. * {
  305. * $this->belongsTo('Users');
  306. * $this->belongsToMany('Tagging.Tags');
  307. * $this->setPrimaryKey('something_else');
  308. * }
  309. * ```
  310. *
  311. * @param array $config Configuration options passed to the constructor
  312. * @return void
  313. */
  314. public function initialize(array $config)
  315. {
  316. }
  317. /**
  318. * Sets the database table name.
  319. *
  320. * This can include the database schema name in the form 'schema.table'.
  321. * If the name must be quoted, enable automatic identifier quoting.
  322. *
  323. * @param string $table Table name.
  324. * @return $this
  325. */
  326. public function setTable($table)
  327. {
  328. $this->_table = $table;
  329. return $this;
  330. }
  331. /**
  332. * Returns the database table name.
  333. *
  334. * This can include the database schema name if set using `setTable()`.
  335. *
  336. * @return string
  337. */
  338. public function getTable()
  339. {
  340. if ($this->_table === null) {
  341. $table = namespaceSplit(get_class($this));
  342. $table = substr(end($table), 0, -5);
  343. if (!$table) {
  344. $table = $this->getAlias();
  345. }
  346. $this->_table = Inflector::underscore($table);
  347. }
  348. return $this->_table;
  349. }
  350. /**
  351. * Returns the database table name or sets a new one.
  352. *
  353. * @deprecated 3.4.0 Use setTable()/getTable() instead.
  354. * @param string|null $table the new table name
  355. * @return string
  356. */
  357. public function table($table = null)
  358. {
  359. deprecationWarning(
  360. get_called_class() . '::table() is deprecated. ' .
  361. 'Use setTable()/getTable() instead.'
  362. );
  363. if ($table !== null) {
  364. $this->setTable($table);
  365. }
  366. return $this->getTable();
  367. }
  368. /**
  369. * Sets the table alias.
  370. *
  371. * @param string $alias Table alias
  372. * @return $this
  373. */
  374. public function setAlias($alias)
  375. {
  376. $this->_alias = $alias;
  377. return $this;
  378. }
  379. /**
  380. * Returns the table alias.
  381. *
  382. * @return string
  383. */
  384. public function getAlias()
  385. {
  386. if ($this->_alias === null) {
  387. $alias = namespaceSplit(get_class($this));
  388. $alias = substr(end($alias), 0, -5) ?: $this->_table;
  389. $this->_alias = $alias;
  390. }
  391. return $this->_alias;
  392. }
  393. /**
  394. * {@inheritDoc}
  395. * @deprecated 3.4.0 Use setAlias()/getAlias() instead.
  396. */
  397. public function alias($alias = null)
  398. {
  399. deprecationWarning(
  400. get_called_class() . '::alias() is deprecated. ' .
  401. 'Use setAlias()/getAlias() instead.'
  402. );
  403. if ($alias !== null) {
  404. $this->setAlias($alias);
  405. }
  406. return $this->getAlias();
  407. }
  408. /**
  409. * Alias a field with the table's current alias.
  410. *
  411. * If field is already aliased it will result in no-op.
  412. *
  413. * @param string $field The field to alias.
  414. * @return string The field prefixed with the table alias.
  415. */
  416. public function aliasField($field)
  417. {
  418. if (strpos($field, '.') !== false) {
  419. return $field;
  420. }
  421. return $this->getAlias() . '.' . $field;
  422. }
  423. /**
  424. * Sets the table registry key used to create this table instance.
  425. *
  426. * @param string $registryAlias The key used to access this object.
  427. * @return $this
  428. */
  429. public function setRegistryAlias($registryAlias)
  430. {
  431. $this->_registryAlias = $registryAlias;
  432. return $this;
  433. }
  434. /**
  435. * Returns the table registry key used to create this table instance.
  436. *
  437. * @return string
  438. */
  439. public function getRegistryAlias()
  440. {
  441. if ($this->_registryAlias === null) {
  442. $this->_registryAlias = $this->getAlias();
  443. }
  444. return $this->_registryAlias;
  445. }
  446. /**
  447. * Returns the table registry key used to create this table instance or sets one.
  448. *
  449. * @deprecated 3.4.0 Use setRegistryAlias()/getRegistryAlias() instead.
  450. * @param string|null $registryAlias the key used to access this object
  451. * @return string
  452. */
  453. public function registryAlias($registryAlias = null)
  454. {
  455. deprecationWarning(
  456. get_called_class() . '::registryAlias() is deprecated. ' .
  457. 'Use setRegistryAlias()/getRegistryAlias() instead.'
  458. );
  459. if ($registryAlias !== null) {
  460. $this->setRegistryAlias($registryAlias);
  461. }
  462. return $this->getRegistryAlias();
  463. }
  464. /**
  465. * Sets the connection instance.
  466. *
  467. * @param \Cake\Database\Connection $connection The connection instance
  468. * @return $this
  469. */
  470. public function setConnection(ConnectionInterface $connection)
  471. {
  472. $this->_connection = $connection;
  473. return $this;
  474. }
  475. /**
  476. * Returns the connection instance.
  477. *
  478. * @return \Cake\Database\Connection
  479. */
  480. public function getConnection()
  481. {
  482. return $this->_connection;
  483. }
  484. /**
  485. * Returns the connection instance or sets a new one
  486. *
  487. * @deprecated 3.4.0 Use setConnection()/getConnection() instead.
  488. * @param \Cake\Datasource\ConnectionInterface|null $connection The new connection instance
  489. * @return \Cake\Datasource\ConnectionInterface
  490. */
  491. public function connection(ConnectionInterface $connection = null)
  492. {
  493. deprecationWarning(
  494. get_called_class() . '::connection() is deprecated. ' .
  495. 'Use setConnection()/getConnection() instead.'
  496. );
  497. if ($connection !== null) {
  498. $this->setConnection($connection);
  499. }
  500. return $this->getConnection();
  501. }
  502. /**
  503. * Returns the schema table object describing this table's properties.
  504. *
  505. * @return \Cake\Database\Schema\TableSchema
  506. */
  507. public function getSchema()
  508. {
  509. if ($this->_schema === null) {
  510. $this->_schema = $this->_initializeSchema(
  511. $this->getConnection()
  512. ->getSchemaCollection()
  513. ->describe($this->getTable())
  514. );
  515. }
  516. return $this->_schema;
  517. }
  518. /**
  519. * Sets the schema table object describing this table's properties.
  520. *
  521. * If an array is passed, a new TableSchema will be constructed
  522. * out of it and used as the schema for this table.
  523. *
  524. * @param array|\Cake\Database\Schema\TableSchema $schema Schema to be used for this table
  525. * @return $this
  526. */
  527. public function setSchema($schema)
  528. {
  529. if (is_array($schema)) {
  530. $constraints = [];
  531. if (isset($schema['_constraints'])) {
  532. $constraints = $schema['_constraints'];
  533. unset($schema['_constraints']);
  534. }
  535. $schema = new TableSchema($this->getTable(), $schema);
  536. foreach ($constraints as $name => $value) {
  537. $schema->addConstraint($name, $value);
  538. }
  539. }
  540. $this->_schema = $schema;
  541. return $this;
  542. }
  543. /**
  544. * Returns the schema table object describing this table's properties.
  545. *
  546. * If a TableSchema is passed, it will be used for this table
  547. * instead of the default one.
  548. *
  549. * If an array is passed, a new TableSchema will be constructed
  550. * out of it and used as the schema for this table.
  551. *
  552. * @deprecated 3.4.0 Use setSchema()/getSchema() instead.
  553. * @param array|\Cake\Database\Schema\TableSchema|null $schema New schema to be used for this table
  554. * @return \Cake\Database\Schema\TableSchema
  555. */
  556. public function schema($schema = null)
  557. {
  558. deprecationWarning(
  559. get_called_class() . '::schema() is deprecated. ' .
  560. 'Use setSchema()/getSchema() instead.'
  561. );
  562. if ($schema !== null) {
  563. $this->setSchema($schema);
  564. }
  565. return $this->getSchema();
  566. }
  567. /**
  568. * Override this function in order to alter the schema used by this table.
  569. * This function is only called after fetching the schema out of the database.
  570. * If you wish to provide your own schema to this table without touching the
  571. * database, you can override schema() or inject the definitions though that
  572. * method.
  573. *
  574. * ### Example:
  575. *
  576. * ```
  577. * protected function _initializeSchema(\Cake\Database\Schema\TableSchema $schema) {
  578. * $schema->setColumnType('preferences', 'json');
  579. * return $schema;
  580. * }
  581. * ```
  582. *
  583. * @param \Cake\Database\Schema\TableSchema $schema The table definition fetched from database.
  584. * @return \Cake\Database\Schema\TableSchema the altered schema
  585. */
  586. protected function _initializeSchema(TableSchema $schema)
  587. {
  588. return $schema;
  589. }
  590. /**
  591. * Test to see if a Table has a specific field/column.
  592. *
  593. * Delegates to the schema object and checks for column presence
  594. * using the Schema\Table instance.
  595. *
  596. * @param string $field The field to check for.
  597. * @return bool True if the field exists, false if it does not.
  598. */
  599. public function hasField($field)
  600. {
  601. $schema = $this->getSchema();
  602. return $schema->getColumn($field) !== null;
  603. }
  604. /**
  605. * Sets the primary key field name.
  606. *
  607. * @param string|array $key Sets a new name to be used as primary key
  608. * @return $this
  609. */
  610. public function setPrimaryKey($key)
  611. {
  612. $this->_primaryKey = $key;
  613. return $this;
  614. }
  615. /**
  616. * Returns the primary key field name.
  617. *
  618. * @return string|array
  619. */
  620. public function getPrimaryKey()
  621. {
  622. if ($this->_primaryKey === null) {
  623. $key = (array)$this->getSchema()->primaryKey();
  624. if (count($key) === 1) {
  625. $key = $key[0];
  626. }
  627. $this->_primaryKey = $key;
  628. }
  629. return $this->_primaryKey;
  630. }
  631. /**
  632. * Returns the primary key field name or sets a new one
  633. *
  634. * @deprecated 3.4.0 Use setPrimaryKey()/getPrimaryKey() instead.
  635. * @param string|array|null $key Sets a new name to be used as primary key
  636. * @return string|array
  637. */
  638. public function primaryKey($key = null)
  639. {
  640. deprecationWarning(
  641. get_called_class() . '::primaryKey() is deprecated. ' .
  642. 'Use setPrimaryKey()/getPrimaryKey() instead.'
  643. );
  644. if ($key !== null) {
  645. $this->setPrimaryKey($key);
  646. }
  647. return $this->getPrimaryKey();
  648. }
  649. /**
  650. * Sets the display field.
  651. *
  652. * @param string $key Name to be used as display field.
  653. * @return $this
  654. */
  655. public function setDisplayField($key)
  656. {
  657. $this->_displayField = $key;
  658. return $this;
  659. }
  660. /**
  661. * Returns the display field.
  662. *
  663. * @return string
  664. */
  665. public function getDisplayField()
  666. {
  667. if ($this->_displayField === null) {
  668. $schema = $this->getSchema();
  669. $primary = (array)$this->getPrimaryKey();
  670. $this->_displayField = array_shift($primary);
  671. if ($schema->getColumn('title')) {
  672. $this->_displayField = 'title';
  673. }
  674. if ($schema->getColumn('name')) {
  675. $this->_displayField = 'name';
  676. }
  677. }
  678. return $this->_displayField;
  679. }
  680. /**
  681. * Returns the display field or sets a new one
  682. *
  683. * @deprecated 3.4.0 Use setDisplayField()/getDisplayField() instead.
  684. * @param string|null $key sets a new name to be used as display field
  685. * @return string
  686. */
  687. public function displayField($key = null)
  688. {
  689. deprecationWarning(
  690. get_called_class() . '::displayField() is deprecated. ' .
  691. 'Use setDisplayField()/getDisplayField() instead.'
  692. );
  693. if ($key !== null) {
  694. $this->setDisplayField($key);
  695. return $key;
  696. }
  697. return $this->getDisplayField();
  698. }
  699. /**
  700. * Returns the class used to hydrate rows for this table.
  701. *
  702. * @return string
  703. */
  704. public function getEntityClass()
  705. {
  706. if (!$this->_entityClass) {
  707. $default = Entity::class;
  708. $self = get_called_class();
  709. $parts = explode('\\', $self);
  710. if ($self === __CLASS__ || count($parts) < 3) {
  711. return $this->_entityClass = $default;
  712. }
  713. $alias = Inflector::classify(Inflector::underscore(substr(array_pop($parts), 0, -5)));
  714. $name = implode('\\', array_slice($parts, 0, -1)) . '\\Entity\\' . $alias;
  715. if (!class_exists($name)) {
  716. return $this->_entityClass = $default;
  717. }
  718. $class = App::className($name, 'Model/Entity');
  719. if (!$class) {
  720. throw new MissingEntityException([$name]);
  721. }
  722. $this->_entityClass = $class;
  723. }
  724. return $this->_entityClass;
  725. }
  726. /**
  727. * Sets the class used to hydrate rows for this table.
  728. *
  729. * @param string $name The name of the class to use
  730. * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found
  731. * @return $this
  732. */
  733. public function setEntityClass($name)
  734. {
  735. $class = App::className($name, 'Model/Entity');
  736. if (!$class) {
  737. throw new MissingEntityException([$name]);
  738. }
  739. $this->_entityClass = $class;
  740. return $this;
  741. }
  742. /**
  743. * Returns the class used to hydrate rows for this table or sets
  744. * a new one
  745. *
  746. * @deprecated 3.4.0 Use setEntityClass()/getEntityClass() instead.
  747. * @param string|null $name The name of the class to use
  748. * @throws \Cake\ORM\Exception\MissingEntityException when the entity class cannot be found
  749. * @return string
  750. */
  751. public function entityClass($name = null)
  752. {
  753. deprecationWarning(
  754. get_called_class() . '::entityClass() is deprecated. ' .
  755. 'Use setEntityClass()/getEntityClass() instead.'
  756. );
  757. if ($name !== null) {
  758. $this->setEntityClass($name);
  759. }
  760. return $this->getEntityClass();
  761. }
  762. /**
  763. * Add a behavior.
  764. *
  765. * Adds a behavior to this table's behavior collection. Behaviors
  766. * provide an easy way to create horizontally re-usable features
  767. * that can provide trait like functionality, and allow for events
  768. * to be listened to.
  769. *
  770. * Example:
  771. *
  772. * Load a behavior, with some settings.
  773. *
  774. * ```
  775. * $this->addBehavior('Tree', ['parent' => 'parentId']);
  776. * ```
  777. *
  778. * Behaviors are generally loaded during Table::initialize().
  779. *
  780. * @param string $name The name of the behavior. Can be a short class reference.
  781. * @param array $options The options for the behavior to use.
  782. * @return $this
  783. * @throws \RuntimeException If a behavior is being reloaded.
  784. * @see \Cake\ORM\Behavior
  785. */
  786. public function addBehavior($name, array $options = [])
  787. {
  788. $this->_behaviors->load($name, $options);
  789. return $this;
  790. }
  791. /**
  792. * Adds an array of behaviors to the table's behavior collection.
  793. *
  794. * Example:
  795. *
  796. * ```
  797. * $this->addBehaviors([
  798. * 'Timestamp',
  799. * 'Tree' => ['level' => 'level'],
  800. * ]);
  801. * ```
  802. *
  803. * @param array $behaviors All of the behaviors to load.
  804. * @return $this
  805. * @throws \RuntimeException If a behavior is being reloaded.
  806. */
  807. public function addBehaviors(array $behaviors)
  808. {
  809. foreach ($behaviors as $name => $options) {
  810. if (is_int($name)) {
  811. $name = $options;
  812. $options = [];
  813. }
  814. $this->addBehavior($name, $options);
  815. }
  816. return $this;
  817. }
  818. /**
  819. * Removes a behavior from this table's behavior registry.
  820. *
  821. * Example:
  822. *
  823. * Remove a behavior from this table.
  824. *
  825. * ```
  826. * $this->removeBehavior('Tree');
  827. * ```
  828. *
  829. * @param string $name The alias that the behavior was added with.
  830. * @return $this
  831. * @see \Cake\ORM\Behavior
  832. */
  833. public function removeBehavior($name)
  834. {
  835. $this->_behaviors->unload($name);
  836. return $this;
  837. }
  838. /**
  839. * Returns the behavior registry for this table.
  840. *
  841. * @return \Cake\ORM\BehaviorRegistry The BehaviorRegistry instance.
  842. */
  843. public function behaviors()
  844. {
  845. return $this->_behaviors;
  846. }
  847. /**
  848. * Get a behavior from the registry.
  849. *
  850. * @param string $name The behavior alias to get from the registry.
  851. * @return \Cake\ORM\Behavior
  852. * @throws \InvalidArgumentException If the behavior does not exist.
  853. */
  854. public function getBehavior($name)
  855. {
  856. /** @var \Cake\ORM\Behavior $behavior */
  857. $behavior = $this->_behaviors->get($name);
  858. if ($behavior === null) {
  859. throw new InvalidArgumentException(sprintf(
  860. 'The %s behavior is not defined on %s.',
  861. $name,
  862. get_class($this)
  863. ));
  864. }
  865. return $behavior;
  866. }
  867. /**
  868. * Check if a behavior with the given alias has been loaded.
  869. *
  870. * @param string $name The behavior alias to check.
  871. * @return bool Whether or not the behavior exists.
  872. */
  873. public function hasBehavior($name)
  874. {
  875. return $this->_behaviors->has($name);
  876. }
  877. /**
  878. * Returns an association object configured for the specified alias if any.
  879. *
  880. * @deprecated 3.6.0 Use getAssociation() and Table::hasAssociation() instead.
  881. * @param string $name the alias used for the association.
  882. * @return \Cake\ORM\Association|null Either the association or null.
  883. */
  884. public function association($name)
  885. {
  886. deprecationWarning('Use Table::getAssociation() and Table::hasAssociation() instead.');
  887. return $this->findAssociation($name);
  888. }
  889. /**
  890. * Returns an association object configured for the specified alias.
  891. *
  892. * The name argument also supports dot syntax to access deeper associations.
  893. *
  894. * ```
  895. * $users = $this->getAssociation('Articles.Comments.Users');
  896. * ```
  897. *
  898. * Note that this method requires the association to be present or otherwise
  899. * throws an exception.
  900. * If you are not sure, use hasAssociation() before calling this method.
  901. *
  902. * @param string $name The alias used for the association.
  903. * @return \Cake\ORM\Association The association.
  904. * @throws \InvalidArgumentException
  905. */
  906. public function getAssociation($name)
  907. {
  908. $association = $this->findAssociation($name);
  909. if (!$association) {
  910. throw new InvalidArgumentException("The {$name} association is not defined on {$this->getAlias()}.");
  911. }
  912. return $association;
  913. }
  914. /**
  915. * Checks whether a specific association exists on this Table instance.
  916. *
  917. * The name argument also supports dot syntax to access deeper associations.
  918. *
  919. * ```
  920. * $hasUsers = $this->hasAssociation('Articles.Comments.Users');
  921. * ```
  922. *
  923. * @param string $name The alias used for the association.
  924. * @return bool
  925. */
  926. public function hasAssociation($name)
  927. {
  928. return $this->findAssociation($name) !== null;
  929. }
  930. /**
  931. * Returns an association object configured for the specified alias if any.
  932. *
  933. * The name argument also supports dot syntax to access deeper associations.
  934. *
  935. * ```
  936. * $users = $this->getAssociation('Articles.Comments.Users');
  937. * ```
  938. *
  939. * @param string $name The alias used for the association.
  940. * @return \Cake\ORM\Association|null Either the association or null.
  941. */
  942. protected function findAssociation($name)
  943. {
  944. if (strpos($name, '.') === false) {
  945. return $this->_associations->get($name);
  946. }
  947. list($name, $next) = array_pad(explode('.', $name, 2), 2, null);
  948. $result = $this->_associations->get($name);
  949. if ($result !== null && $next !== null) {
  950. $result = $result->getTarget()->getAssociation($next);
  951. }
  952. return $result;
  953. }
  954. /**
  955. * Get the associations collection for this table.
  956. *
  957. * @return \Cake\ORM\AssociationCollection|\Cake\ORM\Association[] The collection of association objects.
  958. */
  959. public function associations()
  960. {
  961. return $this->_associations;
  962. }
  963. /**
  964. * Setup multiple associations.
  965. *
  966. * It takes an array containing set of table names indexed by association type
  967. * as argument:
  968. *
  969. * ```
  970. * $this->Posts->addAssociations([
  971. * 'belongsTo' => [
  972. * 'Users' => ['className' => 'App\Model\Table\UsersTable']
  973. * ],
  974. * 'hasMany' => ['Comments'],
  975. * 'belongsToMany' => ['Tags']
  976. * ]);
  977. * ```
  978. *
  979. * Each association type accepts multiple associations where the keys
  980. * are the aliases, and the values are association config data. If numeric
  981. * keys are used the values will be treated as association aliases.
  982. *
  983. * @param array $params Set of associations to bind (indexed by association type)
  984. * @return $this
  985. * @see \Cake\ORM\Table::belongsTo()
  986. * @see \Cake\ORM\Table::hasOne()
  987. * @see \Cake\ORM\Table::hasMany()
  988. * @see \Cake\ORM\Table::belongsToMany()
  989. */
  990. public function addAssociations(array $params)
  991. {
  992. foreach ($params as $assocType => $tables) {
  993. foreach ($tables as $associated => $options) {
  994. if (is_numeric($associated)) {
  995. $associated = $options;
  996. $options = [];
  997. }
  998. $this->{$assocType}($associated, $options);
  999. }
  1000. }
  1001. return $this;
  1002. }
  1003. /**
  1004. * Creates a new BelongsTo association between this table and a target
  1005. * table. A "belongs to" association is a N-1 relationship where this table
  1006. * is the N side, and where there is a single associated record in the target
  1007. * table for each one in this table.
  1008. *
  1009. * Target table can be inferred by its name, which is provided in the
  1010. * first argument, or you can either pass the to be instantiated or
  1011. * an instance of it directly.
  1012. *
  1013. * The options array accept the following keys:
  1014. *
  1015. * - className: The class name of the target table object
  1016. * - targetTable: An instance of a table object to be used as the target table
  1017. * - foreignKey: The name of the field to use as foreign key, if false none
  1018. * will be used
  1019. * - conditions: array with a list of conditions to filter the join with
  1020. * - joinType: The type of join to be used (e.g. INNER)
  1021. * - strategy: The loading strategy to use. 'join' and 'select' are supported.
  1022. * - finder: The finder method to use when loading records from this association.
  1023. * Defaults to 'all'. When the strategy is 'join', only the fields, containments,
  1024. * and where conditions will be used from the finder.
  1025. *
  1026. * This method will return the association object that was built.
  1027. *
  1028. * @param string $associated the alias for the target table. This is used to
  1029. * uniquely identify the association
  1030. * @param array $options list of options to configure the association definition
  1031. * @return \Cake\ORM\Association\BelongsTo
  1032. */
  1033. public function belongsTo($associated, array $options = [])
  1034. {
  1035. $options += ['sourceTable' => $this];
  1036. /** @var \Cake\ORM\Association\BelongsTo $association */
  1037. $association = $this->_associations->load(BelongsTo::class, $associated, $options);
  1038. return $association;
  1039. }
  1040. /**
  1041. * Creates a new HasOne association between this table and a target
  1042. * table. A "has one" association is a 1-1 relationship.
  1043. *
  1044. * Target table can be inferred by its name, which is provided in the
  1045. * first argument, or you can either pass the class name to be instantiated or
  1046. * an instance of it directly.
  1047. *
  1048. * The options array accept the following keys:
  1049. *
  1050. * - className: The class name of the target table object
  1051. * - targetTable: An instance of a table object to be used as the target table
  1052. * - foreignKey: The name of the field to use as foreign key, if false none
  1053. * will be used
  1054. * - dependent: Set to true if you want CakePHP to cascade deletes to the
  1055. * associated table when an entity is removed on this table. The delete operation
  1056. * on the associated table will not cascade further. To get recursive cascades enable
  1057. * `cascadeCallbacks` as well. Set to false if you don't want CakePHP to remove
  1058. * associated data, or when you are using database constraints.
  1059. * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on
  1060. * cascaded deletes. If false the ORM will use deleteAll() to remove data.
  1061. * When true records will be loaded and then deleted.
  1062. * - conditions: array with a list of conditions to filter the join with
  1063. * - joinType: The type of join to be used (e.g. LEFT)
  1064. * - strategy: The loading strategy to use. 'join' and 'select' are supported.
  1065. * - finder: The finder method to use when loading records from this association.
  1066. * Defaults to 'all'. When the strategy is 'join', only the fields, containments,
  1067. * and where conditions will be used from the finder.
  1068. *
  1069. * This method will return the association object that was built.
  1070. *
  1071. * @param string $associated the alias for the target table. This is used to
  1072. * uniquely identify the association
  1073. * @param array $options list of options to configure the association definition
  1074. * @return \Cake\ORM\Association\HasOne
  1075. */
  1076. public function hasOne($associated, array $options = [])
  1077. {
  1078. $options += ['sourceTable' => $this];
  1079. /** @var \Cake\ORM\Association\HasOne $association */
  1080. $association = $this->_associations->load(HasOne::class, $associated, $options);
  1081. return $association;
  1082. }
  1083. /**
  1084. * Creates a new HasMany association between this table and a target
  1085. * table. A "has many" association is a 1-N relationship.
  1086. *
  1087. * Target table can be inferred by its name, which is provided in the
  1088. * first argument, or you can either pass the class name to be instantiated or
  1089. * an instance of it directly.
  1090. *
  1091. * The options array accept the following keys:
  1092. *
  1093. * - className: The class name of the target table object
  1094. * - targetTable: An instance of a table object to be used as the target table
  1095. * - foreignKey: The name of the field to use as foreign key, if false none
  1096. * will be used
  1097. * - dependent: Set to true if you want CakePHP to cascade deletes to the
  1098. * associated table when an entity is removed on this table. The delete operation
  1099. * on the associated table will not cascade further. To get recursive cascades enable
  1100. * `cascadeCallbacks` as well. Set to false if you don't want CakePHP to remove
  1101. * associated data, or when you are using database constraints.
  1102. * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on
  1103. * cascaded deletes. If false the ORM will use deleteAll() to remove data.
  1104. * When true records will be loaded and then deleted.
  1105. * - conditions: array with a list of conditions to filter the join with
  1106. * - sort: The order in which results for this association should be returned
  1107. * - saveStrategy: Either 'append' or 'replace'. When 'append' the current records
  1108. * are appended to any records in the database. When 'replace' associated records
  1109. * not in the current set will be removed. If the foreign key is a null able column
  1110. * or if `dependent` is true records will be orphaned.
  1111. * - strategy: The strategy to be used for selecting results Either 'select'
  1112. * or 'subquery'. If subquery is selected the query used to return results
  1113. * in the source table will be used as conditions for getting rows in the
  1114. * target table.
  1115. * - finder: The finder method to use when loading records from this association.
  1116. * Defaults to 'all'.
  1117. *
  1118. * This method will return the association object that was built.
  1119. *
  1120. * @param string $associated the alias for the target table. This is used to
  1121. * uniquely identify the association
  1122. * @param array $options list of options to configure the association definition
  1123. * @return \Cake\ORM\Association\HasMany
  1124. */
  1125. public function hasMany($associated, array $options = [])
  1126. {
  1127. $options += ['sourceTable' => $this];
  1128. /** @var \Cake\ORM\Association\HasMany $association */
  1129. $association = $this->_associations->load(HasMany::class, $associated, $options);
  1130. return $association;
  1131. }
  1132. /**
  1133. * Creates a new BelongsToMany association between this table and a target
  1134. * table. A "belongs to many" association is a M-N relationship.
  1135. *
  1136. * Target table can be inferred by its name, which is provided in the
  1137. * first argument, or you can either pass the class name to be instantiated or
  1138. * an instance of it directly.
  1139. *
  1140. * The options array accept the following keys:
  1141. *
  1142. * - className: The class name of the target table object.
  1143. * - targetTable: An instance of a table object to be used as the target table.
  1144. * - foreignKey: The name of the field to use as foreign key.
  1145. * - targetForeignKey: The name of the field to use as the target foreign key.
  1146. * - joinTable: The name of the table representing the link between the two
  1147. * - through: If you choose to use an already instantiated link table, set this
  1148. * key to a configured Table instance containing associations to both the source
  1149. * and target tables in this association.
  1150. * - dependent: Set to false, if you do not want junction table records removed
  1151. * when an owning record is removed.
  1152. * - cascadeCallbacks: Set to true if you want CakePHP to fire callbacks on
  1153. * cascaded deletes. If false the ORM will use deleteAll() to remove data.
  1154. * When true join/junction table records will be loaded and then deleted.
  1155. * - conditions: array with a list of conditions to filter the join with.
  1156. * - sort: The order in which results for this association should be returned.
  1157. * - strategy: The strategy to be used for selecting results Either 'select'
  1158. * or 'subquery'. If subquery is selected the query used to return results
  1159. * in the source table will be used as conditions for getting rows in the
  1160. * target table.
  1161. * - saveStrategy: Either 'append' or 'replace'. Indicates the mode to be used
  1162. * for saving associated entities. The former will only create new links
  1163. * between both side of the relation and the latter will do a wipe and
  1164. * replace to create the links between the passed entities when saving.
  1165. * - strategy: The loading strategy to use. 'select' and 'subquery' are supported.
  1166. * - finder: The finder method to use when loading records from this association.
  1167. * Defaults to 'all'.
  1168. *
  1169. * This method will return the association object that was built.
  1170. *
  1171. * @param string $associated the alias for the target table. This is used to
  1172. * uniquely identify the association
  1173. * @param array $options list of options to configure the association definition
  1174. * @return \Cake\ORM\Association\BelongsToMany
  1175. */
  1176. public function belongsToMany($associated, array $options = [])
  1177. {
  1178. $options += ['sourceTable' => $this];
  1179. /** @var \Cake\ORM\Association\BelongsToMany $association */
  1180. $association = $this->_associations->load(BelongsToMany::class, $associated, $options);
  1181. return $association;
  1182. }
  1183. /**
  1184. * Creates a new Query for this repository and applies some defaults based on the
  1185. * type of search that was selected.
  1186. *
  1187. * ### Model.beforeFind event
  1188. *
  1189. * Each find() will trigger a `Model.beforeFind` event for all attached
  1190. * listeners. Any listener can set a valid result set using $query
  1191. *
  1192. * By default, `$options` will recognize the following keys:
  1193. *
  1194. * - fields
  1195. * - conditions
  1196. * - order
  1197. * - limit
  1198. * - offset
  1199. * - page
  1200. * - group
  1201. * - having
  1202. * - contain
  1203. * - join
  1204. *
  1205. * ### Usage
  1206. *
  1207. * Using the options array:
  1208. *
  1209. * ```
  1210. * $query = $articles->find('all', [
  1211. * 'conditions' => ['published' => 1],
  1212. * 'limit' => 10,
  1213. * 'contain' => ['Users', 'Comments']
  1214. * ]);
  1215. * ```
  1216. *
  1217. * Using the builder interface:
  1218. *
  1219. * ```
  1220. * $query = $articles->find()
  1221. * ->where(['published' => 1])
  1222. * ->limit(10)
  1223. * ->contain(['Users', 'Comments']);
  1224. * ```
  1225. *
  1226. * ### Calling finders
  1227. *
  1228. * The find() method is the entry point for custom finder methods.
  1229. * You can invoke a finder by specifying the type:
  1230. *
  1231. * ```
  1232. * $query = $articles->find('published');
  1233. * ```
  1234. *
  1235. * Would invoke the `findPublished` method.
  1236. *
  1237. * @param string $type the type of query to perform
  1238. * @param array|\ArrayAccess $options An array that will be passed to Query::applyOptions()
  1239. * @return \Cake\ORM\Query The query builder
  1240. */
  1241. public function find($type = 'all', $options = [])
  1242. {
  1243. $query = $this->query();
  1244. $query->select();
  1245. return $this->callFinder($type, $query, $options);
  1246. }
  1247. /**
  1248. * Returns the query as passed.
  1249. *
  1250. * By default findAll() applies no conditions, you
  1251. * can override this method in subclasses to modify how `find('all')` works.
  1252. *
  1253. * @param \Cake\ORM\Query $query The query to find with
  1254. * @param array $options The options to use for the find
  1255. * @return \Cake\ORM\Query The query builder
  1256. */
  1257. public function findAll(Query $query, array $options)
  1258. {
  1259. return $query;
  1260. }
  1261. /**
  1262. * Sets up a query object so results appear as an indexed array, useful for any
  1263. * place where you would want a list such as for populating input select boxes.
  1264. *
  1265. * When calling this finder, the fields passed are used to determine what should
  1266. * be used as the array key, value and optionally what to group the results by.
  1267. * By default the primary key for the model is used for the key, and the display
  1268. * field as value.
  1269. *
  1270. * The results of this finder will be in the following form:
  1271. *
  1272. * ```
  1273. * [
  1274. * 1 => 'value for id 1',
  1275. * 2 => 'value for id 2',
  1276. * 4 => 'value for id 4'
  1277. * ]
  1278. * ```
  1279. *
  1280. * You can specify which property will be used as the key and which as value
  1281. * by using the `$options` array, when not specified, it will use the results
  1282. * of calling `primaryKey` and `displayField` respectively in this table:
  1283. *
  1284. * ```
  1285. * $table->find('list', [
  1286. * 'keyField' => 'name',
  1287. * 'valueField' => 'age'
  1288. * ]);
  1289. * ```
  1290. *
  1291. * Results can be put together in bigger groups when they share a property, you
  1292. * can customize the property to use for grouping by setting `groupField`:
  1293. *
  1294. * ```
  1295. * $table->find('list', [
  1296. * 'groupField' => 'category_id',
  1297. * ]);
  1298. * ```
  1299. *
  1300. * When using a `groupField` results will be returned in this format:
  1301. *
  1302. * ```
  1303. * [
  1304. * 'group_1' => [
  1305. * 1 => 'value for id 1',
  1306. * 2 => 'value for id 2',
  1307. * ]
  1308. * 'group_2' => [
  1309. * 4 => 'value for id 4'
  1310. * ]
  1311. * ]
  1312. * ```
  1313. *
  1314. * @param \Cake\ORM\Query $query The query to find with
  1315. * @param array $options The options for the find
  1316. * @return \Cake\ORM\Query The query builder
  1317. */
  1318. public function findList(Query $query, array $options)
  1319. {
  1320. $options += [
  1321. 'keyField' => $this->getPrimaryKey(),
  1322. 'valueField' => $this->getDisplayField(),
  1323. 'groupField' => null
  1324. ];
  1325. if (isset($options['idField'])) {
  1326. $options['keyField'] = $options['idField'];
  1327. unset($options['idField']);
  1328. deprecationWarning('Option "idField" is deprecated, use "keyField" instead.');
  1329. }
  1330. if (!$query->clause('select') &&
  1331. !is_object($options['keyField']) &&
  1332. !is_object($options['valueField']) &&
  1333. !is_object($options['groupField'])
  1334. ) {
  1335. $fields = array_merge(
  1336. (array)$options['keyField'],
  1337. (array)$options['valueField'],
  1338. (array)$options['groupField']
  1339. );
  1340. $columns = $this->getSchema()->columns();
  1341. if (count($fields) === count(array_intersect($fields, $columns))) {
  1342. $query->select($fields);
  1343. }
  1344. }
  1345. $options = $this->_setFieldMatchers(
  1346. $options,
  1347. ['keyField', 'valueField', 'groupField']
  1348. );
  1349. return $query->formatResults(function ($results) use ($options) {
  1350. /** @var \Cake\Collection\CollectionInterface $results */
  1351. return $results->combine(
  1352. $options['keyField'],
  1353. $options['valueField'],
  1354. $options['groupField']
  1355. );
  1356. });
  1357. }
  1358. /**
  1359. * Results for this finder will be a nested array, and is appropriate if you want
  1360. * to use the parent_id field of your model data to build nested results.
  1361. *
  1362. * Values belonging to a parent row based on their parent_id value will be
  1363. * recursively nested inside the parent row values using the `children` property
  1364. *
  1365. * You can customize what fields are used for nesting results, by default the
  1366. * primary key and the `parent_id` fields are used. If you wish to change
  1367. * these defaults you need to provide the keys `keyField`, `parentField` or `nestingKey` in
  1368. * `$options`:
  1369. *
  1370. * ```
  1371. * $table->find('threaded', [
  1372. * 'keyField' => 'id',
  1373. * 'parentField' => 'ancestor_id'
  1374. * 'nestingKey' => 'children'
  1375. * ]);
  1376. * ```
  1377. *
  1378. * @param \Cake\ORM\Query $query The query to find with
  1379. * @param array $options The options to find with
  1380. * @return \Cake\ORM\Query The query builder
  1381. */
  1382. public function findThreaded(Query $query, array $options)
  1383. {
  1384. $options += [
  1385. 'keyField' => $this->getPrimaryKey(),
  1386. 'parentField' => 'parent_id',
  1387. 'nestingKey' => 'children'
  1388. ];
  1389. if (isset($options['idField'])) {
  1390. $options['keyField'] = $options['idField'];
  1391. unset($options['idField']);
  1392. deprecationWarning('Option "idField" is deprecated, use "keyField" instead.');
  1393. }
  1394. $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']);
  1395. return $query->formatResults(function ($results) use ($options) {
  1396. /** @var \Cake\Collection\CollectionInterface $results */
  1397. return $results->nest($options['keyField'], $options['parentField'], $options['nestingKey']);
  1398. });
  1399. }
  1400. /**
  1401. * Out of an options array, check if the keys described in `$keys` are arrays
  1402. * and change the values for closures that will concatenate the each of the
  1403. * properties in the value array when passed a row.
  1404. *
  1405. * This is an auxiliary function used for result formatters that can accept
  1406. * composite keys when comparing values.
  1407. *
  1408. * @param array $options the original options passed to a finder
  1409. * @param array $keys the keys to check in $options to build matchers from
  1410. * the associated value
  1411. * @return array
  1412. */
  1413. protected function _setFieldMatchers($options, $keys)
  1414. {
  1415. foreach ($keys as $field) {
  1416. if (!is_array($options[$field])) {
  1417. continue;
  1418. }
  1419. if (count($options[$field]) === 1) {
  1420. $options[$field] = current($options[$field]);
  1421. continue;
  1422. }
  1423. $fields = $options[$field];
  1424. $options[$field] = function ($row) use ($fields) {
  1425. $matches = [];
  1426. foreach ($fields as $field) {
  1427. $matches[] = $row[$field];
  1428. }
  1429. return implode(';', $matches);
  1430. };
  1431. }
  1432. return $options;
  1433. }
  1434. /**
  1435. * {@inheritDoc}
  1436. *
  1437. * ### Usage
  1438. *
  1439. * Get an article and some relationships:
  1440. *
  1441. * ```
  1442. * $article = $articles->get(1, ['contain' => ['Users', 'Comments']]);
  1443. * ```
  1444. *
  1445. * @throws \Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an
  1446. * incorrect number of elements.
  1447. */
  1448. public function get($primaryKey, $options = [])
  1449. {
  1450. $key = (array)$this->getPrimaryKey();
  1451. $alias = $this->getAlias();
  1452. foreach ($key as $index => $keyname) {
  1453. $key[$index] = $alias . '.' . $keyname;
  1454. }
  1455. $primaryKey = (array)$primaryKey;
  1456. if (count($key) !== count($primaryKey)) {
  1457. $primaryKey = $primaryKey ?: [null];
  1458. $primaryKey = array_map(function ($key) {
  1459. return var_export($key, true);
  1460. }, $primaryKey);
  1461. throw new InvalidPrimaryKeyException(sprintf(
  1462. 'Record not found in table "%s" with primary key [%s]',
  1463. $this->getTable(),
  1464. implode(', ', $primaryKey)
  1465. ));
  1466. }
  1467. $conditions = array_combine($key, $primaryKey);
  1468. $cacheConfig = isset($options['cache']) ? $options['cache'] : false;
  1469. $cacheKey = isset($options['key']) ? $options['key'] : false;
  1470. $finder = isset($options['finder']) ? $options['finder'] : 'all';
  1471. unset($options['key'], $options['cache'], $options['finder']);
  1472. $query = $this->find($finder, $options)->where($conditions);
  1473. if ($cacheConfig) {
  1474. if (!$cacheKey) {
  1475. $cacheKey = sprintf(
  1476. 'get:%s.%s%s',
  1477. $this->getConnection()->configName(),
  1478. $this->getTable(),
  1479. json_encode($primaryKey)
  1480. );
  1481. }
  1482. $query->cache($cacheKey, $cacheConfig);
  1483. }
  1484. return $query->firstOrFail();
  1485. }
  1486. /**
  1487. * Handles the logic executing of a worker inside a transaction.
  1488. *
  1489. * @param callable $worker The worker that will run inside the transaction.
  1490. * @param bool $atomic Whether to execute the worker inside a database transaction.
  1491. * @return mixed
  1492. */
  1493. protected function _executeTransaction(callable $worker, $atomic = true)
  1494. {
  1495. if ($atomic) {
  1496. return $this->getConnection()->transactional(function () use ($worker) {
  1497. return $worker();
  1498. });
  1499. }
  1500. return $worker();
  1501. }
  1502. /**
  1503. * Checks if the caller would have executed a commit on a transaction.
  1504. *
  1505. * @param bool $atomic True if an atomic transaction was used.
  1506. * @param bool $primary True if a primary was used.
  1507. * @return bool Returns true if a transaction was committed.
  1508. */
  1509. protected function _transactionCommitted($atomic, $primary)
  1510. {
  1511. return !$this->getConnection()->inTransaction() && ($atomic || (!$atomic && $primary));
  1512. }
  1513. /**
  1514. * Finds an existing record or creates a new one.
  1515. *
  1516. * A find() will be done to locate an existing record using the attributes
  1517. * defined in $search. If records matches the conditions, the first record
  1518. * will be returned.
  1519. *
  1520. * If no record can be found, a new entity will be created
  1521. * with the $search properties. If a callback is provided, it will be
  1522. * called allowing you to define additional default values. The new
  1523. * entity will be saved and returned.
  1524. *
  1525. * If your find conditions require custom order, associations or conditions, then the $search
  1526. * parameter can be a callable that takes the Query as the argument, or a \Cake\ORM\Query object passed
  1527. * as the $search parameter. Allowing you to customize the find results.
  1528. *
  1529. * ### Options
  1530. *
  1531. * The options array is passed to the save method with exception to the following keys:
  1532. *
  1533. * - atomic: Whether to execute the methods for find, save and callbacks inside a database
  1534. * transaction (default: true)
  1535. * - defaults: Whether to use the search criteria as default values for the new entity (default: true)
  1536. *
  1537. * @param array|callable|\Cake\ORM\Query $search The criteria to find existing
  1538. * records by. Note that when you pass a query object you'll have to use
  1539. * the 2nd arg of the method to modify the entity data before saving.
  1540. * @param callable|null $callback A callback that will be invoked for newly
  1541. * created entities. This callback will be called *before* the entity
  1542. * is persisted.
  1543. * @param array $options The options to use when saving.
  1544. * @return \Cake\Datasource\EntityInterface An entity.
  1545. */
  1546. public function findOrCreate($search, callable $callback = null, $options = [])
  1547. {
  1548. $options = new ArrayObject($options + [
  1549. 'atomic' => true,
  1550. 'defaults' => true,
  1551. ]);
  1552. $entity = $this->_executeTransaction(function () use ($search, $callback, $options) {
  1553. return $this->_processFindOrCreate($search, $callback, $options->getArrayCopy());
  1554. }, $options['atomic']);
  1555. if ($entity && $this->_transactionCommitted($options['atomic'], true)) {
  1556. $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options'));
  1557. }
  1558. return $entity;
  1559. }
  1560. /**
  1561. * Performs the actual find and/or create of an entity based on the passed options.
  1562. *
  1563. * @param array|callable|\Cake\ORM\Query $search The criteria to find an existing record by, or a callable tha will
  1564. * customize the find query.
  1565. * @param callable|null $callback A callback that will be invoked for newly
  1566. * created entities. This callback will be called *before* the entity
  1567. * is persisted.
  1568. * @param array $options The options to use when saving.
  1569. * @return \Cake\Datasource\EntityInterface An entity.
  1570. */
  1571. protected function _processFindOrCreate($search, callable $callback = null, $options = [])
  1572. {
  1573. $query = $this->_getFindOrCreateQuery($search);
  1574. $row = $query->first();
  1575. if ($row !== null) {
  1576. return $row;
  1577. }
  1578. $entity = $this->newEntity();
  1579. if ($options['defaults'] && is_array($search)) {
  1580. $entity->set($search, ['guard' => false]);
  1581. }
  1582. if ($callback !== null) {
  1583. $entity = $callback($entity) ?: $entity;
  1584. }
  1585. unset($options['defaults']);
  1586. return $this->save($entity, $options) ?: $entity;
  1587. }
  1588. /**
  1589. * Gets the query object for findOrCreate().
  1590. *
  1591. * @param array|callable|\Cake\ORM\Query $search The criteria to find existing records by.
  1592. * @return \Cake\ORM\Query
  1593. */
  1594. protected function _getFindOrCreateQuery($search)
  1595. {
  1596. if (is_callable($search)) {
  1597. $query = $this->find();
  1598. $search($query);
  1599. } elseif (is_array($search)) {
  1600. $query = $this->find()->where($search);
  1601. } elseif ($search instanceof Query) {
  1602. $query = $search;
  1603. } else {
  1604. throw new InvalidArgumentException('Search criteria must be an array, callable or Query');
  1605. }
  1606. return $query;
  1607. }
  1608. /**
  1609. * Creates a new Query instance for a table.
  1610. *
  1611. * @return \Cake\ORM\Query
  1612. */
  1613. public function query()
  1614. {
  1615. return new Query($this->getConnection(), $this);
  1616. }
  1617. /**
  1618. * {@inheritDoc}
  1619. */
  1620. public function updateAll($fields, $conditions)
  1621. {
  1622. $query = $this->query();
  1623. $query->update()
  1624. ->set($fields)
  1625. ->where($conditions);
  1626. $statement = $query->execute();
  1627. $statement->closeCursor();
  1628. return $statement->rowCount();
  1629. }
  1630. /**
  1631. * {@inheritDoc}
  1632. */
  1633. public function deleteAll($conditions)
  1634. {
  1635. $query = $this->query()
  1636. ->delete()
  1637. ->where($conditions);
  1638. $statement = $query->execute();
  1639. $statement->closeCursor();
  1640. return $statement->rowCount();
  1641. }
  1642. /**
  1643. * {@inheritDoc}
  1644. */
  1645. public function exists($conditions)
  1646. {
  1647. return (bool)count(
  1648. $this->find('all')
  1649. ->select(['existing' => 1])
  1650. ->where($conditions)
  1651. ->limit(1)
  1652. ->disableHydration()
  1653. ->toArray()
  1654. );
  1655. }
  1656. /**
  1657. * {@inheritDoc}
  1658. *
  1659. * ### Options
  1660. *
  1661. * The options array accepts the following keys:
  1662. *
  1663. * - atomic: Whether to execute the save and callbacks inside a database
  1664. * transaction (default: true)
  1665. * - checkRules: Whether or not to check the rules on entity before saving, if the checking
  1666. * fails, it will abort the save operation. (default:true)
  1667. * - associated: If `true` it will save 1st level associated entities as they are found
  1668. * in the passed `$entity` whenever the property defined for the association
  1669. * is marked as dirty. If an array, it will be interpreted as the list of associations
  1670. * to be saved. It is possible to provide different options for saving on associated
  1671. * table objects using this key by making the custom options the array value.
  1672. * If `false` no associated records will be saved. (default: `true`)
  1673. * - checkExisting: Whether or not to check if the entity already exists, assuming that the
  1674. * entity is marked as not new, and the primary key has been set.
  1675. *
  1676. * ### Events
  1677. *
  1678. * When saving, this method will trigger four events:
  1679. *
  1680. * - Model.beforeRules: Will be triggered right before any rule checking is done
  1681. * for the passed entity if the `checkRules` key in $options is not set to false.
  1682. * Listeners will receive as arguments the entity, options array and the operation type.
  1683. * If the event is stopped the rules check result will be set to the result of the event itself.
  1684. * - Model.afterRules: Will be triggered right after the `checkRules()` method is
  1685. * called for the entity. Listeners will receive as arguments the entity,
  1686. * options array, the result of checking the rules and the operation type.
  1687. * If the event is stopped the checking result will be set to the result of
  1688. * the event itself.
  1689. * - Model.beforeSave: Will be triggered just before the list of fields to be
  1690. * persisted is calculated. It receives both the entity and the options as
  1691. * arguments. The options array is passed as an ArrayObject, so any changes in
  1692. * it will be reflected in every listener and remembered at the end of the event
  1693. * so it can be used for the rest of the save operation. Returning false in any
  1694. * of the listeners will abort the saving process. If the event is stopped
  1695. * using the event API, the event object's `result` property will be returned.
  1696. * This can be useful when having your own saving strategy implemented inside a
  1697. * listener.
  1698. * - Model.afterSave: Will be triggered after a successful insert or save,
  1699. * listeners will receive the entity and the options array as arguments. The type
  1700. * of operation performed (insert or update) can be determined by checking the
  1701. * entity's method `isNew`, true meaning an insert and false an update.
  1702. * - Model.afterSaveCommit: Will be triggered after the transaction is committed
  1703. * for atomic save, listeners will receive the entity and the options array
  1704. * as arguments.
  1705. *
  1706. * This method will determine whether the passed entity needs to be
  1707. * inserted or updated in the database. It does that by checking the `isNew`
  1708. * method on the entity. If the entity to be saved returns a non-empty value from
  1709. * its `errors()` method, it will not be saved.
  1710. *
  1711. * ### Saving on associated tables
  1712. *
  1713. * This method will by default persist entities belonging to associated tables,
  1714. * whenever a dirty property matching the name of the property name set for an
  1715. * association in this table. It is possible to control what associations will
  1716. * be saved and to pass additional option for saving them.
  1717. *
  1718. * ```
  1719. * // Only save the comments association
  1720. * $articles->save($entity, ['associated' => ['Comments']]);
  1721. *
  1722. * // Save the company, the employees and related addresses for each of them.
  1723. * // For employees do not check the entity rules
  1724. * $companies->save($entity, [
  1725. * 'associated' => [
  1726. * 'Employees' => [
  1727. * 'associated' => ['Addresses'],
  1728. * 'checkRules' => false
  1729. * ]
  1730. * ]
  1731. * ]);
  1732. *
  1733. * // Save no associations
  1734. * $articles->save($entity, ['associated' => false]);
  1735. * ```
  1736. *
  1737. * @param \Cake\Datasource\EntityInterface $entity
  1738. * @param array $options
  1739. * @return \Cake\Datasource\EntityInterface|false
  1740. * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event.
  1741. */
  1742. public function save(EntityInterface $entity, $options = [])
  1743. {
  1744. if ($options instanceof SaveOptionsBuilder) {
  1745. $options = $options->toArray();
  1746. }
  1747. $options = new ArrayObject((array)$options + [
  1748. 'atomic' => true,
  1749. 'associated' => true,
  1750. 'checkRules' => true,
  1751. 'checkExisting' => true,
  1752. '_primary' => true
  1753. ]);
  1754. if ($entity->hasErrors($options['associated'])) {
  1755. return false;
  1756. }
  1757. if ($entity->isNew() === false && !$entity->isDirty()) {
  1758. return $entity;
  1759. }
  1760. $success = $this->_executeTransaction(function () use ($entity, $options) {
  1761. return $this->_processSave($entity, $options);
  1762. }, $options['atomic']);
  1763. if ($success) {
  1764. if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) {
  1765. $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options'));
  1766. }
  1767. if ($options['atomic'] || $options['_primary']) {
  1768. $entity->clean();
  1769. $entity->isNew(false);
  1770. $entity->setSource($this->getRegistryAlias());
  1771. }
  1772. }
  1773. return $success;
  1774. }
  1775. /**
  1776. * Try to save an entity or throw a PersistenceFailedException if the application rules checks failed,
  1777. * the entity contains errors or the save was aborted by a callback.
  1778. *
  1779. * @param \Cake\Datasource\EntityInterface $entity the entity to be saved
  1780. * @param array|\ArrayAccess $options The options to use when saving.
  1781. * @return \Cake\Datasource\EntityInterface
  1782. * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved
  1783. * @see \Cake\ORM\Table::save()
  1784. */
  1785. public function saveOrFail(EntityInterface $entity, $options = [])
  1786. {
  1787. $saved = $this->save($entity, $options);
  1788. if ($saved === false) {
  1789. throw new PersistenceFailedException($entity, ['save']);
  1790. }
  1791. return $saved;
  1792. }
  1793. /**
  1794. * Performs the actual saving of an entity based on the passed options.
  1795. *
  1796. * @param \Cake\Datasource\EntityInterface $entity the entity to be saved
  1797. * @param \ArrayObject $options the options to use for the save operation
  1798. * @return \Cake\Datasource\EntityInterface|bool
  1799. * @throws \RuntimeException When an entity is missing some of the primary keys.
  1800. * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction
  1801. * is aborted in the afterSave event.
  1802. */
  1803. protected function _processSave($entity, $options)
  1804. {
  1805. $primaryColumns = (array)$this->getPrimaryKey();
  1806. if ($options['checkExisting'] && $primaryColumns && $entity->isNew() && $entity->has($primaryColumns)) {
  1807. $alias = $this->getAlias();
  1808. $conditions = [];
  1809. foreach ($entity->extract($primaryColumns) as $k => $v) {
  1810. $conditions["$alias.$k"] = $v;
  1811. }
  1812. $entity->isNew(!$this->exists($conditions));
  1813. }
  1814. $mode = $entity->isNew() ? RulesChecker::CREATE : RulesChecker::UPDATE;
  1815. if ($options['checkRules'] && !$this->checkRules($entity, $mode, $options)) {
  1816. return false;
  1817. }
  1818. $options['associated'] = $this->_associations->normalizeKeys($options['associated']);
  1819. $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options'));
  1820. if ($event->isStopped()) {
  1821. return $event->getResult();
  1822. }
  1823. $saved = $this->_associations->saveParents(
  1824. $this,
  1825. $entity,
  1826. $options['associated'],
  1827. ['_primary' => false] + $options->getArrayCopy()
  1828. );
  1829. if (!$saved && $options['atomic']) {
  1830. return false;
  1831. }
  1832. $data = $entity->extract($this->getSchema()->columns(), true);
  1833. $isNew = $entity->isNew();
  1834. if ($isNew) {
  1835. $success = $this->_insert($entity, $data);
  1836. } else {
  1837. $success = $this->_update($entity, $data);
  1838. }
  1839. if ($success) {
  1840. $success = $this->_onSaveSuccess($entity, $options);
  1841. }
  1842. if (!$success && $isNew) {
  1843. $entity->unsetProperty($this->getPrimaryKey());
  1844. $entity->isNew(true);
  1845. }
  1846. return $success ? $entity : false;
  1847. }
  1848. /**
  1849. * Handles the saving of children associations and executing the afterSave logic
  1850. * once the entity for this table has been saved successfully.
  1851. *
  1852. * @param \Cake\Datasource\EntityInterface $entity the entity to be saved
  1853. * @param \ArrayObject $options the options to use for the save operation
  1854. * @return bool True on success
  1855. * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction
  1856. * is aborted in the afterSave event.
  1857. */
  1858. protected function _onSaveSuccess($entity, $options)
  1859. {
  1860. $success = $this->_associations->saveChildren(
  1861. $this,
  1862. $entity,
  1863. $options['associated'],
  1864. ['_primary' => false] + $options->getArrayCopy()
  1865. );
  1866. if (!$success && $options['atomic']) {
  1867. return false;
  1868. }
  1869. $this->dispatchEvent('Model.afterSave', compact('entity', 'options'));
  1870. if ($options['atomic'] && !$this->getConnection()->inTransaction()) {
  1871. throw new RolledbackTransactionException(['table' => get_class($this)]);
  1872. }
  1873. if (!$options['atomic'] && !$options['_primary']) {
  1874. $entity->clean();
  1875. $entity->isNew(false);
  1876. $entity->setSource($this->getRegistryAlias());
  1877. }
  1878. return true;
  1879. }
  1880. /**
  1881. * Auxiliary function to handle the insert of an entity's data in the table
  1882. *
  1883. * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted
  1884. * @param array $data The actual data that needs to be saved
  1885. * @return \Cake\Datasource\EntityInterface|bool
  1886. * @throws \RuntimeException if not all the primary keys where supplied or could
  1887. * be generated when the table has composite primary keys. Or when the table has no primary key.
  1888. */
  1889. protected function _insert($entity, $data)
  1890. {
  1891. $primary = (array)$this->getPrimaryKey();
  1892. if (empty($primary)) {
  1893. $msg = sprintf(
  1894. 'Cannot insert row in "%s" table, it has no primary key.',
  1895. $this->getTable()
  1896. );
  1897. throw new RuntimeException($msg);
  1898. }
  1899. $keys = array_fill(0, count($primary), null);
  1900. $id = (array)$this->_newId($primary) + $keys;
  1901. // Generate primary keys preferring values in $data.
  1902. $primary = array_combine($primary, $id);
  1903. $primary = array_intersect_key($data, $primary) + $primary;
  1904. $filteredKeys = array_filter($primary, function ($v) {
  1905. return $v !== null;
  1906. });
  1907. $data += $filteredKeys;
  1908. if (count($primary) > 1) {
  1909. $schema = $this->getSchema();
  1910. foreach ($primary as $k => $v) {
  1911. if (!isset($data[$k]) && empty($schema->getColumn($k)['autoIncrement'])) {
  1912. $msg = 'Cannot insert row, some of the primary key values are missing. ';
  1913. $msg .= sprintf(
  1914. 'Got (%s), expecting (%s)',
  1915. implode(', ', $filteredKeys + $entity->extract(array_keys($primary))),
  1916. implode(', ', array_keys($primary))
  1917. );
  1918. throw new RuntimeException($msg);
  1919. }
  1920. }
  1921. }
  1922. $success = false;
  1923. if (empty($data)) {
  1924. return $success;
  1925. }
  1926. $statement = $this->query()->insert(array_keys($data))
  1927. ->values($data)
  1928. ->execute();
  1929. if ($statement->rowCount() !== 0) {
  1930. $success = $entity;
  1931. $entity->set($filteredKeys, ['guard' => false]);
  1932. $schema = $this->getSchema();
  1933. $driver = $this->getConnection()->getDriver();
  1934. foreach ($primary as $key => $v) {
  1935. if (!isset($data[$key])) {
  1936. $id = $statement->lastInsertId($this->getTable(), $key);
  1937. $type = $schema->getColumnType($key);
  1938. $entity->set($key, Type::build($type)->toPHP($id, $driver));
  1939. break;
  1940. }
  1941. }
  1942. }
  1943. $statement->closeCursor();
  1944. return $success;
  1945. }
  1946. /**
  1947. * Generate a primary key value for a new record.
  1948. *
  1949. * By default, this uses the type system to generate a new primary key
  1950. * value if possible. You can override this method if you have specific requirements
  1951. * for id generation.
  1952. *
  1953. * Note: The ORM will not generate primary key values for composite primary keys.
  1954. * You can overwrite _newId() in your table class.
  1955. *
  1956. * @param array $primary The primary key columns to get a new ID for.
  1957. * @return null|string|array Either null or the primary key value or a list of primary key values.
  1958. */
  1959. protected function _newId($primary)
  1960. {
  1961. if (!$primary || count((array)$primary) > 1) {
  1962. return null;
  1963. }
  1964. $typeName = $this->getSchema()->getColumnType($primary[0]);
  1965. $type = Type::build($typeName);
  1966. return $type->newId();
  1967. }
  1968. /**
  1969. * Auxiliary function to handle the update of an entity's data in the table
  1970. *
  1971. * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted
  1972. * @param array $data The actual data that needs to be saved
  1973. * @return \Cake\Datasource\EntityInterface|bool
  1974. * @throws \InvalidArgumentException When primary key data is missing.
  1975. */
  1976. protected function _update($entity, $data)
  1977. {
  1978. $primaryColumns = (array)$this->getPrimaryKey();
  1979. $primaryKey = $entity->extract($primaryColumns);
  1980. $data = array_diff_key($data, $primaryKey);
  1981. if (empty($data)) {
  1982. return $entity;
  1983. }
  1984. if (count($primaryColumns) === 0) {
  1985. $entityClass = get_class($entity);
  1986. $table = $this->getTable();
  1987. $message = "Cannot update `$entityClass`. The `$table` has no primary key.";
  1988. throw new InvalidArgumentException($message);
  1989. }
  1990. if (!$entity->has($primaryColumns)) {
  1991. $message = 'All primary key value(s) are needed for updating, ';
  1992. $message .= get_class($entity) . ' is missing ' . implode(', ', $primaryColumns);
  1993. throw new InvalidArgumentException($message);
  1994. }
  1995. $query = $this->query();
  1996. $statement = $query->update()
  1997. ->set($data)
  1998. ->where($primaryKey)
  1999. ->execute();
  2000. $success = false;
  2001. if ($statement->errorCode() === '00000') {
  2002. $success = $entity;
  2003. }
  2004. $statement->closeCursor();
  2005. return $success;
  2006. }
  2007. /**
  2008. * Persists multiple entities of a table.
  2009. *
  2010. * The records will be saved in a transaction which will be rolled back if
  2011. * any one of the records fails to save due to failed validation or database
  2012. * error.
  2013. *
  2014. * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save.
  2015. * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity.
  2016. * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface False on failure, entities list on success.
  2017. * @throws \Exception
  2018. */
  2019. public function saveMany($entities, $options = [])
  2020. {
  2021. $isNew = [];
  2022. $cleanup = function ($entities) use (&$isNew) {
  2023. foreach ($entities as $key => $entity) {
  2024. if (isset($isNew[$key]) && $isNew[$key]) {
  2025. $entity->unsetProperty($this->getPrimaryKey());
  2026. $entity->isNew(true);
  2027. }
  2028. }
  2029. };
  2030. try {
  2031. $return = $this->getConnection()
  2032. ->transactional(function () use ($entities, $options, &$isNew) {
  2033. foreach ($entities as $key => $entity) {
  2034. $isNew[$key] = $entity->isNew();
  2035. if ($this->save($entity, $options) === false) {
  2036. return false;
  2037. }
  2038. }
  2039. });
  2040. } catch (\Exception $e) {
  2041. $cleanup($entities);
  2042. throw $e;
  2043. }
  2044. if ($return === false) {
  2045. $cleanup($entities);
  2046. return false;
  2047. }
  2048. return $entities;
  2049. }
  2050. /**
  2051. * {@inheritDoc}
  2052. *
  2053. * For HasMany and HasOne associations records will be removed based on
  2054. * the dependent option. Join table records in BelongsToMany associations
  2055. * will always be removed. You can use the `cascadeCallbacks` option
  2056. * when defining associations to change how associated data is deleted.
  2057. *
  2058. * ### Options
  2059. *
  2060. * - `atomic` Defaults to true. When true the deletion happens within a transaction.
  2061. * - `checkRules` Defaults to true. Check deletion rules before deleting the record.
  2062. *
  2063. * ### Events
  2064. *
  2065. * - `Model.beforeDelete` Fired before the delete occurs. If stopped the delete
  2066. * will be aborted. Receives the event, entity, and options.
  2067. * - `Model.afterDelete` Fired after the delete has been successful. Receives
  2068. * the event, entity, and options.
  2069. * - `Model.afterDeleteCommit` Fired after the transaction is committed for
  2070. * an atomic delete. Receives the event, entity, and options.
  2071. *
  2072. * The options argument will be converted into an \ArrayObject instance
  2073. * for the duration of the callbacks, this allows listeners to modify
  2074. * the options used in the delete operation.
  2075. *
  2076. */
  2077. public function delete(EntityInterface $entity, $options = [])
  2078. {
  2079. $options = new ArrayObject((array)$options + [
  2080. 'atomic' => true,
  2081. 'checkRules' => true,
  2082. '_primary' => true,
  2083. ]);
  2084. $success = $this->_executeTransaction(function () use ($entity, $options) {
  2085. return $this->_processDelete($entity, $options);
  2086. }, $options['atomic']);
  2087. if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) {
  2088. $this->dispatchEvent('Model.afterDeleteCommit', [
  2089. 'entity' => $entity,
  2090. 'options' => $options
  2091. ]);
  2092. }
  2093. return $success;
  2094. }
  2095. /**
  2096. * Try to delete an entity or throw a PersistenceFailedException if the entity is new,
  2097. * has no primary key value, application rules checks failed or the delete was aborted by a callback.
  2098. *
  2099. * @param \Cake\Datasource\EntityInterface $entity The entity to remove.
  2100. * @param array|\ArrayAccess $options The options for the delete.
  2101. * @return bool success
  2102. * @throws \Cake\ORM\Exception\PersistenceFailedException
  2103. * @see \Cake\ORM\Table::delete()
  2104. */
  2105. public function deleteOrFail(EntityInterface $entity, $options = [])
  2106. {
  2107. $deleted = $this->delete($entity, $options);
  2108. if ($deleted === false) {
  2109. throw new PersistenceFailedException($entity, ['delete']);
  2110. }
  2111. return $deleted;
  2112. }
  2113. /**
  2114. * Perform the delete operation.
  2115. *
  2116. * Will delete the entity provided. Will remove rows from any
  2117. * dependent associations, and clear out join tables for BelongsToMany associations.
  2118. *
  2119. * @param \Cake\Datasource\EntityInterface $entity The entity to delete.
  2120. * @param \ArrayObject $options The options for the delete.
  2121. * @throws \InvalidArgumentException if there are no primary key values of the
  2122. * passed entity
  2123. * @return bool success
  2124. */
  2125. protected function _processDelete($entity, $options)
  2126. {
  2127. if ($entity->isNew()) {
  2128. return false;
  2129. }
  2130. $primaryKey = (array)$this->getPrimaryKey();
  2131. if (!$entity->has($primaryKey)) {
  2132. $msg = 'Deleting requires all primary key values.';
  2133. throw new InvalidArgumentException($msg);
  2134. }
  2135. if ($options['checkRules'] && !$this->checkRules($entity, RulesChecker::DELETE, $options)) {
  2136. return false;
  2137. }
  2138. $event = $this->dispatchEvent('Model.beforeDelete', [
  2139. 'entity' => $entity,
  2140. 'options' => $options
  2141. ]);
  2142. if ($event->isStopped()) {
  2143. return $event->getResult();
  2144. }
  2145. $this->_associations->cascadeDelete(
  2146. $entity,
  2147. ['_primary' => false] + $options->getArrayCopy()
  2148. );
  2149. $query = $this->query();
  2150. $conditions = (array)$entity->extract($primaryKey);
  2151. $statement = $query->delete()
  2152. ->where($conditions)
  2153. ->execute();
  2154. $success = $statement->rowCount() > 0;
  2155. if (!$success) {
  2156. return $success;
  2157. }
  2158. $this->dispatchEvent('Model.afterDelete', [
  2159. 'entity' => $entity,
  2160. 'options' => $options
  2161. ]);
  2162. return $success;
  2163. }
  2164. /**
  2165. * Returns true if the finder exists for the table
  2166. *
  2167. * @param string $type name of finder to check
  2168. *
  2169. * @return bool
  2170. */
  2171. public function hasFinder($type)
  2172. {
  2173. $finder = 'find' . $type;
  2174. return method_exists($this, $finder) || ($this->_behaviors && $this->_behaviors->hasFinder($type));
  2175. }
  2176. /**
  2177. * Calls a finder method directly and applies it to the passed query,
  2178. * if no query is passed a new one will be created and returned
  2179. *
  2180. * @param string $type name of the finder to be called
  2181. * @param \Cake\ORM\Query $query The query object to apply the finder options to
  2182. * @param array $options List of options to pass to the finder
  2183. * @return \Cake\ORM\Query
  2184. * @throws \BadMethodCallException
  2185. */
  2186. public function callFinder($type, Query $query, array $options = [])
  2187. {
  2188. $query->applyOptions($options);
  2189. $options = $query->getOptions();
  2190. $finder = 'find' . $type;
  2191. if (method_exists($this, $finder)) {
  2192. return $this->{$finder}($query, $options);
  2193. }
  2194. if ($this->_behaviors && $this->_behaviors->hasFinder($type)) {
  2195. return $this->_behaviors->callFinder($type, [$query, $options]);
  2196. }
  2197. throw new BadMethodCallException(
  2198. sprintf('Unknown finder method "%s"', $type)
  2199. );
  2200. }
  2201. /**
  2202. * Provides the dynamic findBy and findByAll methods.
  2203. *
  2204. * @param string $method The method name that was fired.
  2205. * @param array $args List of arguments passed to the function.
  2206. * @return mixed
  2207. * @throws \BadMethodCallException when there are missing arguments, or when
  2208. * and & or are combined.
  2209. */
  2210. protected function _dynamicFinder($method, $args)
  2211. {
  2212. $method = Inflector::underscore($method);
  2213. preg_match('/^find_([\w]+)_by_/', $method, $matches);
  2214. if (empty($matches)) {
  2215. // find_by_ is 8 characters.
  2216. $fields = substr($method, 8);
  2217. $findType = 'all';
  2218. } else {
  2219. $fields = substr($method, strlen($matches[0]));
  2220. $findType = Inflector::variable($matches[1]);
  2221. }
  2222. $hasOr = strpos($fields, '_or_');
  2223. $hasAnd = strpos($fields, '_and_');
  2224. $makeConditions = function ($fields, $args) {
  2225. $conditions = [];
  2226. if (count($args) < count($fields)) {
  2227. throw new BadMethodCallException(sprintf(
  2228. 'Not enough arguments for magic finder. Got %s required %s',
  2229. count($args),
  2230. count($fields)
  2231. ));
  2232. }
  2233. foreach ($fields as $field) {
  2234. $conditions[$this->aliasField($field)] = array_shift($args);
  2235. }
  2236. return $conditions;
  2237. };
  2238. if ($hasOr !== false && $hasAnd !== false) {
  2239. throw new BadMethodCallException(
  2240. 'Cannot mix "and" & "or" in a magic finder. Use find() instead.'
  2241. );
  2242. }
  2243. $conditions = [];
  2244. if ($hasOr === false && $hasAnd === false) {
  2245. $conditions = $makeConditions([$fields], $args);
  2246. } elseif ($hasOr !== false) {
  2247. $fields = explode('_or_', $fields);
  2248. $conditions = [
  2249. 'OR' => $makeConditions($fields, $args)
  2250. ];
  2251. } elseif ($hasAnd !== false) {
  2252. $fields = explode('_and_', $fields);
  2253. $conditions = $makeConditions($fields, $args);
  2254. }
  2255. return $this->find($findType, [
  2256. 'conditions' => $conditions,
  2257. ]);
  2258. }
  2259. /**
  2260. * Handles behavior delegation + dynamic finders.
  2261. *
  2262. * If your Table uses any behaviors you can call them as if
  2263. * they were on the table object.
  2264. *
  2265. * @param string $method name of the method to be invoked
  2266. * @param array $args List of arguments passed to the function
  2267. * @return mixed
  2268. * @throws \BadMethodCallException
  2269. */
  2270. public function __call($method, $args)
  2271. {
  2272. if ($this->_behaviors && $this->_behaviors->hasMethod($method)) {
  2273. return $this->_behaviors->call($method, $args);
  2274. }
  2275. if (preg_match('/^find(?:\w+)?By/', $method) > 0) {
  2276. return $this->_dynamicFinder($method, $args);
  2277. }
  2278. throw new BadMethodCallException(
  2279. sprintf('Unknown method "%s"', $method)
  2280. );
  2281. }
  2282. /**
  2283. * Returns the association named after the passed value if exists, otherwise
  2284. * throws an exception.
  2285. *
  2286. * @param string $property the association name
  2287. * @return \Cake\ORM\Association
  2288. * @throws \RuntimeException if no association with such name exists
  2289. */
  2290. public function __get($property)
  2291. {
  2292. $association = $this->_associations->get($property);
  2293. if (!$association) {
  2294. throw new RuntimeException(sprintf(
  2295. 'Undefined property `%s`. ' .
  2296. 'You have not defined the `%s` association on `%s`.',
  2297. $property,
  2298. $property,
  2299. static::class
  2300. ));
  2301. }
  2302. return $association;
  2303. }
  2304. /**
  2305. * Returns whether an association named after the passed value
  2306. * exists for this table.
  2307. *
  2308. * @param string $property the association name
  2309. * @return bool
  2310. */
  2311. public function __isset($property)
  2312. {
  2313. return $this->_associations->has($property);
  2314. }
  2315. /**
  2316. * Get the object used to marshal/convert array data into objects.
  2317. *
  2318. * Override this method if you want a table object to use custom
  2319. * marshalling logic.
  2320. *
  2321. * @return \Cake\ORM\Marshaller
  2322. * @see \Cake\ORM\Marshaller
  2323. */
  2324. public function marshaller()
  2325. {
  2326. return new Marshaller($this);
  2327. }
  2328. /**
  2329. * {@inheritDoc}
  2330. *
  2331. * By default all the associations on this table will be hydrated. You can
  2332. * limit which associations are built, or include deeper associations
  2333. * using the options parameter:
  2334. *
  2335. * ```
  2336. * $article = $this->Articles->newEntity(
  2337. * $this->request->getData(),
  2338. * ['associated' => ['Tags', 'Comments.Users']]
  2339. * );
  2340. * ```
  2341. *
  2342. * You can limit fields that will be present in the constructed entity by
  2343. * passing the `fields` option, which is also accepted for associations:
  2344. *
  2345. * ```
  2346. * $article = $this->Articles->newEntity($this->request->getData(), [
  2347. * 'fields' => ['title', 'body', 'tags', 'comments'],
  2348. * 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']]
  2349. * ]
  2350. * );
  2351. * ```
  2352. *
  2353. * The `fields` option lets remove or restrict input data from ending up in
  2354. * the entity. If you'd like to relax the entity's default accessible fields,
  2355. * you can use the `accessibleFields` option:
  2356. *
  2357. * ```
  2358. * $article = $this->Articles->newEntity(
  2359. * $this->request->getData(),
  2360. * ['accessibleFields' => ['protected_field' => true]]
  2361. * );
  2362. * ```
  2363. *
  2364. * By default, the data is validated before being passed to the new entity. In
  2365. * the case of invalid fields, those will not be present in the resulting object.
  2366. * The `validate` option can be used to disable validation on the passed data:
  2367. *
  2368. * ```
  2369. * $article = $this->Articles->newEntity(
  2370. * $this->request->getData(),
  2371. * ['validate' => false]
  2372. * );
  2373. * ```
  2374. *
  2375. * You can also pass the name of the validator to use in the `validate` option.
  2376. * If `null` is passed to the first param of this function, no validation will
  2377. * be performed.
  2378. *
  2379. * You can use the `Model.beforeMarshal` event to modify request data
  2380. * before it is converted into entities.
  2381. */
  2382. public function newEntity($data = null, array $options = [])
  2383. {
  2384. if ($data === null) {
  2385. $class = $this->getEntityClass();
  2386. return new $class([], ['source' => $this->getRegistryAlias()]);
  2387. }
  2388. if (!isset($options['associated'])) {
  2389. $options['associated'] = $this->_associations->keys();
  2390. }
  2391. $marshaller = $this->marshaller();
  2392. return $marshaller->one($data, $options);
  2393. }
  2394. /**
  2395. * {@inheritDoc}
  2396. *
  2397. * By default all the associations on this table will be hydrated. You can
  2398. * limit which associations are built, or include deeper associations
  2399. * using the options parameter:
  2400. *
  2401. * ```
  2402. * $articles = $this->Articles->newEntities(
  2403. * $this->request->getData(),
  2404. * ['associated' => ['Tags', 'Comments.Users']]
  2405. * );
  2406. * ```
  2407. *
  2408. * You can limit fields that will be present in the constructed entities by
  2409. * passing the `fields` option, which is also accepted for associations:
  2410. *
  2411. * ```
  2412. * $articles = $this->Articles->newEntities($this->request->getData(), [
  2413. * 'fields' => ['title', 'body', 'tags', 'comments'],
  2414. * 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']]
  2415. * ]
  2416. * );
  2417. * ```
  2418. *
  2419. * You can use the `Model.beforeMarshal` event to modify request data
  2420. * before it is converted into entities.
  2421. */
  2422. public function newEntities(array $data, array $options = [])
  2423. {
  2424. if (!isset($options['associated'])) {
  2425. $options['associated'] = $this->_associations->keys();
  2426. }
  2427. $marshaller = $this->marshaller();
  2428. return $marshaller->many($data, $options);
  2429. }
  2430. /**
  2431. * {@inheritDoc}
  2432. *
  2433. * When merging HasMany or BelongsToMany associations, all the entities in the
  2434. * `$data` array will appear, those that can be matched by primary key will get
  2435. * the data merged, but those that cannot, will be discarded.
  2436. *
  2437. * You can limit fields that will be present in the merged entity by
  2438. * passing the `fields` option, which is also accepted for associations:
  2439. *
  2440. * ```
  2441. * $article = $this->Articles->patchEntity($article, $this->request->getData(), [
  2442. * 'fields' => ['title', 'body', 'tags', 'comments'],
  2443. * 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']]
  2444. * ]
  2445. * );
  2446. * ```
  2447. *
  2448. * By default, the data is validated before being passed to the entity. In
  2449. * the case of invalid fields, those will not be assigned to the entity.
  2450. * The `validate` option can be used to disable validation on the passed data:
  2451. *
  2452. * ```
  2453. * $article = $this->patchEntity($article, $this->request->getData(),[
  2454. * 'validate' => false
  2455. * ]);
  2456. * ```
  2457. *
  2458. * You can use the `Model.beforeMarshal` event to modify request data
  2459. * before it is converted into entities.
  2460. *
  2461. * When patching scalar values (null/booleans/string/integer/float), if the property
  2462. * presently has an identical value, the setter will not be called, and the
  2463. * property will not be marked as dirty. This is an optimization to prevent unnecessary field
  2464. * updates when persisting entities.
  2465. */
  2466. public function patchEntity(EntityInterface $entity, array $data, array $options = [])
  2467. {
  2468. if (!isset($options['associated'])) {
  2469. $options['associated'] = $this->_associations->keys();
  2470. }
  2471. $marshaller = $this->marshaller();
  2472. return $marshaller->merge($entity, $data, $options);
  2473. }
  2474. /**
  2475. * {@inheritDoc}
  2476. *
  2477. * Those entries in `$entities` that cannot be matched to any record in
  2478. * `$data` will be discarded. Records in `$data` that could not be matched will
  2479. * be marshalled as a new entity.
  2480. *
  2481. * When merging HasMany or BelongsToMany associations, all the entities in the
  2482. * `$data` array will appear, those that can be matched by primary key will get
  2483. * the data merged, but those that cannot, will be discarded.
  2484. *
  2485. * You can limit fields that will be present in the merged entities by
  2486. * passing the `fields` option, which is also accepted for associations:
  2487. *
  2488. * ```
  2489. * $articles = $this->Articles->patchEntities($articles, $this->request->getData(), [
  2490. * 'fields' => ['title', 'body', 'tags', 'comments'],
  2491. * 'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']]
  2492. * ]
  2493. * );
  2494. * ```
  2495. *
  2496. * You can use the `Model.beforeMarshal` event to modify request data
  2497. * before it is converted into entities.
  2498. */
  2499. public function patchEntities($entities, array $data, array $options = [])
  2500. {
  2501. if (!isset($options['associated'])) {
  2502. $options['associated'] = $this->_associations->keys();
  2503. }
  2504. $marshaller = $this->marshaller();
  2505. return $marshaller->mergeMany($entities, $data, $options);
  2506. }
  2507. /**
  2508. * Validator method used to check the uniqueness of a value for a column.
  2509. * This is meant to be used with the validation API and not to be called
  2510. * directly.
  2511. *
  2512. * ### Example:
  2513. *
  2514. * ```
  2515. * $validator->add('email', [
  2516. * 'unique' => ['rule' => 'validateUnique', 'provider' => 'table']
  2517. * ])
  2518. * ```
  2519. *
  2520. * Unique validation can be scoped to the value of another column:
  2521. *
  2522. * ```
  2523. * $validator->add('email', [
  2524. * 'unique' => [
  2525. * 'rule' => ['validateUnique', ['scope' => 'site_id']],
  2526. * 'provider' => 'table'
  2527. * ]
  2528. * ]);
  2529. * ```
  2530. *
  2531. * In the above example, the email uniqueness will be scoped to only rows having
  2532. * the same site_id. Scoping will only be used if the scoping field is present in
  2533. * the data to be validated.
  2534. *
  2535. * @param mixed $value The value of column to be checked for uniqueness.
  2536. * @param array $options The options array, optionally containing the 'scope' key.
  2537. * May also be the validation context, if there are no options.
  2538. * @param array|null $context Either the validation context or null.
  2539. * @return bool True if the value is unique, or false if a non-scalar, non-unique value was given.
  2540. */
  2541. public function validateUnique($value, array $options, array $context = null)
  2542. {
  2543. if ($context === null) {
  2544. $context = $options;
  2545. }
  2546. $entity = new Entity(
  2547. $context['data'],
  2548. [
  2549. 'useSetters' => false,
  2550. 'markNew' => $context['newRecord'],
  2551. 'source' => $this->getRegistryAlias()
  2552. ]
  2553. );
  2554. $fields = array_merge(
  2555. [$context['field']],
  2556. isset($options['scope']) ? (array)$options['scope'] : []
  2557. );
  2558. $values = $entity->extract($fields);
  2559. foreach ($values as $field) {
  2560. if ($field !== null && !is_scalar($field)) {
  2561. return false;
  2562. }
  2563. }
  2564. $class = static::IS_UNIQUE_CLASS;
  2565. $rule = new $class($fields, $options);
  2566. return $rule($entity, ['repository' => $this]);
  2567. }
  2568. /**
  2569. * Get the Model callbacks this table is interested in.
  2570. *
  2571. * By implementing the conventional methods a table class is assumed
  2572. * to be interested in the related event.
  2573. *
  2574. * Override this method if you need to add non-conventional event listeners.
  2575. * Or if you want you table to listen to non-standard events.
  2576. *
  2577. * The conventional method map is:
  2578. *
  2579. * - Model.beforeMarshal => beforeMarshal
  2580. * - Model.buildValidator => buildValidator
  2581. * - Model.beforeFind => beforeFind
  2582. * - Model.beforeSave => beforeSave
  2583. * - Model.afterSave => afterSave
  2584. * - Model.afterSaveCommit => afterSaveCommit
  2585. * - Model.beforeDelete => beforeDelete
  2586. * - Model.afterDelete => afterDelete
  2587. * - Model.afterDeleteCommit => afterDeleteCommit
  2588. * - Model.beforeRules => beforeRules
  2589. * - Model.afterRules => afterRules
  2590. *
  2591. * @return array
  2592. */
  2593. public function implementedEvents()
  2594. {
  2595. $eventMap = [
  2596. 'Model.beforeMarshal' => 'beforeMarshal',
  2597. 'Model.buildValidator' => 'buildValidator',
  2598. 'Model.beforeFind' => 'beforeFind',
  2599. 'Model.beforeSave' => 'beforeSave',
  2600. 'Model.afterSave' => 'afterSave',
  2601. 'Model.afterSaveCommit' => 'afterSaveCommit',
  2602. 'Model.beforeDelete' => 'beforeDelete',
  2603. 'Model.afterDelete' => 'afterDelete',
  2604. 'Model.afterDeleteCommit' => 'afterDeleteCommit',
  2605. 'Model.beforeRules' => 'beforeRules',
  2606. 'Model.afterRules' => 'afterRules',
  2607. ];
  2608. $events = [];
  2609. foreach ($eventMap as $event => $method) {
  2610. if (!method_exists($this, $method)) {
  2611. continue;
  2612. }
  2613. $events[$event] = $method;
  2614. }
  2615. return $events;
  2616. }
  2617. /**
  2618. * {@inheritDoc}
  2619. *
  2620. * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
  2621. * @return \Cake\ORM\RulesChecker
  2622. */
  2623. public function buildRules(RulesChecker $rules)
  2624. {
  2625. return $rules;
  2626. }
  2627. /**
  2628. * Gets a SaveOptionsBuilder instance.
  2629. *
  2630. * @param array $options Options to parse by the builder.
  2631. * @return \Cake\ORM\SaveOptionsBuilder
  2632. */
  2633. public function getSaveOptionsBuilder(array $options = [])
  2634. {
  2635. return new SaveOptionsBuilder($this, $options);
  2636. }
  2637. /**
  2638. * Loads the specified associations in the passed entity or list of entities
  2639. * by executing extra queries in the database and merging the results in the
  2640. * appropriate properties.
  2641. *
  2642. * ### Example:
  2643. *
  2644. * ```
  2645. * $user = $usersTable->get(1);
  2646. * $user = $usersTable->loadInto($user, ['Articles.Tags', 'Articles.Comments']);
  2647. * echo $user->articles[0]->title;
  2648. * ```
  2649. *
  2650. * You can also load associations for multiple entities at once
  2651. *
  2652. * ### Example:
  2653. *
  2654. * ```
  2655. * $users = $usersTable->find()->where([...])->toList();
  2656. * $users = $usersTable->loadInto($users, ['Articles.Tags', 'Articles.Comments']);
  2657. * echo $user[1]->articles[0]->title;
  2658. * ```
  2659. *
  2660. * The properties for the associations to be loaded will be overwritten on each entity.
  2661. *
  2662. * @param \Cake\Datasource\EntityInterface|array $entities a single entity or list of entities
  2663. * @param array $contain A `contain()` compatible array.
  2664. * @see \Cake\ORM\Query::contain()
  2665. * @return \Cake\Datasource\EntityInterface|array
  2666. */
  2667. public function loadInto($entities, array $contain)
  2668. {
  2669. return (new LazyEagerLoader)->loadInto($entities, $contain, $this);
  2670. }
  2671. /**
  2672. * {@inheritDoc}
  2673. */
  2674. protected function validationMethodExists($method)
  2675. {
  2676. return method_exists($this, $method) || $this->behaviors()->hasMethod($method);
  2677. }
  2678. /**
  2679. * Returns an array that can be used to describe the internal state of this
  2680. * object.
  2681. *
  2682. * @return array
  2683. */
  2684. public function __debugInfo()
  2685. {
  2686. $conn = $this->getConnection();
  2687. $associations = $this->_associations;
  2688. $behaviors = $this->_behaviors;
  2689. return [
  2690. 'registryAlias' => $this->getRegistryAlias(),
  2691. 'table' => $this->getTable(),
  2692. 'alias' => $this->getAlias(),
  2693. 'entityClass' => $this->getEntityClass(),
  2694. 'associations' => $associations ? $associations->keys() : false,
  2695. 'behaviors' => $behaviors ? $behaviors->loaded() : false,
  2696. 'defaultConnection' => static::defaultConnectionName(),
  2697. 'connectionName' => $conn ? $conn->configName() : null
  2698. ];
  2699. }
  2700. }