sobota, 21 września 2013

Internacjonalizacja w CakePHP - ciąg dalszy rozbudowy

W poprzednim poście opisującym internacjonalizacje w Cake'u mogłoby się wydawać, że wyczerpałem temat. Jednak podczas używania Translate Behavior występują inne problemy, które należy rozwiązać - niestety samodzielnie.

Pierwszy z problemów, który spotyka nas używając Translate Behavior jest brak możliwości zwracania treści tłumaczonych dynamicznie z modeli z którymi mamy relacje (belongsTo, hasOne, hasMany i hasAndBelongsToMany). CakePHP nie posiada takiego udogodnienia i podczas zwracania treści przez modele które występują w relacjach otrzymamy wynik bez treści tych pól. Jak zatem rozwiązać problem, aby relacyjne modele zwracały także treść tłumaczoną?
Najprostszym rozwiązaniem jest opracowanie metody, która nam te treści wyszuka i doda do wyniku.

AppModel.php (app/Model/):
public function afterFind($results, $primary = false) {
  if (!empty($this->belongsTo)) {
   foreach ($this->belongsTo as $model => $settings) {
    if (isset($this->{$model}->actsAs['Translate'])) {
     if (!empty($results)) {
      foreach ($results as $row => $result) {
       if (!empty($result[$model])) {
        $supplement = $this->{$model}->find('first', array(
         'conditions' => array(
          $model . '.' . $this->{$model}->primaryKey => $result[$model][$this->{$model}->primaryKey]
         ),
         'fields' => $this->{$model}->actsAs['Translate'],
         'recursive' => -1
        ));

        if (!empty($supplement)) {
         $results[$row][$model] = array_merge($result[$model], $supplement[$model]);
        }
       }
      }
     }
    }
   }
  }
  
  if (!empty($this->hasMany)) {
   foreach ($this->hasMany as $model => $settings) {
    if (isset($this->{$model}->actsAs['Translate'])) {
     foreach ($results as $row => $result) {
      if (!empty($results[$row][$model])) {
       $ids = array();

       foreach ($results[$row][$model] as $result) {
        $ids[] = $result[$this->{$model}->primaryKey];
       }

       if (!empty($ids)) {
        $this->{$model}->actsAs['Translate'][] = $this->{$model}->primaryKey;

        $supplements = $this->{$model}->find('all', array(
         'conditions' => array(
          $model . '.' . $this->{$model}->primaryKey => $ids
         ),
         'fields' => $this->{$model}->actsAs['Translate'],
         'recursive' => -1
        ));

        if (!empty($supplements)) {
         foreach ($results[$row][$model] as $order => $result) {
          foreach ($supplements as $supplement) {
           if ($supplement[$model][$this->{$model}->primaryKey] == $result[$this->{$model}->primaryKey]) {
            $results[$row][$model][$order] = array_merge($result, $supplement[$model]);
           }
          }
         }
        }
       }
      }
     }
    }
   }
  }
  
 return $results;
}
Wykorzystałem tutaj metodę afterFind();, która wywoływana jest zaraz po zwróceniu wyniku poprzez standardowe zapytania z Cake'u. Powyższy kod działa przy relacjach belongsTo i hasMany.
W pierwszym fragmencie sprawdzamy czy nasz model posiada relacje, a następnie poprzez pętle sprawdzamy jaki to model i jakie posiada pola przeznaczone do tłumaczeń. Teraz musimy stworzyć zapytanie, które wyszuka nam treść przetłumaczoną tylko i wyłącznie zawierając te pola, które potrzebujemy oraz dodatkowo identyfikator - klucz rekordu (nie potrzebujemy żadnej innej treści ze względu, na to iż są one zawarte już w zmiennej $results, gdzie zostały pobrane w standardowej procedurze). Następnie wyszukaną treść ($supplements) łączymy z istniejącym rezultatem ($results) poprzez funkcje array_merge();. Od teraz posiadamy już cały wynik z przetłumaczonymi modelami i ich relacjami.

Kolejny problem jaki może nas spotkać przy używaniu tłumaczeń dynamicznych w Cake'u jest chęć dodania nowego pola w bazie danych aby został i był traktowany jako dynamiczna tłumaczona treść. Załóżmy, że posiadamy już bazę danych, który zawiera sporą cześć treści, w tym wypełnioną po brzegi tabelę i18n. Posiadając dowolny model oraz zdeklarowane już pola do tłumaczeń, dopiszemy nowe pole location (dla przykładu nazwa miasta w danym języku: Warszawa/Warsaw), które posiadamy w naszym modelu:
public $actsAs = array(
 'Translate' => array(
  'name',
  'slug',
  'location'));
Niestety, przy istniejącej tabeli i18n z przetłumaczonymi polami name i slug, Cake zacznie zwracać puste wyniki lub co gorsze wywali nam błędy. Spowodowane jest to, iż w tabeli i18n będzie szukał rekordów które zawierają pole name, slug i location. Niestety w tej tabeli nie istnieją jeszcze rekordy z polem location. Co teraz? Należy stworzyć zapytanie SQL dla języka domyślnego i wywołać je w naszej bazie (polecam zrobić backup bazy w celu bezpieczeństwa).

Zapytanie SQL:
INSERT INTO i18n (locale, model, foreign_key, field, content) SELECT  'pl', MODEL, TABELA.id, 'location', TABELA.location FROM TABELA
Powyższe zapytanie w najprostrzym tłumaczeniu kopiuje nam dane z naszego modelu do tabeli i18n. Należy podmienić słowa MODEL na nazwę naszego modeu (np. Project) oraz słowo TABELA na nazwę tabeli przypisanej do naszego modelu (np. projects). Szczególną uwagę należy zwrócić na stosowane cudzysłowy, tzn. że 'location' nie jest tym samym co location. W cudzysłowach wprowadzamy nazwę pola, czyli location, natomiast TABELA.location ma za zadanie wstawić treść dla pola location z naszego modelu.

W następnym poście o internacjonalizacji w Cake'u pokaże jak rozwiązać dwa kolejne problemy podczas używania Translate Behavior, będą one tyczyły się zwracania wyników przy relacjach hasOne oraz hasAndBelongsToMany.

1 komentarz:

  1. Raczej nie powinno się robić takich dużych zagłebień, kod jest bardzo nieczytelny.

    OdpowiedzUsuń