Witajcie! Ze względu na to, że ostatnio mam coraz więcej wspólnego z Symfony2, możecie spodziewać się nieco więcej wpisów o tym frameworku na niniejszym blogu. Założyłem już kategorię “Symfony2”, a znajduje się w niej raptem jeden wpis, także czas nadrobić to niedociągnięcie. Dzisiaj chciałbym Wam pokazać jedno fajne usprawnienie, które pozwoli nam na lepszą kontrolę importowania danych mapowania encji z bazy danych do poszczególnych plików wybranego formatu. Zapraszam do lektury.
Fotografia: kopeckyk @ Fotolia.
Symfony2: Importowanie danych mapowania encji z filtrowaniem wyrażeniami regularnymi.
Tytuł dzisiejszego wpisu brzmi co najmniej jak nagłówek jakiegoś opracowania naukowego, ale spokojnie, nie bójcie się - wszystko będzie wytłumaczone jasno i prosto, jak to tylko możliwe. Znajomi z reguły kwitują tego typu stwierdzenia uniesieniem brwi, ponieważ potrafię z najbardziej błahego powodu zrobić kilkugodzinny wykład (szczególnie w temacie, który mnie interesuje :)), ale słowo pisane ma to do siebie, że widać, ile tekstu jest na ekranie, a więc będę się kontrolował. Tyle w kwestii wstępu, czas przejść “do rzeczy”.Jak wiadomo, Symfony2 oferuje nam bardzo wygodne narzędzie konsolowe dostępne (w konsoli :)) poprzez polecenie:
./app/console
wykonane w katalogu głównym projektu. Mamy tam wiele funkcji, jedną z nich jest interesująca nas dzisiaj
doctrine:mapping:importktóra pozwala na zaimportowanie danych poszczególnych encji do plików konfiguracyjnych używając jednego z obsługiwanych formatów. Ja preferuję pliki YAML, więc wszystkie polecenia będą się opierały na tym właśnie formacie - zwolenników XMLa i adnotacji proszę o “niewyzłośliwianie” się w komentarzach. :)
Przykładowy zapis tego polecenia wygląda następująco:
./app/console doctrine:mapping:import ThunderSampleBundle yml
Zaimportuje ono wszystkie dane tabel do encji o takich samych nazwach. Nie będę podawał przykładów wygenerowanych plików, bo wtedy ten wpis rozciągnąłby się niemiłosiernie - polecam spróbowanie samemu, jak to wszystko działa, a działa naprawdę genialnie. Warto jedynie nadmienić, że plik definicji encji zawiera pełną nazwę klasy encji, a więc także jej namespace. Dla tabeli “User” wyglądałoby to następująco:
Thunder\Bundle\SampleBundle\Entity\User
Problem.
W moim przypadku sytuacja wyglądała następująco: w MySQL Workbenchu przygotowałem sobie schemat bazy danych całego projektu i następnie chciałem zaimportować te dane jako encje do projektu Symfony2. Okazało się jednak, że poszczególne bundle (jeden bundel… dwa bundle? przyjmijmy, że to słowo przyjęło się w środowisku :)) będą używały poszczególnych tabel wybiórczo, a więc trzeba je odpowiednio posegregować.Tutaj z pomocą przychodzi modyfikator “–filter” opisanej wyżej opcji importu, który potrafi przyjąć nazwy encji, jakie mają zostać umieszczone w danym bundlu (M. ten bundel, D. tego bundla, C. tym bundlu). Można go użyć wiele razy w jednym poleceniu, żeby przekazać różne nazwy:
./app/console doctrine:mapping:import ThunderSampleBundle yml --force --filter="Category" --filter="User"
Problem polega na tym, że (jak się okaże w dalszej części artykułu) na nazwach tych jest wykonywana bardzo prosta funkcja strpos(), a więc przechodzą wszystkie encje, jakie zawierają w sobie wartość parametru filter - w tym wypadku do bundla dostanie się np. encja Users, UserFriends, ItemCategories, itd.
Rozwiązanie.
Ze względu na fakt, że takie “nadmiarowe” umieszczanie encji w bundlach oraz późniejsze kasowanie niepotrzebnych plików jest mało zabawne, stwierdziłem, że “coś” z tym trzeba zrobić. Rozwiązanie pojawiło mi się automatycznie przed oczami - “a co, jeśli dałoby się filtrować po wyrażeniu regularnym?”. Znalazłem w kodzie Symfony2 klasę odpowiedzialną za komendę importowania danych encji:Symfony\Bundle\DoctrineBundle\Command\ImportMappingDoctrineCommand“Po nitce do kłębka” doszedłem do klasy:
Doctrine\ORM\Tools\Console\MetadataFiltergdzie znalazłem metodę accept():
public function accept()
{
if (count($this->_filter) == 0) {
return true;
}
$it = $this->getInnerIterator();
$metadata = $it->current();
foreach ($this->_filter AS $filter) {
if (strpos($metadata->name, $filter) !== false) {
return true;
}
}
return false;
}
I zgadnijcie co? Zmieniłem warunek w pętli foreach na (cała nowa linijka):
if (preg_match('/'.$filter.'/', $metadata->name)) {
i… śmiga! Biorąc pod uwagę, że jest to zmiana w rdzeniu frameworka, należałoby się zastanowić nad tym, czy nie spowoduje to błędu w innych miejscach w kodzie, ale rozważając dwa przypadki:
- wykorzystanie “po staremu” modyfikatora “–filter” wpisując samą nazwę encji,
- wykorzystanie wyrażenia regularnego,
Przykładowe wywołanie dla mojego przypadku wygląda następująco:
./app/console doctrine:mapping:import ThunderSampleBundle yml --force --filter="^Category" --filter="^User$"
W ten sposób zostaną zaimportowane jedynie encje rozpoczynające się od “Category” i pojedyncza encja “User”.
Oczywiście jeśli widzicie tutaj jakieś błędy, to proszę o wyjaśnienie, dlaczego nie mam racji - chciałbym zgłosić moją małą “kontrybucję” do repozytorium Symfony2, a nie chciałbym wyglądać jak pospolity klepacz przed samym Fabienem. :) Z góry dzięki i do zobaczenia w kolejnym wpisie!