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 (
1454:             !$query->clause('select') &&
1455:             !is_object($options['keyField']) &&
1456:             !is_object($options['valueField']) &&
1457:             !is_object($options['groupField'])
1458:         ) {
1459:             $fields = array_merge(
1460:                 (array)$options['keyField'],
1461:                 (array)$options['valueField'],
1462:                 (array)$options['groupField']
1463:             );
1464:             $columns = $this->getSchema()->columns();
1465:             if (count($fields) === count(array_intersect($fields, $columns))) {
1466:                 $query->select($fields);
1467:             }
1468:         }
1469: 
1470:         $options = $this->_setFieldMatchers(
1471:             $options,
1472:             ['keyField', 'valueField', 'groupField']
1473:         );
1474: 
1475:         return $query->formatResults(function ($results) use ($options) {
1476:             /** @var \Cake\Collection\CollectionInterface $results */
1477:             return $results->combine(
1478:                 $options['keyField'],
1479:                 $options['valueField'],
1480:                 $options['groupField']
1481:             );
1482:         });
1483:     }
1484: 
1485:     /**
1486:      * Results for this finder will be a nested array, and is appropriate if you want
1487:      * to use the parent_id field of your model data to build nested results.
1488:      *
1489:      * Values belonging to a parent row based on their parent_id value will be
1490:      * recursively nested inside the parent row values using the `children` property
1491:      *
1492:      * You can customize what fields are used for nesting results, by default the
1493:      * primary key and the `parent_id` fields are used. If you wish to change
1494:      * these defaults you need to provide the keys `keyField`, `parentField` or `nestingKey` in
1495:      * `$options`:
1496:      *
1497:      * ```
1498:      * $table->find('threaded', [
1499:      *  'keyField' => 'id',
1500:      *  'parentField' => 'ancestor_id'
1501:      *  'nestingKey' => 'children'
1502:      * ]);
1503:      * ```
1504:      *
1505:      * @param \Cake\ORM\Query $query The query to find with
1506:      * @param array $options The options to find with
1507:      * @return \Cake\ORM\Query The query builder
1508:      */
1509:     public function findThreaded(Query $query, array $options)
1510:     {
1511:         $options += [
1512:             'keyField' => $this->getPrimaryKey(),
1513:             'parentField' => 'parent_id',
1514:             'nestingKey' => 'children',
1515:         ];
1516: 
1517:         if (isset($options['idField'])) {
1518:             $options['keyField'] = $options['idField'];
1519:             unset($options['idField']);
1520:             deprecationWarning('Option "idField" is deprecated, use "keyField" instead.');
1521:         }
1522: 
1523:         $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']);
1524: 
1525:         return $query->formatResults(function ($results) use ($options) {
1526:             /** @var \Cake\Collection\CollectionInterface $results */
1527:             return $results->nest($options['keyField'], $options['parentField'], $options['nestingKey']);
1528:         });
1529:     }
1530: 
1531:     /**
1532:      * Out of an options array, check if the keys described in `$keys` are arrays
1533:      * and change the values for closures that will concatenate the each of the
1534:      * properties in the value array when passed a row.
1535:      *
1536:      * This is an auxiliary function used for result formatters that can accept
1537:      * composite keys when comparing values.
1538:      *
1539:      * @param array $options the original options passed to a finder
1540:      * @param array $keys the keys to check in $options to build matchers from
1541:      * the associated value
1542:      * @return array
1543:      */
1544:     protected function _setFieldMatchers($options, $keys)
1545:     {
1546:         foreach ($keys as $field) {
1547:             if (!is_array($options[$field])) {
1548:                 continue;
1549:             }
1550: 
1551:             if (count($options[$field]) === 1) {
1552:                 $options[$field] = current($options[$field]);
1553:                 continue;
1554:             }
1555: 
1556:             $fields = $options[$field];
1557:             $options[$field] = function ($row) use ($fields) {
1558:                 $matches = [];
1559:                 foreach ($fields as $field) {
1560:                     $matches[] = $row[$field];
1561:                 }
1562: 
1563:                 return implode(';', $matches);
1564:             };
1565:         }
1566: 
1567:         return $options;
1568:     }
1569: 
1570:     /**
1571:      * {@inheritDoc}
1572:      *
1573:      * ### Usage
1574:      *
1575:      * Get an article and some relationships:
1576:      *
1577:      * ```
1578:      * $article = $articles->get(1, ['contain' => ['Users', 'Comments']]);
1579:      * ```
1580:      *
1581:      * @throws \Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an
1582:      *      incorrect number of elements.
1583:      */
1584:     public function get($primaryKey, $options = [])
1585:     {
1586:         $key = (array)$this->getPrimaryKey();
1587:         $alias = $this->getAlias();
1588:         foreach ($key as $index => $keyname) {
1589:             $key[$index] = $alias . '.' . $keyname;
1590:         }
1591:         $primaryKey = (array)$primaryKey;
1592:         if (count($key) !== count($primaryKey)) {
1593:             $primaryKey = $primaryKey ?: [null];
1594:             $primaryKey = array_map(function ($key) {
1595:                 return var_export($key, true);
1596:             }, $primaryKey);
1597: 
1598:             throw new InvalidPrimaryKeyException(sprintf(
1599:                 'Record not found in table "%s" with primary key [%s]',
1600:                 $this->getTable(),
1601:                 implode(', ', $primaryKey)
1602:             ));
1603:         }
1604:         $conditions = array_combine($key, $primaryKey);
1605: 
1606:         $cacheConfig = isset($options['cache']) ? $options['cache'] : false;
1607:         $cacheKey = isset($options['key']) ? $options['key'] : false;
1608:         $finder = isset($options['finder']) ? $options['finder'] : 'all';
1609:         unset($options['key'], $options['cache'], $options['finder']);
1610: 
1611:         $query = $this->find($finder, $options)->where($conditions);
1612: 
1613:         if ($cacheConfig) {
1614:             if (!$cacheKey) {
1615:                 $cacheKey = sprintf(
1616:                     'get:%s.%s%s',
1617:                     $this->getConnection()->configName(),
1618:                     $this->getTable(),
1619:                     json_encode($primaryKey)
1620:                 );
1621:             }
1622:             $query->cache($cacheKey, $cacheConfig);
1623:         }
1624: 
1625:         return $query->firstOrFail();
1626:     }
1627: 
1628:     /**
1629:      * Handles the logic executing of a worker inside a transaction.
1630:      *
1631:      * @param callable $worker The worker that will run inside the transaction.
1632:      * @param bool $atomic Whether to execute the worker inside a database transaction.
1633:      * @return mixed
1634:      */
1635:     protected function _executeTransaction(callable $worker, $atomic = true)
1636:     {
1637:         if ($atomic) {
1638:             return $this->getConnection()->transactional(function () use ($worker) {
1639:                 return $worker();
1640:             });
1641:         }
1642: 
1643:         return $worker();
1644:     }
1645: 
1646:     /**
1647:      * Checks if the caller would have executed a commit on a transaction.
1648:      *
1649:      * @param bool $atomic True if an atomic transaction was used.
1650:      * @param bool $primary True if a primary was used.
1651:      * @return bool Returns true if a transaction was committed.
1652:      */
1653:     protected function _transactionCommitted($atomic, $primary)
1654:     {
1655:         return !$this->getConnection()->inTransaction() && ($atomic || (!$atomic && $primary));
1656:     }
1657: 
1658:     /**
1659:      * Finds an existing record or creates a new one.
1660:      *
1661:      * A find() will be done to locate an existing record using the attributes
1662:      * defined in $search. If records matches the conditions, the first record
1663:      * will be returned.
1664:      *
1665:      * If no record can be found, a new entity will be created
1666:      * with the $search properties. If a callback is provided, it will be
1667:      * called allowing you to define additional default values. The new
1668:      * entity will be saved and returned.
1669:      *
1670:      * If your find conditions require custom order, associations or conditions, then the $search
1671:      * parameter can be a callable that takes the Query as the argument, or a \Cake\ORM\Query object passed
1672:      * as the $search parameter. Allowing you to customize the find results.
1673:      *
1674:      * ### Options
1675:      *
1676:      * The options array is passed to the save method with exception to the following keys:
1677:      *
1678:      * - atomic: Whether to execute the methods for find, save and callbacks inside a database
1679:      *   transaction (default: true)
1680:      * - defaults: Whether to use the search criteria as default values for the new entity (default: true)
1681:      *
1682:      * @param array|callable|\Cake\ORM\Query $search The criteria to find existing
1683:      *   records by. Note that when you pass a query object you'll have to use
1684:      *   the 2nd arg of the method to modify the entity data before saving.
1685:      * @param callable|null $callback A callback that will be invoked for newly
1686:      *   created entities. This callback will be called *before* the entity
1687:      *   is persisted.
1688:      * @param array $options The options to use when saving.
1689:      * @return \Cake\Datasource\EntityInterface An entity.
1690:      * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved
1691:      */
1692:     public function findOrCreate($search, callable $callback = null, $options = [])
1693:     {
1694:         $options = new ArrayObject($options + [
1695:             'atomic' => true,
1696:             'defaults' => true,
1697:         ]);
1698: 
1699:         $entity = $this->_executeTransaction(function () use ($search, $callback, $options) {
1700:             return $this->_processFindOrCreate($search, $callback, $options->getArrayCopy());
1701:         }, $options['atomic']);
1702: 
1703:         if ($entity && $this->_transactionCommitted($options['atomic'], true)) {
1704:             $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options'));
1705:         }
1706: 
1707:         return $entity;
1708:     }
1709: 
1710:     /**
1711:      * Performs the actual find and/or create of an entity based on the passed options.
1712:      *
1713:      * @param array|callable|\Cake\ORM\Query $search The criteria to find an existing record by, or a callable tha will
1714:      *   customize the find query.
1715:      * @param callable|null $callback A callback that will be invoked for newly
1716:      *   created entities. This callback will be called *before* the entity
1717:      *   is persisted.
1718:      * @param array $options The options to use when saving.
1719:      * @return \Cake\Datasource\EntityInterface An entity.
1720:      * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved
1721:      */
1722:     protected function _processFindOrCreate($search, callable $callback = null, $options = [])
1723:     {
1724:         $query = $this->_getFindOrCreateQuery($search);
1725:         $row = $query->first();
1726:         if ($row !== null) {
1727:             return $row;
1728:         }
1729: 
1730:         $entity = $this->newEntity();
1731:         if ($options['defaults'] && is_array($search)) {
1732:             $accessibleFields = array_combine(array_keys($search), array_fill(0, count($search), true));
1733:             $entity = $this->patchEntity($entity, $search, ['accessibleFields' => $accessibleFields]);
1734:         }
1735:         if ($callback !== null) {
1736:             $entity = $callback($entity) ?: $entity;
1737:         }
1738:         unset($options['defaults']);
1739: 
1740:         $result = $this->save($entity, $options);
1741: 
1742:         if ($result === false) {
1743:             throw new PersistenceFailedException($entity, ['findOrCreate']);
1744:         }
1745: 
1746:         return $entity;
1747:     }
1748: 
1749:     /**
1750:      * Gets the query object for findOrCreate().
1751:      *
1752:      * @param array|callable|\Cake\ORM\Query $search The criteria to find existing records by.
1753:      * @return \Cake\ORM\Query
1754:      */
1755:     protected function _getFindOrCreateQuery($search)
1756:     {
1757:         if (is_callable($search)) {
1758:             $query = $this->find();
1759:             $search($query);
1760:         } elseif (is_array($search)) {
1761:             $query = $this->find()->where($search);
1762:         } elseif ($search instanceof Query) {
1763:             $query = $search;
1764:         } else {
1765:             throw new InvalidArgumentException('Search criteria must be an array, callable or Query');
1766:         }
1767: 
1768:         return $query;
1769:     }
1770: 
1771:     /**
1772:      * Creates a new Query instance for a table.
1773:      *
1774:      * @return \Cake\ORM\Query
1775:      */
1776:     public function query()
1777:     {
1778:         return new Query($this->getConnection(), $this);
1779:     }
1780: 
1781:     /**
1782:      * {@inheritDoc}
1783:      */
1784:     public function updateAll($fields, $conditions)
1785:     {
1786:         $query = $this->query();
1787:         $query->update()
1788:             ->set($fields)
1789:             ->where($conditions);
1790:         $statement = $query->execute();
1791:         $statement->closeCursor();
1792: 
1793:         return $statement->rowCount();
1794:     }
1795: 
1796:     /**
1797:      * {@inheritDoc}
1798:      */
1799:     public function deleteAll($conditions)
1800:     {
1801:         $query = $this->query()
1802:             ->delete()
1803:             ->where($conditions);
1804:         $statement = $query->execute();
1805:         $statement->closeCursor();
1806: 
1807:         return $statement->rowCount();
1808:     }
1809: 
1810:     /**
1811:      * {@inheritDoc}
1812:      */
1813:     public function exists($conditions)
1814:     {
1815:         return (bool)count(
1816:             $this->find('all')
1817:             ->select(['existing' => 1])
1818:             ->where($conditions)
1819:             ->limit(1)
1820:             ->disableHydration()
1821:             ->toArray()
1822:         );
1823:     }
1824: 
1825:     /**
1826:      * {@inheritDoc}
1827:      *
1828:      * ### Options
1829:      *
1830:      * The options array accepts the following keys:
1831:      *
1832:      * - atomic: Whether to execute the save and callbacks inside a database
1833:      *   transaction (default: true)
1834:      * - checkRules: Whether or not to check the rules on entity before saving, if the checking
1835:      *   fails, it will abort the save operation. (default:true)
1836:      * - associated: If `true` it will save 1st level associated entities as they are found
1837:      *   in the passed `$entity` whenever the property defined for the association
1838:      *   is marked as dirty. If an array, it will be interpreted as the list of associations
1839:      *   to be saved. It is possible to provide different options for saving on associated
1840:      *   table objects using this key by making the custom options the array value.
1841:      *   If `false` no associated records will be saved. (default: `true`)
1842:      * - checkExisting: Whether or not to check if the entity already exists, assuming that the
1843:      *   entity is marked as not new, and the primary key has been set.
1844:      *
1845:      * ### Events
1846:      *
1847:      * When saving, this method will trigger four events:
1848:      *
1849:      * - Model.beforeRules: Will be triggered right before any rule checking is done
1850:      *   for the passed entity if the `checkRules` key in $options is not set to false.
1851:      *   Listeners will receive as arguments the entity, options array and the operation type.
1852:      *   If the event is stopped the rules check result will be set to the result of the event itself.
1853:      * - Model.afterRules: Will be triggered right after the `checkRules()` method is
1854:      *   called for the entity. Listeners will receive as arguments the entity,
1855:      *   options array, the result of checking the rules and the operation type.
1856:      *   If the event is stopped the checking result will be set to the result of
1857:      *   the event itself.
1858:      * - Model.beforeSave: Will be triggered just before the list of fields to be
1859:      *   persisted is calculated. It receives both the entity and the options as
1860:      *   arguments. The options array is passed as an ArrayObject, so any changes in
1861:      *   it will be reflected in every listener and remembered at the end of the event
1862:      *   so it can be used for the rest of the save operation. Returning false in any
1863:      *   of the listeners will abort the saving process. If the event is stopped
1864:      *   using the event API, the event object's `result` property will be returned.
1865:      *   This can be useful when having your own saving strategy implemented inside a
1866:      *   listener.
1867:      * - Model.afterSave: Will be triggered after a successful insert or save,
1868:      *   listeners will receive the entity and the options array as arguments. The type
1869:      *   of operation performed (insert or update) can be determined by checking the
1870:      *   entity's method `isNew`, true meaning an insert and false an update.
1871:      * - Model.afterSaveCommit: Will be triggered after the transaction is committed
1872:      *   for atomic save, listeners will receive the entity and the options array
1873:      *   as arguments.
1874:      *
1875:      * This method will determine whether the passed entity needs to be
1876:      * inserted or updated in the database. It does that by checking the `isNew`
1877:      * method on the entity. If the entity to be saved returns a non-empty value from
1878:      * its `errors()` method, it will not be saved.
1879:      *
1880:      * ### Saving on associated tables
1881:      *
1882:      * This method will by default persist entities belonging to associated tables,
1883:      * whenever a dirty property matching the name of the property name set for an
1884:      * association in this table. It is possible to control what associations will
1885:      * be saved and to pass additional option for saving them.
1886:      *
1887:      * ```
1888:      * // Only save the comments association
1889:      * $articles->save($entity, ['associated' => ['Comments']]);
1890:      *
1891:      * // Save the company, the employees and related addresses for each of them.
1892:      * // For employees do not check the entity rules
1893:      * $companies->save($entity, [
1894:      *   'associated' => [
1895:      *     'Employees' => [
1896:      *       'associated' => ['Addresses'],
1897:      *       'checkRules' => false
1898:      *     ]
1899:      *   ]
1900:      * ]);
1901:      *
1902:      * // Save no associations
1903:      * $articles->save($entity, ['associated' => false]);
1904:      * ```
1905:      *
1906:      * @param \Cake\Datasource\EntityInterface $entity
1907:      * @param array $options
1908:      * @return \Cake\Datasource\EntityInterface|false
1909:      * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event.
1910:      */
1911:     public function save(EntityInterface $entity, $options = [])
1912:     {
1913:         if ($options instanceof SaveOptionsBuilder) {
1914:             $options = $options->toArray();
1915:         }
1916: 
1917:         $options = new ArrayObject((array)$options + [
1918:             'atomic' => true,
1919:             'associated' => true,
1920:             'checkRules' => true,
1921:             'checkExisting' => true,
1922:             '_primary' => true,
1923:         ]);
1924: 
1925:         if ($entity->hasErrors($options['associated'])) {
1926:             return false;
1927:         }
1928: 
1929:         if ($entity->isNew() === false && !$entity->isDirty()) {
1930:             return $entity;
1931:         }
1932: 
1933:         $success = $this->_executeTransaction(function () use ($entity, $options) {
1934:             return $this->_processSave($entity, $options);
1935:         }, $options['atomic']);
1936: 
1937:         if ($success) {
1938:             if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) {
1939:                 $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options'));
1940:             }
1941:             if ($options['atomic'] || $options['_primary']) {
1942:                 $entity->clean();
1943:                 $entity->isNew(false);
1944:                 $entity->setSource($this->getRegistryAlias());
1945:             }
1946:         }
1947: 
1948:         return $success;
1949:     }
1950: 
1951:     /**
1952:      * Try to save an entity or throw a PersistenceFailedException if the application rules checks failed,
1953:      * the entity contains errors or the save was aborted by a callback.
1954:      *
1955:      * @param \Cake\Datasource\EntityInterface $entity the entity to be saved
1956:      * @param array|\ArrayAccess $options The options to use when saving.
1957:      * @return \Cake\Datasource\EntityInterface
1958:      * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved
1959:      * @see \Cake\ORM\Table::save()
1960:      */
1961:     public function saveOrFail(EntityInterface $entity, $options = [])
1962:     {
1963:         $saved = $this->save($entity, $options);
1964:         if ($saved === false) {
1965:             throw new PersistenceFailedException($entity, ['save']);
1966:         }
1967: 
1968:         return $saved;
1969:     }
1970: 
1971:     /**
1972:      * Performs the actual saving of an entity based on the passed options.
1973:      *
1974:      * @param \Cake\Datasource\EntityInterface $entity the entity to be saved
1975:      * @param \ArrayObject $options the options to use for the save operation
1976:      * @return \Cake\Datasource\EntityInterface|bool
1977:      * @throws \RuntimeException When an entity is missing some of the primary keys.
1978:      * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction
1979:      *   is aborted in the afterSave event.
1980:      */
1981:     protected function _processSave($entity, $options)
1982:     {
1983:         $primaryColumns = (array)$this->getPrimaryKey();
1984: 
1985:         if ($options['checkExisting'] && $primaryColumns && $entity->isNew() && $entity->has($primaryColumns)) {
1986:             $alias = $this->getAlias();
1987:             $conditions = [];
1988:             foreach ($entity->extract($primaryColumns) as $k => $v) {
1989:                 $conditions["$alias.$k"] = $v;
1990:             }
1991:             $entity->isNew(!$this->exists($conditions));
1992:         }
1993: 
1994:         $mode = $entity->isNew() ? RulesChecker::CREATE : RulesChecker::UPDATE;
1995:         if ($options['checkRules'] && !$this->checkRules($entity, $mode, $options)) {
1996:             return false;
1997:         }
1998: 
1999:         $options['associated'] = $this->_associations->normalizeKeys($options['associated']);
2000:         $event = $this->dispatchEvent('Model.beforeSave', compact('entity', 'options'));
2001: 
2002:         if ($event->isStopped()) {
2003:             return $event->getResult();
2004:         }
2005: 
2006:         $saved = $this->_associations->saveParents(
2007:             $this,
2008:             $entity,
2009:             $options['associated'],
2010:             ['_primary' => false] + $options->getArrayCopy()
2011:         );
2012: 
2013:         if (!$saved && $options['atomic']) {
2014:             return false;
2015:         }
2016: 
2017:         $data = $entity->extract($this->getSchema()->columns(), true);
2018:         $isNew = $entity->isNew();
2019: 
2020:         if ($isNew) {
2021:             $success = $this->_insert($entity, $data);
2022:         } else {
2023:             $success = $this->_update($entity, $data);
2024:         }
2025: 
2026:         if ($success) {
2027:             $success = $this->_onSaveSuccess($entity, $options);
2028:         }
2029: 
2030:         if (!$success && $isNew) {
2031:             $entity->unsetProperty($this->getPrimaryKey());
2032:             $entity->isNew(true);
2033:         }
2034: 
2035:         return $success ? $entity : false;
2036:     }
2037: 
2038:     /**
2039:      * Handles the saving of children associations and executing the afterSave logic
2040:      * once the entity for this table has been saved successfully.
2041:      *
2042:      * @param \Cake\Datasource\EntityInterface $entity the entity to be saved
2043:      * @param \ArrayObject $options the options to use for the save operation
2044:      * @return bool True on success
2045:      * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction
2046:      *   is aborted in the afterSave event.
2047:      */
2048:     protected function _onSaveSuccess($entity, $options)
2049:     {
2050:         $success = $this->_associations->saveChildren(
2051:             $this,
2052:             $entity,
2053:             $options['associated'],
2054:             ['_primary' => false] + $options->getArrayCopy()
2055:         );
2056: 
2057:         if (!$success && $options['atomic']) {
2058:             return false;
2059:         }
2060: 
2061:         $this->dispatchEvent('Model.afterSave', compact('entity', 'options'));
2062: 
2063:         if ($options['atomic'] && !$this->getConnection()->inTransaction()) {
2064:             throw new RolledbackTransactionException(['table' => get_class($this)]);
2065:         }
2066: 
2067:         if (!$options['atomic'] && !$options['_primary']) {
2068:             $entity->clean();
2069:             $entity->isNew(false);
2070:             $entity->setSource($this->getRegistryAlias());
2071:         }
2072: 
2073:         return true;
2074:     }
2075: 
2076:     /**
2077:      * Auxiliary function to handle the insert of an entity's data in the table
2078:      *
2079:      * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted
2080:      * @param array $data The actual data that needs to be saved
2081:      * @return \Cake\Datasource\EntityInterface|bool
2082:      * @throws \RuntimeException if not all the primary keys where supplied or could
2083:      * be generated when the table has composite primary keys. Or when the table has no primary key.
2084:      */
2085:     protected function _insert($entity, $data)
2086:     {
2087:         $primary = (array)$this->getPrimaryKey();
2088:         if (empty($primary)) {
2089:             $msg = sprintf(
2090:                 'Cannot insert row in "%s" table, it has no primary key.',
2091:                 $this->getTable()
2092:             );
2093:             throw new RuntimeException($msg);
2094:         }
2095:         $keys = array_fill(0, count($primary), null);
2096:         $id = (array)$this->_newId($primary) + $keys;
2097: 
2098:         // Generate primary keys preferring values in $data.
2099:         $primary = array_combine($primary, $id);
2100:         $primary = array_intersect_key($data, $primary) + $primary;
2101: 
2102:         $filteredKeys = array_filter($primary, function ($v) {
2103:             return $v !== null;
2104:         });
2105:         $data += $filteredKeys;
2106: 
2107:         if (count($primary) > 1) {
2108:             $schema = $this->getSchema();
2109:             foreach ($primary as $k => $v) {
2110:                 if (!isset($data[$k]) && empty($schema->getColumn($k)['autoIncrement'])) {
2111:                     $msg = 'Cannot insert row, some of the primary key values are missing. ';
2112:                     $msg .= sprintf(
2113:                         'Got (%s), expecting (%s)',
2114:                         implode(', ', $filteredKeys + $entity->extract(array_keys($primary))),
2115:                         implode(', ', array_keys($primary))
2116:                     );
2117:                     throw new RuntimeException($msg);
2118:                 }
2119:             }
2120:         }
2121: 
2122:         $success = false;
2123:         if (empty($data)) {
2124:             return $success;
2125:         }
2126: 
2127:         $statement = $this->query()->insert(array_keys($data))
2128:             ->values($data)
2129:             ->execute();
2130: 
2131:         if ($statement->rowCount() !== 0) {
2132:             $success = $entity;
2133:             $entity->set($filteredKeys, ['guard' => false]);
2134:             $schema = $this->getSchema();
2135:             $driver = $this->getConnection()->getDriver();
2136:             foreach ($primary as $key => $v) {
2137:                 if (!isset($data[$key])) {
2138:                     $id = $statement->lastInsertId($this->getTable(), $key);
2139:                     $type = $schema->getColumnType($key);
2140:                     $entity->set($key, Type::build($type)->toPHP($id, $driver));
2141:                     break;
2142:                 }
2143:             }
2144:         }
2145:         $statement->closeCursor();
2146: 
2147:         return $success;
2148:     }
2149: 
2150:     /**
2151:      * Generate a primary key value for a new record.
2152:      *
2153:      * By default, this uses the type system to generate a new primary key
2154:      * value if possible. You can override this method if you have specific requirements
2155:      * for id generation.
2156:      *
2157:      * Note: The ORM will not generate primary key values for composite primary keys.
2158:      * You can overwrite _newId() in your table class.
2159:      *
2160:      * @param string[] $primary The primary key columns to get a new ID for.
2161:      * @return string|null Either null or the primary key value or a list of primary key values.
2162:      */
2163:     protected function _newId($primary)
2164:     {
2165:         if (!$primary || count((array)$primary) > 1) {
2166:             return null;
2167:         }
2168:         $typeName = $this->getSchema()->getColumnType($primary[0]);
2169:         $type = Type::build($typeName);
2170: 
2171:         return $type->newId();
2172:     }
2173: 
2174:     /**
2175:      * Auxiliary function to handle the update of an entity's data in the table
2176:      *
2177:      * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted
2178:      * @param array $data The actual data that needs to be saved
2179:      * @return \Cake\Datasource\EntityInterface|bool
2180:      * @throws \InvalidArgumentException When primary key data is missing.
2181:      */
2182:     protected function _update($entity, $data)
2183:     {
2184:         $primaryColumns = (array)$this->getPrimaryKey();
2185:         $primaryKey = $entity->extract($primaryColumns);
2186: 
2187:         $data = array_diff_key($data, $primaryKey);
2188:         if (empty($data)) {
2189:             return $entity;
2190:         }
2191: 
2192:         if (count($primaryColumns) === 0) {
2193:             $entityClass = get_class($entity);
2194:             $table = $this->getTable();
2195:             $message = "Cannot update `$entityClass`. The `$table` has no primary key.";
2196:             throw new InvalidArgumentException($message);
2197:         }
2198: 
2199:         if (!$entity->has($primaryColumns)) {
2200:             $message = 'All primary key value(s) are needed for updating, ';
2201:             $message .= get_class($entity) . ' is missing ' . implode(', ', $primaryColumns);
2202:             throw new InvalidArgumentException($message);
2203:         }
2204: 
2205:         $query = $this->query();
2206:         $statement = $query->update()
2207:             ->set($data)
2208:             ->where($primaryKey)
2209:             ->execute();
2210: 
2211:         $success = false;
2212:         if ($statement->errorCode() === '00000') {
2213:             $success = $entity;
2214:         }
2215:         $statement->closeCursor();
2216: 
2217:         return $success;
2218:     }
2219: 
2220:     /**
2221:      * Persists multiple entities of a table.
2222:      *
2223:      * The records will be saved in a transaction which will be rolled back if
2224:      * any one of the records fails to save due to failed validation or database
2225:      * error.
2226:      *
2227:      * @param \Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface $entities Entities to save.
2228:      * @param array|\ArrayAccess $options Options used when calling Table::save() for each entity.
2229:      * @return bool|\Cake\Datasource\EntityInterface[]|\Cake\Datasource\ResultSetInterface False on failure, entities list on success.
2230:      * @throws \Exception
2231:      */
2232:     public function saveMany($entities, $options = [])
2233:     {
2234:         $isNew = [];
2235:         $cleanup = function ($entities) use (&$isNew) {
2236:             foreach ($entities as $key => $entity) {
2237:                 if (isset($isNew[$key]) && $isNew[$key]) {
2238:                     $entity->unsetProperty($this->getPrimaryKey());
2239:                     $entity->isNew(true);
2240:                 }
2241:             }
2242:         };
2243: 
2244:         try {
2245:             $return = $this->getConnection()
2246:                 ->transactional(function () use ($entities, $options, &$isNew) {
2247:                     foreach ($entities as $key => $entity) {
2248:                         $isNew[$key] = $entity->isNew();
2249:                         if ($this->save($entity, $options) === false) {
2250:                             return false;
2251:                         }
2252:                     }
2253:                 });
2254:         } catch (\Exception $e) {
2255:             $cleanup($entities);
2256: 
2257:             throw $e;
2258:         }
2259: 
2260:         if ($return === false) {
2261:             $cleanup($entities);
2262: 
2263:             return false;
2264:         }
2265: 
2266:         return $entities;
2267:     }
2268: 
2269:     /**
2270:      * {@inheritDoc}
2271:      *
2272:      * For HasMany and HasOne associations records will be removed based on
2273:      * the dependent option. Join table records in BelongsToMany associations
2274:      * will always be removed. You can use the `cascadeCallbacks` option
2275:      * when defining associations to change how associated data is deleted.
2276:      *
2277:      * ### Options
2278:      *
2279:      * - `atomic` Defaults to true. When true the deletion happens within a transaction.
2280:      * - `checkRules` Defaults to true. Check deletion rules before deleting the record.
2281:      *
2282:      * ### Events
2283:      *
2284:      * - `Model.beforeDelete` Fired before the delete occurs. If stopped the delete
2285:      *   will be aborted. Receives the event, entity, and options.
2286:      * - `Model.afterDelete` Fired after the delete has been successful. Receives
2287:      *   the event, entity, and options.
2288:      * - `Model.afterDeleteCommit` Fired after the transaction is committed for
2289:      *   an atomic delete. Receives the event, entity, and options.
2290:      *
2291:      * The options argument will be converted into an \ArrayObject instance
2292:      * for the duration of the callbacks, this allows listeners to modify
2293:      * the options used in the delete operation.
2294:      *
2295:      */
2296:     public function delete(EntityInterface $entity, $options = [])
2297:     {
2298:         $options = new ArrayObject((array)$options + [
2299:             'atomic' => true,
2300:             'checkRules' => true,
2301:             '_primary' => true,
2302:         ]);
2303: 
2304:         $success = $this->_executeTransaction(function () use ($entity, $options) {
2305:             return $this->_processDelete($entity, $options);
2306:         }, $options['atomic']);
2307: 
2308:         if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) {
2309:             $this->dispatchEvent('Model.afterDeleteCommit', [
2310:                 'entity' => $entity,
2311:                 'options' => $options,
2312:             ]);
2313:         }
2314: 
2315:         return $success;
2316:     }
2317: 
2318:     /**
2319:      * Try to delete an entity or throw a PersistenceFailedException if the entity is new,
2320:      * has no primary key value, application rules checks failed or the delete was aborted by a callback.
2321:      *
2322:      * @param \Cake\Datasource\EntityInterface $entity The entity to remove.
2323:      * @param array|\ArrayAccess $options The options for the delete.
2324:      * @return bool success
2325:      * @throws \Cake\ORM\Exception\PersistenceFailedException
2326:      * @see \Cake\ORM\Table::delete()
2327:      */
2328:     public function deleteOrFail(EntityInterface $entity, $options = [])
2329:     {
2330:         $deleted = $this->delete($entity, $options);
2331:         if ($deleted === false) {
2332:             throw new PersistenceFailedException($entity, ['delete']);
2333:         }
2334: 
2335:         return $deleted;
2336:     }
2337: 
2338:     /**
2339:      * Perform the delete operation.
2340:      *
2341:      * Will delete the entity provided. Will remove rows from any
2342:      * dependent associations, and clear out join tables for BelongsToMany associations.
2343:      *
2344:      * @param \Cake\Datasource\EntityInterface $entity The entity to delete.
2345:      * @param \ArrayObject $options The options for the delete.
2346:      * @throws \InvalidArgumentException if there are no primary key values of the
2347:      * passed entity
2348:      * @return bool success
2349:      */
2350:     protected function _processDelete($entity, $options)
2351:     {
2352:         if ($entity->isNew()) {
2353:             return false;
2354:         }
2355: 
2356:         $primaryKey = (array)$this->getPrimaryKey();
2357:         if (!$entity->has($primaryKey)) {
2358:             $msg = 'Deleting requires all primary key values.';
2359:             throw new InvalidArgumentException($msg);
2360:         }
2361: 
2362:         if ($options['checkRules'] && !$this->checkRules($entity, RulesChecker::DELETE, $options)) {
2363:             return false;
2364:         }
2365: 
2366:         $event = $this->dispatchEvent('Model.beforeDelete', [
2367:             'entity' => $entity,
2368:             'options' => $options,
2369:         ]);
2370: 
2371:         if ($event->isStopped()) {
2372:             return $event->getResult();
2373:         }
2374: 
2375:         $this->_associations->cascadeDelete(
2376:             $entity,
2377:             ['_primary' => false] + $options->getArrayCopy()
2378:         );
2379: 
2380:         $query = $this->query();
2381:         $conditions = (array)$entity->extract($primaryKey);
2382:         $statement = $query->delete()
2383:             ->where($conditions)
2384:             ->execute();
2385: 
2386:         $success = $statement->rowCount() > 0;
2387:         if (!$success) {
2388:             return $success;
2389:         }
2390: 
2391:         $this->dispatchEvent('Model.afterDelete', [
2392:             'entity' => $entity,
2393:             'options' => $options,
2394:         ]);
2395: 
2396:         return $success;
2397:     }
2398: 
2399:     /**
2400:      * Returns true if the finder exists for the table
2401:      *
2402:      * @param string $type name of finder to check
2403:      *
2404:      * @return bool
2405:      */
2406:     public function hasFinder($type)
2407:     {
2408:         $finder = 'find' . $type;
2409: 
2410:         return method_exists($this, $finder) || ($this->_behaviors && $this->_behaviors->hasFinder($type));
2411:     }
2412: 
2413:     /**
2414:      * Calls a finder method directly and applies it to the passed query,
2415:      * if no query is passed a new one will be created and returned
2416:      *
2417:      * @param string $type name of the finder to be called
2418:      * @param \Cake\ORM\Query $query The query object to apply the finder options to
2419:      * @param array $options List of options to pass to the finder
2420:      * @return \Cake\ORM\Query
2421:      * @throws \BadMethodCallException
2422:      */
2423:     public function callFinder($type, Query $query, array $options = [])
2424:     {
2425:         $query->applyOptions($options);
2426:         $options = $query->getOptions();
2427:         $finder = 'find' . $type;
2428:         if (method_exists($this, $finder)) {
2429:             return $this->{$finder}($query, $options);
2430:         }
2431: 
2432:         if ($this->_behaviors && $this->_behaviors->hasFinder($type)) {
2433:             return $this->_behaviors->callFinder($type, [$query, $options]);
2434:         }
2435: 
2436:         throw new BadMethodCallException(
2437:             sprintf('Unknown finder method "%s"', $type)
2438:         );
2439:     }
2440: 
2441:     /**
2442:      * Provides the dynamic findBy and findByAll methods.
2443:      *
2444:      * @param string $method The method name that was fired.
2445:      * @param array $args List of arguments passed to the function.
2446:      * @return mixed
2447:      * @throws \BadMethodCallException when there are missing arguments, or when
2448:      *  and & or are combined.
2449:      */
2450:     protected function _dynamicFinder($method, $args)
2451:     {
2452:         $method = Inflector::underscore($method);
2453:         preg_match('/^find_([\w]+)_by_/', $method, $matches);
2454:         if (empty($matches)) {
2455:             // find_by_ is 8 characters.
2456:             $fields = substr($method, 8);
2457:             $findType = 'all';
2458:         } else {
2459:             $fields = substr($method, strlen($matches[0]));
2460:             $findType = Inflector::variable($matches[1]);
2461:         }
2462:         $hasOr = strpos($fields, '_or_');
2463:         $hasAnd = strpos($fields, '_and_');
2464: 
2465:         $makeConditions = function ($fields, $args) {
2466:             $conditions = [];
2467:             if (count($args) < count($fields)) {
2468:                 throw new BadMethodCallException(sprintf(
2469:                     'Not enough arguments for magic finder. Got %s required %s',
2470:                     count($args),
2471:                     count($fields)
2472:                 ));
2473:             }
2474:             foreach ($fields as $field) {
2475:                 $conditions[$this->aliasField($field)] = array_shift($args);
2476:             }
2477: 
2478:             return $conditions;
2479:         };
2480: 
2481:         if ($hasOr !== false && $hasAnd !== false) {
2482:             throw new BadMethodCallException(
2483:                 'Cannot mix "and" & "or" in a magic finder. Use find() instead.'
2484:             );
2485:         }
2486: 
2487:         $conditions = [];
2488:         if ($hasOr === false && $hasAnd === false) {
2489:             $conditions = $makeConditions([$fields], $args);
2490:         } elseif ($hasOr !== false) {
2491:             $fields = explode('_or_', $fields);
2492:             $conditions = [
2493:             'OR' => $makeConditions($fields, $args),
2494:             ];
2495:         } elseif ($hasAnd !== false) {
2496:             $fields = explode('_and_', $fields);
2497:             $conditions = $makeConditions($fields, $args);
2498:         }
2499: 
2500:         return $this->find($findType, [
2501:             'conditions' => $conditions,
2502:         ]);
2503:     }
2504: 
2505:     /**
2506:      * Handles behavior delegation + dynamic finders.
2507:      *
2508:      * If your Table uses any behaviors you can call them as if
2509:      * they were on the table object.
2510:      *
2511:      * @param string $method name of the method to be invoked
2512:      * @param array $args List of arguments passed to the function
2513:      * @return mixed
2514:      * @throws \BadMethodCallException
2515:      */
2516:     public function __call($method, $args)
2517:     {
2518:         if ($this->_behaviors && $this->_behaviors->hasMethod($method)) {
2519:             return $this->_behaviors->call($method, $args);
2520:         }
2521:         if (preg_match('/^find(?:\w+)?By/', $method) > 0) {
2522:             return $this->_dynamicFinder($method, $args);
2523:         }
2524: 
2525:         throw new BadMethodCallException(
2526:             sprintf('Unknown method "%s"', $method)
2527:         );
2528:     }
2529: 
2530:     /**
2531:      * Returns the association named after the passed value if exists, otherwise
2532:      * throws an exception.
2533:      *
2534:      * @param string $property the association name
2535:      * @return \Cake\ORM\Association
2536:      * @throws \RuntimeException if no association with such name exists
2537:      */
2538:     public function __get($property)
2539:     {
2540:         $association = $this->_associations->get($property);
2541:         if (!$association) {
2542:             throw new RuntimeException(sprintf(
2543:                 'Undefined property `%s`. ' .
2544:                 'You have not defined the `%s` association on `%s`.',
2545:                 $property,
2546:                 $property,
2547:                 static::class
2548:             ));
2549:         }
2550: 
2551:         return $association;
2552:     }
2553: 
2554:     /**
2555:      * Returns whether an association named after the passed value
2556:      * exists for this table.
2557:      *
2558:      * @param string $property the association name
2559:      * @return bool
2560:      */
2561:     public function __isset($property)
2562:     {
2563:         return $this->_associations->has($property);
2564:     }
2565: 
2566:     /**
2567:      * Get the object used to marshal/convert array data into objects.
2568:      *
2569:      * Override this method if you want a table object to use custom
2570:      * marshalling logic.
2571:      *
2572:      * @return \Cake\ORM\Marshaller
2573:      * @see \Cake\ORM\Marshaller
2574:      */
2575:     public function marshaller()
2576:     {
2577:         return new Marshaller($this);
2578:     }
2579: 
2580:     /**
2581:      * {@inheritDoc}
2582:      *
2583:      * By default all the associations on this table will be hydrated. You can
2584:      * limit which associations are built, or include deeper associations
2585:      * using the options parameter:
2586:      *
2587:      * ```
2588:      * $article = $this->Articles->newEntity(
2589:      *   $this->request->getData(),
2590:      *   ['associated' => ['Tags', 'Comments.Users']]
2591:      * );
2592:      * ```
2593:      *
2594:      * You can limit fields that will be present in the constructed entity by
2595:      * passing the `fields` option, which is also accepted for associations:
2596:      *
2597:      * ```
2598:      * $article = $this->Articles->newEntity($this->request->getData(), [
2599:      *  'fields' => ['title', 'body', 'tags', 'comments'],
2600:      *  'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']]
2601:      * ]
2602:      * );
2603:      * ```
2604:      *
2605:      * The `fields` option lets remove or restrict input data from ending up in
2606:      * the entity. If you'd like to relax the entity's default accessible fields,
2607:      * you can use the `accessibleFields` option:
2608:      *
2609:      * ```
2610:      * $article = $this->Articles->newEntity(
2611:      *   $this->request->getData(),
2612:      *   ['accessibleFields' => ['protected_field' => true]]
2613:      * );
2614:      * ```
2615:      *
2616:      * By default, the data is validated before being passed to the new entity. In
2617:      * the case of invalid fields, those will not be present in the resulting object.
2618:      * The `validate` option can be used to disable validation on the passed data:
2619:      *
2620:      * ```
2621:      * $article = $this->Articles->newEntity(
2622:      *   $this->request->getData(),
2623:      *   ['validate' => false]
2624:      * );
2625:      * ```
2626:      *
2627:      * You can also pass the name of the validator to use in the `validate` option.
2628:      * If `null` is passed to the first param of this function, no validation will
2629:      * be performed.
2630:      *
2631:      * You can use the `Model.beforeMarshal` event to modify request data
2632:      * before it is converted into entities.
2633:      */
2634:     public function newEntity($data = null, array $options = [])
2635:     {
2636:         if ($data === null) {
2637:             $class = $this->getEntityClass();
2638: 
2639:             return new $class([], ['source' => $this->getRegistryAlias()]);
2640:         }
2641:         if (!isset($options['associated'])) {
2642:             $options['associated'] = $this->_associations->keys();
2643:         }
2644:         $marshaller = $this->marshaller();
2645: 
2646:         return $marshaller->one($data, $options);
2647:     }
2648: 
2649:     /**
2650:      * {@inheritDoc}
2651:      *
2652:      * By default all the associations on this table will be hydrated. You can
2653:      * limit which associations are built, or include deeper associations
2654:      * using the options parameter:
2655:      *
2656:      * ```
2657:      * $articles = $this->Articles->newEntities(
2658:      *   $this->request->getData(),
2659:      *   ['associated' => ['Tags', 'Comments.Users']]
2660:      * );
2661:      * ```
2662:      *
2663:      * You can limit fields that will be present in the constructed entities by
2664:      * passing the `fields` option, which is also accepted for associations:
2665:      *
2666:      * ```
2667:      * $articles = $this->Articles->newEntities($this->request->getData(), [
2668:      *  'fields' => ['title', 'body', 'tags', 'comments'],
2669:      *  'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']]
2670:      *  ]
2671:      * );
2672:      * ```
2673:      *
2674:      * You can use the `Model.beforeMarshal` event to modify request data
2675:      * before it is converted into entities.
2676:      */
2677:     public function newEntities(array $data, array $options = [])
2678:     {
2679:         if (!isset($options['associated'])) {
2680:             $options['associated'] = $this->_associations->keys();
2681:         }
2682:         $marshaller = $this->marshaller();
2683: 
2684:         return $marshaller->many($data, $options);
2685:     }
2686: 
2687:     /**
2688:      * {@inheritDoc}
2689:      *
2690:      * When merging HasMany or BelongsToMany associations, all the entities in the
2691:      * `$data` array will appear, those that can be matched by primary key will get
2692:      * the data merged, but those that cannot, will be discarded.
2693:      *
2694:      * You can limit fields that will be present in the merged entity by
2695:      * passing the `fields` option, which is also accepted for associations:
2696:      *
2697:      * ```
2698:      * $article = $this->Articles->patchEntity($article, $this->request->getData(), [
2699:      *  'fields' => ['title', 'body', 'tags', 'comments'],
2700:      *  'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']]
2701:      *  ]
2702:      * );
2703:      * ```
2704:      *
2705:      * By default, the data is validated before being passed to the entity. In
2706:      * the case of invalid fields, those will not be assigned to the entity.
2707:      * The `validate` option can be used to disable validation on the passed data:
2708:      *
2709:      * ```
2710:      * $article = $this->patchEntity($article, $this->request->getData(),[
2711:      *  'validate' => false
2712:      * ]);
2713:      * ```
2714:      *
2715:      * You can use the `Model.beforeMarshal` event to modify request data
2716:      * before it is converted into entities.
2717:      *
2718:      * When patching scalar values (null/booleans/string/integer/float), if the property
2719:      * presently has an identical value, the setter will not be called, and the
2720:      * property will not be marked as dirty. This is an optimization to prevent unnecessary field
2721:      * updates when persisting entities.
2722:      */
2723:     public function patchEntity(EntityInterface $entity, array $data, array $options = [])
2724:     {
2725:         if (!isset($options['associated'])) {
2726:             $options['associated'] = $this->_associations->keys();
2727:         }
2728:         $marshaller = $this->marshaller();
2729: 
2730:         return $marshaller->merge($entity, $data, $options);
2731:     }
2732: 
2733:     /**
2734:      * {@inheritDoc}
2735:      *
2736:      * Those entries in `$entities` that cannot be matched to any record in
2737:      * `$data` will be discarded. Records in `$data` that could not be matched will
2738:      * be marshalled as a new entity.
2739:      *
2740:      * When merging HasMany or BelongsToMany associations, all the entities in the
2741:      * `$data` array will appear, those that can be matched by primary key will get
2742:      * the data merged, but those that cannot, will be discarded.
2743:      *
2744:      * You can limit fields that will be present in the merged entities by
2745:      * passing the `fields` option, which is also accepted for associations:
2746:      *
2747:      * ```
2748:      * $articles = $this->Articles->patchEntities($articles, $this->request->getData(), [
2749:      *  'fields' => ['title', 'body', 'tags', 'comments'],
2750:      *  'associated' => ['Tags', 'Comments.Users' => ['fields' => 'username']]
2751:      *  ]
2752:      * );
2753:      * ```
2754:      *
2755:      * You can use the `Model.beforeMarshal` event to modify request data
2756:      * before it is converted into entities.
2757:      */
2758:     public function patchEntities($entities, array $data, array $options = [])
2759:     {
2760:         if (!isset($options['associated'])) {
2761:             $options['associated'] = $this->_associations->keys();
2762:         }
2763:         $marshaller = $this->marshaller();
2764: 
2765:         return $marshaller->mergeMany($entities, $data, $options);
2766:     }
2767: 
2768:     /**
2769:      * Validator method used to check the uniqueness of a value for a column.
2770:      * This is meant to be used with the validation API and not to be called
2771:      * directly.
2772:      *
2773:      * ### Example:
2774:      *
2775:      * ```
2776:      * $validator->add('email', [
2777:      *  'unique' => ['rule' => 'validateUnique', 'provider' => 'table']
2778:      * ])
2779:      * ```
2780:      *
2781:      * Unique validation can be scoped to the value of another column:
2782:      *
2783:      * ```
2784:      * $validator->add('email', [
2785:      *  'unique' => [
2786:      *      'rule' => ['validateUnique', ['scope' => 'site_id']],
2787:      *      'provider' => 'table'
2788:      *  ]
2789:      * ]);
2790:      * ```
2791:      *
2792:      * In the above example, the email uniqueness will be scoped to only rows having
2793:      * the same site_id. Scoping will only be used if the scoping field is present in
2794:      * the data to be validated.
2795:      *
2796:      * @param mixed $value The value of column to be checked for uniqueness.
2797:      * @param array $options The options array, optionally containing the 'scope' key.
2798:      *   May also be the validation context, if there are no options.
2799:      * @param array|null $context Either the validation context or null.
2800:      * @return bool True if the value is unique, or false if a non-scalar, non-unique value was given.
2801:      */
2802:     public function validateUnique($value, array $options, array $context = null)
2803:     {
2804:         if ($context === null) {
2805:             $context = $options;
2806:         }
2807:         $entity = new Entity(
2808:             $context['data'],
2809:             [
2810:                 'useSetters' => false,
2811:                 'markNew' => $context['newRecord'],
2812:                 'source' => $this->getRegistryAlias(),
2813:             ]
2814:         );
2815:         $fields = array_merge(
2816:             [$context['field']],
2817:             isset($options['scope']) ? (array)$options['scope'] : []
2818:         );
2819:         $values = $entity->extract($fields);
2820:         foreach ($values as $field) {
2821:             if ($field !== null && !is_scalar($field)) {
2822:                 return false;
2823:             }
2824:         }
2825:         $class = static::IS_UNIQUE_CLASS;
2826:         $rule = new $class($fields, $options);
2827: 
2828:         return $rule($entity, ['repository' => $this]);
2829:     }
2830: 
2831:     /**
2832:      * Get the Model callbacks this table is interested in.
2833:      *
2834:      * By implementing the conventional methods a table class is assumed
2835:      * to be interested in the related event.
2836:      *
2837:      * Override this method if you need to add non-conventional event listeners.
2838:      * Or if you want you table to listen to non-standard events.
2839:      *
2840:      * The conventional method map is:
2841:      *
2842:      * - Model.beforeMarshal => beforeMarshal
2843:      * - Model.buildValidator => buildValidator
2844:      * - Model.beforeFind => beforeFind
2845:      * - Model.beforeSave => beforeSave
2846:      * - Model.afterSave => afterSave
2847:      * - Model.afterSaveCommit => afterSaveCommit
2848:      * - Model.beforeDelete => beforeDelete
2849:      * - Model.afterDelete => afterDelete
2850:      * - Model.afterDeleteCommit => afterDeleteCommit
2851:      * - Model.beforeRules => beforeRules
2852:      * - Model.afterRules => afterRules
2853:      *
2854:      * @return array
2855:      */
2856:     public function implementedEvents()
2857:     {
2858:         $eventMap = [
2859:             'Model.beforeMarshal' => 'beforeMarshal',
2860:             'Model.buildValidator' => 'buildValidator',
2861:             'Model.beforeFind' => 'beforeFind',
2862:             'Model.beforeSave' => 'beforeSave',
2863:             'Model.afterSave' => 'afterSave',
2864:             'Model.afterSaveCommit' => 'afterSaveCommit',
2865:             'Model.beforeDelete' => 'beforeDelete',
2866:             'Model.afterDelete' => 'afterDelete',
2867:             'Model.afterDeleteCommit' => 'afterDeleteCommit',
2868:             'Model.beforeRules' => 'beforeRules',
2869:             'Model.afterRules' => 'afterRules',
2870:         ];
2871:         $events = [];
2872: 
2873:         foreach ($eventMap as $event => $method) {
2874:             if (!method_exists($this, $method)) {
2875:                 continue;
2876:             }
2877:             $events[$event] = $method;
2878:         }
2879: 
2880:         return $events;
2881:     }
2882: 
2883:     /**
2884:      * {@inheritDoc}
2885:      *
2886:      * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
2887:      * @return \Cake\ORM\RulesChecker
2888:      */
2889:     public function buildRules(RulesChecker $rules)
2890:     {
2891:         return $rules;
2892:     }
2893: 
2894:     /**
2895:      * Gets a SaveOptionsBuilder instance.
2896:      *
2897:      * @param array $options Options to parse by the builder.
2898:      * @return \Cake\ORM\SaveOptionsBuilder
2899:      */
2900:     public function getSaveOptionsBuilder(array $options = [])
2901:     {
2902:         return new SaveOptionsBuilder($this, $options);
2903:     }
2904: 
2905:     /**
2906:      * Loads the specified associations in the passed entity or list of entities
2907:      * by executing extra queries in the database and merging the results in the
2908:      * appropriate properties.
2909:      *
2910:      * ### Example:
2911:      *
2912:      * ```
2913:      * $user = $usersTable->get(1);
2914:      * $user = $usersTable->loadInto($user, ['Articles.Tags', 'Articles.Comments']);
2915:      * echo $user->articles[0]->title;
2916:      * ```
2917:      *
2918:      * You can also load associations for multiple entities at once
2919:      *
2920:      * ### Example:
2921:      *
2922:      * ```
2923:      * $users = $usersTable->find()->where([...])->toList();
2924:      * $users = $usersTable->loadInto($users, ['Articles.Tags', 'Articles.Comments']);
2925:      * echo $user[1]->articles[0]->title;
2926:      * ```
2927:      *
2928:      * The properties for the associations to be loaded will be overwritten on each entity.
2929:      *
2930:      * @param \Cake\Datasource\EntityInterface|array $entities a single entity or list of entities
2931:      * @param array $contain A `contain()` compatible array.
2932:      * @see \Cake\ORM\Query::contain()
2933:      * @return \Cake\Datasource\EntityInterface|array
2934:      */
2935:     public function loadInto($entities, array $contain)
2936:     {
2937:         return (new LazyEagerLoader())->loadInto($entities, $contain, $this);
2938:     }
2939: 
2940:     /**
2941:      * {@inheritDoc}
2942:      */
2943:     protected function validationMethodExists($method)
2944:     {
2945:         return method_exists($this, $method) || $this->behaviors()->hasMethod($method);
2946:     }
2947: 
2948:     /**
2949:      * Returns an array that can be used to describe the internal state of this
2950:      * object.
2951:      *
2952:      * @return array
2953:      */
2954:     public function __debugInfo()
2955:     {
2956:         $conn = $this->getConnection();
2957:         $associations = $this->_associations;
2958:         $behaviors = $this->_behaviors;
2959: 
2960:         return [
2961:             'registryAlias' => $this->getRegistryAlias(),
2962:             'table' => $this->getTable(),
2963:             'alias' => $this->getAlias(),
2964:             'entityClass' => $this->getEntityClass(),
2965:             'associations' => $associations ? $associations->keys() : false,
2966:             'behaviors' => $behaviors ? $behaviors->loaded() : false,
2967:             'defaultConnection' => static::defaultConnectionName(),
2968:             'connectionName' => $conn ? $conn->configName() : null,
2969:         ];
2970:     }
2971: }
2972: 
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