Framework symfony jest na tyle złożonym tworem programistycznym, że czasami możemy po prostu „odbić się od ściany” próbując osiągnąć teoretycznie prostą rzecz. Moim zdaniem to dobrze, że wymaga on tak dużo od programisty – dzięki temu programista musi rozumieć, dlaczego i jak działają tworzone przez niego rozwiązania. W dzisiejszym wpisie chciałbym podjąć dosyć zaawansowaną kwestię związaną z modułem routingu – automatyczną zamianę adresu front-controllera backend.php [lub innego, nie będącego domyślnym] na „ładny” prefiks – np. /admin.
Fotografia: AgencePix, CC-BY-SA.
symfony: Zamiana adresu front-controllera backend.php na /admin.
Sztuka ta zostanie odegrana w trzech aktach:
- Dodanie nowego wpisu do pliku .htaccess,
- Dodanie filtra routingu w pliku factories.yml,
- Dodanie klasy filtra w aplikacji backend.
Gasną światła, kurtyna w górę, a więc do dzieła: otwieramy plik /web/.htaccess naszego projektu symfony, który domyślnie wygląda tak [lub bardzo podobnie ;]]:
Options +FollowSymLinks +ExecCGI
<IfModule mod_rewrite.c>
RewriteEngine On
# uncomment the following line, if you are having trouble
# getting no_script_name to work
RewriteBase /
# we skip all files with .something
#RewriteCond %{REQUEST_URI} \..+$
#RewriteCond %{REQUEST_URI} !\.html$
#RewriteRule .* - [L]
# we check if the .html version is here (caching)
RewriteRule ^$ index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
# no, so we redirect to our front web controller
RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
i dodajemy do niego następujące linijki:
RewriteCond %{REQUEST_URI} ^/admin/?
RewriteRule ^(.*)$ backend.php [QSA,L]
Całość wygląda następująco:
Options +FollowSymLinks +ExecCGI
<IfModule mod_rewrite.c>
RewriteEngine On
# uncomment the following line, if you are having trouble
# getting no_script_name to work
RewriteBase /
# we skip all files with .something
#RewriteCond %{REQUEST_URI} \..+$
#RewriteCond %{REQUEST_URI} !\.html$
#RewriteRule .* - [L]
# we check if the .html version is here (caching)
RewriteRule ^$ index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_URI} ^/admin/?
RewriteRule ^(.*)$ backend.php [QSA,L]
RewriteCond %{REQUEST_FILENAME} !-f
# no, so we redirect to our front web controller
RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
Spowoduje to, że wszystkie żądania rozpoczynające się od frazy „admin” będą przekazywane nie do domyślnego front-controllera index.php, ale do wybranej przez nas aplikacji. Nie musi być to backend.php – możemy w ten sposób podstawić dowolną aplikację zawartą w naszym projekcie.
Teraz czas na plik factories.yml, który wygląda nastepująco [apps/backend/config/factories.yml]:
# You can find more information about this file on the symfony website:
# http://www.symfony-project.org/reference/1_4/en/05-Factories
prod:
logger:
class: sfNoLogger
param:
level: err
loggers: ~
test:
storage:
class: sfSessionTestStorage
param:
session_path: %SF_TEST_CACHE_DIR%/sessions
response:
class: sfWebResponse
param:
send_http_headers: false
mailer:
param:
delivery_strategy: none
dev:
mailer:
param:
delivery_strategy: none
all:
view_cache_manager:
class: sfViewCacheManager
param:
cache_key_use_vary_headers: true
cache_key_use_host_name: true
Dodajemy do niego filtr routingu [w tym przypadku jego nazwa to RoutingFilter] wpisując następujące linijki:
routing:
class: RoutingFilter
param:
prefix: /admin
generate_shortest_url: true
extra_parameters_as_query_string: true
Po edycji całość prezentuje się tak:
# You can find more information about this file on the symfony website:
# http://www.symfony-project.org/reference/1_4/en/05-Factories
prod:
logger:
class: sfNoLogger
param:
level: err
loggers: ~
test:
storage:
class: sfSessionTestStorage
param:
session_path: %SF_TEST_CACHE_DIR%/sessions
response:
class: sfWebResponse
param:
send_http_headers: false
mailer:
param:
delivery_strategy: none
dev:
mailer:
param:
delivery_strategy: none
all:
routing:
class: RoutingFilter
param:
prefix: /admin
generate_shortest_url: true
extra_parameters_as_query_string: true
view_cache_manager:
class: sfViewCacheManager
param:
cache_key_use_vary_headers: true
cache_key_use_host_name: true
Ta zmiana spowoduje, że na etapie przetwarzania routingu i wyboru właściwej ścieżki zostanie wykorzystana nasza klasa RoutingFilter, która „pomoże” nieco frameworkowi skierować go na dobrą ścieżkę. Zobaczmy więc, jak wygląda kod tej klasy [/apps/backend/lib/RoutingFilter.class.php]:
<?php
class RoutingFilter extends sfPatternRouting
{
public function initialize(sfEventDispatcher $dispatcher, sfCache $cache = null, $options = array())
{
$options['context']['prefix'] = isset($options['prefix']) ? $options['prefix'] : '';
parent::initialize($dispatcher, $cache, $options);
}
public function getRouteThatMatchesUrl($url)
{
if(isset($this->options['context']['prefix']))
{
$url = substr($url, strlen($this->options['context']['prefix']));
}
if($url == '')
{
$url = '/';
}
return parent::getRouteThatMatchesUrl($url);
}
}
Jak widzimy klasa RoutingFilter dziedziczy po sfPatternRouting, co znaczy, że może być filtrem dla routingu symfony. Jeśli przeczytaliście dokładnie zmiany wprowadzone do pliku factories.yml, na pewno zauważyliście, że jedna z linijek zawierała informację:
prefix: /admin
Ten prefiks właśnie określa początek części parametrów URLa przychodzącego żądania. W metodzie initialize() dane dotyczące tego prefiksu są pobierane z pliku factories.yml [a dokładniej odczytywane z tablicy parametrów - $options], a następnie w metodzie getRouteThatMatchesUrl() [nie ma to, jak opisowe nazwy metod, co nie? ;]] sprawdzamy, czy dany prefiks występuje w adresie. Jeśli występuje, jest wycinany po to, aby mógł zostać poprawnie dopasowany do danych w pliku routing.yml [którego ścieżki nie zawierają prefiksu /admin], jeśli nie, jest zostawiany bez zmian. oprócz tego znajduje się oczywiście przypadek, kiedy URL jest pusty – kierujemy wtedy framework w stron routingu obsługującego stronę główną aplikacji.
Jak sami widzicie to, co zrobiłem nie było wcale takie trudne. Jednakże sam nie wiedziałbym o tym, gdybym w pewnym momencie nie trafił na odpowiednie informacje gdzie indziej. To jest z jednej strony plus, a z drugiej strony zmora programistów wykorzystujących framework symfony, że o wielu rzeczach po prostu trzeba wiedzieć, bo domyślić się zwyczajnie nie da. Cóż, zobaczymy, jak przy „jedynce” wypadnie stabilna wersja Symfony2, która może za parę miesięcy ujrzy światło dzienne. ;]
Warto przeczytać.
Trwa ładowanie…
Prościej puścić jako subdomenę
Brak pomysłu na życie Jakie studia- zawód wybrać
Dokładnie, ja też robię subdomeny, a obsługę zostawiam plikowi index.php – dzięki temu nie jestem uzależniony od .htaccess (nie działa w nginx).
Bardzo prosta wersja:
if( $_SERVER['HTTP_HOST'] == 'admin.mojadomena.pl' ) {
$configuration = ProjectConfiguration::getApplicationConfiguration('admin', 'prod', false);
}
else {
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', false);
}
Mam wrażenie, że jest to właśnie problem współczesnych frameworków – dużo ułatwiają, ale łatwe rzeczy znacząco utrudniają…
KrakSpot Tech5
Ja jestem generalnie zwolennikiem tworzenia subdomen dla oddzielnych tworów, nie dla każdej sytuacji, w której jest to po prostu wygodne. Dlatego preferowałbym jednak rozwiązanie opierające się na jednej domenie.
Konferencja Falsy Values 2011
Tak jak napisałem w odpowiedzi do komentarza Mateusza – nie lubię robić subdomen tylko dlatego, że tak jest wygodnie. Jak widać opisany problem też nie jest taki trudny w rozwiązaniu, a zawsze mniej zbędnych rekordów DNS. Rozwiązanie, które tutaj zamieściłeś byłoby dla mnie ok, gdyby warunek był na fragment query stringa, a nie domenę.
Konferencja Falsy Values 2011
Każdy framework niesie ze sobą pewną filozofię i sposób rozumienia pewnych rozwiązań narzucony przez jego twórcę / zespół. Niestety mamy na to niewielki wpływ, dlatego jest tyle wojen, w których jedni przekrzykują drugich, który framework jest lepszy. Prawda jest taka, że na pewnym poziomie żaden z nich nie jest lepszy – po prostu potencjalny programista bardziej utożsamia się z filozofią jednego, a mnie z ideami drugiego.
Zawsze jest tak, że pewne udogodnienia przychodzą z pewnym kosztem, który możemy wziąć na siebie albo odrzucić. Frameworki wiele ułatwiają, jeśli ktoś potrafi się „wczuć” w myślenie jego twórcy. Próba tworzenia czegokolwiek własnego na kanwie tak dużej biblioteki w zdecydowanej większości przypadków kończy się raczej smutno.
Konferencja Falsy Values 2011
Dlatego ja na wszelki wypadek staram się robić *.mojadomena.pl => moja_aplikacja_sf :) Tam mogę sobie sterować ruchem w zależności od subdomeny.
Zdaję sobie jednak sprawę, że nie zawsze jest to rozwiązanie pożądane i lub w ogóle możliwe. Wtedy rzeczywiście można posiłkować się Twoim rozwiązaniem.
Szybkie losowanie rekordów w SQL – jeszcze jeden sposób
Nie w każdym hostingu masz dostęp do wildcard DNS – np. u mnie w DreamHoście nie jest to obsługiwane, bo twierdzą, że za bardzo im to serwery obciąża. Skoro się jednak zgadzamy do ogólnego podejścia to nie ma sprawy. ;]
Konferencja Falsy Values 2011
Muszę przyznać że masz rację.. Osobiście nie lubię w frameworkach kilku rzeczy, między innymi różnego rodzaju generatorów widzetów, czy też formularzy, bo jakoś wydajnościowo dla mnie jest to marnotrawstwo, żeby wyświetlić jeden komentarz uruchamiam kobylastą bibliotekę która ładuje dziesiątki klasy tylko po to aby ładniej to wyglądało.. Wolałbym taką sprawę załatwić gdzieś w szablonie, w ten sposób oddzielając logikę od prezentacji, niż ładować dziesiątkę klas która mi wrzuci prosty formularz..
Dlatego właśnie nie potrafię się utożsamić z żadnym frameworkiem, bo wszystkie mają wrzucone niemal wszystko, z obsługą wszystkiego. Wielu rzeczy się nie używa a i tak wiszą i denerwują.. Nie mniej, najbardziej podoba mi się sposób podejścia w niektórych przypadkach (config, routing, drzewo katalogów, sposób zapisywania ustawień itp) twórców symfony. Nie mniej, pierwsze starcie z tą kobyłą było dość bolesne ;) fajny framework, aczkolwiek dopiero po wywaleni rzeczy które imho są zbędne. Wówczas znacznie przyspiesza i nie wpierdziela już tyle pamięci..
Wiesz co – ja przez długi czas przez to, o czym piszesz nie mogłem w ogóle zaakceptować idei frameworków – wydawało mi się, że napisany na poczekaniu worek klas, z których korzystałem [żeby nie było - napisany porządnie, bardzo lekki, o wiele mniej skomplikowany niż dowolny framework] ogarnie wszystko lepiej. Nie powiem, że się myliłem, bo sprawa nie jest wcale prosta i w wielu przypadkach wróciłbym do tego „własnego frameworka”, kiedy tylko zobaczę, ile pracy muszę włożyć w zrobienie czegoś w Zendzie, czy w symfony. Jednak jakość kodu, który jest generowany przez Symfony jest na tyle duża, że nawet taki malkontent jak ja dał się przekonać. Naprawdę.
Frameworki stają się coraz lżejsze [albo sprzęt coraz szybszy ;]], coraz lepiej zaprojektowane z użyciem idei „leniwego ładowania” poszczególnych komponentów na żądanie, także przyszłość jest całkiem różowa, jeśli mogę tak się wyrazić. Nie będzie źle. ;]
Konferencja Falsy Values 2011
Wcale do mnie to nie przemawia, nawet odrobinę.. Swego czasu miałem stronę na home, powyżej 300-500 odwiedzin error 503 i zawieszenie strony przez jakieś 20minut. Nie było to nic skomplikowanego, wówczas strona stała na podstawowym phpfusion, który nie należy do grupy cms’ów które pochłaniają jakoś specjalnie dużo pamięci.. Przeniosłem stronę na inny serwer i już było lepiej. Nie mniej od tamtego czasu, wraz z wzrostem odwiedzin na stronie starałem się sprostać wymaganiom jakie strona stawiała. cms się zmienił na coś mojego tworu, lecz jeszcze bardziej wydajnego.. I tak od tamtego czasu staram się dążyć do minimalizmu, dlatego wszelkiego rodzaju szablony pokroju smarty, czy też biblioteki do wyświetlenia form’a z 1 inputem raczej u mnie nie zagoszczą. To samo tyczy się odnośnie ORM’ów. Podejście dobre jest w takich bibliotekach, nie mówię że nie, lecz dla mnie jest to zwykła próba zabicia mrówki za pomocą wyrzutni rakiet.. OOP jest fajne, ale według mnie nadużywane przez niektórych ludzi. Wypracowałem sobie odpowiednie poglądy i jakoś nie jestem w stanie uwierzyć aby ktoś mnie przekonał do używania chociażby bibliotek obsługujących formularze, czy też jakichś śliników obsługujących szablony. Zwyczajny przerost formy nad treścią jak dla mnie :))
Odnośnie wpisu, symfony jest bardzo elastyczny, uważam go za jednego z najlepszych frameworków dostępnych na rynku pod względem innowacyjności i podejścia do tematu. Zapisywanie ustawień, wykorzystanie yaml i wiele innych cech tego frameworka zdecydowanie przypadło mi do gustu.
A backend wolałbym mieć na subdomenie, lub nawet jakiejś zewnętrznej domenie, w ostateczności na localhoście, o ile chodzi o własną stronę, u klienta to jest już inna bajka ;)
Nie wiem, nie chcę pisać, że do frameworków „trzeba dorosnąć” albo „dojrzeć” – bo to nie o to chodzi. Używasz takich narzędzi, jakie są dla Ciebie wystarczające – ani lepszych, ani gorszych. Jeśli potrafisz komuś pro-framework wyperswadować swoje argumenty – nie ma problemu. Moim zdaniem można nawet rzucić całe OOP jeśli się będzie miało dobre argumenty za czymś lepszym i wydajniejszym. ;]
Ja po prostu na pewnym etapie stwierdziłem, że jednak to nie jest wcale takie zło, a jak w symfony po raz kolejny odpalasz generowanie całego modułu na podstawie YAMLa jednym poleceniem i potem zmieniasz parę linijek i puszczasz na produkcję, to naprawdę jest istotny plus. Szczególnie, że tak jak powiedziałem w ostatnim komentarzu – wygenerowany kod jest praktycznie lepszy od tego, który napisałbyś z palca, bo używa maksimum możliwości frameworka.
I na koniec – backend na subdomenie, czy w /admin to już kwestia preferencji programisty – ja wolę /admin, Ty wolisz domenę i nie ma się o co spierać. ;]
Konferencja Falsy Values 2011
Nie mam zamiaru nikomu nic perswadować, jest to jakby rzecz gustu, który nie podlega ocenie na lepszy lub gorszy. Nie chodzi znów o nie używanie możliwości języka, i pisanie strukturalne, lecz jak to kiedyś gdzieś przeczytałem, o dobranie stosownych narzędzi do wykonania odpowiedniej czynności. Wiele osób uważa że szablony to zło, również tak uważam, bo to jest język w języku, który jest sztuką dla sztuki.. Naprawiając koło w rowerze, przecież nie będę podłączać go pod komputer sprawdzając niektóre elementy na poziomie niemal atomowym, bo to jest przesadą. Dla mnie przesadą jest właśnie używanie do banalnie prostych rzeczy „działa gustav’a”, kłóci się to jakby nie patrzeć z KISS.
Zgadzam się z Tobą. Chciałbym tylko zwrócić uwagę na jedną rzecz – nie możesz powiedzieć „Nie mam zamiaru nikomu nic perswadować”, ponieważ wypowiadając pewne zdanie musisz się przygotować na to, że ktoś potencjalnie się z nim nie zgodzi. Dlatego mówiłem o perswazji jako posiadaniu dobrych argumentów na obronę swojej pozycji – nic poza tym.
BTW. „Szablony to zło” – ja tak wcale nie uważam i cały czas polecam OPTa. ;] Chociaż z drugiej strony Twig też mi jakoś nie przypadł do gustu i w Symfony2 od razu kombinowałem ze zmianą silnika na PHP.
Konferencja Falsy Values 2011
Aż dziw bierze, że coś takiego nie ma natywnego wsparcia w symfony, jak dla mnie to duża niedoróbka.
PHP 5.3 – część pierwsza: przestrzenie nazw (namespace)
Jak rozumiem ten wpis pomógł w rozwiązaniu problemu, który opisałeś w mailu? ;]
Linkdump #45: Symfony2.
Można również posłużyć się następującym sposobem
Dla aplikacji ‘admin’ ustawiamy:
factories.yml prod: request: class: sfWebRequest param: relative_url_root: /adminDzięki temu będą generowane ładne adresy url na postawie pliku routing.yml z prefiksem /admin.
Teraz pozostaje tylko jeszcze jedna kwestia.
Ustawiając prefiks zmodyfikowane również zostaną również adresy do plików js i css generowane przez symfony.
Framework szuka ich teraz w lokalizacji:
/admin/css/
/admin/js/
Co w sumie ma sens, bo chcemy rozdzielić aplikacje.
Sposobów na rozwiązanie pewnie jest wiele, ja mam na tą chwilę dwa.
(może ktoś ma ochotę jeszcze poeksperymentować :D )
Pierwszy.
Jeśli chcemy używać dotychczasowej lokalizacji plików js i css możemy w view.yml dodawać style i js’y w następujący sposób:
co może piękne nie jest, ale działa
Drugi:
Tworzymy w katalogu ‘web’ odpowiednią strukturę katalogów, czyli:
‘web/admin/css’ i analogicznie pozostałe,
do ‘web/admin’ przenosimy plik ‘admin.php’,
do ‘.htaccess’ dodajemy linie:
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_URI} ^/admin/? RewriteRule ^(.*)$ admin/index.php [QSA,L]pomiędzy:
a
RewriteCond %{REQUEST_FILENAME} !-fTen sposób definitywnie rozdziela nam aplikacje, ale wymaga modyfikowania pliku ‘.htaccess’ :/
Oba sposoby wydają mi się mniej pracochłonne niż z głównego wpisu.
Jednak to wszystko kwestia gustu, co komu się bardziej podoba.
PHP 5.3 – część pierwsza: przestrzenie nazw (namespace)
Ładne eksperymenty, mówiłeś, że da się to zrobić prościej i faktycznie się da. Na pewno Czytelnicy na tym skorzystają. A ja jestem bogatszy o kolejną informację o symfony. ;]
Linkdump #45: Symfony2.
majne, twój sposób powoduje, że w panelu powstają linki typu: /admin/backend.php/articles
@Piotrek: Próbowałeś wyłączyć opcję „no_script_name”, czyli ustawić ją na wartość „false” w app.yml?
WAMPServer, PHP: Problem z aktywacją rozszerzenia php_intl.
Pingback: symfony: Błąd: „Catchable fatal error: Argument 1 passed to sfForm::bind() must be an array, string given”. « Tomasz Kowalczyk