/** * 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; }
/** * Merge the special _joinData property into the entity set. * * @param \Cake\Datasource\EntityInterface $original The original entity * @param \Cake\ORM\Association $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. * @return array An array of entities */ protected function _mergeJoinData($original, $assoc, $value, $options) { $associated = isset($options['associated']) ? $options['associated'] : []; $extra = []; foreach ($original as $entity) { // Mark joinData as accessible so we can marshal it properly. $entity->accessible('_joinData', true); $joinData = $entity->get('_joinData'); if ($joinData && $joinData instanceof EntityInterface) { $extra[spl_object_hash($entity)] = $joinData; } } $joint = $assoc->junction(); $marshaller = $joint->marshaller(); $nested = []; if (isset($associated['_joinData'])) { $nested = (array) $associated['_joinData']; } $options['accessibleFields'] = ['_joinData' => true]; $records = $this->mergeMany($original, $value, $options); foreach ($records as $record) { $hash = spl_object_hash($record); $value = $record->get('_joinData'); // Already an entity, no further marshalling required. if ($value instanceof EntityInterface) { continue; } // Scalar data can't be handled if (!is_array($value)) { $record->unsetProperty('_joinData'); continue; } // Marshal data into the old object, or make a new joinData object. if (isset($extra[$hash])) { $record->set('_joinData', $marshaller->merge($extra[$hash], $value, $nested)); } elseif (is_array($value)) { $joinData = $marshaller->one($value, $nested); $record->set('_joinData', $joinData); } } return $records; }
/** * Merge the special _joinData property into the entity set. * * @param \Cake\Datasource\EntityInterface $original The original entity * @param \Cake\ORM\Association $assoc The association to marshall * @param array $value The data to hydrate * @param array $options List of options. * @return array An array of entities */ protected function _mergeJoinData($original, $assoc, $value, $options) { $associated = isset($options['associated']) ? $options['associated'] : []; $extra = []; foreach ($original as $entity) { // Mark joinData as accessible so we can marshal it properly. $entity->accessible('_joinData', true); $joinData = $entity->get('_joinData'); if ($joinData && $joinData instanceof EntityInterface) { $extra[spl_object_hash($entity)] = $joinData; } } $joint = $assoc->junction(); $marshaller = $joint->marshaller(); $nested = []; if (isset($associated['_joinData'])) { $nested = (array) $associated['_joinData']; } $records = $this->mergeMany($original, $value, $options); foreach ($records as $record) { $hash = spl_object_hash($record); $value = $record->get('_joinData'); if (!is_array($value)) { $record->unsetProperty('_joinData'); continue; } if (isset($extra[$hash])) { $record->set('_joinData', $marshaller->merge($extra[$hash], $value, $nested)); } else { $joinData = $marshaller->one($value, $nested); $record->set('_joinData', $joinData); } } return $records; }