3. Tworzenie schematu bazy danych

Doctrine wykorzystuje pliki YAML do tworzenia i zarządzania schematem bazy danych. Składają się na niego dwa elementy:

  1. Struktura
  2. Początkowe dane (tzw. fixtures), które są wgrywane wraz ze strukturą.

My zajmiemy się jedynie pierwszym elementem, jako że do drugiego nie bardzo mamy co wgrywać. Nasza baza będzie bardzo prosta, a w jej skład wejdą tylko dwie tabele: na zdjęcia oraz na komentarze. Utwórzmy plik /doctrine/schema/schema.yml i na jego początku umieśćmy następujący tekst:

---
options:
    type: InnoDB
    collate: utf8_polish_ci
    charset: utf8

Są to opcje dotyczące tabel i zależą od wyboru konkretnego silnika bazodanowego. Artykuł ten pisany jest z myślą o bazie MySQL, stąd ustaliliśmy, iż żądamy tabel w formacie InnoDB oraz obsługi unikodu.

Jak widać, składnia YAML jest bardzo czytelna dla człowieka, a co ważniejsze, jest konwertowana bezpośrednio do tablic PHP, w przeciwieństwie np. do XML. Wadą są niestety dość powolne parsery, ale Doctrine korzysta z nich jedynie od czasu do czasu, na nasze żądanie. Niezmiernie istotne jest, aby nie używać tabulacji, lecz wcięcia robić stałą, określoną ilością spacji – ja przyjąłem cztery. Dodajmy teraz definicję pierwszej tabeli, przechowującej informacje o zdjęciach:

Photo:
    tableName: photos
    columns:
        id:
            type: integer(4)
            primary: true
            notnull: true
            autoincrement: true
        title:
            type: string(50)
            notnull: true
        filename:
            type: string(50)
            notnull: true
        date:
            type: integer(4)
            notnull: true
    listeners: [PhotoListener]
    relations:
        Comments:
            class: Comment
            local: id
            foreign: photo_id
            onUpdate: CASCADE
            onDelete: CASCADE
            type: many
            foreignType: one
            foreignAlias: Photo

Pierwszy identyfikator jest nazwą, jaką będziemy używać do odnoszenia się do danej tabeli w Doctrine. U nas będzie to Photo. Możemy również określić, jaka nazwa ma być faktycznie używana po stronie bazy danych. W sekcji columns umieszczamy informacje o wszystkich kolumnach. Jeśli chcemy jedynie zdefiniować typ kolumny, możemy użyć skróconej formy zapisu:

pole1: typ
pole2: typ
pole3: typ(długość)

Doctrine posiada własny system typów, które są automatycznie konwertowane na typy charakterystyczne dla wybranego silnika bazodanowego. My korzystamy z dwóch standardowych: integer oraz string, w obydwu podajemy ich długość w bajtach. Doctrine musi również wiedzieć, jakich relacji między tabelami będziemy używać. Służy do tego sekcja relations. Każdej relacji w obrębie tabeli musimy nadać unikalną nazwę, poprzez którą będziemy mogli się do niej odnosić w DQL-u. Następnie określamy jej atrybuty:

  1. class – nazwa tabeli (klasy) PHP, do której tworzymy relację.
  2. local – nazwa pola w aktualnej tabeli, które ma być użyte.
  3. foregin – nazwa pola w drugiej z tabel, które ma być użyte
  4. onUpdate, onDelete – zdefiniowanie akcji do wykonania na powiązanych relacją wierszach obcej tabeli w przypadku modyfikacji (usunięcia) wiersza określającego dane zdjęcie. Opcja CASCADE jest jedną ze standardowych akcji silników bazodanowych, powoduje ona automatyczną modyfikację (usunięcie) wszystkich powiązanych wierszy.
  5. type – kierunek relacji Photo -> Comment (wiele komentarzy)
  6. foreignType – określamy kierunek relacji Comment -> Photo (jedno zdjęcie dla komentarza)
  7. foreignAlias – nazwa, pod jaką będzie widziany model Photo z punktu widzenia komentarzy.

Mamy tutaj jeszcze jedną sekcję, listeners, jednak zajmiemy się nią nieco później.

Tabela, w której będziemy przechowywać komentarze, zbudowana jest następująco:

Comment:
    tableName: comments
    columns:
        id:
            type: integer(4)
            primary: true
            notnull: true
            autoincrement: true
        author:
            type: string(50)
            notnull: true
        date:
            type: integer(4)
            notnull: true
        content:
            type: string(4000)
            notnull: true
        photo_id:
            type: integer(4)
            notnull: true
    listeners: [CommentListener]
    indexes:
        Photo_Index:
            fields: [photo_id]

Struktura jest tu podobna do poprzedniej tabeli, lecz pojawiła się nam nowa sekcja: indexes. Służy ona do definiowania indeksów, jakie będą użyte w tabelach. Analogicznie, każdemu indeksowi musimy nadać unikalną nazwę oraz zdefiniować listę kolumn, na które zostanie on nałożony. Służy do tego nawias kwadratowy, a poszczególne nazwy wymieniamy po przecinku.