/** * Updates counter cache for a single association * * @param \Cake\Event\Event $event Event instance. * @param \Cake\ORM\Entity $entity Entity * @param Association $assoc The association object * @param array $settings The settings for for counter cache for this association * @return void */ protected function _processAssociation(Event $event, Entity $entity, Association $assoc, array $settings) { $foreignKeys = (array) $assoc->foreignKey(); $primaryKeys = (array) $assoc->target()->primaryKey(); $countConditions = $entity->extract($foreignKeys); $updateConditions = array_combine($primaryKeys, $countConditions); $countOriginalConditions = $entity->extractOriginal($foreignKeys); if ($countOriginalConditions !== []) { $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions); } foreach ($settings as $field => $config) { if (is_int($field)) { $field = $config; $config = []; } if (!is_string($config) && is_callable($config)) { $count = $config($event, $entity, $this->_table, false); } else { $count = $this->_getCount($config, $countConditions); } $assoc->target()->updateAll([$field => $count], $updateConditions); if (isset($updateOriginalConditions)) { if (!is_string($config) && is_callable($config)) { $count = $config($event, $entity, $this->_table, true); } else { $count = $this->_getCount($config, $countOriginalConditions); } $assoc->target()->updateAll([$field => $count], $updateOriginalConditions); } } }
/** * Get finder to use for provided association. * * @param \Cake\ORM\Association $association Association instance * @return string */ public function finder(Association $association) { if ($association->target()->behaviors()->has('Tree')) { return 'treeList'; } return 'list'; }
/** * Loads a list of belongs to many from ids. * * @param \Cake\ORM\Association $assoc The association class for the belongsToMany association. * @param array $ids The list of ids to load. * @return array An array of entities. */ protected function _loadAssociatedByIds($assoc, $ids) { if (empty($ids)) { return []; } $target = $assoc->target(); $primaryKey = (array) $target->primaryKey(); $multi = count($primaryKey) > 1; $primaryKey = array_map([$target, 'aliasField'], $primaryKey); if ($multi) { if (count(current($ids)) !== count($primaryKey)) { return []; } $filter = new TupleComparison($primaryKey, $ids, [], 'IN'); } else { $filter = [$primaryKey[0] . ' IN' => $ids]; } return $target->find()->where($filter)->toArray(); }
/** * Marshals data for belongsToMany associations. * * Builds the related entities and handles the special casing * for junction table entities. * * @param \Cake\ORM\Association $assoc The association to marshal. * @param array $data The data to convert into entities. * @param array $options List of options. * @return array An array of built entities. */ protected function _belongsToMany(Association $assoc, array $data, $options = []) { $associated = isset($options['associated']) ? $options['associated'] : []; $forceNew = isset($options['forceNew']) ? $options['forceNew'] : false; $data = array_values($data); $target = $assoc->target(); $primaryKey = array_flip((array) $target->primaryKey()); $records = $conditions = []; $primaryCount = count($primaryKey); $conditions = []; foreach ($data as $i => $row) { if (!is_array($row)) { continue; } if (array_intersect_key($primaryKey, $row) === $primaryKey) { $keys = array_intersect_key($row, $primaryKey); if (count($keys) === $primaryCount) { $rowConditions = []; foreach ($keys as $key => $value) { $rowConditions[][$target->aliasfield($key)] = $value; } if ($forceNew && !$target->exists($rowConditions)) { $records[$i] = $this->one($row, $options); } $conditions = array_merge($conditions, $rowConditions); } } else { $records[$i] = $this->one($row, $options); } } if (!empty($conditions)) { $query = $target->find(); $query->andWhere(function ($exp) use($conditions) { return $exp->or_($conditions); }); $keyFields = array_keys($primaryKey); $existing = []; foreach ($query as $row) { $k = implode(';', $row->extract($keyFields)); $existing[$k] = $row; } foreach ($data as $i => $row) { $key = []; foreach ($keyFields as $k) { if (isset($row[$k])) { $key[] = $row[$k]; } } $key = implode(';', $key); // Update existing record and child associations if (isset($existing[$key])) { $records[$i] = $this->merge($existing[$key], $data[$i], $options); } } } $jointMarshaller = $assoc->junction()->marshaller(); $nested = []; if (isset($associated['_joinData'])) { $nested = (array) $associated['_joinData']; } foreach ($records as $i => $record) { // Update junction table data in _joinData. if (isset($data[$i]['_joinData'])) { $joinData = $jointMarshaller->one($data[$i]['_joinData'], $nested); $record->set('_joinData', $joinData); } } return $records; }
/** * Create a new sub-marshaller and marshal the associated data. * * @param \Cake\ORM\Association $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. * @return mixed */ protected function _marshalAssociation($assoc, $value, $options) { if (!is_array($value)) { return null; } $targetTable = $assoc->target(); $marshaller = $targetTable->marshaller(); $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; if (in_array($assoc->type(), $types)) { return $marshaller->one($value, (array) $options); } if ($assoc->type() === Association::ONE_TO_MANY || $assoc->type() === Association::MANY_TO_MANY) { $hasIds = array_key_exists('_ids', $value); $onlyIds = array_key_exists('onlyIds', $options) && $options['onlyIds']; if ($hasIds && is_array($value['_ids'])) { return $this->_loadAssociatedByIds($assoc, $value['_ids']); } if ($hasIds || $onlyIds) { return []; } } if ($assoc->type() === Association::MANY_TO_MANY) { return $marshaller->_belongsToMany($assoc, $value, (array) $options); } return $marshaller->many($value, (array) $options); }
/** * Loads a list of belongs to many from ids. * * @param Association $assoc The association class for the belongsToMany association. * @param array $ids The list of ids to load. * @return array An array of entities. */ protected function _loadBelongsToMany($assoc, $ids) { $target = $assoc->target(); $primaryKey = (array) $target->primaryKey(); $multi = count($primaryKey) > 1; $primaryKey = array_map(function ($key) use($target) { return $target->alias() . '.' . $key; }, $primaryKey); if ($multi) { if (count(current($ids)) !== count($primaryKey)) { return []; } $filter = new TupleComparison($primaryKey, $ids, [], 'IN'); } else { $filter = [$primaryKey[0] . ' IN' => $ids]; } return $target->find()->where($filter)->toArray(); }