Сен 27 2008
Добавление и удаление ассоциаций между записями
Во многих приложениях используются связи типа многие-ко-многим - HasAndBelongsToMany или HABTM в терминологии CakePHP. Они нужны для создания меток к записям в блоге, привязки атрибутов к товарам в интернет магазине, пользователей к пользователям в социальных сетях и т.д.
Для создания связи HABTM в CakePHP используется вспомогательная таблица, в которой хранятся два идентификатора связываемых данных. Например, если нам надо связать таблицы posts и tags, то вспомогательная таблица будет иметь вид:
1 2 3 4 5 | CREATE TABLE `posts_tags` ( `post_id` int(11) NOT NULL, `tag_id` int(11) NOT NULL, PRIMARY KEY (`post_id`,`tag_id`) ) ENGINE=MyISAM; |
При создании записи, если для нее выбраны метки, т.е. присутствуют в массиве, передаваемом в $this->Post->save(), CakePHP автоматически создаст связи в этой вспомогательной таблице. Но это только при создании записи. А как сделать редактирование именно связей? Допустим, мне надо добавить метку для записи в блоге. И запись, и такая метка уже существуют, но не связаны. Как их связать?
В своей студии мы разрабатывали приложение, активно использующее редактирование связей на CakePHP 1.1. На сайте http://bakery.cakephp.org удалось найти следующие две функции, как раз подходящие для этого. Функции надо добавить в файл /app/app_model.php, если нужно чтобы они были доступны для всех моделей приложения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | /** * Добавляет связь между двумя записями * * @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); } // Для избежания дублирования $this->deleteAssoc($assoc,$assoc_ids,$id); foreach ($assoc_ids as $assoc_id) { $values[] = $db->value($id, $this->getColumnType($this->primaryKey)); $values[] = $db->value($assoc_id); $values = join(',', $values); $db->execute("INSERT INTO {$table} ({$fields}) VALUES ({$values})"); unset ($values); } return true; } else { return false; } } /** * Удаляет связи между записями * * @param integer $id Идентификатор записи в этой модели * @param mixed $assoc С какой моделью установлена HABTM связь * @param mixed $assoc_ids Идентификатор или массив идентификаторов записей, привязанных к выбранной записи * @return boolean Success */ function deleteAssoc($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)); $mainKey = $this->hasAndBelongsToMany[$assoc]['foreignKey']; $assocKey = $this->hasAndBelongsToMany[$assoc]['associationForeignKey']; if(!is_array($assoc_ids)) { $assoc_ids = array($assoc_ids); } foreach ($assoc_ids as $assoc_id) { $db->execute("DELETE FROM {$table} WHERE {$mainKey} = '{$id}' AND {$assocKey} = '{$assoc_id}'"); } return true; } else { return false; } } |
Использовать их очень просто. Например, при редактировании записи в блоге, нам надо привязать к ней метку:
1 | $this->Post->addAssoc('Tag', $tag['id'], $post_id); |
Надеюсь, этот код работает и в CakePHP 1.2. Будет время — проверю.
Октябрь 6th, 2008 at 14:55
Не совсем удачный пример
“…..надо связать таблицы posts и tags
то..
CREATE TABLE `vdv_articles_cats` (
`article_id` int(11) NOT NULL,
`cat_id` int(11) NOT NULL,
PRIMARY KEY (`article_id`,`cat_id`)
) ENGINE=MyISAM; ”
Насколько я помню, таблица должна быть типа posts_tags
(в альфавитном порядке)
т.е.
CREATE TABLE `posts_tags` (
`post_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`post_id`,`tag_id`)
) ENGINE=MyISAM;
…
Октябрь 7th, 2008 at 20:13
Да, точно, в примере таблицы опечатка - копировал из одного своего проекта и забыл поправить. Спасибо что заметили.
Теперь поправил.
Октябрь 9th, 2008 at 00:27
Буквально сегодня маялся с many-to-many. В скором времени опробую и этот пример.
У меня была такая задача:
допустим у связи есть еще и свойства, ну например (хотя в данном контексте это скорее бессмысленно) в таблице posts_tags помимо стандартных столбцов есть еще дополнительный столбец, допустим, времени отнесения того или иного тега к данному посту.
Данный столбец “время отнесения” должен быть именно в этой таблице в силу нормализации и рефакторинга базы.
Так вот, при такой организации не выходило выцепить это свойство (отобразить в вивере и т.п.) при связи объектов.
Решал проблему по разному, в одном случае просто денормализовал, в другом - отказался от many-to-many, а в третьем - создал отдельную модель над данной таблицей many-to-many связями и внес в нее belongsTo к таблицам Tags и Posts. Может кто еще что подскажет? (Надеюсь, я понятно выразился)
Октябрь 9th, 2008 at 10:54
По рекомендации разработчиков CakePHP, в случае если для связи нужно хранить дополнительные параметры, то надо просто создавать для таблицы связей собственную модель (как вы и сделали в третьем случае).
Ноябрь 15th, 2008 at 02:36
[...] Как обычно, начал с поиска уже готовых решений и в блоге “Программируем на CakePHP” нашел код нужных