Jakiś czas temu rozpoczynałem kolejny projekt oparty o framework symfony. Do tej pory tworzenie modeli na podstawie klas wygenerowanych z opisu w pliku schema.yml było najprzyjemniejszą częścią pracy. Okazało się jednak, że nie wszystko wygląda tak różowo, jakby mogło się na pierwszy rzut oka zdawać. Mechanizm przetwarzający ma problem z pewnymi nazwami, który to problem ujawnia się pod postacią dosyć nieciekawego, tytułowego błędu. Zapraszam do lektury.

Fotografia: stephenyeargin, CC-BY-SA.

symfony: Błąd “When using the attribute ATTR_AUTO_ACCESSOR_OVERRIDE you cannot use the field name”.

Zdecydowana większość wpisów na tym blogu zawiera ostateczne rozwiązania różnego rodzaju mniej lub bardziej poważnych problemów. W tym wpisie jednak zaprezentuję wyłącznie “obejście”, ponieważ nie udało mi się znaleźć niczego lepszego na ten temat. Jeśli trafię na faktyczne rozwiązanie, oczywiście zaktualizuję niniejszy wpis. Znalazłem dobry sposób, zapraszam do zapoznania się z “porządnym” rozwiązaniem. Przy okazji, zapraszam do podzielenia się własnymi spostrzeżeniami, być może coś istotnego mnie ominęło. ;]

Weźmy pod uwagę przykładowy model zawierający problematyczne pole:

CustomModel:
  tableName: customModels
  columns:
    id:
      type: integer(4)
      primary: true
      notnull: true
      autoincrement: true
    name:
      type: string(255)
    data:
      type: string(255)

Jeśli wykorzystując konsolowe narzędzie symfony wykonamy polecenie:

./symfony doctrine:build –all-classes
To generator w momencie, kiedy natrafi na ten model, szybko wyrzuci na ekran komunikat:
When using the attribute ATTR_AUTO_ACCESSOR_OVERRIDE you cannot use the field name “data”.
Nie udało mi się ustalić, do czego dokładnie służy rzeczony atrybut, ale na podstawie lektury kilku stron w Internecie podejrzewam, że ma to związek z dynamicznym mapowaniem wywołań metod setX() / getX na odpowiednie pola modelu, gdzie X oczywiście oznacza nazwę konkretnego pola, np. setId(), getId().

Cały problem polega na tym, że niektóre z tych akcesorów kolidują z istniejącymi funkcjami klasy, przez co nasza dowolność w nazewnictwie pól nie jest aż tak nieograniczona jak moglibyśmy chcieć. ;]

Jedynym rozwiązaniem tego problemu, jakie do tej pory znalazłem, to czasowe wyłączenie wymienionego wyżej ustawienia na czas generowania modeli. Tutaj pojawia się jednak kolejna “kłoda pod nogi” - skoro wykonujemy konsolowe polecenie generatora, to nie mamy dostępu do kodu, a nie ma możliwości uruchomienia go z pewnego rodzaju “przełącznikiem”, który ustawiałby odpowiednie wartości zmiennych.

Próbowałem trochę “bić się” z frameworkiem, ale bez większego skutku, dlatego trochę nieśmiało, ale jednak sugeruję rozwiązanie ręczne. Plik do zmiany to:

sfDoctrinePluginConfiguration.class.php
Znajduje się on w drzewie:
/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/config/
Linijki 46-51 tego pliku zawierają kilka ustawień:

$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine_Core::ATTR_EXPORT, Doctrine_Core::EXPORT_ALL);
$manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_NONE);
$manager->setAttribute(Doctrine_Core::ATTR_RECURSIVE_MERGE_FIXTURES, true);
$manager->setAttribute(Doctrine_Core::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
$manager->setAttribute(Doctrine_Core::ATTR_AUTOLOAD_TABLE_CLASSES, true);

Zmieniamy interesującą nas wartość [linia 50] na false, uruchamiamy polecenie, a po jego wykonaniu przestawiamy ponownie na true.

Trochę wstyd programiście stosować takie “hacki”, ale czasem niestety nie ma innej opcji, a projekt sam się nie zrobi, także uprzejmie proszę o wybaczenie. Ważne, że w kodzie końcowym wszystko jest robione tak, jak trzeba. ;]

* AKTUALIZACJA 17.09.2011 *

Jeden z komentujących, Damian Zientalak [dzięki!], zasugerował użycie metody configureDoctrine() do wyłączenia niepotrzebnej opcji. Nie wziąłem tego wcześniej pod uwagę, aczkolwiek trafiła się chwila wolnego czasu, którego efektem jest poniższy kod, jaki należy umieścić w pliku klasy ProjectConfiguration w katalogu config:

    public function configureDoctrine(Doctrine_Manager $manager)
        {
        $isCli = (php_sapi_name() == "cli");
        if(true == $isCli)
            {
            $manager->setAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, false);
            }
        }

Po zapisaniu pliku nie będziemy mieli już żadnych problemów - skrypt wykrywa kontekst, w jakim się znajduje [w tym przypadku jest to kontekst CLI - konsoli] i jeśli znajdujemy się w “czarnym okienku”, to problematyczna opcja zostaje po prostu wyłączona. W końcu mogę spać spokojnie. ;]