Многие ко многим – опасные связи
На этой неделе обновил CakePHP из SVN и тут же перестало работать добавление связей «многие-ко-многим» (hasAndBelongsToMany, HABTM). Небольшое расследование, сравнение изменений в коде и вопросы в гугл группе дали неутешительные результаты.
Во-первых у меня сложилось впечатление, что разработчики, несмотря на статус ReleaseCandidate (RC3) все еще не пришли к единому мнению относительно структуры данных, которую надо скармливать методу save(). :-/
Во-вторых естественные ключи в CakePHP практически «вне закона». Вот непонятно мне это – чем с точки зрения методов find(), save() и др. так сильно отличаются естественные ключи от синтетических? Тем более, что нечисловые первичные ключи все-таки поддерживаются. Это я про поля с UUID.
Проанализировав ситуацию, пришел к выводу, что на жанном этапе отказываться от естественных ключей пока неразумно и надо, видимо, добавлять такие связи самостоятельно. Тем более, что выборка по прежнему работает отлично. Т.е. нужно только реализовать добавление и удаление связей.
Как обычно, начал с поиска уже готовых решений и в блоге «Программируем на CakePHP» нашел код нужных методов. Работает даже лучше прежнего – передаем id записи из модели, массив id записей другой модели и все добавляется в связующую таблицу. Это, конечно, не «автомагия», но зато просто и понятно. :-)
Обратил внимание на некоторую неоптимальность в работе кода. Алгоритм метода добавления примерно такой:
- Как уже написал, получаем id записи и массив id связанных записей
- Проверяем есть-ли уже какие-нибудь пары id-id и удаляем их
- Добавляем связи
Мне это не очень понравилось – считаю, что один SELECT, лучше, чем 5 DELETE. :-) Поэтому немного переписал метод. Во-первых отказался от принудительного удаления уже существующих связей, просто удаляю из массива те id связанных записей, отношение с которыми уже есть. Во-вторых заменил серию INSERT’ов, по одному на каждую связь, на один общий INSERT. Получился вот такой код:
/**
* Добавляет связь между двумя записями
*
* @param mixed $assoc С какой моделью установлена HABTM связь
* @param mixed $assoc_ids Идентификатор или массив идентификаторов записей, привязываемых к выбранной записи
* @param integer $id Идентификатор выбранной записи в этой модели
* @return boolean Success
*/
function addAssoc($assoc, $assoc_ids, $id = null)
{
if ($id != null) {
$this->id = $id;
}
$id = $this->id;
if (is_array($this->id)) {
$id = $this->id[0];
}
if ($this->id !== null && $this->id !== false) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$joinTable = $this->hasAndBelongsToMany[$assoc]['joinTable'];
$table = $db->name($db->fullTableName($joinTable));
$keys[] = $this->hasAndBelongsToMany[$assoc]['foreignKey'];
$keys[] = $this->hasAndBelongsToMany[$assoc]['associationForeignKey'];
$fields = join(',', $keys);
if(!is_array($assoc_ids)) {
$assoc_ids = array($assoc_ids);
}
$found_assoc = $db->query(
"SELECT `{$keys[1]}` FROM $table WHERE `{$keys[0]}` = " .
$db->value($id, $this->getColumnType($this->primaryKey)) .
" AND `{$keys[1]}` IN (" .
join(',', $db->value($assoc_ids)) .
")");
$found_assoc = Set::classicExtract(
$found_assoc,
'{n}.' . $db->fullTableName($joinTable, false) . '.' . $keys[1]
);
$assoc_ids = array_diff($assoc_ids, $found_assoc);
if (!empty($assoc_ids))
{
foreach ($assoc_ids as $assoc_id)
{
$insert_values[] =
'(' .
$db->value($id, $this->getColumnType($this->primaryKey)) .
',' .
$db->value($assoc_id) .
')';
}
$db->execute("INSERT INTO {$table} ({$fields}) VALUES " . join(',', $insert_values));
unset ($values);
}
return true;
} else {
return false;
}
}
Метод удаления связей переделал похожим образом.
Рубрики: CakePHP · Теги: HABTM, hasAndBelongsToMany, Модель
-
http://com.spweb.ru/ Мета
-
http://com.spweb.ru Мета
-
Сергей
-
Сергей
-
evilbloodydemon
-
evilbloodydemon
-
Сергей
-
Сергей
-
Сергей
-
Сергей

