| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942 |
- <?php
- /**
- * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- *
- * Licensed under The MIT License
- * For full copyright and license information, please see the LICENSE.txt
- * Redistributions of files must retain the above copyright notice.
- *
- * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
- * @link http://cakephp.org CakePHP(tm) Project
- * @since 3.0.0
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
- */
- namespace Cake\ORM;
- use Cake\Core\ConventionsTrait;
- use Cake\Database\Expression\IdentifierExpression;
- use Cake\Datasource\EntityInterface;
- use Cake\Datasource\ResultSetDecorator;
- use Cake\ORM\Query;
- use Cake\ORM\Table;
- use Cake\ORM\TableRegistry;
- use Cake\Utility\Inflector;
- use InvalidArgumentException;
- use RuntimeException;
- /**
- * An Association is a relationship established between two tables and is used
- * to configure and customize the way interconnected records are retrieved.
- *
- */
- abstract class Association
- {
- use ConventionsTrait;
- /**
- * Strategy name to use joins for fetching associated records
- *
- * @var string
- */
- const STRATEGY_JOIN = 'join';
- /**
- * Strategy name to use a subquery for fetching associated records
- *
- * @var string
- */
- const STRATEGY_SUBQUERY = 'subquery';
- /**
- * Strategy name to use a select for fetching associated records
- *
- * @var string
- */
- const STRATEGY_SELECT = 'select';
- /**
- * Association type for one to one associations.
- *
- * @var string
- */
- const ONE_TO_ONE = 'oneToOne';
- /**
- * Association type for one to many associations.
- *
- * @var string
- */
- const ONE_TO_MANY = 'oneToMany';
- /**
- * Association type for many to many associations.
- *
- * @var string
- */
- const MANY_TO_MANY = 'manyToMany';
- /**
- * Association type for many to one associations.
- *
- * @var string
- */
- const MANY_TO_ONE = 'manyToOne';
- /**
- * Name given to the association, it usually represents the alias
- * assigned to the target associated table
- *
- * @var string
- */
- protected $_name;
- /**
- * The class name of the target table object
- *
- * @var string
- */
- protected $_className;
- /**
- * The field name in the owning side table that is used to match with the foreignKey
- *
- * @var string|array
- */
- protected $_bindingKey;
- /**
- * The name of the field representing the foreign key to the table to load
- *
- * @var string|array
- */
- protected $_foreignKey;
- /**
- * A list of conditions to be always included when fetching records from
- * the target association
- *
- * @var array
- */
- protected $_conditions = [];
- /**
- * Whether the records on the target table are dependent on the source table,
- * often used to indicate that records should be removed if the owning record in
- * the source table is deleted.
- *
- * @var bool
- */
- protected $_dependent = false;
- /**
- * Whether or not cascaded deletes should also fire callbacks.
- *
- * @var string
- */
- protected $_cascadeCallbacks = false;
- /**
- * Source table instance
- *
- * @var \Cake\ORM\Table
- */
- protected $_sourceTable;
- /**
- * Target table instance
- *
- * @var \Cake\ORM\Table
- */
- protected $_targetTable;
- /**
- * The type of join to be used when adding the association to a query
- *
- * @var string
- */
- protected $_joinType = 'LEFT';
- /**
- * The property name that should be filled with data from the target table
- * in the source table record.
- *
- * @var string
- */
- protected $_propertyName;
- /**
- * The strategy name to be used to fetch associated records. Some association
- * types might not implement but one strategy to fetch records.
- *
- * @var string
- */
- protected $_strategy = self::STRATEGY_JOIN;
- /**
- * The default finder name to use for fetching rows from the target table
- *
- * @var string
- */
- protected $_finder = 'all';
- /**
- * Valid strategies for this association. Subclasses can narrow this down.
- *
- * @var array
- */
- protected $_validStrategies = [self::STRATEGY_JOIN, self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY];
- /**
- * Constructor. Subclasses can override _options function to get the original
- * list of passed options if expecting any other special key
- *
- * @param string $alias The name given to the association
- * @param array $options A list of properties to be set on this object
- */
- public function __construct($alias, array $options = [])
- {
- $defaults = [
- 'cascadeCallbacks',
- 'className',
- 'conditions',
- 'dependent',
- 'finder',
- 'bindingKey',
- 'foreignKey',
- 'joinType',
- 'propertyName',
- 'sourceTable',
- 'targetTable'
- ];
- foreach ($defaults as $property) {
- if (isset($options[$property])) {
- $this->{'_' . $property} = $options[$property];
- }
- }
- if (empty($this->_className) && strpos($alias, '.')) {
- $this->_className = $alias;
- }
- list(, $name) = pluginSplit($alias);
- $this->_name = $name;
- $this->_options($options);
- if (!empty($options['strategy'])) {
- $this->strategy($options['strategy']);
- }
- }
- /**
- * Sets the name for this association. If no argument is passed then the current
- * configured name will be returned
- *
- * @param string|null $name Name to be assigned
- * @return string
- */
- public function name($name = null)
- {
- if ($name !== null) {
- $this->_name = $name;
- }
- return $this->_name;
- }
- /**
- * Sets whether or not cascaded deletes should also fire callbacks. If no
- * arguments are passed, the current configured value is returned
- *
- * @param bool|null $cascadeCallbacks cascade callbacks switch value
- * @return bool
- */
- public function cascadeCallbacks($cascadeCallbacks = null)
- {
- if ($cascadeCallbacks !== null) {
- $this->_cascadeCallbacks = $cascadeCallbacks;
- }
- return $this->_cascadeCallbacks;
- }
- /**
- * Sets the table instance for the source side of the association. If no arguments
- * are passed, the current configured table instance is returned
- *
- * @param \Cake\ORM\Table|null $table the instance to be assigned as source side
- * @return \Cake\ORM\Table
- */
- public function source(Table $table = null)
- {
- if ($table === null) {
- return $this->_sourceTable;
- }
- return $this->_sourceTable = $table;
- }
- /**
- * Sets the table instance for the target side of the association. If no arguments
- * are passed, the current configured table instance is returned
- *
- * @param \Cake\ORM\Table|null $table the instance to be assigned as target side
- * @return \Cake\ORM\Table
- */
- public function target(Table $table = null)
- {
- if ($table === null && $this->_targetTable) {
- return $this->_targetTable;
- }
- if ($table !== null) {
- return $this->_targetTable = $table;
- }
- if (strpos($this->_className, '.')) {
- list($plugin) = pluginSplit($this->_className, true);
- $registryAlias = $plugin . $this->_name;
- } else {
- $registryAlias = $this->_name;
- }
- $config = [];
- if (!TableRegistry::exists($registryAlias)) {
- $config = ['className' => $this->_className];
- }
- $this->_targetTable = TableRegistry::get($registryAlias, $config);
- return $this->_targetTable;
- }
- /**
- * Sets a list of conditions to be always included when fetching records from
- * the target association. If no parameters are passed the current list is returned
- *
- * @param array|null $conditions list of conditions to be used
- * @see \Cake\Database\Query::where() for examples on the format of the array
- * @return array
- */
- public function conditions($conditions = null)
- {
- if ($conditions !== null) {
- $this->_conditions = $conditions;
- }
- return $this->_conditions;
- }
- /**
- * Sets the name of the field representing the binding field with the target table.
- * When not manually specified the primary key of the owning side table is used.
- *
- * If no parameters are passed the current field is returned
- *
- * @param string|null $key the table field to be used to link both tables together
- * @return string|array
- */
- public function bindingKey($key = null)
- {
- if ($key !== null) {
- $this->_bindingKey = $key;
- }
- if ($this->_bindingKey === null) {
- $this->_bindingKey = $this->isOwningSide($this->source()) ?
- $this->source()->primaryKey() :
- $this->target()->primaryKey();
- }
- return $this->_bindingKey;
- }
- /**
- * Sets the name of the field representing the foreign key to the target table.
- * If no parameters are passed the current field is returned
- *
- * @param string|null $key the key to be used to link both tables together
- * @return string|array
- */
- public function foreignKey($key = null)
- {
- if ($key !== null) {
- $this->_foreignKey = $key;
- }
- return $this->_foreignKey;
- }
- /**
- * Sets whether the records on the target table are dependent on the source table.
- *
- * This is primarily used to indicate that records should be removed if the owning record in
- * the source table is deleted.
- *
- * If no parameters are passed the current setting is returned.
- *
- * @param bool|null $dependent Set the dependent mode. Use null to read the current state.
- * @return bool
- */
- public function dependent($dependent = null)
- {
- if ($dependent !== null) {
- $this->_dependent = $dependent;
- }
- return $this->_dependent;
- }
- /**
- * Whether this association can be expressed directly in a query join
- *
- * @param array $options custom options key that could alter the return value
- * @return bool
- */
- public function canBeJoined(array $options = [])
- {
- $strategy = isset($options['strategy']) ? $options['strategy'] : $this->strategy();
- return $strategy == $this::STRATEGY_JOIN;
- }
- /**
- * Sets the type of join to be used when adding the association to a query.
- * If no arguments are passed, the currently configured type is returned.
- *
- * @param string $type the join type to be used (e.g. INNER)
- * @return string
- */
- public function joinType($type = null)
- {
- if ($type === null) {
- return $this->_joinType;
- }
- return $this->_joinType = $type;
- }
- /**
- * Sets the property name that should be filled with data from the target table
- * in the source table record.
- * If no arguments are passed, the currently configured type is returned.
- *
- * @param string|null $name The name of the association property. Use null to read the current value.
- * @return string
- */
- public function property($name = null)
- {
- if ($name !== null) {
- $this->_propertyName = $name;
- }
- if ($name === null && !$this->_propertyName) {
- list(, $name) = pluginSplit($this->_name);
- $this->_propertyName = Inflector::underscore($name);
- }
- return $this->_propertyName;
- }
- /**
- * Sets the strategy name to be used to fetch associated records. Keep in mind
- * that some association types might not implement but a default strategy,
- * rendering any changes to this setting void.
- * If no arguments are passed, the currently configured strategy is returned.
- *
- * @param string|null $name The strategy type. Use null to read the current value.
- * @return string
- * @throws \InvalidArgumentException When an invalid strategy is provided.
- */
- public function strategy($name = null)
- {
- if ($name !== null) {
- if (!in_array($name, $this->_validStrategies)) {
- throw new InvalidArgumentException(
- sprintf('Invalid strategy "%s" was provided', $name)
- );
- }
- $this->_strategy = $name;
- }
- return $this->_strategy;
- }
- /**
- * Sets the default finder to use for fetching rows from the target table.
- * If no parameters are passed, it will return the currently configured
- * finder name.
- *
- * @param string|null $finder the finder name to use
- * @return string
- */
- public function finder($finder = null)
- {
- if ($finder !== null) {
- $this->_finder = $finder;
- }
- return $this->_finder;
- }
- /**
- * Override this function to initialize any concrete association class, it will
- * get passed the original list of options used in the constructor
- *
- * @param array $options List of options used for initialization
- * @return void
- */
- protected function _options(array $options)
- {
- }
- /**
- * Alters a Query object to include the associated target table data in the final
- * result
- *
- * The options array accept the following keys:
- *
- * - includeFields: Whether to include target model fields in the result or not
- * - foreignKey: The name of the field to use as foreign key, if false none
- * will be used
- * - conditions: array with a list of conditions to filter the join with, this
- * will be merged with any conditions originally configured for this association
- * - fields: a list of fields in the target table to include in the result
- * - type: The type of join to be used (e.g. INNER)
- * - matching: Indicates whether the query records should be filtered based on
- * the records found on this association. This will force a 'INNER JOIN'
- * - aliasPath: A dot separated string representing the path of association names
- * followed from the passed query main table to this association.
- * - propertyPath: A dot separated string representing the path of association
- * properties to be followed from the passed query main entity to this
- * association
- * - joinType: The SQL join type to use in the query.
- *
- * @param Query $query the query to be altered to include the target table data
- * @param array $options Any extra options or overrides to be taken in account
- * @return void
- * @throws \RuntimeException if the query builder passed does not return a query
- * object
- */
- public function attachTo(Query $query, array $options = [])
- {
- $target = $this->target();
- $joinType = empty($options['joinType']) ? $this->joinType() : $options['joinType'];
- $options += [
- 'includeFields' => true,
- 'foreignKey' => $this->foreignKey(),
- 'conditions' => [],
- 'fields' => [],
- 'type' => empty($options['matching']) ? $joinType : 'INNER',
- 'table' => $target->table(),
- 'finder' => $this->finder()
- ];
- if (!empty($options['foreignKey'])) {
- $joinCondition = $this->_joinCondition($options);
- if ($joinCondition) {
- $options['conditions'][] = $joinCondition;
- }
- }
- list($finder, $opts) = $this->_extractFinder($options['finder']);
- $dummy = $this
- ->find($finder, $opts)
- ->eagerLoaded(true);
- if (!empty($options['queryBuilder'])) {
- $dummy = $options['queryBuilder']($dummy);
- if (!($dummy instanceof Query)) {
- throw new RuntimeException(sprintf(
- 'Query builder for association "%s" did not return a query',
- $this->name()
- ));
- }
- }
- $dummy->where($options['conditions']);
- $this->_dispatchBeforeFind($dummy);
- $joinOptions = ['table' => 1, 'conditions' => 1, 'type' => 1];
- $options['conditions'] = $dummy->clause('where');
- $query->join([$target->alias() => array_intersect_key($options, $joinOptions)]);
- $this->_appendFields($query, $dummy, $options);
- $this->_formatAssociationResults($query, $dummy, $options);
- $this->_bindNewAssociations($query, $dummy, $options);
- }
- /**
- * Correctly nests a result row associated values into the correct array keys inside the
- * source results.
- *
- * @param array $row The row to transform
- * @param string $nestKey The array key under which the results for this association
- * should be found
- * @param bool $joined Whether or not the row is a result of a direct join
- * with this association
- * @return array
- */
- public function transformRow($row, $nestKey, $joined)
- {
- $sourceAlias = $this->source()->alias();
- $nestKey = $nestKey ?: $this->_name;
- if (isset($row[$sourceAlias])) {
- $row[$sourceAlias][$this->property()] = $row[$nestKey];
- unset($row[$nestKey]);
- }
- return $row;
- }
- /**
- * Returns a modified row after appending a property for this association
- * with the default empty value according to whether the association was
- * joined or fetched externally.
- *
- * @param array $row The row to set a default on.
- * @param bool $joined Whether or not the row is a result of a direct join
- * with this association
- * @return array
- */
- public function defaultRowValue($row, $joined)
- {
- $sourceAlias = $this->source()->alias();
- if (isset($row[$sourceAlias])) {
- $row[$sourceAlias][$this->property()] = null;
- }
- return $row;
- }
- /**
- * Proxies the finding operation to the target table's find method
- * and modifies the query accordingly based of this association
- * configuration
- *
- * @param string|array $type the type of query to perform, if an array is passed,
- * it will be interpreted as the `$options` parameter
- * @param array $options The options to for the find
- * @see \Cake\ORM\Table::find()
- * @return \Cake\ORM\Query
- */
- public function find($type = null, array $options = [])
- {
- $type = $type ?: $this->finder();
- list($type, $opts) = $this->_extractFinder($type, $options);
- return $this->target()
- ->find($type, $options + $opts)
- ->where($this->conditions());
- }
- /**
- * Proxies the update operation to the target table's updateAll method
- *
- * @param array $fields A hash of field => new value.
- * @param mixed $conditions Conditions to be used, accepts anything Query::where()
- * can take.
- * @see \Cake\ORM\Table::updateAll()
- * @return bool Success Returns true if one or more rows are affected.
- */
- public function updateAll($fields, $conditions)
- {
- $target = $this->target();
- $expression = $target->query()
- ->where($this->conditions())
- ->where($conditions)
- ->clause('where');
- return $target->updateAll($fields, $expression);
- }
- /**
- * Proxies the delete operation to the target table's deleteAll method
- *
- * @param mixed $conditions Conditions to be used, accepts anything Query::where()
- * can take.
- * @return bool Success Returns true if one or more rows are affected.
- * @see \Cake\ORM\Table::deleteAll()
- */
- public function deleteAll($conditions)
- {
- $target = $this->target();
- $expression = $target->query()
- ->where($this->conditions())
- ->where($conditions)
- ->clause('where');
- return $target->deleteAll($expression);
- }
- /**
- * Triggers beforeFind on the target table for the query this association is
- * attaching to
- *
- * @param \Cake\ORM\Query $query the query this association is attaching itself to
- * @return void
- */
- protected function _dispatchBeforeFind($query)
- {
- $query->triggerBeforeFind();
- }
- /**
- * Helper function used to conditionally append fields to the select clause of
- * a query from the fields found in another query object.
- *
- * @param \Cake\ORM\Query $query the query that will get the fields appended to
- * @param \Cake\ORM\Query $surrogate the query having the fields to be copied from
- * @param array $options options passed to the method `attachTo`
- * @return void
- */
- protected function _appendFields($query, $surrogate, $options)
- {
- $fields = $surrogate->clause('select') ?: $options['fields'];
- $target = $this->_targetTable;
- $autoFields = $surrogate->autoFields();
- if ($query->eagerLoader()->autoFields() === false) {
- return;
- }
- if (empty($fields) && !$autoFields) {
- if ($options['includeFields'] && ($fields === null || $fields !== false)) {
- $fields = $target->schema()->columns();
- }
- }
- if ($autoFields === true) {
- $fields = array_merge((array)$fields, $target->schema()->columns());
- }
- if (!empty($fields)) {
- $query->select($query->aliasFields($fields, $target->alias()));
- }
- }
- /**
- * Adds a formatter function to the passed `$query` if the `$surrogate` query
- * declares any other formatter. Since the `$surrogate` query correspond to
- * the associated target table, the resulting formatter will be the result of
- * applying the surrogate formatters to only the property corresponding to
- * such table.
- *
- * @param \Cake\ORM\Query $query the query that will get the formatter applied to
- * @param \Cake\ORM\Query $surrogate the query having formatters for the associated
- * target table.
- * @param array $options options passed to the method `attachTo`
- * @return void
- */
- protected function _formatAssociationResults($query, $surrogate, $options)
- {
- $formatters = $surrogate->formatResults();
- if (!$formatters || empty($options['propertyPath'])) {
- return;
- }
- $property = $options['propertyPath'];
- $query->formatResults(function ($results) use ($formatters, $property) {
- $extracted = $results->extract($property)->compile();
- foreach ($formatters as $callable) {
- $extracted = new ResultSetDecorator($callable($extracted));
- }
- return $results->insert($property, $extracted);
- }, Query::PREPEND);
- }
- /**
- * Applies all attachable associations to `$query` out of the containments found
- * in the `$surrogate` query.
- *
- * Copies all contained associations from the `$surrogate` query into the
- * passed `$query`. Containments are altered so that they respect the associations
- * chain from which they originated.
- *
- * @param \Cake\ORM\Query $query the query that will get the associations attached to
- * @param \Cake\ORM\Query $surrogate the query having the containments to be attached
- * @param array $options options passed to the method `attachTo`
- * @return void
- */
- protected function _bindNewAssociations($query, $surrogate, $options)
- {
- $loader = $surrogate->eagerLoader();
- $contain = $loader->contain();
- $matching = $loader->matching();
- $target = $this->_targetTable;
- if (!$contain && !$matching) {
- return;
- }
- $loader->attachAssociations($query, $target, $options['includeFields']);
- $newContain = [];
- foreach ($contain as $alias => $value) {
- $newContain[$options['aliasPath'] . '.' . $alias] = $value;
- }
- $query->contain($newContain);
- foreach ($matching as $alias => $value) {
- $query->matching($options['aliasPath'] . '.' . $alias, $value['queryBuilder']);
- }
- }
- /**
- * Returns a single or multiple conditions to be appended to the generated join
- * clause for getting the results on the target table.
- *
- * @param array $options list of options passed to attachTo method
- * @return array
- * @throws \RuntimeException if the number of columns in the foreignKey do not
- * match the number of columns in the source table primaryKey
- */
- protected function _joinCondition($options)
- {
- $conditions = [];
- $tAlias = $this->target()->alias();
- $sAlias = $this->source()->alias();
- $foreignKey = (array)$options['foreignKey'];
- $bindingKey = (array)$this->bindingKey();
- if (count($foreignKey) !== count($bindingKey)) {
- $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"';
- throw new RuntimeException(sprintf(
- $msg,
- $this->_name,
- implode(', ', $foreignKey),
- implode(', ', $bindingKey)
- ));
- }
- foreach ($foreignKey as $k => $f) {
- $field = sprintf('%s.%s', $sAlias, $bindingKey[$k]);
- $value = new IdentifierExpression(sprintf('%s.%s', $tAlias, $f));
- $conditions[$field] = $value;
- }
- return $conditions;
- }
- /**
- * Helper method to infer the requested finder and its options.
- *
- * Returns the inferred options from the finder $type.
- *
- * ### Examples:
- *
- * The following will call the finder 'translations' with the value of the finder as its options:
- * $query->contain(['Comments' => ['finder' => ['translations']]]);
- * $query->contain(['Comments' => ['finder' => ['translations' => []]]]);
- * $query->contain(['Comments' => ['finder' => ['translations' => ['locales' => ['en_US']]]]]);
- *
- * @param string|array $finderData The finder name or an array having the name as key
- * and options as value.
- * @return array
- */
- protected function _extractFinder($finderData)
- {
- $finderData = (array)$finderData;
- if (is_numeric(key($finderData))) {
- return [current($finderData), []];
- }
- return [key($finderData), current($finderData)];
- }
- /**
- * Proxies property retrieval to the target table. This is handy for getting this
- * association's associations
- *
- * @param string $property the property name
- * @return \Cake\ORM\Association
- * @throws \RuntimeException if no association with such name exists
- */
- public function __get($property)
- {
- return $this->target()->{$property};
- }
- /**
- * Proxies the isset call to the target table. This is handy to check if the
- * target table has another association with the passed name
- *
- * @param string $property the property name
- * @return bool true if the property exists
- */
- public function __isset($property)
- {
- return isset($this->target()->{$property});
- }
- /**
- * Proxies method calls to the target table.
- *
- * @param string $method name of the method to be invoked
- * @param array $argument List of arguments passed to the function
- * @return mixed
- * @throws \BadMethodCallException
- */
- public function __call($method, $argument)
- {
- return call_user_func_array([$this->target(), $method], $argument);
- }
- /**
- * Get the relationship type.
- *
- * @return string Constant of either ONE_TO_ONE, MANY_TO_ONE, ONE_TO_MANY or MANY_TO_MANY.
- */
- abstract public function type();
- /**
- * Eager loads a list of records in the target table that are related to another
- * set of records in the source table. Source records can specified in two ways:
- * first one is by passing a Query object setup to find on the source table and
- * the other way is by explicitly passing an array of primary key values from
- * the source table.
- *
- * The required way of passing related source records is controlled by "strategy"
- * When the subquery strategy is used it will require a query on the source table.
- * When using the select strategy, the list of primary keys will be used.
- *
- * Returns a closure that should be run for each record returned in a specific
- * Query. This callable will be responsible for injecting the fields that are
- * related to each specific passed row.
- *
- * Options array accepts the following keys:
- *
- * - query: Query object setup to find the source table records
- * - keys: List of primary key values from the source table
- * - foreignKey: The name of the field used to relate both tables
- * - conditions: List of conditions to be passed to the query where() method
- * - sort: The direction in which the records should be returned
- * - fields: List of fields to select from the target table
- * - contain: List of related tables to eager load associated to the target table
- * - strategy: The name of strategy to use for finding target table records
- * - nestKey: The array key under which results will be found when transforming the row
- *
- * @param array $options The options for eager loading.
- * @return \Closure
- */
- abstract public function eagerLoader(array $options);
- /**
- * Handles cascading a delete from an associated model.
- *
- * Each implementing class should handle the cascaded delete as
- * required.
- *
- * @param \Cake\Datasource\EntityInterface $entity The entity that started the cascaded delete.
- * @param array $options The options for the original delete.
- * @return bool Success
- */
- abstract public function cascadeDelete(EntityInterface $entity, array $options = []);
- /**
- * Returns whether or not the passed table is the owning side for this
- * association. This means that rows in the 'target' table would miss important
- * or required information if the row in 'source' did not exist.
- *
- * @param \Cake\ORM\Table $side The potential Table with ownership
- * @return bool
- */
- abstract public function isOwningSide(Table $side);
- /**
- * Extract the target's association data our from the passed entity and proxies
- * the saving operation to the target table.
- *
- * @param \Cake\Datasource\EntityInterface $entity the data to be saved
- * @param array|\ArrayObject $options The options for saving associated data.
- * @return bool|\Cake\Datasource\EntityInterface false if $entity could not be saved, otherwise it returns
- * the saved entity
- * @see Table::save()
- */
- abstract public function saveAssociated(EntityInterface $entity, array $options = []);
- }
|