CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Team
    • Issues (Github)
    • YouTube Channel
    • Get Involved
    • Bakery
    • Featured Resources
    • Newsletter
    • Certification
    • My CakePHP
    • CakeFest
    • Facebook
    • Twitter
    • Help & Support
    • Forum
    • Stack Overflow
    • IRC
    • Slack
    • Paid Support
CakePHP

C CakePHP 3.8 Red Velvet API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 3.8
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Namespaces

  • Cake
    • Auth
      • Storage
    • Cache
      • Engine
    • Collection
      • Iterator
    • Command
    • Console
      • Exception
    • Controller
      • Component
      • Exception
    • Core
      • Configure
        • Engine
      • Exception
      • Retry
    • Database
      • Driver
      • Exception
      • Expression
      • Schema
      • Statement
      • Type
    • Datasource
      • Exception
    • Error
      • Middleware
    • Event
      • Decorator
    • Filesystem
    • Form
    • Http
      • Client
        • Adapter
        • Auth
      • Cookie
      • Exception
      • Middleware
      • Session
    • I18n
      • Formatter
      • Middleware
      • Parser
    • Log
      • Engine
    • Mailer
      • Exception
      • Transport
    • Network
      • Exception
    • ORM
      • Association
      • Behavior
        • Translate
      • Exception
      • Locator
      • Rule
    • Routing
      • Exception
      • Filter
      • Middleware
      • Route
    • Shell
      • Helper
      • Task
    • TestSuite
      • Fixture
      • Stub
    • Utility
      • Exception
    • Validation
    • View
      • Exception
      • Form
      • Helper
      • Widget
  • None

Classes

  • Association
  • AssociationCollection
  • Behavior
  • BehaviorRegistry
  • EagerLoader
  • Entity
  • Marshaller
  • Query
  • ResultSet
  • RulesChecker
  • SaveOptionsBuilder
  • Table
  • TableRegistry

Interfaces

  • PropertyMarshalInterface

Traits

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

Generated using CakePHP API Docs