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 Cake\Collection\Collection;
  18: use Cake\Core\App;
  19: use Cake\Core\ConventionsTrait;
  20: use Cake\Database\Expression\IdentifierExpression;
  21: use Cake\Datasource\EntityInterface;
  22: use Cake\Datasource\QueryInterface;
  23: use Cake\Datasource\ResultSetDecorator;
  24: use Cake\ORM\Locator\LocatorAwareTrait;
  25: use Cake\Utility\Inflector;
  26: use InvalidArgumentException;
  27: use RuntimeException;
  28: 
  29: /**
  30:  * An Association is a relationship established between two tables and is used
  31:  * to configure and customize the way interconnected records are retrieved.
  32:  *
  33:  * @mixin \Cake\ORM\Table
  34:  */
  35: abstract class Association
  36: {
  37:     use ConventionsTrait;
  38:     use LocatorAwareTrait;
  39: 
  40:     /**
  41:      * Strategy name to use joins for fetching associated records
  42:      *
  43:      * @var string
  44:      */
  45:     const STRATEGY_JOIN = 'join';
  46: 
  47:     /**
  48:      * Strategy name to use a subquery for fetching associated records
  49:      *
  50:      * @var string
  51:      */
  52:     const STRATEGY_SUBQUERY = 'subquery';
  53: 
  54:     /**
  55:      * Strategy name to use a select for fetching associated records
  56:      *
  57:      * @var string
  58:      */
  59:     const STRATEGY_SELECT = 'select';
  60: 
  61:     /**
  62:      * Association type for one to one associations.
  63:      *
  64:      * @var string
  65:      */
  66:     const ONE_TO_ONE = 'oneToOne';
  67: 
  68:     /**
  69:      * Association type for one to many associations.
  70:      *
  71:      * @var string
  72:      */
  73:     const ONE_TO_MANY = 'oneToMany';
  74: 
  75:     /**
  76:      * Association type for many to many associations.
  77:      *
  78:      * @var string
  79:      */
  80:     const MANY_TO_MANY = 'manyToMany';
  81: 
  82:     /**
  83:      * Association type for many to one associations.
  84:      *
  85:      * @var string
  86:      */
  87:     const MANY_TO_ONE = 'manyToOne';
  88: 
  89:     /**
  90:      * Name given to the association, it usually represents the alias
  91:      * assigned to the target associated table
  92:      *
  93:      * @var string
  94:      */
  95:     protected $_name;
  96: 
  97:     /**
  98:      * The class name of the target table object
  99:      *
 100:      * @var string
 101:      */
 102:     protected $_className;
 103: 
 104:     /**
 105:      * The field name in the owning side table that is used to match with the foreignKey
 106:      *
 107:      * @var string|string[]
 108:      */
 109:     protected $_bindingKey;
 110: 
 111:     /**
 112:      * The name of the field representing the foreign key to the table to load
 113:      *
 114:      * @var string|string[]
 115:      */
 116:     protected $_foreignKey;
 117: 
 118:     /**
 119:      * A list of conditions to be always included when fetching records from
 120:      * the target association
 121:      *
 122:      * @var array|callable
 123:      */
 124:     protected $_conditions = [];
 125: 
 126:     /**
 127:      * Whether the records on the target table are dependent on the source table,
 128:      * often used to indicate that records should be removed if the owning record in
 129:      * the source table is deleted.
 130:      *
 131:      * @var bool
 132:      */
 133:     protected $_dependent = false;
 134: 
 135:     /**
 136:      * Whether or not cascaded deletes should also fire callbacks.
 137:      *
 138:      * @var bool
 139:      */
 140:     protected $_cascadeCallbacks = false;
 141: 
 142:     /**
 143:      * Source table instance
 144:      *
 145:      * @var \Cake\ORM\Table
 146:      */
 147:     protected $_sourceTable;
 148: 
 149:     /**
 150:      * Target table instance
 151:      *
 152:      * @var \Cake\ORM\Table
 153:      */
 154:     protected $_targetTable;
 155: 
 156:     /**
 157:      * The type of join to be used when adding the association to a query
 158:      *
 159:      * @var string
 160:      */
 161:     protected $_joinType = QueryInterface::JOIN_TYPE_LEFT;
 162: 
 163:     /**
 164:      * The property name that should be filled with data from the target table
 165:      * in the source table record.
 166:      *
 167:      * @var string
 168:      */
 169:     protected $_propertyName;
 170: 
 171:     /**
 172:      * The strategy name to be used to fetch associated records. Some association
 173:      * types might not implement but one strategy to fetch records.
 174:      *
 175:      * @var string
 176:      */
 177:     protected $_strategy = self::STRATEGY_JOIN;
 178: 
 179:     /**
 180:      * The default finder name to use for fetching rows from the target table
 181:      * With array value, finder name and default options are allowed.
 182:      *
 183:      * @var string|array
 184:      */
 185:     protected $_finder = 'all';
 186: 
 187:     /**
 188:      * Valid strategies for this association. Subclasses can narrow this down.
 189:      *
 190:      * @var array
 191:      */
 192:     protected $_validStrategies = [
 193:         self::STRATEGY_JOIN,
 194:         self::STRATEGY_SELECT,
 195:         self::STRATEGY_SUBQUERY
 196:     ];
 197: 
 198:     /**
 199:      * Constructor. Subclasses can override _options function to get the original
 200:      * list of passed options if expecting any other special key
 201:      *
 202:      * @param string $alias The name given to the association
 203:      * @param array $options A list of properties to be set on this object
 204:      */
 205:     public function __construct($alias, array $options = [])
 206:     {
 207:         $defaults = [
 208:             'cascadeCallbacks',
 209:             'className',
 210:             'conditions',
 211:             'dependent',
 212:             'finder',
 213:             'bindingKey',
 214:             'foreignKey',
 215:             'joinType',
 216:             'tableLocator',
 217:             'propertyName',
 218:             'sourceTable',
 219:             'targetTable'
 220:         ];
 221:         foreach ($defaults as $property) {
 222:             if (isset($options[$property])) {
 223:                 $this->{'_' . $property} = $options[$property];
 224:             }
 225:         }
 226: 
 227:         if (empty($this->_className) && strpos($alias, '.')) {
 228:             $this->_className = $alias;
 229:         }
 230: 
 231:         list(, $name) = pluginSplit($alias);
 232:         $this->_name = $name;
 233: 
 234:         $this->_options($options);
 235: 
 236:         if (!empty($options['strategy'])) {
 237:             $this->setStrategy($options['strategy']);
 238:         }
 239:     }
 240: 
 241:     /**
 242:      * Sets the name for this association, usually the alias
 243:      * assigned to the target associated table
 244:      *
 245:      * @param string $name Name to be assigned
 246:      * @return $this
 247:      */
 248:     public function setName($name)
 249:     {
 250:         if ($this->_targetTable !== null) {
 251:             $alias = $this->_targetTable->getAlias();
 252:             if ($alias !== $name) {
 253:                 throw new InvalidArgumentException('Association name does not match target table alias.');
 254:             }
 255:         }
 256: 
 257:         $this->_name = $name;
 258: 
 259:         return $this;
 260:     }
 261: 
 262:     /**
 263:      * Gets the name for this association, usually the alias
 264:      * assigned to the target associated table
 265:      *
 266:      * @return string
 267:      */
 268:     public function getName()
 269:     {
 270:         return $this->_name;
 271:     }
 272: 
 273:     /**
 274:      * Sets the name for this association.
 275:      *
 276:      * @deprecated 3.4.0 Use setName()/getName() instead.
 277:      * @param string|null $name Name to be assigned
 278:      * @return string
 279:      */
 280:     public function name($name = null)
 281:     {
 282:         deprecationWarning(
 283:             get_called_class() . '::name() is deprecated. ' .
 284:             'Use setName()/getName() instead.'
 285:         );
 286:         if ($name !== null) {
 287:             $this->setName($name);
 288:         }
 289: 
 290:         return $this->getName();
 291:     }
 292: 
 293:     /**
 294:      * Sets whether or not cascaded deletes should also fire callbacks.
 295:      *
 296:      * @param bool $cascadeCallbacks cascade callbacks switch value
 297:      * @return $this
 298:      */
 299:     public function setCascadeCallbacks($cascadeCallbacks)
 300:     {
 301:         $this->_cascadeCallbacks = $cascadeCallbacks;
 302: 
 303:         return $this;
 304:     }
 305: 
 306:     /**
 307:      * Gets whether or not cascaded deletes should also fire callbacks.
 308:      *
 309:      * @return bool
 310:      */
 311:     public function getCascadeCallbacks()
 312:     {
 313:         return $this->_cascadeCallbacks;
 314:     }
 315: 
 316:     /**
 317:      * Sets whether or not cascaded deletes should also fire callbacks. If no
 318:      * arguments are passed, the current configured value is returned
 319:      *
 320:      * @deprecated 3.4.0 Use setCascadeCallbacks()/getCascadeCallbacks() instead.
 321:      * @param bool|null $cascadeCallbacks cascade callbacks switch value
 322:      * @return bool
 323:      */
 324:     public function cascadeCallbacks($cascadeCallbacks = null)
 325:     {
 326:         deprecationWarning(
 327:             get_called_class() . '::cascadeCallbacks() is deprecated. ' .
 328:             'Use setCascadeCallbacks()/getCascadeCallbacks() instead.'
 329:         );
 330:         if ($cascadeCallbacks !== null) {
 331:             $this->setCascadeCallbacks($cascadeCallbacks);
 332:         }
 333: 
 334:         return $this->getCascadeCallbacks();
 335:     }
 336: 
 337:     /**
 338:      * Sets the class name of the target table object.
 339:      *
 340:      * @param string $className Class name to set.
 341:      * @return $this
 342:      * @throws \InvalidArgumentException In case the class name is set after the target table has been
 343:      *  resolved, and it doesn't match the target table's class name.
 344:      */
 345:     public function setClassName($className)
 346:     {
 347:         if ($this->_targetTable !== null &&
 348:             get_class($this->_targetTable) !== App::className($className, 'Model/Table', 'Table')
 349:         ) {
 350:             throw new InvalidArgumentException(
 351:                 'The class name doesn\'t match the target table\'s class name.'
 352:             );
 353:         }
 354: 
 355:         $this->_className = $className;
 356: 
 357:         return $this;
 358:     }
 359: 
 360:     /**
 361:      * Gets the class name of the target table object.
 362:      *
 363:      * @return string
 364:      */
 365:     public function getClassName()
 366:     {
 367:         return $this->_className;
 368:     }
 369: 
 370:     /**
 371:      * The class name of the target table object
 372:      *
 373:      * @deprecated 3.7.0 Use getClassName() instead.
 374:      * @return string
 375:      */
 376:     public function className()
 377:     {
 378:         deprecationWarning(
 379:             get_called_class() . '::className() is deprecated. ' .
 380:             'Use getClassName() instead.'
 381:         );
 382: 
 383:         return $this->getClassName();
 384:     }
 385: 
 386:     /**
 387:      * Sets the table instance for the source side of the association.
 388:      *
 389:      * @param \Cake\ORM\Table $table the instance to be assigned as source side
 390:      * @return $this
 391:      */
 392:     public function setSource(Table $table)
 393:     {
 394:         $this->_sourceTable = $table;
 395: 
 396:         return $this;
 397:     }
 398: 
 399:     /**
 400:      * Gets the table instance for the source side of the association.
 401:      *
 402:      * @return \Cake\ORM\Table
 403:      */
 404:     public function getSource()
 405:     {
 406:         return $this->_sourceTable;
 407:     }
 408: 
 409:     /**
 410:      * Sets the table instance for the source side of the association. If no arguments
 411:      * are passed, the current configured table instance is returned
 412:      *
 413:      * @deprecated 3.4.0 Use setSource()/getSource() instead.
 414:      * @param \Cake\ORM\Table|null $table the instance to be assigned as source side
 415:      * @return \Cake\ORM\Table
 416:      */
 417:     public function source(Table $table = null)
 418:     {
 419:         deprecationWarning(
 420:             get_called_class() . '::source() is deprecated. ' .
 421:             'Use setSource()/getSource() instead.'
 422:         );
 423:         if ($table === null) {
 424:             return $this->_sourceTable;
 425:         }
 426: 
 427:         return $this->_sourceTable = $table;
 428:     }
 429: 
 430:     /**
 431:      * Sets the table instance for the target side of the association.
 432:      *
 433:      * @param \Cake\ORM\Table $table the instance to be assigned as target side
 434:      * @return $this
 435:      */
 436:     public function setTarget(Table $table)
 437:     {
 438:         $this->_targetTable = $table;
 439: 
 440:         return $this;
 441:     }
 442: 
 443:     /**
 444:      * Gets the table instance for the target side of the association.
 445:      *
 446:      * @return \Cake\ORM\Table
 447:      */
 448:     public function getTarget()
 449:     {
 450:         if (!$this->_targetTable) {
 451:             if (strpos($this->_className, '.')) {
 452:                 list($plugin) = pluginSplit($this->_className, true);
 453:                 $registryAlias = $plugin . $this->_name;
 454:             } else {
 455:                 $registryAlias = $this->_name;
 456:             }
 457: 
 458:             $tableLocator = $this->getTableLocator();
 459: 
 460:             $config = [];
 461:             $exists = $tableLocator->exists($registryAlias);
 462:             if (!$exists) {
 463:                 $config = ['className' => $this->_className];
 464:             }
 465:             $this->_targetTable = $tableLocator->get($registryAlias, $config);
 466: 
 467:             if ($exists) {
 468:                 $className = $this->_getClassName($registryAlias, ['className' => $this->_className]);
 469: 
 470:                 if (!$this->_targetTable instanceof $className) {
 471:                     $errorMessage = '%s association "%s" of type "%s" to "%s" doesn\'t match the expected class "%s". ';
 472:                     $errorMessage .= 'You can\'t have an association of the same name with a different target "className" option anywhere in your app.';
 473: 
 474:                     throw new RuntimeException(sprintf(
 475:                         $errorMessage,
 476:                         $this->_sourceTable ? get_class($this->_sourceTable) : 'null',
 477:                         $this->getName(),
 478:                         $this->type(),
 479:                         $this->_targetTable ? get_class($this->_targetTable) : 'null',
 480:                         $className
 481:                     ));
 482:                 }
 483:             }
 484:         }
 485: 
 486:         return $this->_targetTable;
 487:     }
 488: 
 489:     /**
 490:      * Sets the table instance for the target side of the association. If no arguments
 491:      * are passed, the current configured table instance is returned
 492:      *
 493:      * @deprecated 3.4.0 Use setTarget()/getTarget() instead.
 494:      * @param \Cake\ORM\Table|null $table the instance to be assigned as target side
 495:      * @return \Cake\ORM\Table
 496:      */
 497:     public function target(Table $table = null)
 498:     {
 499:         deprecationWarning(
 500:             get_called_class() . '::target() is deprecated. ' .
 501:             'Use setTarget()/getTarget() instead.'
 502:         );
 503:         if ($table !== null) {
 504:             $this->setTarget($table);
 505:         }
 506: 
 507:         return $this->getTarget();
 508:     }
 509: 
 510:     /**
 511:      * Sets a list of conditions to be always included when fetching records from
 512:      * the target association.
 513:      *
 514:      * @param array|callable $conditions list of conditions to be used
 515:      * @see \Cake\Database\Query::where() for examples on the format of the array
 516:      * @return $this
 517:      */
 518:     public function setConditions($conditions)
 519:     {
 520:         $this->_conditions = $conditions;
 521: 
 522:         return $this;
 523:     }
 524: 
 525:     /**
 526:      * Gets a list of conditions to be always included when fetching records from
 527:      * the target association.
 528:      *
 529:      * @see \Cake\Database\Query::where() for examples on the format of the array
 530:      * @return array|callable
 531:      */
 532:     public function getConditions()
 533:     {
 534:         return $this->_conditions;
 535:     }
 536: 
 537:     /**
 538:      * Sets a list of conditions to be always included when fetching records from
 539:      * the target association. If no parameters are passed the current list is returned
 540:      *
 541:      * @deprecated 3.4.0 Use setConditions()/getConditions() instead.
 542:      * @param array|null $conditions list of conditions to be used
 543:      * @see \Cake\Database\Query::where() for examples on the format of the array
 544:      * @return array|callable
 545:      */
 546:     public function conditions($conditions = null)
 547:     {
 548:         deprecationWarning(
 549:             get_called_class() . '::conditions() is deprecated. ' .
 550:             'Use setConditions()/getConditions() instead.'
 551:         );
 552:         if ($conditions !== null) {
 553:             $this->setConditions($conditions);
 554:         }
 555: 
 556:         return $this->getConditions();
 557:     }
 558: 
 559:     /**
 560:      * Sets the name of the field representing the binding field with the target table.
 561:      * When not manually specified the primary key of the owning side table is used.
 562:      *
 563:      * @param string|string[] $key the table field or fields to be used to link both tables together
 564:      * @return $this
 565:      */
 566:     public function setBindingKey($key)
 567:     {
 568:         $this->_bindingKey = $key;
 569: 
 570:         return $this;
 571:     }
 572: 
 573:     /**
 574:      * Gets the name of the field representing the binding field with the target table.
 575:      * When not manually specified the primary key of the owning side table is used.
 576:      *
 577:      * @return string|string[]
 578:      */
 579:     public function getBindingKey()
 580:     {
 581:         if ($this->_bindingKey === null) {
 582:             $this->_bindingKey = $this->isOwningSide($this->getSource()) ?
 583:                 $this->getSource()->getPrimaryKey() :
 584:                 $this->getTarget()->getPrimaryKey();
 585:         }
 586: 
 587:         return $this->_bindingKey;
 588:     }
 589: 
 590:     /**
 591:      * Sets the name of the field representing the binding field with the target table.
 592:      * When not manually specified the primary key of the owning side table is used.
 593:      *
 594:      * If no parameters are passed the current field is returned
 595:      *
 596:      * @deprecated 3.4.0 Use setBindingKey()/getBindingKey() instead.
 597:      * @param string|null $key the table field to be used to link both tables together
 598:      * @return string|array
 599:      */
 600:     public function bindingKey($key = null)
 601:     {
 602:         deprecationWarning(
 603:             get_called_class() . '::bindingKey() is deprecated. ' .
 604:             'Use setBindingKey()/getBindingKey() instead.'
 605:         );
 606:         if ($key !== null) {
 607:             $this->setBindingKey($key);
 608:         }
 609: 
 610:         return $this->getBindingKey();
 611:     }
 612: 
 613:     /**
 614:      * Gets the name of the field representing the foreign key to the target table.
 615:      *
 616:      * @return string|string[]
 617:      */
 618:     public function getForeignKey()
 619:     {
 620:         return $this->_foreignKey;
 621:     }
 622: 
 623:     /**
 624:      * Sets the name of the field representing the foreign key to the target table.
 625:      *
 626:      * @param string|string[] $key the key or keys to be used to link both tables together
 627:      * @return $this
 628:      */
 629:     public function setForeignKey($key)
 630:     {
 631:         $this->_foreignKey = $key;
 632: 
 633:         return $this;
 634:     }
 635: 
 636:     /**
 637:      * Sets the name of the field representing the foreign key to the target table.
 638:      * If no parameters are passed the current field is returned
 639:      *
 640:      * @deprecated 3.4.0 Use setForeignKey()/getForeignKey() instead.
 641:      * @param string|null $key the key to be used to link both tables together
 642:      * @return string|array
 643:      */
 644:     public function foreignKey($key = null)
 645:     {
 646:         deprecationWarning(
 647:             get_called_class() . '::foreignKey() is deprecated. ' .
 648:             'Use setForeignKey()/getForeignKey() instead.'
 649:         );
 650:         if ($key !== null) {
 651:             $this->setForeignKey($key);
 652:         }
 653: 
 654:         return $this->getForeignKey();
 655:     }
 656: 
 657:     /**
 658:      * Sets whether the records on the target table are dependent on the source table.
 659:      *
 660:      * This is primarily used to indicate that records should be removed if the owning record in
 661:      * the source table is deleted.
 662:      *
 663:      * If no parameters are passed the current setting is returned.
 664:      *
 665:      * @param bool $dependent Set the dependent mode. Use null to read the current state.
 666:      * @return $this
 667:      */
 668:     public function setDependent($dependent)
 669:     {
 670:         $this->_dependent = $dependent;
 671: 
 672:         return $this;
 673:     }
 674: 
 675:     /**
 676:      * Sets whether the records on the target table are dependent on the source table.
 677:      *
 678:      * This is primarily used to indicate that records should be removed if the owning record in
 679:      * the source table is deleted.
 680:      *
 681:      * @return bool
 682:      */
 683:     public function getDependent()
 684:     {
 685:         return $this->_dependent;
 686:     }
 687: 
 688:     /**
 689:      * Sets whether the records on the target table are dependent on the source table.
 690:      *
 691:      * This is primarily used to indicate that records should be removed if the owning record in
 692:      * the source table is deleted.
 693:      *
 694:      * If no parameters are passed the current setting is returned.
 695:      *
 696:      * @deprecated 3.4.0 Use setDependent()/getDependent() instead.
 697:      * @param bool|null $dependent Set the dependent mode. Use null to read the current state.
 698:      * @return bool
 699:      */
 700:     public function dependent($dependent = null)
 701:     {
 702:         deprecationWarning(
 703:             get_called_class() . '::dependent() is deprecated. ' .
 704:             'Use setDependent()/getDependent() instead.'
 705:         );
 706:         if ($dependent !== null) {
 707:             $this->setDependent($dependent);
 708:         }
 709: 
 710:         return $this->getDependent();
 711:     }
 712: 
 713:     /**
 714:      * Whether this association can be expressed directly in a query join
 715:      *
 716:      * @param array $options custom options key that could alter the return value
 717:      * @return bool
 718:      */
 719:     public function canBeJoined(array $options = [])
 720:     {
 721:         $strategy = isset($options['strategy']) ? $options['strategy'] : $this->getStrategy();
 722: 
 723:         return $strategy == $this::STRATEGY_JOIN;
 724:     }
 725: 
 726:     /**
 727:      * Sets the type of join to be used when adding the association to a query.
 728:      *
 729:      * @param string $type the join type to be used (e.g. INNER)
 730:      * @return $this
 731:      */
 732:     public function setJoinType($type)
 733:     {
 734:         $this->_joinType = $type;
 735: 
 736:         return $this;
 737:     }
 738: 
 739:     /**
 740:      * Gets the type of join to be used when adding the association to a query.
 741:      *
 742:      * @return string
 743:      */
 744:     public function getJoinType()
 745:     {
 746:         return $this->_joinType;
 747:     }
 748: 
 749:     /**
 750:      * Sets the type of join to be used when adding the association to a query.
 751:      * If no arguments are passed, the currently configured type is returned.
 752:      *
 753:      * @deprecated 3.4.0 Use setJoinType()/getJoinType() instead.
 754:      * @param string|null $type the join type to be used (e.g. INNER)
 755:      * @return string
 756:      */
 757:     public function joinType($type = null)
 758:     {
 759:         deprecationWarning(
 760:             get_called_class() . '::joinType() is deprecated. ' .
 761:             'Use setJoinType()/getJoinType() instead.'
 762:         );
 763:         if ($type !== null) {
 764:             $this->setJoinType($type);
 765:         }
 766: 
 767:         return $this->getJoinType();
 768:     }
 769: 
 770:     /**
 771:      * Sets the property name that should be filled with data from the target table
 772:      * in the source table record.
 773:      *
 774:      * @param string $name The name of the association property. Use null to read the current value.
 775:      * @return $this
 776:      */
 777:     public function setProperty($name)
 778:     {
 779:         $this->_propertyName = $name;
 780: 
 781:         return $this;
 782:     }
 783: 
 784:     /**
 785:      * Gets the property name that should be filled with data from the target table
 786:      * in the source table record.
 787:      *
 788:      * @return string
 789:      */
 790:     public function getProperty()
 791:     {
 792:         if (!$this->_propertyName) {
 793:             $this->_propertyName = $this->_propertyName();
 794:             if (in_array($this->_propertyName, $this->_sourceTable->getSchema()->columns())) {
 795:                 $msg = 'Association property name "%s" clashes with field of same name of table "%s".' .
 796:                     ' You should explicitly specify the "propertyName" option.';
 797:                 trigger_error(
 798:                     sprintf($msg, $this->_propertyName, $this->_sourceTable->getTable()),
 799:                     E_USER_WARNING
 800:                 );
 801:             }
 802:         }
 803: 
 804:         return $this->_propertyName;
 805:     }
 806: 
 807:     /**
 808:      * Sets the property name that should be filled with data from the target table
 809:      * in the source table record.
 810:      * If no arguments are passed, the currently configured type is returned.
 811:      *
 812:      * @deprecated 3.4.0 Use setProperty()/getProperty() instead.
 813:      * @param string|null $name The name of the association property. Use null to read the current value.
 814:      * @return string
 815:      */
 816:     public function property($name = null)
 817:     {
 818:         deprecationWarning(
 819:             get_called_class() . '::property() is deprecated. ' .
 820:             'Use setProperty()/getProperty() instead.'
 821:         );
 822:         if ($name !== null) {
 823:             $this->setProperty($name);
 824:         }
 825: 
 826:         return $this->getProperty();
 827:     }
 828: 
 829:     /**
 830:      * Returns default property name based on association name.
 831:      *
 832:      * @return string
 833:      */
 834:     protected function _propertyName()
 835:     {
 836:         list(, $name) = pluginSplit($this->_name);
 837: 
 838:         return Inflector::underscore($name);
 839:     }
 840: 
 841:     /**
 842:      * Sets the strategy name to be used to fetch associated records. Keep in mind
 843:      * that some association types might not implement but a default strategy,
 844:      * rendering any changes to this setting void.
 845:      *
 846:      * @param string $name The strategy type. Use null to read the current value.
 847:      * @return $this
 848:      * @throws \InvalidArgumentException When an invalid strategy is provided.
 849:      */
 850:     public function setStrategy($name)
 851:     {
 852:         if (!in_array($name, $this->_validStrategies)) {
 853:             throw new InvalidArgumentException(
 854:                 sprintf('Invalid strategy "%s" was provided', $name)
 855:             );
 856:         }
 857:         $this->_strategy = $name;
 858: 
 859:         return $this;
 860:     }
 861: 
 862:     /**
 863:      * Gets the strategy name to be used to fetch associated records. Keep in mind
 864:      * that some association types might not implement but a default strategy,
 865:      * rendering any changes to this setting void.
 866:      *
 867:      * @return string
 868:      */
 869:     public function getStrategy()
 870:     {
 871:         return $this->_strategy;
 872:     }
 873: 
 874:     /**
 875:      * Sets the strategy name to be used to fetch associated records. Keep in mind
 876:      * that some association types might not implement but a default strategy,
 877:      * rendering any changes to this setting void.
 878:      * If no arguments are passed, the currently configured strategy is returned.
 879:      *
 880:      * @deprecated 3.4.0 Use setStrategy()/getStrategy() instead.
 881:      * @param string|null $name The strategy type. Use null to read the current value.
 882:      * @return string
 883:      * @throws \InvalidArgumentException When an invalid strategy is provided.
 884:      */
 885:     public function strategy($name = null)
 886:     {
 887:         deprecationWarning(
 888:             get_called_class() . '::strategy() is deprecated. ' .
 889:             'Use setStrategy()/getStrategy() instead.'
 890:         );
 891:         if ($name !== null) {
 892:             $this->setStrategy($name);
 893:         }
 894: 
 895:         return $this->getStrategy();
 896:     }
 897: 
 898:     /**
 899:      * Gets the default finder to use for fetching rows from the target table.
 900:      *
 901:      * @return string|array
 902:      */
 903:     public function getFinder()
 904:     {
 905:         return $this->_finder;
 906:     }
 907: 
 908:     /**
 909:      * Sets the default finder to use for fetching rows from the target table.
 910:      *
 911:      * @param string|array $finder the finder name to use or array of finder name and option.
 912:      * @return $this
 913:      */
 914:     public function setFinder($finder)
 915:     {
 916:         $this->_finder = $finder;
 917: 
 918:         return $this;
 919:     }
 920: 
 921:     /**
 922:      * Sets the default finder to use for fetching rows from the target table.
 923:      * If no parameters are passed, it will return the currently configured
 924:      * finder name.
 925:      *
 926:      * @deprecated 3.4.0 Use setFinder()/getFinder() instead.
 927:      * @param string|null $finder the finder name to use
 928:      * @return string|array
 929:      */
 930:     public function finder($finder = null)
 931:     {
 932:         deprecationWarning(
 933:             get_called_class() . '::finder() is deprecated. ' .
 934:             'Use setFinder()/getFinder() instead.'
 935:         );
 936:         if ($finder !== null) {
 937:             $this->setFinder($finder);
 938:         }
 939: 
 940:         return $this->getFinder();
 941:     }
 942: 
 943:     /**
 944:      * Override this function to initialize any concrete association class, it will
 945:      * get passed the original list of options used in the constructor
 946:      *
 947:      * @param array $options List of options used for initialization
 948:      * @return void
 949:      */
 950:     protected function _options(array $options)
 951:     {
 952:     }
 953: 
 954:     /**
 955:      * Alters a Query object to include the associated target table data in the final
 956:      * result
 957:      *
 958:      * The options array accept the following keys:
 959:      *
 960:      * - includeFields: Whether to include target model fields in the result or not
 961:      * - foreignKey: The name of the field to use as foreign key, if false none
 962:      *   will be used
 963:      * - conditions: array with a list of conditions to filter the join with, this
 964:      *   will be merged with any conditions originally configured for this association
 965:      * - fields: a list of fields in the target table to include in the result
 966:      * - type: The type of join to be used (e.g. INNER)
 967:      *   the records found on this association
 968:      * - aliasPath: A dot separated string representing the path of association names
 969:      *   followed from the passed query main table to this association.
 970:      * - propertyPath: A dot separated string representing the path of association
 971:      *   properties to be followed from the passed query main entity to this
 972:      *   association
 973:      * - joinType: The SQL join type to use in the query.
 974:      * - negateMatch: Will append a condition to the passed query for excluding matches.
 975:      *   with this association.
 976:      *
 977:      * @param \Cake\ORM\Query $query the query to be altered to include the target table data
 978:      * @param array $options Any extra options or overrides to be taken in account
 979:      * @return void
 980:      * @throws \RuntimeException if the query builder passed does not return a query
 981:      * object
 982:      */
 983:     public function attachTo(Query $query, array $options = [])
 984:     {
 985:         $target = $this->getTarget();
 986:         $joinType = empty($options['joinType']) ? $this->getJoinType() : $options['joinType'];
 987:         $table = $target->getTable();
 988: 
 989:         $options += [
 990:             'includeFields' => true,
 991:             'foreignKey' => $this->getForeignKey(),
 992:             'conditions' => [],
 993:             'fields' => [],
 994:             'type' => $joinType,
 995:             'table' => $table,
 996:             'finder' => $this->getFinder()
 997:         ];
 998: 
 999:         if (!empty($options['foreignKey'])) {
1000:             $joinCondition = $this->_joinCondition($options);
1001:             if ($joinCondition) {
1002:                 $options['conditions'][] = $joinCondition;
1003:             }
1004:         }
1005: 
1006:         list($finder, $opts) = $this->_extractFinder($options['finder']);
1007:         $dummy = $this
1008:             ->find($finder, $opts)
1009:             ->eagerLoaded(true);
1010: 
1011:         if (!empty($options['queryBuilder'])) {
1012:             $dummy = $options['queryBuilder']($dummy);
1013:             if (!($dummy instanceof Query)) {
1014:                 throw new RuntimeException(sprintf(
1015:                     'Query builder for association "%s" did not return a query',
1016:                     $this->getName()
1017:                 ));
1018:             }
1019:         }
1020: 
1021:         $dummy->where($options['conditions']);
1022:         $this->_dispatchBeforeFind($dummy);
1023: 
1024:         $joinOptions = ['table' => 1, 'conditions' => 1, 'type' => 1];
1025:         $options['conditions'] = $dummy->clause('where');
1026:         $query->join([$this->_name => array_intersect_key($options, $joinOptions)]);
1027: 
1028:         $this->_appendFields($query, $dummy, $options);
1029:         $this->_formatAssociationResults($query, $dummy, $options);
1030:         $this->_bindNewAssociations($query, $dummy, $options);
1031:         $this->_appendNotMatching($query, $options);
1032:     }
1033: 
1034:     /**
1035:      * Conditionally adds a condition to the passed Query that will make it find
1036:      * records where there is no match with this association.
1037:      *
1038:      * @param \Cake\Datasource\QueryInterface $query The query to modify
1039:      * @param array $options Options array containing the `negateMatch` key.
1040:      * @return void
1041:      */
1042:     protected function _appendNotMatching($query, $options)
1043:     {
1044:         $target = $this->_targetTable;
1045:         if (!empty($options['negateMatch'])) {
1046:             $primaryKey = $query->aliasFields((array)$target->getPrimaryKey(), $this->_name);
1047:             $query->andWhere(function ($exp) use ($primaryKey) {
1048:                 array_map([$exp, 'isNull'], $primaryKey);
1049: 
1050:                 return $exp;
1051:             });
1052:         }
1053:     }
1054: 
1055:     /**
1056:      * Correctly nests a result row associated values into the correct array keys inside the
1057:      * source results.
1058:      *
1059:      * @param array $row The row to transform
1060:      * @param string $nestKey The array key under which the results for this association
1061:      *   should be found
1062:      * @param bool $joined Whether or not the row is a result of a direct join
1063:      *   with this association
1064:      * @param string|null $targetProperty The property name in the source results where the association
1065:      * data shuld be nested in. Will use the default one if not provided.
1066:      * @return array
1067:      */
1068:     public function transformRow($row, $nestKey, $joined, $targetProperty = null)
1069:     {
1070:         $sourceAlias = $this->getSource()->getAlias();
1071:         $nestKey = $nestKey ?: $this->_name;
1072:         $targetProperty = $targetProperty ?: $this->getProperty();
1073:         if (isset($row[$sourceAlias])) {
1074:             $row[$sourceAlias][$targetProperty] = $row[$nestKey];
1075:             unset($row[$nestKey]);
1076:         }
1077: 
1078:         return $row;
1079:     }
1080: 
1081:     /**
1082:      * Returns a modified row after appending a property for this association
1083:      * with the default empty value according to whether the association was
1084:      * joined or fetched externally.
1085:      *
1086:      * @param array $row The row to set a default on.
1087:      * @param bool $joined Whether or not the row is a result of a direct join
1088:      *   with this association
1089:      * @return array
1090:      */
1091:     public function defaultRowValue($row, $joined)
1092:     {
1093:         $sourceAlias = $this->getSource()->getAlias();
1094:         if (isset($row[$sourceAlias])) {
1095:             $row[$sourceAlias][$this->getProperty()] = null;
1096:         }
1097: 
1098:         return $row;
1099:     }
1100: 
1101:     /**
1102:      * Proxies the finding operation to the target table's find method
1103:      * and modifies the query accordingly based of this association
1104:      * configuration
1105:      *
1106:      * @param string|array|null $type the type of query to perform, if an array is passed,
1107:      *   it will be interpreted as the `$options` parameter
1108:      * @param array $options The options to for the find
1109:      * @see \Cake\ORM\Table::find()
1110:      * @return \Cake\ORM\Query
1111:      */
1112:     public function find($type = null, array $options = [])
1113:     {
1114:         $type = $type ?: $this->getFinder();
1115:         list($type, $opts) = $this->_extractFinder($type);
1116: 
1117:         return $this->getTarget()
1118:             ->find($type, $options + $opts)
1119:             ->where($this->getConditions());
1120:     }
1121: 
1122:     /**
1123:      * Proxies the operation to the target table's exists method after
1124:      * appending the default conditions for this association
1125:      *
1126:      * @param array|callable|\Cake\Database\ExpressionInterface $conditions The conditions to use
1127:      * for checking if any record matches.
1128:      * @see \Cake\ORM\Table::exists()
1129:      * @return bool
1130:      */
1131:     public function exists($conditions)
1132:     {
1133:         if ($this->_conditions) {
1134:             $conditions = $this
1135:                 ->find('all', ['conditions' => $conditions])
1136:                 ->clause('where');
1137:         }
1138: 
1139:         return $this->getTarget()->exists($conditions);
1140:     }
1141: 
1142:     /**
1143:      * Proxies the update operation to the target table's updateAll method
1144:      *
1145:      * @param array $fields A hash of field => new value.
1146:      * @param mixed $conditions Conditions to be used, accepts anything Query::where()
1147:      * can take.
1148:      * @see \Cake\ORM\Table::updateAll()
1149:      * @return int Count Returns the affected rows.
1150:      */
1151:     public function updateAll($fields, $conditions)
1152:     {
1153:         $target = $this->getTarget();
1154:         $expression = $target->query()
1155:             ->where($this->getConditions())
1156:             ->where($conditions)
1157:             ->clause('where');
1158: 
1159:         return $target->updateAll($fields, $expression);
1160:     }
1161: 
1162:     /**
1163:      * Proxies the delete operation to the target table's deleteAll method
1164:      *
1165:      * @param mixed $conditions Conditions to be used, accepts anything Query::where()
1166:      * can take.
1167:      * @return int Returns the number of affected rows.
1168:      * @see \Cake\ORM\Table::deleteAll()
1169:      */
1170:     public function deleteAll($conditions)
1171:     {
1172:         $target = $this->getTarget();
1173:         $expression = $target->query()
1174:             ->where($this->getConditions())
1175:             ->where($conditions)
1176:             ->clause('where');
1177: 
1178:         return $target->deleteAll($expression);
1179:     }
1180: 
1181:     /**
1182:      * Returns true if the eager loading process will require a set of the owning table's
1183:      * binding keys in order to use them as a filter in the finder query.
1184:      *
1185:      * @param array $options The options containing the strategy to be used.
1186:      * @return bool true if a list of keys will be required
1187:      */
1188:     public function requiresKeys(array $options = [])
1189:     {
1190:         $strategy = isset($options['strategy']) ? $options['strategy'] : $this->getStrategy();
1191: 
1192:         return $strategy === static::STRATEGY_SELECT;
1193:     }
1194: 
1195:     /**
1196:      * Triggers beforeFind on the target table for the query this association is
1197:      * attaching to
1198:      *
1199:      * @param \Cake\ORM\Query $query the query this association is attaching itself to
1200:      * @return void
1201:      */
1202:     protected function _dispatchBeforeFind($query)
1203:     {
1204:         $query->triggerBeforeFind();
1205:     }
1206: 
1207:     /**
1208:      * Helper function used to conditionally append fields to the select clause of
1209:      * a query from the fields found in another query object.
1210:      *
1211:      * @param \Cake\ORM\Query $query the query that will get the fields appended to
1212:      * @param \Cake\ORM\Query $surrogate the query having the fields to be copied from
1213:      * @param array $options options passed to the method `attachTo`
1214:      * @return void
1215:      */
1216:     protected function _appendFields($query, $surrogate, $options)
1217:     {
1218:         if ($query->getEagerLoader()->isAutoFieldsEnabled() === false) {
1219:             return;
1220:         }
1221: 
1222:         $fields = $surrogate->clause('select') ?: $options['fields'];
1223:         $target = $this->_targetTable;
1224:         $autoFields = $surrogate->isAutoFieldsEnabled();
1225: 
1226:         if (empty($fields) && !$autoFields) {
1227:             if ($options['includeFields'] && ($fields === null || $fields !== false)) {
1228:                 $fields = $target->getSchema()->columns();
1229:             }
1230:         }
1231: 
1232:         if ($autoFields === true) {
1233:             $fields = array_filter((array)$fields);
1234:             $fields = array_merge($fields, $target->getSchema()->columns());
1235:         }
1236: 
1237:         if ($fields) {
1238:             $query->select($query->aliasFields($fields, $this->_name));
1239:         }
1240:         $query->addDefaultTypes($target);
1241:     }
1242: 
1243:     /**
1244:      * Adds a formatter function to the passed `$query` if the `$surrogate` query
1245:      * declares any other formatter. Since the `$surrogate` query correspond to
1246:      * the associated target table, the resulting formatter will be the result of
1247:      * applying the surrogate formatters to only the property corresponding to
1248:      * such table.
1249:      *
1250:      * @param \Cake\ORM\Query $query the query that will get the formatter applied to
1251:      * @param \Cake\ORM\Query $surrogate the query having formatters for the associated
1252:      * target table.
1253:      * @param array $options options passed to the method `attachTo`
1254:      * @return void
1255:      */
1256:     protected function _formatAssociationResults($query, $surrogate, $options)
1257:     {
1258:         $formatters = $surrogate->getResultFormatters();
1259: 
1260:         if (!$formatters || empty($options['propertyPath'])) {
1261:             return;
1262:         }
1263: 
1264:         $property = $options['propertyPath'];
1265:         $propertyPath = explode('.', $property);
1266:         $query->formatResults(function ($results) use ($formatters, $property, $propertyPath) {
1267:             $extracted = [];
1268:             foreach ($results as $result) {
1269:                 foreach ($propertyPath as $propertyPathItem) {
1270:                     if (!isset($result[$propertyPathItem])) {
1271:                         $result = null;
1272:                         break;
1273:                     }
1274:                     $result = $result[$propertyPathItem];
1275:                 }
1276:                 $extracted[] = $result;
1277:             }
1278:             $extracted = new Collection($extracted);
1279:             foreach ($formatters as $callable) {
1280:                 $extracted = new ResultSetDecorator($callable($extracted));
1281:             }
1282: 
1283:             /* @var \Cake\Collection\CollectionInterface $results */
1284:             return $results->insert($property, $extracted);
1285:         }, Query::PREPEND);
1286:     }
1287: 
1288:     /**
1289:      * Applies all attachable associations to `$query` out of the containments found
1290:      * in the `$surrogate` query.
1291:      *
1292:      * Copies all contained associations from the `$surrogate` query into the
1293:      * passed `$query`. Containments are altered so that they respect the associations
1294:      * chain from which they originated.
1295:      *
1296:      * @param \Cake\ORM\Query $query the query that will get the associations attached to
1297:      * @param \Cake\ORM\Query $surrogate the query having the containments to be attached
1298:      * @param array $options options passed to the method `attachTo`
1299:      * @return void
1300:      */
1301:     protected function _bindNewAssociations($query, $surrogate, $options)
1302:     {
1303:         $loader = $surrogate->getEagerLoader();
1304:         $contain = $loader->getContain();
1305:         $matching = $loader->getMatching();
1306: 
1307:         if (!$contain && !$matching) {
1308:             return;
1309:         }
1310: 
1311:         $newContain = [];
1312:         foreach ($contain as $alias => $value) {
1313:             $newContain[$options['aliasPath'] . '.' . $alias] = $value;
1314:         }
1315: 
1316:         $eagerLoader = $query->getEagerLoader();
1317:         if ($newContain) {
1318:             $eagerLoader->contain($newContain);
1319:         }
1320: 
1321:         foreach ($matching as $alias => $value) {
1322:             $eagerLoader->setMatching(
1323:                 $options['aliasPath'] . '.' . $alias,
1324:                 $value['queryBuilder'],
1325:                 $value
1326:             );
1327:         }
1328:     }
1329: 
1330:     /**
1331:      * Returns a single or multiple conditions to be appended to the generated join
1332:      * clause for getting the results on the target table.
1333:      *
1334:      * @param array $options list of options passed to attachTo method
1335:      * @return array
1336:      * @throws \RuntimeException if the number of columns in the foreignKey do not
1337:      * match the number of columns in the source table primaryKey
1338:      */
1339:     protected function _joinCondition($options)
1340:     {
1341:         $conditions = [];
1342:         $tAlias = $this->_name;
1343:         $sAlias = $this->getSource()->getAlias();
1344:         $foreignKey = (array)$options['foreignKey'];
1345:         $bindingKey = (array)$this->getBindingKey();
1346: 
1347:         if (count($foreignKey) !== count($bindingKey)) {
1348:             if (empty($bindingKey)) {
1349:                 $table = $this->getTarget()->getTable();
1350:                 if ($this->isOwningSide($this->getSource())) {
1351:                     $table = $this->getSource()->getTable();
1352:                 }
1353:                 $msg = 'The "%s" table does not define a primary key, and cannot have join conditions generated.';
1354:                 throw new RuntimeException(sprintf($msg, $table));
1355:             }
1356: 
1357:             $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"';
1358:             throw new RuntimeException(sprintf(
1359:                 $msg,
1360:                 $this->_name,
1361:                 implode(', ', $foreignKey),
1362:                 implode(', ', $bindingKey)
1363:             ));
1364:         }
1365: 
1366:         foreach ($foreignKey as $k => $f) {
1367:             $field = sprintf('%s.%s', $sAlias, $bindingKey[$k]);
1368:             $value = new IdentifierExpression(sprintf('%s.%s', $tAlias, $f));
1369:             $conditions[$field] = $value;
1370:         }
1371: 
1372:         return $conditions;
1373:     }
1374: 
1375:     /**
1376:      * Helper method to infer the requested finder and its options.
1377:      *
1378:      * Returns the inferred options from the finder $type.
1379:      *
1380:      * ### Examples:
1381:      *
1382:      * The following will call the finder 'translations' with the value of the finder as its options:
1383:      * $query->contain(['Comments' => ['finder' => ['translations']]]);
1384:      * $query->contain(['Comments' => ['finder' => ['translations' => []]]]);
1385:      * $query->contain(['Comments' => ['finder' => ['translations' => ['locales' => ['en_US']]]]]);
1386:      *
1387:      * @param string|array $finderData The finder name or an array having the name as key
1388:      * and options as value.
1389:      * @return array
1390:      */
1391:     protected function _extractFinder($finderData)
1392:     {
1393:         $finderData = (array)$finderData;
1394: 
1395:         if (is_numeric(key($finderData))) {
1396:             return [current($finderData), []];
1397:         }
1398: 
1399:         return [key($finderData), current($finderData)];
1400:     }
1401: 
1402:     /**
1403:      * Gets the table class name.
1404:      *
1405:      * @param string $alias The alias name you want to get.
1406:      * @param array $options Table options array.
1407:      * @return string
1408:      */
1409:     protected function _getClassName($alias, array $options = [])
1410:     {
1411:         if (empty($options['className'])) {
1412:             $options['className'] = Inflector::camelize($alias);
1413:         }
1414: 
1415:         $className = App::className($options['className'], 'Model/Table', 'Table') ?: 'Cake\ORM\Table';
1416: 
1417:         return ltrim($className, '\\');
1418:     }
1419: 
1420:     /**
1421:      * Proxies property retrieval to the target table. This is handy for getting this
1422:      * association's associations
1423:      *
1424:      * @param string $property the property name
1425:      * @return \Cake\ORM\Association
1426:      * @throws \RuntimeException if no association with such name exists
1427:      */
1428:     public function __get($property)
1429:     {
1430:         return $this->getTarget()->{$property};
1431:     }
1432: 
1433:     /**
1434:      * Proxies the isset call to the target table. This is handy to check if the
1435:      * target table has another association with the passed name
1436:      *
1437:      * @param string $property the property name
1438:      * @return bool true if the property exists
1439:      */
1440:     public function __isset($property)
1441:     {
1442:         return isset($this->getTarget()->{$property});
1443:     }
1444: 
1445:     /**
1446:      * Proxies method calls to the target table.
1447:      *
1448:      * @param string $method name of the method to be invoked
1449:      * @param array $argument List of arguments passed to the function
1450:      * @return mixed
1451:      * @throws \BadMethodCallException
1452:      */
1453:     public function __call($method, $argument)
1454:     {
1455:         return $this->getTarget()->$method(...$argument);
1456:     }
1457: 
1458:     /**
1459:      * Get the relationship type.
1460:      *
1461:      * @return string Constant of either ONE_TO_ONE, MANY_TO_ONE, ONE_TO_MANY or MANY_TO_MANY.
1462:      */
1463:     abstract public function type();
1464: 
1465:     /**
1466:      * Eager loads a list of records in the target table that are related to another
1467:      * set of records in the source table. Source records can specified in two ways:
1468:      * first one is by passing a Query object setup to find on the source table and
1469:      * the other way is by explicitly passing an array of primary key values from
1470:      * the source table.
1471:      *
1472:      * The required way of passing related source records is controlled by "strategy"
1473:      * When the subquery strategy is used it will require a query on the source table.
1474:      * When using the select strategy, the list of primary keys will be used.
1475:      *
1476:      * Returns a closure that should be run for each record returned in a specific
1477:      * Query. This callable will be responsible for injecting the fields that are
1478:      * related to each specific passed row.
1479:      *
1480:      * Options array accepts the following keys:
1481:      *
1482:      * - query: Query object setup to find the source table records
1483:      * - keys: List of primary key values from the source table
1484:      * - foreignKey: The name of the field used to relate both tables
1485:      * - conditions: List of conditions to be passed to the query where() method
1486:      * - sort: The direction in which the records should be returned
1487:      * - fields: List of fields to select from the target table
1488:      * - contain: List of related tables to eager load associated to the target table
1489:      * - strategy: The name of strategy to use for finding target table records
1490:      * - nestKey: The array key under which results will be found when transforming the row
1491:      *
1492:      * @param array $options The options for eager loading.
1493:      * @return \Closure
1494:      */
1495:     abstract public function eagerLoader(array $options);
1496: 
1497:     /**
1498:      * Handles cascading a delete from an associated model.
1499:      *
1500:      * Each implementing class should handle the cascaded delete as
1501:      * required.
1502:      *
1503:      * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete.
1504:      * @param array $options The options for the original delete.
1505:      * @return bool Success
1506:      */
1507:     abstract public function cascadeDelete(EntityInterface $entity, array $options = []);
1508: 
1509:     /**
1510:      * Returns whether or not the passed table is the owning side for this
1511:      * association. This means that rows in the 'target' table would miss important
1512:      * or required information if the row in 'source' did not exist.
1513:      *
1514:      * @param \Cake\ORM\Table $side The potential Table with ownership
1515:      * @return bool
1516:      */
1517:     abstract public function isOwningSide(Table $side);
1518: 
1519:     /**
1520:      * Extract the target's association data our from the passed entity and proxies
1521:      * the saving operation to the target table.
1522:      *
1523:      * @param \Cake\Datasource\EntityInterface $entity the data to be saved
1524:      * @param array $options The options for saving associated data.
1525:      * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns
1526:      * the saved entity
1527:      * @see \Cake\ORM\Table::save()
1528:      */
1529:     abstract public function saveAssociated(EntityInterface $entity, array $options = []);
1530: }
1531: 
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