This post comes from the first version of this blog.
Please send me an email if anything needs an update. Thanks!

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:
  1. Dodanie nowego wpisu do pliku .htaccess,
  2. Dodanie filtra routingu w pliku factories.yml,
  3. 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:

[code highlight=“19,20”] Options +FollowSymLinks +ExecCGI

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]


<strong>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.</strong> 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:

[code highlight="32,33,34,35,36,37"]
# 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]:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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. ;]