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.7 Red Velvet API

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