CakePHP 3 relacje między tabelami

Tematem tej części są relacje między tabelami. Jeśli nazwiesz tabele i pola bazy danych zgodnie z konwencją CakePHP, ustalenie relacji między nimi będzie banalnie proste. Zacznijmy od przykładu. Nasza aplikacja zawiera użytkowników w tabeli users. Każdy użytkownik w naszej aplikacji może mieć wiele numerów telefonów i chociaż możemy mieć wiele pól numeru telefonu w tabeli użytkowników, nie jest to baza nie jest wtedy znormalizowana, ani nie jest to efektywne przechowywanie danych. Stworzymy więc tabelę phone_numbers, aby przechowywać numery telefonów.

Przypominamy tworzenie tabeli users.
CREATE TABLE `users` (
  `id` bigint(11) NOT NULL,
  `username` varchar(15) COLLATE utf8mb4_unicode_ci NOT NULL,
  `password` varchar(61) COLLATE utf8mb4_unicode_ci NOT NULL,
  `email` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  `first_name` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL,
  `last_name` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL,
  `role` varchar(8) COLLATE utf8mb4_unicode_ci NOT NULL,
  `passkey` varchar(13) COLLATE utf8mb4_unicode_ci NOT NULL,
  `timeout` timestamp NULL DEFAULT NULL,
  `modified` datetime NOT NULL,
  `created` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Teraz czas na tabelę phone_numbers.
CREATE TABLE `phone_numbers` (
  `id` bigint(11) NOT NULL,
  `user_id` bigint(11) NOT NULL,
  `number` varchar(14) COLLATE utf8mb4_unicode_ci NOT NULL,
  `type` varchar(1) COLLATE utf8mb4_unicode_ci NOT NULL
  `modified` datetime NOT NULL,
  `created` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Zgodnie z konwencją CakePHP nazwy tabel są w liczbie mnogiej (przechowują wiele rekordów, więc podczas gdy jeden rekord jest phone number, wiele rekordów to phone numbers). Użyj podkreślenia "_" zamiast spacji między wyrazami. Tak więc nasza tabela numerów telefonów to phone_numbers. Jeśli używasz nazw, które nie są zgodne z konwencją CakePHP musisz ręcznie identyfikować wszystko. Inną konwencją używaną do oszczędzania czasu i energii jest identyfikacja związków z polem wiążącym tabele. Każdy użytkownik może "mieć wiele" numerów telefonów, każdy numer telefonu "należy do użytkownika". Jest to zwykle określane jako relacja jeden do wielu. Aby zidentyfikować użytkownika, do którego należy każdy numer telefonu, zapisy numeru telefonu będą zawierały klucz obcy zawierający unikatowy identyfikator lub identyfikator użytkownika. To pole powinno być nazwane po tabeli, a następnie dodajemy do niego string "_id". Ponownie, nie jest to wymagane, ale po prostu ułatwia pracę. Zajmiemy się teraz ustawieniem powiązań modeli. Moglibyśmy teraz łatwo wypiec modele, kontrolery i widoki z konsoli (cake bake), a CakePHP automatycznie ustawiałby wszystko, łącznie z powiązaniami. Ale wtedy nic się nie nauczymy, więc zróbmy to ręcznie. Dla użytkownika możemy użyć modelu, kontrolerów i widoków, które utworzyliśmy wcześniej. Dla tabeli phone_numbers będą prawie identyczne, dopóki nie dojdziemy do konfigurowania powiązań. W naszej tabeli numerów telefonu (/src/Model/Tables/PhoneNumbersTable.php) chcemy zidentyfikować relację belongsTo z tabelą użytkowników, co czyni kod poniżej.
public function initilize(array $config)
{
    ...
    $this->belongsTo('Users');
}
Jeśli nie masz lub nie możesz ustawić swojego klucza obcego w tabeli phone_numbers na domyślną wartość user_id, możesz ją jawnie zadeklarować.
public function initilize(array $config)
{
    ...
    $this->belongsTo('Users', [
        'foreignKey' => 'not_user_id',
    ]);
}
oraz do modelu użytkownika (src/Model/Table/UsersTable.php), dodaj relację hasMany.
public function initilize(array $config)
{
    ...
    $this->hasMany('PhoneNumbers', [
        'foreignKey' => 'not_user_id', // unnecessary if user_id
    ]);
}
Zajmiemy się teraz tworzeniem reguł walidacji. Podczas edytowania pliku /src/Model/Table/PhoneNumbersTable.php warto dodać regułę walidacji, aby potwierdzić, że identyfikator dodany do pola user_id został wprowadzony i rzeczywiście istnieje w tabeli.
public function validationDefault(Validator $validator)
{
    ...
    $validator
        ->requirePresence('user_id')
        ->notEmpty('user_id');
    return $validator
}
...
public function buildRules(RulesChecker $rules)
{
    ...
    $rules->add($rules->existsIn(['user_id'], 'Users'));
    return $rules;
}
Teraz, gdy otrzymujemy nasze dane do wyświetlenia, musimy upewnić się, że nasze zapytanie SQL zawiera powiązane dane z tabeli numerów telefonów. W /src/Controller/UsersController.php zaktualizuje funkcję view:
public function view($id = null)
{
    $user = $this->Users->get($id, [
        'contain' => ['PhoneNumbers']
    ]);
    ...
}
Teraz, po załadowaniu rekordu użytkownika, załaduje on również powiązane numery telefonów. Zrób to samo dla kontrolera numerów telefonów, jeśli chcesz wyświetlać dane użytkownika. Można już teraz wyświetlać załadowane informacje w widoku. Zaktualizujemy nasz widok (/src/Template/Users/view.ctp) tak by zawierał numery telefonów.
...
<div class="related">
    <h4><?php echo __('Phone Numbers'); ?>
    <?php if (!empty($user->phone_numbers)): ?>
    <table>
        <tr>
            <th><?php echo __('Type'); ?></th>
            <th><?php echo __('Number'); ?></th>
            <th class="actions"><?php echo __('Actions'); ?></th>
        </tr> 
        <?php foreach ($user->phone_numbers as $number): ?>
        <tr>
            <td><?php echo $number->type; ?>
            <td><?php echo $number->number; ?>
            <td class="actions">
                <?php echo $this->Html->link(__('Edit'), ['controller' => 'PhoneNumbers', $number->id]); ?>
            </td>
        </tr>
        <?php endforeach; ?>
    </table>
    <?php endif; ?>
</div>