Prowadzenie blogu ma, a przynajmniej powinno mieć na celu niesienie pomocy Czytelnikom. Czasem jednak trzeba też pomóc sobie – autorowi. Najlepiej jest wtedy, kiedy przy okazji spełniania tego pierwszego wymagania, spełnia się przy okazji to drugie. Ze względu na to, że tworzenie wpisów pomaga mi w uporządkowaniu wielu informacji kołaczących się nieskładnie w głowie, czasem będę się dzielił z Wami krótkimi wpisami o problemach, których rozwiązań często zapominam. Jednym z takich problemów jest właśnie tytułowe generowanie adresu URL w symfony, pochodzącego z routingu projektu… w kontrolerze.
Fotografia: Andreia, CC-BY.
symfony: Generowanie adresu URL route’a w kontrolerze modułu.
Kiedy szukałem rozwiązania tego problemu po raz pierwszy, podobnie jak we wtorkowym wpisie o pobieraniu flashów, niestety nie udało mi się trafić na gotowy „jednolinijkowiec” – kawałek kodu, który by wszystko bezproblemowo obsłużył. Stąd też pomysł na podzielenie się tym, co udało mi się uzyskać.
W czym problem, zapytacie? Otóż, jeśli mieliście okazję kiedyś realizować jakikolwiek projekt z użyciem frameworka symfony w wersji 1.x, na pewno trafiliście na funkcję – helper url_for(). Przyjmuje ona za parametr string zawierający nazwę żądanej „ścieżki” – route’a oraz jej parametry zapisane w formie query stringa. Biorąc pod uwagę w miarę prosty przykład:
showPage:
url: /page/:id
param:
module: page
action: show requirements:
id: \d+
Następujący kod:
echo url_for('@showPage?id=5');
Wygeneruje nam odpowiedni link:
/page/5
Prawda, że proste? To wszystko jednak dzieje się w widoku, a więc w miejscu, w którym generowanie linków jest w pewnym sensie „naturalne”. Co jednak, jeśli potrzebowalibyśmy wygenerować taki link w kontrolerze?
W tym momencie oczywiście otwiera się dyskusja na temat sensowności takiej operacji. Na potrzeby artykułu załóżmy, że tworzymy funkcjonalność rejestracji konta użytkownika. Po zapisaniu danych formularza do bazy danych chcielibyśmy wysłać mu maila z linkiem zawierającym kod aktywacyjny dla jego profilu. Nie chcemy renderować całego szablonu emaila – wystarczy nam kawałek tekstu zapisanego np. w formie HEREDOCa:
$mailContents = <<<EOF
Witaj!
Aktywuj konto klikając w link:
{$activateUrl}
Pozdrawiamy.
EOF;
Załóżmy też, że dla ułatwienia sobie życia zdefiniowaliśmy route’a:
activateAccount:
url: /user/activate/:code
param:
module: user
action: activate
do którego wystarczy podać kod aktywacyjny celem otrzymania gotowego odnośnika aktywacyjnego. Proste? Teoretycznie.
Na jednym z forów internetowych znalazłem odpowiedź na moje pytanie. Bynajmniej nie było ono intuicyjne, czyli nie należało do kategorii „doświadczenie” – zdecydowanie była to „wiedza”. ;] Aby dostać się do funkcjonalności generowania URLa w kontrolerze, należy użyć następującego fragmentu kodu:
$activateUrl = $this->getController()->genUrl('@activateAccount?code='.$user->activationCode, true);
Parametr true oznacza, że link ma być „bezwzględny”, a więc http:// z domeną, katalogami, itd.
Mam nadzieję, że będę teraz pamiętał ten „skrawek”, a w razie czego będę mógł wrócić do tego wpisu i przypomnieć sobie, „jak to wszystko leciało”. ;]
Warto przeczytać.
Trwa ładowanie…
Nieco szybciej będzie: $this->generateUrl(‘@route_name’) :)
Tego nikt nigdzie nie napisał. ;] Niby trywialna rzecz, a doprosić się o informację jest straaasznie ciężko.
symfony- Generowanie adresu URL route’a w kontrolerze modułu
Ja powoli zaczynam z sf ale bardzo mi sie podobają ciekawostki które podrzucasz, oby tak dalej, pewnie kiedyś mi się przydadzą a będzie gdzie zajrzeć po stosowną wiedzę
To są naprawdę bardzo proste rzeczy, ale czasem się po prostu o nich nie wie. Widzisz, tak to jest, że ja mam zawsze problemy z takimi „pierdołami” a te zaawansowane mechanizmy wchodzą gładko. ;]
symfony- Generowanie adresu URL route’a w kontrolerze modułu
Jeżeli musiałbyś w modelu dobrać się do tego typu rzeczy, to zawsze możesz użyc
sfContext::getInstance()->getConfiguration()->loadHelpers(array(‘Url’));
W ten sposób załadujesz różne helpery, w tym np helper z funkcją url_for :)
Ja osobiście preferuję taki zapis:
$this->generateUrl(‘route_name’, array(‘param_1′ => val_1), true);
Nie był wcześniej podany więc wrzucam w ramach ciekawostki
Do takiego rozwiązania udało mi się dojść za pierwszym razem, aczkolwiek korzystanie z sfContext nie jest zbyt szczęśliwym pomysłem. Temat ten był poruszany już na wielu zagranicznych blogach / forach. Wykorzystanie
wydaje się być jak na razie najlepszą opcją rozwiązującą ten problem.
symfony- Generowanie adresu URL route’a w kontrolerze modułu
Dzięki – im więcej, tym lepiej – wpis będzie pełniejszy dla ludzi szukających rozwiązania. ;]
symfony- Generowanie adresu URL route’a w kontrolerze modułu
Pisali pisali, sam kiedyś $this->generateUrl wyczytałem z dokumentów Symfony
Ale pisanie o tych rozwiązaniach Symfony to lekkie przegięcie. Dawno nie widziałem gorszych funkcji niż url_for w tym frameworku.
Nie mówię, że nie, może moja umiejętność szukania w Google tutaj zawiodła, zapytania „symfony generate url controller”, „symfony module generate url” pewnie zwróciły, czego potrzebowałem, ale zwyczajnie to przeoczyłem.
Dlaczego według Ciebie są to „złe” funkcje? W jaki sposób Ty wygenerowałbyś adresy URL na podstawie danych z routingu? Bo dla mnie to całkiem wygodne i przyjemne rozwiązanie. Poza tym jest na tyle spójne z resztą FW, że inne mogą chyba tylko pozazdrościć.
„W tym momencie oczywiście otwiera się dyskusja na temat sensowności takiej operacji.”
Taka operacja ma bardzo dużo sensu, na przykład podczas robienia przekierowywania (forward($url), redirect($url)) po submicie formularza :)
Wiele osób potrafi jednak czasem się kłócić o to, że można to załatwić w inny, lepszy według nich sposób. Np. jeśli robimy redirect w ramach naszej aplikacji w symfony, nie musimy generować całego URLa – możemy po prostu podać nazwę routingu i parametry, a symfony skorzysta z własnego kodu przekierowującego.
Zgadzam się z tobą, że istnieje wiele sytuacji, w których taka operacja ma sens, aczkolwiek trzeba się zawsze zabezpieczyć przed potencjalnym wytknięciem „propagowania złych praktyk”. ;]
Nie trzeba osobno generować adresu url.
Wystarczy użyć:
$this->redirect(‘route_name’);
$this->forward(‘route_name’);
Dzięki za gotowy kod – powiedziałem, że da się to zrobić, a nie pokazałem jak. ;]
patrzyłeś w kod tych stworków? Po 5 minutach patrzenia w kod SF spadła reputacja kilku osób w moich oczach.
Będziemy mieli ciężke życie…
Kod url_for():
function url_for() { // for BC with 1.1 $arguments = func_get_args(); if (is_array($arguments[0]) || '@' == substr($arguments[0], 0, 1) || false !== strpos($arguments[0], '/')) { return call_user_func_array('url_for1', $arguments); } else { return call_user_func_array('url_for2', $arguments); } }Oraz url_for1() i url_for2():
function url_for2($routeName, $params = array(), $absolute = false) { $params = array_merge(array('sf_route' => $routeName), is_object($params) ? array('sf_subject' => $params) : $params); return url_for1($params, $absolute); } function url_for1($internal_uri, $absolute = false) { return sfContext::getInstance()->getController()->genUrl($internal_uri, $absolute); }Nie ma aż takiej znowu tragedii, Mateuszu. ;] Fakt, że ten kod mógłby wyglądać ładniej i np. tak „bezczelnie” nie odwoływać się do sfContext w przypadku url_for1(), aczkolwiek nie widzę tu nic takiego, żeby zaraz obniżać reputację programistom symfony.
Widać przy okazji, że lekką hipokryzją z mojej strony było zabranianie używania sfContext, skoro sam helper go wykorzystuje, chociaż tutaj mogę powiedzieć na swoją obronę tyle, że nie należy powielać złych schematów programistycznych.
Linkdump #45: Symfony2.
„Oraz url_for1() i url_for2()”
Ktoś kto nazywał te funkcje wykazał się ogromną kreatywnością ;).
Kolejne by były url_for3 … url_forN :/
PHP 5.3 – część pierwsza: przestrzenie nazw (namespace)
Kolejnych nie ma, helper url_for() wykorzystuje tylko te dwie. Aczkolwiek cały czas się zastanawiam, dlaczego ciała tych funkcji zostały wydzielone, zamiast wstawić je w odpowiednie miejsca głównej.
Linkdump #45: Symfony2.
Jedno pytanie, skoro jesteśmy już przy generowaniu adresów w Symfony.
Tomaszu, wiesz może jak wygenerować adres bezwzględny na subdomenie, aczkolwiek prowadzący do domeny głównej (bazowej)?
Wyjaśniam:
Chciałbym, aby na stronie – wizytówce użytkownika (uzytkownik.strona.pl) linki prowadziły do bazowej strony
http://www.strona.pl/regulamin, http://www.strona.pl/faq itp.
Najprostszym rozwiązaniem, jakie przychodzi mi do głowy, jest generowanie adresów względnych i doklejanie domeny bazowej. Innym rozwiązaniem jest „nadpisanie” klasy sfRequestRoute. Aczkolwiek tutaj pojawiają się problemy z żądaniami POST.
Znalazłem ciekawe rozwiązanie tutaj: http://symfony.com/blog/call-the-expert-adding-subdomain-requirements-to-routing-yml . Nie testowałem tego, aczkolwiek całość polega na tym, że piszesz własny filtr:
class sfRequestHostRoute extends sfRequestRoute { public function matchesUrl($url, $context = array()) { if(isset($this->requirements['sf_host']) && $this->requirements['sf_host'] != $context['host']) { return false; } return parent::matchesUrl($url, $context); } public function generate($params, $context = array(), $absolute = false) { $url = parent::generate($params, $context, $absolute); if(isset($this->requirements['sf_host']) && $this->requirements['sf_host'] != $context['host']) { // apply the required host $protocol = $context['is_secure'] ? 'https' : 'http'; $url = $protocol . '://' . $this->requirements['sf_host'] . $url; } return $url; } }I jeśli potem zdefiniujesz odpowiednio ścieżki w routing.yml:
# apps/*/config/routing.yml homepage_sub1: url: / param: { module: main, action: homepage1 } class: sfRequestHostRoute requirements: sf_host: sub1.example.com homepage_sub2: url: / param: { module: main, action: homepage2 } class: sfRequestHostRoute requirements: sf_host: sub2.example.comTo potem mechanizm routingu sam będzie generował odpowiednie adresy URL, w zależności od parametru sf_host. Możesz w nich zapisać takie ustawienia, żeby jedne ścieżki odpowiadały subdomenom, a inne domenie głównej.
Zobacz, czy to wystarczy, jeśli będziesz miał kolejne problemy, postaram się to rozwinąć.
Linkdump #45: Symfony2.
Z podobnego rozwiązania korzystałem wcześniej. Był jeden problem. Strony przesyłane POST linkowały do nieistniejących akcji. Wystarczyło zrobić dwa routingi dla danej akcji. Jeden podlinkowany na sudomenie i drugi „zwykły” linkowany z poziomu formulrza.
Dzięki za odzew.
Może właśnie ta subdomena była problemem, a nie sam fakt wykorzystania tego rozwiązania? Ew. może miałeś ustawione ograniczenia na zmienną sf_method, przez co żądania z innych typów nie przechodziły walidacji w routingu?
symfony: sfValidatorChoice i ciągły błąd „Invalid.”.
Przyczyną było nieustawienie sf_method. Defaultowo musiało być ograniczenie na „get”.
Wielki dzięki! :)
Jeśli nie ustawisz w ogóle sf_method, to wszystkie typy żądań powinny przechodzić bez problemu. Aczkolwiek skoro ustawiłeś je i zadziałało, to gratuluję i cieszę się, że mogłem wskazać potencjalny błąd. ;]
Linkdump #46: A. J. A. X.