Table.php 73 KB

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