CakePHP: HABTM – podstawy relacji hasAndBelongsToMany

1. Wprowadzenie

W artykule tym opiszę wszystkie istotne informacje dotyczące relacji HABTM(hasAndBelongsToMany). Na początku podam przykład takiej relacji wzięty z tej strony. Mamy artykuły i kategorie; artykuł może być przypisany do wielu kategorii, a kategoria może posiadać wiele artykułów. To jest istotą relacji HABTM. Aby utworzyć tą relację konieczne jest stworzenie w bazie danych, tabeli łączącej artykuły z kategoriami. Definicja w kodzie wygląda następująco:

<?php
//definicja modelu artykułów
class Article extends AppModel {
     var $name = 'Article';
     var $hasAndBelongsToMany = array('Category');//definicja relacji HABTM
}
<?php
//definicja modelu kategorii
class Category extends AppModel {
     var $name = 'Category';
     var $hasAndBelongsToMany = array('Article');//definicja relacji HABTM
}

W powyższym kodzie nie zdefiniowałem tabeli łączącej obydwa modele, jest spowodowane tym, że CakePHP domyślnie przyjmuje nazwę wziętą z konwencji; w tym wypadku articles_categories. Dodatkowo zostanie utworzony automodel ArticlesCategory, który umożliwi dostęp bezpośrednio do danych zawartych w tabeli łączącej. Należy jednak pamiętać o tym, że utworzony automodel nie jest powiązany ani z artykułem, ani z kategorią. Powiązanie takie można wywołać ręcznie:

//utworzenie relacji naszego automodelu do kategorii
$this->Article->ArticlesCategory->bindModel(array(‘belongsTo’=>array(‘Category’)));

2. Modyfikacja relacji

Co jednak jeśli nazwa automodelu nam nie pasuje? Albo chcemy użyć innej nazwy tabeli łączącej? Nazwe te możemy dowolnie modyfikować, należy jednak pamiętać o tym aby zmiany wprowadzić w obu modelach. Wprowadzenie ich tylko w jednym modelu będzie skutkowało dużym bałaganem i niespodziewanym zachowaniem aplikacji.

<?php //definicja modelu artykułów class Article extends AppModel { var $name = 'Article'; var $hasAndBelongsToMany = array( 'Category'=>array(
               'with'=>'ArticlesCategories' //nowa nazwa automodelu
               ,'joinTable'=>'articles_categories_joined' //nowa nazwa tabeli łączącej
          )
     );//definicja relacji HABTM
}
<?php //definicja modelu kategorii class Category extends AppModel { var $name = 'Category'; var $hasAndBelongsToMany = array( 'Article'=>array(
               'with'=>'ArticlesCategories' //nowa nazwa automodelu
               ,'joinTable'=>'articles_categories_joined' //nowa nazwa tabeli łączącej
          )
     );//definicja relacji HABTM
}

Po zastosowaniu podanego wyżej kodu nasz automodel będzie się nazywał ArticlesCategories a tabela łącząca articles_categories_joined.

 

3. Analiza relacji

Wrócmy jednak do nazewnictwa proponowanego przez konwencję CakePHP. Jeśli chcemy wejść w szczegóły relacji HABTM, jest to nic innego niż 2 relacje hasMany z zastosowaniem modelu pośredniego. Podaję przykład jak utworzyć relację zachowującą się podobnie jak HABTM:

//definicja modelu artykułów
class Article extends AppModel {
     var $name = 'Article';
     var $hasMany = array('ArticleCategory');
}
 
 
//definicja modelu kategorii
class Category extends AppModel {
     var $name = 'Category';
     var $hasMany = array('ArticleCategory');
}
 
//definicja modelu łączącego
class ArticleCategory extends AppModel {
     var $name = 'ArticleCategory';
     var $belongsTo = array('Article','Category');
}

Jak widać wymaga to zdefiniowania dodatkowego modelu, pełniącego rolę łącznika. Nie polecam definiowania relacji w ten sposób, przykład ten został podany jedynie w celu zrozumienia zasady działania relacji HABTM.

 

4. Tworzenie i usuwanie relacji

Relacja HABTM jest dość specyficzną relacją. Aby przypisać artykuł o id=1 do kategorii o id=1 wywołujemy:

$this->Article->save(array(
    'Article' => array(
        'id' => 1,
    ),
    'Category' => array(
        'Category' => array(1),
    ),
)); 

Jeżeli chcemy przypisać nasz artykuł dodatkowo do kategorii 2 może skorzystać z kodu:

$this->Article->save(array(
    'Article' => array(
        'id' => 1,
    ),
    'Category' => array(
        'Category' => array(2),
    ),
));

Spowoduje to jednak usunięcie relacji z kategoria o id=1. Należy zatem pamietać, że przy modyfikacji relacji HABTM CakePHP najpierw kasuje wszystkie istniejące relacje. Poprawny kod wygląda następująco:

$this->Article->save(array(
    'Article' => array(
        'id' => 1,
    ),
    'Category' => array(
        'Category' => array(1,2),
    ),
));

Jak można się już domyślić, jeśli chcemy aby nasz artykuł nie należał do żadnej kategorii wywołujemy:

$this->Article->save(array(
    'Article' => array(
        'id' => 1,
    ),
    'Category' => array(
        'Category' => array(),
    ),
));

Aby zmienić to zachowanie można skorzystać z add-delete-habtm-behavior. Osobiście nie korzystałem z tego dodatku, ale w niektórych przypadkach może on okazać sie użytyczny.

 

5. Więcej informacji

Cookbook o HABTM

W przyszłości planuję opisać sposób filtrowania danych objętych relacją HABTM.

MySQL: Reorganizacja kluczy głównych tabeli

Krótki post o tym jak należy postępować gdy chcemy dokonać reogranizacji kluczy głównych tabeli. Tyczy się to tylko tabel, które mają indeks główny numerowany automatycznie. Przykładowo mamy tabelę w której istnieją wpisy dla kluczy głównych: 1,3,5,10, a chcemy ją tak przenumerować, żeby te klucze przyjmowały wartości: 1,2,3,4. Rozwiązanie sprowadza się do 3 zapytań SQL:

ALTER TABLE `users` DROP `id`;
ALTER TABLE `users` AUTO_INCREMENT = 1;
ALTER TABLE `users` ADD `id` int UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST;

Oczywiście rozwiązanie posiada jedną ogromną wadę: nadaje się do zastosowania tylko tam, gdzie nie mamy relacji z tabelą którą modyfikujemy. Na szczęscie mój obiekt testowy spełniał to założenie.