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