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