Returns an association object configured for the specified alias if any
public association ( string $name ) : Cake\ORM\Association | null | ||
$name | string | the alias used for the association. |
return | Cake\ORM\Association | null | Either the association or null. |
/** * Method that renders Entity values through Field Handler Factory. * * @param Cake\ORM\Entity $entity Entity instance * @param Cake\ORM\Table|string $table Table instance * @param array $fields Fields to prettify * @return void */ protected function _prettify(Entity $entity, $table, array $fields = []) { if (!$this->__fhf instanceof FieldHandlerFactory) { $this->__fhf = new FieldHandlerFactory(); } if (empty($fields)) { $fields = array_keys($entity->toArray()); } foreach ($fields as $field) { // handle belongsTo associated data if ($entity->{$field} instanceof Entity) { $tableName = $table->association($entity->{$field}->source())->className(); $this->_prettify($entity->{$field}, $tableName); } // handle hasMany associated data if (is_array($entity->{$field})) { if (empty($entity->{$field})) { continue; } foreach ($entity->{$field} as $associatedEntity) { if (!$associatedEntity instanceof Entity) { continue; } $tableName = $table->association($associatedEntity->source())->className(); $this->_prettify($associatedEntity, $tableName); } } $renderOptions = ['entity' => $entity]; $entity->{$field} = $this->__fhf->renderValue($table instanceof Table ? $table->registryAlias() : $table, $field, $entity->{$field}, $renderOptions); } }
/** * Build the map of property => marshalling callable. * * @param array $data The data being marshalled. * @param array $options List of options containing the 'associated' key. * @throws \InvalidArgumentException When associations do not exist. * @return array */ protected function _buildPropertyMap($data, $options) { $map = []; $schema = $this->_table->schema(); // Is a concrete column? foreach (array_keys($data) as $prop) { $columnType = $schema->columnType($prop); if ($columnType) { $map[$prop] = function ($value, $entity) use($columnType) { return Type::build($columnType)->marshal($value); }; } } // Map associations if (!isset($options['associated'])) { $options['associated'] = []; } $include = $this->_normalizeAssociations($options['associated']); foreach ($include as $key => $nested) { if (is_int($key) && is_scalar($nested)) { $key = $nested; $nested = []; } $assoc = $this->_table->association($key); // If the key is not a special field like _ids or _joinData // it is a missing association that we should error on. if (!$assoc) { if (substr($key, 0, 1) !== '_') { throw new \InvalidArgumentException(sprintf('Cannot marshal data for "%s" association. It is not associated with "%s".', $key, $this->_table->alias())); } continue; } if (isset($options['forceNew'])) { $nested['forceNew'] = $options['forceNew']; } if (isset($options['isMerge'])) { $callback = function ($value, $entity) use($assoc, $nested) { $options = $nested + ['associated' => []]; return $this->_mergeAssociation($entity->get($assoc->property()), $assoc, $value, $options); }; } else { $callback = function ($value, $entity) use($assoc, $nested) { $options = $nested + ['associated' => []]; return $this->_marshalAssociation($assoc, $value, $options); }; } $map[$assoc->property()] = $callback; } $behaviors = $this->_table->behaviors(); foreach ($behaviors->loaded() as $name) { $behavior = $behaviors->get($name); if ($behavior instanceof PropertyMarshalInterface) { $map += $behavior->buildMarshalMap($this, $map, $options); } } return $map; }
/** * Build the map of property => association names. * * @param array $include The array of included associations. * @return array */ protected function _buildPropertyMap($include) { $map = []; foreach ($include as $key => $nested) { if (is_int($key) && is_scalar($nested)) { $key = $nested; $nested = []; } $nested = isset($nested['associated']) ? $nested['associated'] : []; $assoc = $this->_table->association($key); if ($assoc) { $map[$assoc->property()] = ['association' => $assoc, 'nested' => $nested]; } } return $map; }
/** * Build the map of property => association names. * * @param array $include The array of included associations. * @return array */ protected function _buildPropertyMap($include) { if (empty($include['associated'])) { return []; } foreach ($include['associated'] as $key => $options) { if (is_int($key) && is_scalar($options)) { $key = $options; $options = []; } $options += ['validate' => true, 'associated' => []]; $assoc = $this->_table->association($key); if ($assoc) { $map[$assoc->property()] = ['association' => $assoc, 'options' => $options]; } } return $map; }
/** * Build the map of property => association names. * * @param array $options List of options containing the 'associated' key. * @return array */ protected function _buildPropertyMap($options) { if (empty($options['associated'])) { return []; } $include = $options['associated']; $map = []; $include = $this->_normalizeAssociations($include); foreach ($include as $key => $nested) { if (is_int($key) && is_scalar($nested)) { $key = $nested; $nested = []; } $assoc = $this->_table->association($key); if ($assoc) { $map[$assoc->property()] = ['association' => $assoc] + $nested + ['associated' => []]; } } return $map; }
/** * Returns the ids found for each of the condition arrays passed for the translations * table. Each records is indexed by the corresponding position to the conditions array * * @param array $ruleSet an array of arary of conditions to be used for finding each * @return array */ protected function _findExistingTranslations($ruleSet) { $association = $this->_table->association($this->_translationTable->alias()); $query = $association->find()->select(['id', 'num' => 0])->where(current($ruleSet))->hydrate(false)->bufferResults(false); unset($ruleSet[0]); foreach ($ruleSet as $i => $conditions) { $q = $association->find()->select(['id', 'num' => $i])->where($conditions); $query->unionAll($q); } return $query->combine('num', 'id')->toArray(); }
/** * Returns association object for all versions or single field version. * * @param string|null $field Field name for per-field association. * @param array $options Association options. * @return \Cake\ORM\Association */ public function versionAssociation($field = null, $options = []) { $name = $this->_associationName($field); if (!$this->_table->associations()->has($name)) { $model = $this->_config['referenceName']; if ($field) { $this->_table->hasOne($name, $options + ['className' => $this->_config['versionTable'], 'foreignKey' => $this->_config['foreignKey'], 'joinType' => 'LEFT', 'conditions' => [$name . '.model' => $model, $name . '.field' => $field], 'propertyName' => $field . '_version']); } else { $this->_table->hasMany($name, $options + ['className' => $this->_config['versionTable'], 'foreignKey' => $this->_config['foreignKey'], 'strategy' => 'subquery', 'conditions' => ["{$name}.model" => $model], 'propertyName' => '__version', 'dependent' => true]); } } return $this->_table->association($name); }
/** * Build the map of property => association names. * * @param array $options List of options containing the 'associated' key. * @throws \RuntimeException When associations do not exist. * @return array */ protected function _buildPropertyMap($options) { if (empty($options['associated'])) { return []; } $include = $options['associated']; $map = []; $include = $this->_normalizeAssociations($include); foreach ($include as $key => $nested) { if (is_int($key) && is_scalar($nested)) { $key = $nested; $nested = []; } if ($key === '_joinData') { continue; } $assoc = $this->_table->association($key); if (!$assoc) { throw new RuntimeException(sprintf('Cannot marshal data for "%s" association. It is not associated with "%s".', $key, $this->_table->alias())); } $map[$assoc->property()] = ['association' => $assoc] + $nested + ['associated' => []]; } return $map; }
/** * @param \ArrayObject $data * @param \Cake\ORM\Table $table * @return \ArrayObject */ protected function _process($data, Table $table) { $associations = []; /* @var \Cake\ORM\Association $association */ foreach ($table->associations() as $association) { $associations[$association->property()] = $association->name(); } foreach ($data as $key => $value) { if (array_key_exists($key, $associations)) { $data[$key] = $this->_process($data[$key], $table->association($associations[$key])->target()); continue; } $nullable = Hash::get((array) $table->schema()->column($key), 'null'); if ($nullable !== true) { continue; } if ($value !== '') { continue; } $default = Hash::get((array) $table->schema()->column($key), 'default'); $data[$key] = $default; } return $data; }
/** * Auxiliary function responsible for fully normalizing deep associations defined * using `contain()` * * @param Table $parent owning side of the association * @param string $alias name of the association to be loaded * @param array $options list of extra options to use for this association * @param array $paths An array with two values, the first one is a list of dot * separated strings representing associations that lead to this `$alias` in the * chain of associations to be loaded. The second value is the path to follow in * entities' properties to fetch a record of the corresponding association. * @return array normalized associations * @throws \InvalidArgumentException When containments refer to associations that do not exist. */ protected function _normalizeContain(Table $parent, $alias, $options, $paths) { $defaults = $this->_containOptions; $instance = $parent->association($alias); if (!$instance) { throw new InvalidArgumentException(sprintf('%s is not associated with %s', $parent->alias(), $alias)); } if ($instance->alias() !== $alias) { throw new InvalidArgumentException(sprintf("You have contained '%s' but that association was bound as '%s'.", $alias, $instance->alias())); } $paths += ['aliasPath' => '', 'propertyPath' => '', 'root' => $alias]; $paths['aliasPath'] .= '.' . $alias; $paths['propertyPath'] .= '.' . $instance->property(); $table = $instance->target(); $extra = array_diff_key($options, $defaults); $config = ['associations' => [], 'instance' => $instance, 'config' => array_diff_key($options, $extra), 'aliasPath' => trim($paths['aliasPath'], '.'), 'propertyPath' => trim($paths['propertyPath'], '.')]; $config['canBeJoined'] = $instance->canBeJoined($config['config']); $eagerLoadable = new EagerLoadable($alias, $config); if ($config['canBeJoined']) { $this->_aliasList[$paths['root']][$alias][] = $eagerLoadable; } else { $paths['root'] = $config['aliasPath']; } foreach ($extra as $t => $assoc) { $eagerLoadable->addAssociation($t, $this->_normalizeContain($table, $t, $assoc, $paths)); } return $eagerLoadable; }
/** * Generate associations on the junction table as necessary * * Generates the following associations: * * - junction belongsTo source e.g. ArticlesTags belongsTo Tags * - junction belongsTo target e.g. ArticlesTags belongsTo Articles * * You can override these generated associations by defining associations * with the correct aliases. * * @param \Cake\ORM\Table $junction The junction table. * @param \Cake\ORM\Table $source The source table. * @param \Cake\ORM\Table $target The target table. * @return void */ protected function _generateJunctionAssociations($junction, $source, $target) { $tAlias = $target->alias(); $sAlias = $source->alias(); if (!$junction->association($tAlias)) { $junction->belongsTo($tAlias, ['foreignKey' => $this->targetForeignKey(), 'targetTable' => $target]); } if (!$junction->association($sAlias)) { $junction->belongsTo($sAlias, ['foreignKey' => $this->foreignKey(), 'targetTable' => $source]); } }
/** * Used to recursively add contained association column types to * the query. * * @param \Cake\ORM\Table $table The table instance to pluck associations from. * @param \Cake\Database\TypeMap $typeMap The typemap to check for columns in. * This typemap is indirectly mutated via Cake\ORM\Query::addDefaultTypes() * @param array $associations The nested tree of associations to walk. * @return void */ protected function _addAssociationsToTypeMap($table, $typeMap, $associations) { $typeMap = $this->typeMap(); foreach ($associations as $name => $nested) { $association = $table->association($name); if (!$association) { continue; } $target = $association->target(); $primary = (array) $target->primaryKey(); if (empty($primary) || $typeMap->type($target->aliasField($primary[0])) === null) { $this->addDefaultTypes($target); } if (!empty($nested)) { $this->_addAssociationsToTypeMap($target, $typeMap, $nested); } } }
/** * Test that save has append as the default save strategy * * @return void */ public function testSaveDefaultSaveStrategy() { $authors = new Table(['table' => 'authors', 'alias' => 'Authors', 'connection' => $this->connection, 'entityClass' => 'Cake\\ORM\\Entity']); $authors->hasMany('Articles', ['saveStrategy' => 'append']); $this->assertEquals('append', $authors->association('articles')->saveStrategy()); }
/** * Tests that BelongsToMany() creates and configures correctly the association * * @return void */ public function testBelongsToMany() { $options = ['foreignKey' => 'thing_id', 'joinTable' => 'things_tags', 'conditions' => ['b' => 'c'], 'sort' => ['foo' => 'asc']]; $table = new Table(['table' => 'authors', 'connection' => $this->connection]); $belongsToMany = $table->belongsToMany('tag', $options); $this->assertInstanceOf('Cake\\ORM\\Association\\BelongsToMany', $belongsToMany); $this->assertSame($belongsToMany, $table->association('tag')); $this->assertEquals('tag', $belongsToMany->name()); $this->assertEquals('thing_id', $belongsToMany->foreignKey()); $this->assertEquals(['b' => 'c'], $belongsToMany->conditions()); $this->assertEquals(['foo' => 'asc'], $belongsToMany->sort()); $this->assertSame($table, $belongsToMany->source()); $this->assertSame('things_tags', $belongsToMany->junction()->table()); }
/** * Iterate all associations and update counter caches. * * @param Event $event * @param Entity $entity * @return void */ protected function _processAssociations(Event $event, Entity $entity) { foreach ($this->_config as $assoc => $settings) { $assoc = $this->_table->association($assoc); $this->_processAssociation($event, $entity, $assoc, $settings); } }
/** * Auxiliary function responsible for fully normalizing deep associations defined * using `contain()` * * @param Table $parent owning side of the association * @param string $alias name of the association to be loaded * @param array $options list of extra options to use for this association * @param array $paths An array with to values, the first one is a list of dot * separated strings representing associations that lead to this `$alias` in the * chain of associaitons to be loaded. The second value is the path to follow in * entities' properties to fetch a record of the corresponding association. * @return array normalized associations * @throws \InvalidArgumentException When containments refer to associations that do not exist. */ protected function _normalizeContain(Table $parent, $alias, $options, $paths) { $defaults = $this->_containOptions; $instance = $parent->association($alias); if (!$instance) { throw new \InvalidArgumentException(sprintf('%s is not associated with %s', $parent->alias(), $alias)); } $paths += ['aliasPath' => '', 'propertyPath' => '', 'root' => $alias]; $paths['aliasPath'] .= '.' . $alias; $paths['propertyPath'] .= '.' . $instance->property(); $table = $instance->target(); $extra = array_diff_key($options, $defaults); $config = ['associations' => [], 'instance' => $instance, 'config' => array_diff_key($options, $extra), 'aliasPath' => trim($paths['aliasPath'], '.'), 'propertyPath' => trim($paths['propertyPath'], '.')]; $config['canBeJoined'] = $instance->canBeJoined($config['config']); $config = $this->_correctStrategy($alias, $config, $paths['root']); if ($config['canBeJoined']) { $this->_aliasList[$paths['root']][$alias] = true; } else { $paths['root'] = $config['aliasPath']; } foreach ($extra as $t => $assoc) { $config['associations'][$t] = $this->_normalizeContain($table, $t, $assoc, $paths); } return $config; }