Development

Documentation/it_IT/book/1.0/09-Links-and-the-Routing-System

You must first sign up to be able to contribute.

Version 11 (modified by giosan, 10 years ago)
--

Capitolo 9 - Link e sistema di routing

Link e URL meritano un trattamento speciale in un framework per applicazioni web. Questo perché l'utilizzo di un front controller (unico punto di accesso all'applicazione) e di helper nelle template permettono di avere una separazione completa tra il modo in cui le URL funzionano e come appaiono. Questo si chiama routing. Più di un gadget, il routing è un utile metodo per rendere le applicazioni web ancora più user-friendly e sicure. Questo capitolo ti spiegherà tutto ciò di cui necessiti per gestire le URL nelle tue applicazioni symfony:

  • Cos'è il sistema di routing e come funziona
  • Come utilizzare gli helper per i link nelle template per abilitare il routing nelle URL in uscita
  • Come impostare le regole di routing per cambiare il modo in cui le URL appaiono

Imparerai anche qualche trucchetto per migliorare le performance del routing ed aggiungere un tocco finale.

Cos'è il routing?

Il routing è un meccanismo per riscrivere le URL e renderle più user-friendly. Ma per capire perché ciò è importante, devi prima prenderti un paio di minuti per pensare alle URL.

URL come istruzioni per il server

Le URL portano dal browser al server informazioni necessarie ad attuare un'azione come desiderato dall'utente. Ad esempio, URL tradizionali contengono un percorso con un nome di script e parametri necessari a completare la richiesta:

http://www.example.com/web/controller/article.php?id=123456&format_code=6532

Tali URL veicolano informazioni riguardo l'architettura dell'applicazione e del database. Gli sviluppatori di solito nascondono l'infrastruttura dell'applicazione nell'interfaccia (ad esempio scelgono titoli di pagina come "Personal profile page" invece di "QZ7.65"). Rivelare informazioni vitali sulla struttura interna dell'applicazione nell'URL contraddice tale sforzo ed ha seri svantaggi:

  • I dati tecnici che appaiono nella URL possono creare potenziali brecce nella sicurezza. Nell'esempio precedente, cosa succederebbe se un utente maligno cambiasse il valore del parametro id? Ciò significa che l'applicazione fornisce accesso diretto al database? Oppure cosa succede se l'utente prova altri nomi di script, come admin.php, giusto per divertimento? Così, URL grezze offrono una facile via per hack dell'applicazione, e sono con esse è praticamente impossibile gestire la sicurezza.
  • URL illeggibili creano disturbo ovunque appaiano, e diluiscono l'impatto del contenuto attorno. E oggi come oggi le URL non appaiono solo nella barra degli indirizzi. Appaiono quando un utente passa con il mouse su un link, e nei risultati delle ricerche. Quando gli utenti cercano informazioni, vuoi fornirgli chiare indicazioni su cosa abbiano trovato, invece di URL confuse come quelle della Figura 9-1.

Figure 9-1 - Le URL appaiono in molti posti, come i risultati delle ricerche

Le URL appaiono in molti posti, come i risultati delle ricerche

  • Se una URL deve essere cambiata (ad esempio se cambia il nome di uno script o di uno dei suoi parametri), anche ogni link a tale URL deve essere modificato. Ciò significa che le modifiche nella struttura del controller sono pesanti e costose, cosa non ideale nello sviluppo agile.

E potrebbe essere molto peggio se symfony non usasse il paradigma del front controller; se l'applicazione contenesse diversi script accessibili da Internet, in diverse cartelle, come:

http://www.example.com/web/gallery/album.php?name=my%20holidays
http://www.example.com/web/weblog/public/post/list.php
http://www.example.com/web/general/content/page.php?name=about%20us

In questo caso, gli sviluppatori dovrebbero far coincidere la struttura dei file con quella delle URL, rendendo un incubo la manutenzione quando una delle due cambia.

URL come parte dell'interfaccia

L'idea dietro il routing è di considerare le URL come parte dell'interfaccia. L'applicazione può formattare un URL per portare informazioni all'utente, ed egli può usare l'URL per accedere a risorse dell'applicazione.

Ciò è possibile in applicazioni symfony, perché l'URL presentata all'utente finale non è relativa alle istruzioni necessarie al server per poter eseguire la richiesta. Invece, è relativa alla risorsa richiesta, e può essere formattata liberamente. Ad esempio, symfony può capire la seguente URL e mostrare lo stesso contenuto della prima URL di questo capitolo:

http://www.example.com/articles/finance/2006/activity-breakdown.html

I benefici sono immensi:

  • Le URL significano effettivamente qualcosa, e possono aiutare l'utente a capire se la pagina dietro ad un link contiene ciò che si aspetta. Un link può contenere maggiori dettagli riguardo la risorsa cui è legato. Questo è particolarmente utile per il risultato dei motori di ricerca. Inoltre, certe volte le URL appaiono senza alcun riferimento al titolo della pagina (pensa a quando copi un link da spedire via e-mail), e in tale caso, devono significare qualcosa. La Figura 9-2 mostra un esempio di URL user-friendly.

Figure 9-2 - Le URL possono portare informazioni aggiuntive sulla pagina, ad esempio la data di pubblicazione

Le URL possono portare informazioni aggiuntive sulla pagina, ad esempio la data di pubblicazione

  • Le URL scritte su documenti di carta sono più facili da scrivere e ricordare. Se la tua azienda compare sui biglietti da visita come http://www.example.com/controller/web/index.jsp?id=ERD4, probabilmente non riceverà molte visite.

  • Le URL possono diventare un modo di eseguire comandi, per recuperare informazioni in modo intuitivo. Le applicazioni che offrono tale possibilità sono più veloci da utilizzare per utenti avanzati.

    // Lista di risultati: aggiungi un nuovo tag per raffinare il risultato
    http://del.icio.us/tag/symfony+ajax
    // Pagina profilo utente: cambia il nome per vedere il profilo di un altro utente
    http://www.askeet.com/user/francois
    
  • Puoi cambiare la formattazione delle URL ed il nome/parametri dell'azione indipendentemente, con una singola modifica. Significa che puoi prima sviluppare, ed alla fine formattare le URL senza creare confusione.

  • Anche quando riorganizzi la tua applicazione, le URL possono rimanere le stesse per il mondo esterno. Ciò rende le URL persistenti, che è vitale in quanto rende possibile il bookmarking di pagine dinamiche.

  • I motori di ricerca tendono ad evitare pagine dinamiche (che finiscono con .php, .asp e così via) quando indicizzano i siti. Così puoi formattare le URL in modo che i motori di ricerca pensino di stare navigando su un sito statico, anche quando incontrano pagine dinamiche, in modo da migliorare il ranking delle tue pagine.

  • E' più sicuro. Una URL non riconosciuta verrà ridirezionata ad una pagina specificata dallo sviluppatore, e gli utenti non possono navigare la struttura di root provando indirizzi. Il nome effettivo dello script, così come i suoi parametri, sono nascosti.

La corrispondenza tra URL presentata all'utente e il nome effettivo dello script ed i suoi parametri viene conseguita dal sistema di routing, basato su pattern che possono essere modificati tramite configurazione.

NOTE E le risorse? Fortunatamente, le URL delle risorse (immagini, fogli di stile e Javascript) non compaiono molto durante la navigazione, per cui non c'è un grande bisogno di routing. In symfony, tutte le risorse sono situate nella cartella web/, e le loro URL coincidono con le loro posizioni nel file system. Comunque, puoi gestire risorse dinamiche (dalle azioni) utilizzando URL generate nei relativi helper. Ad esempio, per visualizzare un'immagine generata dinamicamente, usa image_tag(url_for('captcha/image?key='.$key)).

Come funziona

Symfony scollega la URL esterne dalle proprie URI interne. La corrispondenza tra i due viene eseguita dal sistema di routing. Per facilitare le cose, symfony utilizza per le URI interne una sintassi molto simile a quella delle URL normali. Il Listato 9-1 ne mostra un esempio.

Listato 9-1 - URL esterne e URI interne

// Sintassi URI interne
<module>/<action>[?param1=value1][&param2=value2][&param3=value3]...

// Esempio di URI interna, non compare mai all'utente finale
article/permalink?year=2006&subject=finance&title=activity-breakdown

// Esempio di URL interna, che compare all'utente finale
http://www.example.com/articles/finance/2006/activity-breakdown.html

Il sistema di routing utilizza un file di configurazione speciale, chiamato routing.yml, nel quale ne puoi definire le regole. Considera la regola mostrata nel Listato 9-2. Definisce un pattern come articles/*/*/* e da un nome alle parti di codice che coincidono con le wildcard.

Listato 9-2 - Esempio di regola di routing

article_by_title:
  url:    articles/:subject/:year/:title.html
  param:  { module: article, action: permalink }

Ogni richiesta per un'applicazione symfony viene prima di tutto analizzata dal sistema di routing (la qual cosa risulta piuttosto semplice, in quanto ogni richiesta viene gestita da un unico front controller). Il sistema di routing cerca una corrispondenza tra l'URL della richiesta ed i pattern definiti nelle regole di routing. Se una viene trovata, le wildcard diventano parametri di richiesta e vengono uniti a quelli definiti nella chiave param:. Il Listato 9-3 ne mostra il funzionamento.

Listato 9-3 - Il sistema di routing interpreta le URL della richiesta

// L'utente scrive (o clicca su) questa URL esterna
http://www.example.com/articles/finance/2006/activity-breakdown.html

// Il front controller trova una corrispondenza con la regola article_by_title
// Il sistema di routing crea i parametri seguenti
  'module'  => 'article'
  'action'  => 'permalink'
  'subject' => 'finance'
  'year'    => '2006'
  'title'   => 'activity-breakdown'

TIP L'estensione .html dell'URL esterna è una semplice decorazione, e viene ignorata dal sistema di routing. Il suo unico scopo è quello di mostrare pagine dinamiche come fossero statiche. Vedrai come attivare questa estensione nella sezione "Configurazione del routing" più avanti in questo capitolo.

La richiesta viene quindi passata all'azione permalink del modulo article, il quale in questo modo ha tutti i parametri necessari a mostrare l'articolo richiesto.

Ma questo meccanismo deve anche funzionare in senso opposto. Dato che l'applicazione deve mostrare le URL esterne nei propri link, devi fornire al sistema di routing abbastanza informazioni affinché esso possa capire quale regola applicare. Inoltre non devi scrivere nelle template i link con i tag <a>, direttamente, perché in tal modo bypasseresti completamente il sistema di routing; devi invece utilizzare un helper speciale, come mostrato nel Listato 9-4.

Listato 9-4 - Il sistema di routing formatta le URL nelle template

[php]
// L'helper url_for() trasforma una URI interna in una URL esterna
<a href="<?php echo url_for('article/permalink?subject=finance&year=2006&title=activity-breakdown') ?>">click here</a>

// L'helper riconosce che la URI soddisfa la regola article_by_title
// Per cui il sistema di routing crea la URL
 => <a href="http://www.example.com/articles/finance/2006/activity-breakdown.html">click here</a>

// L'helper link_to() stampa in output un hyperlink evitando di mischiare PHP con HTML
<?php echo link_to(
  'click here',
  'article/permalink?subject=finance&year=2006&title=activity-breakdown'
) ?>

// Internamente, link_to() chiamerà url_for() in modo che il risultato sia lo stesso
=> <a href="http://www.example.com/articles/finance/2006/activity-breakdown.html">click here</a>

Quindi il routing è un meccanismo che funziona in due direzioni, e funziona solo se usi l'helper link_to() per formattare i tuoi link.

URL Rewriting

Prima di approfondire il sistema di routing occorre chiarificare un punto. Negli esempi precedenti non viene fatta menzione del front controller (index.php o myapp_dev.php) nelle URI interne. Il front controller decide l'ambiente, non gli elementi dell'applicazione. Per cui tutti i link devono essere indipendenti dall'ambiente, ed il nome del front controller non deve mai apparire nelle URI interne.

Negli esempi non c'è traccia del nome dello script nemmeno nelle URL. Questo perché per default nell'ambiente di produzione le URL generate non contengono il nome dello script. Il parametro no_script_name nel file settings.yml controlla esattamente questo comportamento; impostalo su off, come nel Listato 9-5, e ogni URL stampata tramite gli helper dei link conterrà il nome dello script del front controller.

Listato 9-5 - Mostrare il nome del front controller nelle URL, in apps/myapp/settings.yml

prod:
  .settings
    no_script_name:  off

Ora le URL generate appariranno così:

http://www.example.com/index.php/articles/finance/2006/activity-breakdown.html

In tutti gli ambienti tranne in quello di produzione, il parametro no_script_name è impostato su off per default. Per cui quando navighi l'applicazione per esempio nell'ambiente di sviluppo, il nome del front controller appare sempre nelle URL.

http://www.example.com/myapp_dev.php/articles/finance/2006/activity-breakdown.html

In produzione, no_script_name è impostato su on, così le URL mostrano solo le informazioni di routing e sono più user-friendly. Non appare alcuna informazione tecnica.

http://www.example.com/articles/finance/2006/activity-breakdown.html

Ma come fa l'applicazione a sapere quale front controller chiamare? Qui è dove entra in gioco l'URL rewriting. Il web server può essere configurato per chiamare un dato script quando non ne viene specificato uno nella URL.

In Apache, ciò è possibile solo dopo aver attivato l'estensione mod_rewrite. Ogni progetto symfony è dotato di un file .htaccess, che aggiunge alcune impostazioni mod_rewrite per la cartella web/ alla configurazione del tuo server. Il contenuto di default di tale file è mostrato nel Listato 9-6.

Listato 9-6 - Regole di rewriting di default per Apache, in myproject/web/.htaccess

<IfModule mod_rewrite.c>
  RewriteEngine On

  # 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>

Il web server controlla la forma delle URL che riceve. Se l'URL non contiene un suffisso e se non c'è già una versione disponibile in cache della pagina (il Capitolo 12 riguarda la cache), allora la richiesta viene gestita da index.php.

Comunque, la cartella web/ di un progetto symfony è condivisa da tutte le applicazioni e da tutti gli ambienti del progetto. Ciò significa che spesso ci sarà più di un front controller in tale cartella. Ad esempio, un progetto che abbia un'applicazione di frontend ed una di backend, un ambiente dev e uno prod conterrà quattro script per i front controller nella cartella web/:

index.php         // frontend in prod
frontend_dev.php  // frontend in dev
backend.php       // backend in prod
backend_dev.php   // backend in dev

Le impostazioni mod_rewrite possono specificare il nome di un solo front controller di default. Se imposti no_script_name ad on per tutte le applicazioni e tutti gli ambienti, tutte le URL saranno interpretate come richieste per l'applicazione frontend nell'ambiente prod. Ecco perché, per ogni progetto, puoi avere solo una applicazione ed un ambiente che sfruttino il vantaggio dell'URL rewriting.

TIP In effetti c'è un modo per avere più di un'applicazione senza script name. Crea sottocartelle nella web root, e mettici i vari front controller dentro. Cambia le costanti SF_ROOT_DIR di conseguenza, e crea le configurazioni .htaccess necessarie ad ogni applicazione.

Link Helpers

Per avvantaggiarti del sistema di routing, dovresti utilizzare gli helper dei link invece dei tag <a> nelle template. Non vedere questo come uno svantaggio, bensì come un modo per tenere la tua applicazione pulita e facile da manutenere. Inoltre, questi helper offrono qualche scorciatoia molto utile da non perdere.

Hyperlink, pulsanti e form

Già conosci l'helper link_to(). Stampa in output un hyperlink che rispetta la sintassi XHTML, e si aspetta due parametri: l'elemento che deve essere cliccato e l'URI interna a cui deve portare. Se invece di un link vuoi un pulsante, usa l'helper button_to(). Anche le form hanno un helper per gestire il valore dell'attributo action. Avrai maggiori informazioni sulle form nel prossimo capitolo. Il Listato 9-7 mostra alcuni esempi di helper per i link.

Listato 9-7 - Alcuni esempi di helper per i tag <a>, <input>, e <form>

[php]
// Hyperlink su una stringa
<?php echo link_to('my article', 'article/read?title=Finance_in_France') ?>
 => <a href="/routed/url/to/Finance_in_France">my article</a>

// Hyperlink su un'immagine
<?php echo link_to(image_tag('read.gif'), 'article/read?title=Finance_in_France') ?>
 => <a href="/routed/url/to/Finance_in_France"><img src="/images/read.gif" /></a>

// Pulsante
<?php echo button_to('my article', 'article/read?title=Finance_in_France') ?>
 => <input value="my article" type="button"onclick="document.location.href='/routed/url/to/Finance_in_France';" />

// Form
<?php echo form_tag('article/read?title=Finance_in_France') ?>
 => <form method="post" action="/routed/url/to/Finance_in_France" />

Tali helper accettano sia URI interne che URL assolute (che cominciano con http://, e vengono ignorate dal sistema di routing) e anchor. Nota che in applicazioni del mondo reale, le URI interne vengono costruite con parametri dinamici. Il Listato 9-8 mostra un esempio di tali casi.

Listato 9-8 - URL accettate dagli helper dei link

[php]
// URI interne
<?php echo link_to('my article', 'article/read?title=Finance_in_France') ?>
 => <a href="/routed/url/to/Finance_in_France">my article</a>

// URI interne con parametri dinamici
<?php echo link_to('my article', 'article/read?title='.$article->getTitle()) ?>

// URI interne con anchor
<?php echo link_to('my article', 'article/read?title=Finance_in_France#foo') ?>
 => <a href="/routed/url/to/Finance_in_France#foo">my article</a>

// URL assolute
<?php echo link_to('my article', 'http://www.example.com/foobar.html') ?>
 => <a href="http://www.example.com/foobar.html">my article</a>

Opzioni degli helper dei link

Come spiegato nel Capitolo 7, gli helper accettano un ulteriore argomento opzionale, che può essere un array associativo od una stringa. Questo è vero anche per gli helper dei link, come mostrato nel Listato 9-9.

Listato 9-9 - Gli helper dei link accettano un parametro addizionale

[php]
// Opzione aggiuntiva come array associativo
<?php echo link_to('my article', 'article/read?title=Finance_in_France', array(
  'class'  => 'foobar',
  'target' => '_blank'
)) ?>

// Opzione aggiuntiva come stringa (stesso risultato)
<?php echo link_to('my article', 'article/read?title=Finance_in_France','class=foobar target=_blank') ?>
 => <a href="/routed/url/to/Finance_in_France" class="foobar" target="_blank">my article</a>

Puoi anche aggiungere una delle opzioni specifiche di symfony, per gli helper dei link: confirm e popup. La prima mostra una finestra di dialogo di conferma JavaScript che appare quando si clicca sul link, mentre la seconda apre il link in una nuova finestra, come mostrato nel Listato 9-10.

Listato 9-10 - Le opzioni 'confirm' e 'popup' per gli helper dei link

[php]
<?php echo link_to('delete item', 'item/delete?id=123', 'confirm=Are you sure?') ?>
 => <a onclick="return confirm('Are you sure?');"
       href="/routed/url/to/delete/123.html">add to cart</a>

<?php echo link_to('add to cart', 'shoppingCart/add?id=100', 'popup=true') ?>
 => <a onclick="window.open(this.href);return false;"
       href="/fo_dev.php/shoppingCart/add/id/100.html">add to cart</a>

<?php echo link_to('add to cart', 'shoppingCart/add?id=100', array(
  'popup' => array('Window title', 'width=310,height=400,left=320,top=0')
)) ?>
 => <a onclick="window.open(this.href,'Window title','width=310,height=400,left=320,top=0');return false;"
       href="/fo_dev.php/shoppingCart/add/id/100.html">add to cart</a>

Tali opzioni possono essere anche usate insieme.

Opzioni GET e POST false

Alcune volte gli sviluppatori web usano richeste GET per utilizzarle in POST. Ad esempio, considera la seguente URL:

http://www.example.com/index.php/shopping_cart/add/id/100

Questa richiesta cambierà i dati contenuti nell'applicazione, aggiungendo un oggetto al carrello, memorizzato in sessione o nel database. Questa URL potrebbe essere messa nei bookmark, in cache ed indicizzata dai motori di ricerca. Immagina tutte le cose "sporche" che potrebbero succedere al database od alla metrica di un sito utilizzando questa tecnica. In effetti, questa richiesta dovrebbe essere considerata come un POST, in quanto i motori di ricerca non indicizzano tali richieste.

Symfony fornisce un modo per trasformare effettivamente una chiamata agli helper link_to() o button_to() in un POST. Aggiungi semplicemente un'opzione post=true, come mostrato nel Listato 9-11.

Listato 9-11 - Trasformare un link in una richiesta in POST

[php]
<?php echo link_to('go to shopping cart', 'shoppingCart/add?id=100', 'post=true') ?>
 => <a onclick="f = document.createElement('form'); document.body.appendChild(f);
                f.method = 'POST'; f.action = this.href; f.submit();return false;"
       href="/shoppingCart/add/id/100.html">go to shopping cart</a>

Questo tag <a> possiede un attributo href, ed i browser senza supporto a JavaScript, come i robot dei motori di ricerca, seguiranno il link come una chiamata in GET. Quindi devi anche fare in modo che la tua azione risponda solo a comandi in POST, ad esempio aggiungendo qualcosa come:

[php]
$this->forward404If($request->getMethod() != sfRequest::POST);

all'inizio dell'azione. Devi solo essere sicuro di non utilizzare questi link all'interno di form, in quanto essi generano il proprio tag <form>.

E' buona abitudine trasformare in POST tutte le chiamate che in effetti spediscono dati.

Forzare parametri di richiesta come variabili GET

A seconda delle tue regole di routing, le variabili passate come parametri a link_to() vengono trasformate in pattern. Se nessuna regola del file routing.yml coincide con la URI interna, la regola di default trasforma module/action?key=value in /module/action/key/value, come mostrato nel Listato 9-12.

Listato 9-12 - Regola di routing di default

[php]
<?php echo link_to('my article', 'article/read?title=Finance_in_France') ?>
=> <a href="/article/read/title/Finance_in_France">my article</a>

Se hai bisogno effettivamente di mantenere la sintassi GET, per avere parametri di richiesta nella forma ?key=value, devi forzare tali variabili fuori dalla URL, nell'opzione query_string. Tutti gli helper dei link accettano questa opzione, come dimostrato nel Listato 9-13.

Listato 9-13 - Forzare parametri in GET con l'opzione query_string

[php]
<?php echo link_to('my article', 'article/read', array(
  'query_string' => 'title=Finance_in_France'
)) ?>
=> <a href="/article/read?title=Finance_in_France">my article</a>

Una URL con parametri di richiesta in GET può venire interpretata da uno script lato client, e dalle variabili $_GET e $_POST lato server.

SIDEBAR Helper per le risorse

Il Capitolo 7 ha introdotto gli helper image_tag(), stylesheet_tag(), e javascript_include_ tag(), che ti permettono di includere un'immagine, un foglio di stile od un file JavaScript nella risposta. I percorsi di tali asset non vengono processati dal sistema di routing, in quanto puntano a risorse situate nella cartella web pubblica.

Non hai bisogno di menzionare l'estensione per un asset. Symfony aggiunge automaticamente .png, .js, o .css ad un'immagine, JavaScript o foglio di stile. Inoltre, symfony cerchera' automaticamente le risorse nelle cartelle web/images/, web/js/, e web/css/. Ovviamente, se vuoi inserire un particolare file situato in una particolare cartella, puoi utilizzare come argomento il percorso completo. E non ti preoccupare di specificare l'attributo alt se la tua immagine ha un nome esplicito, in quanto symfony lo determinera' per te.

[php]
<?php echo image_tag('test') ?>
<?php echo image_tag('test.gif') ?>
<?php echo image_tag('/my_images/test.gif') ?>
 => <img href="/images/test.png" alt="Test" />
    <img href="/images/test.gif" alt="Test" />
    <img href="/my_images/test.gif" alt="Test" />

Per fissare la dimensione di un'immagine, usa l'attributo size. Si aspetta un'altezza ed una larghezza in pixel, separate da una x.

[php]
<?php echo image_tag('test', 'size=100x20')) ?>
 => <img href="/images/test.png" alt="Test" width="100" height="20"/>

Se vuoi che l'inclusione della risorsa avvenga all'interno della sezione </head> (per fogli di stile e JavaScript) usa nelle tue template gli helper use_stylesheet() e use_javascript() helpers in your templates, invece delle rispettive versioni _tag() del layout. Essi aggiungono le risorse nella risposta, e tali risorse vengono incluse prima che la chiusura della sezione </head> sia spedita al browser.

Utilizzare path assoluti

Gli helper dei link e delle risorse per default generano link relativi. Per forzarli assoluti, utilizza l'opzione absolute impostandola a true, come mostrato nel Listato 9-14.

Listato 9-14 - Link assoluti invece di relativi

[php]
<?php echo url_for('article/read?title=Finance_in_France') ?>
 => '/routed/url/to/Finance_in_France'
<?php echo url_for('article/read?title=Finance_in_France', true) ?>
 => 'http://www.example.com/routed/url/to/Finance_in_France'

<?php echo link_to('finance', 'article/read?title=Finance_in_France') ?>
 => <a href="/routed/url/to/Finance_in_France">finance</a>
<?php echo link_to('finance', 'article/read?title=Finance_in_France','absolute=true') ?>
 => <a href=" http://www.example.com/routed/url/to/Finance_in_France">finance</a>

// Lo stesso funziona per gli asset
<?php echo image_tag('test', 'absolute=true') ?>
<?php echo javascript_include_tag('myscript', 'absolute=true') ?>

SIDEBAR L'helper Mail

Ad oggi i robot che raccolgono indirizzi e-mail invadono la rete, e non puoi lasciare tranquillamente il tuo indirizzo sul web senza diventare una vittima dello spam entro pochi giorni. Per tale motivo symfony mette a disposizione un helper mail_to().

L'helper mail_to() accetta due parametri: l'indirizzo e-mail effettivo e la stringa che deve essere visualizzata. Opzioni aggiuntive accettano un parametro encode per stampare codice non leggibile dai robot ma comprensibile dal browser.

[php]
<?php echo mail_to('myaddress@mydomain.com', 'contact') ?>
 => <a href="mailto:myaddress@mydomain.com'>contact</a>
<?php echo mail_to('myaddress@mydomain.com', 'contact', 'encode=true') ?>
 => <a href="&#109;&#x61;... &#111;&#x6d;">&#x63;&#x74;... e&#115;&#x73;</a>

Tali messaggi e-mail sono composti da caratteri trasformati da un encoder decimale/esadecimale casuale. Questo trucco ferma la maggior parte degli spambot attuali, ma fai attenzione al fatto che essi evolvono rapidamente.

Configurazione del routing

Il sistema di routing fa due cose:

  • Interpreta l'URL esterna di una richiesta e la trasforma in una URI interna per capire quale modulo/azione chiamare ed i suoi parametri.
  • Formatta le URI interne usate nei link in URL esterne (se usi gli helper).

La conversione avviene sulla base di una serie di regole di routing. Tali regole sono memorizzate nel file di configurazione routing.yml dentro la cartella config/ dell'applicazione. Il Listato 9-15 mostra le regole di routing di default, incluse in ogni progetto symfony.

Listato 9-15 - Regole di routing di default, in myapp/config/routing.yml

# default rules
homepage:
  url:   /
  param: { module: default, action: index }

default_symfony:
  url:   /symfony/:action/*
  param: { module: default }

default_index:
  url:   /:module
  param: { action: index }

default:
  url:   /:module/:action/*

Regole e pattern

Le regole di routing sono associazioni biiettive tra URI interne e URL esterne. Una regola tipica e' composta da:

  • Una label unica, presente per questioni di velocita' e leggibilita', e puo' essere usata dagli helper dei link
  • Un pattern di cui fare il match (chiave url)
  • Un array di parametri di richiesta (chiave param)

I pattern possono contenere wildcard (rappresentate da un asterisco, *) e wildcard nominate (che cominciano con i due punti, :). Una coincidenza con una wildcard nominata diventa il valore di un parametro di richiesta. Ad esempio, la regola default definita nel Listato 9-15 corrisponde ad ogni URL tipo /foo/bar, ed imposta il parametro module a foo ed il parametro action a bar. E nella regola default_symfony, symfony e' una parola chiave, e action una wildcard nominata.

Il sistema di routing esegue il parsing del file routing.yml dall'inizio alla fine e si ferma alla prima corrispondenza trovata. Per tale motivo dovresti aggiungere le tue regole all'inizio, prima di quelle di default. Ad esempio, la URL /foo/123 corrisponde ad entrambe le regole definite nel Listato 9-16, ma symfony testa prima my_rule:, e dato che questa corrisponde, non prova nemmeno ad andare avanti. La richiesta viene gestita dall'azione mymodule/myaction con il parametro bar impostato a 123 (e non dall'azione foo/123).

Listato 9-16 - Il parsing delle regole procede dall'inizio alla fine

my_rule:
  url:   /foo/:bar
  param: { module: mymodule, action: myaction }

# default rules
default:
  url:   /:module/:action/*

NOTE Quando viene creata una nuova azione, non significa che tu debba creare anche una corrispondente regola di routing. Il pattern di default modulo/azione funziona, per cui puoi evitare di pensare al file routing.yml. Comunque, qualora tu voglia personalizzare le URL esterne delle azioni, aggiungi nuove regola sopra quelle di default.

Il Listato 9-17 mostra il processo di modifica del formato dell'URL esterna per un'azione article/read.

Listato 9-17 - Cambiare il formato dell'URL esterna per un'azione article/read

[php]
<?php echo url_for('my article', 'article/read?id=123) ?>
 => /article/read/id/123       // Default formatting

// Per cambiarla in /article/123 aggiungi una nuova regola all'inizio
// del tuo file routing.yml
article_by_id:
  url:   /article/:id
  param: { module: article, action: read }

Il problema e' che la regola article_by_id del Listato 9-17 interrompe il routing di default per tutte le altre azioni del modulo article. Infatti, una URL tipo article/delete corrispondera' anch'essa a questa regola, invece che a quella di default, e chiamera' l'azione read con il parametro id con valore delete invece dell'azione delete. Per evitare cio', devi aggiungere una costrizione di pattern in modo che la regola article_by_id coincida solo con URL dove la wildcard id sia un intero.

Costrizione di pattern

Quando una URL puo' corrispondere a piu' regole, devi raffinare le regole aggiungendo costrizioni, o requisiti, al pattern. Un requisito e' un insieme di espressioni regolari a cui le wildcard devono corrispondere, perche' tutta la regola coincida.

Ad esempio, per modificare la regola article_by_id in modo che coincida solo con URL che abbiano il parametro id intero, aggiungi una linea come mostrato nel Listato 9-18.

Listato 9-18 - Aggiungere un requisito ad una regola di routing

article_by_id:
  url:   /article/:id
  param: { module: article, action: read }
  requirements: { id: \d+ }

Ora una URL article/delete non puo' piu' corrispondere alla regola article_by_id, perche' la stringa delete non soddisfa il requisito. Percio', il sistema di routing continuera' a cercare per una regola adatta, e trovera' cosi' la default.

SIDEBAR Permalinks

Una buona linea guida per la sicurezza del routing e' quella di nascondere le chiavi primarie il piu' possibile e sostiuirle con stringhe significative. E se tu avessi voluto mostrare gli articoli secondo il loro titolo invece che il loro ID? Implicherebbe URL esterne nel seguente formato:

http://www.example.com/article/Finance_in_France

Per fare cio', hai bisogno di creare una nuova azione permalink, che utilizzera' un parametro slug invece di un id, ed aggiungi una nuova regola di routing:

article_by_id:
  url:          /article/:id
  param:        { module: article, action: read }
  requirements: { id: \d+ }

article_by_slug:
  url:          /article/:slug
  param:        { module: article, action: permalink }

L'azione permalink ha bisogno di determinare l'articolo richiesto dal titolo, per cui il tuo modello deve fornire un metodo appropriato:

[php]
public function executePermalink()
{
  $article = ArticlePeer::retrieveBySlug($this->getRequestParameter('slug');
  $this->forward404Unless($article);  // Mostra 404 se non trova alcun articolo corrispondente a slug
  $this->article = $article;          // Passa l'oggetto alla template
}

Devi anche sostituire i link all'azione read nelle tue template con quelli alla permalink, per abilitare la formattazione delle URI interne.

[php]
// Sostituisci
<?php echo link_to('my article', 'article/read?id='.$article->getId()) ?>

// con
<?php echo link_to('my article', 'article/permalink?slug='.$article->getSlug()) ?>

Grazie alla linea requirements, una URL esterna come /article/Finance_in_France corrisponde alla regola article_by_slug, anche se la regola article_by_id appare prima.

Nota che dato che gli articoli verranno recuperati tramite slug, dovresti aggiungere una indice alla colonna slug della tabella Article per ottimizzare le performance del database.

Impostare valori di default

Puoi assegnare a wildcard nominate dei valori di default per far funzionare la regola, anche se il parametro non e' definito. Imposta tali valori di default nell'array param:.

Ad esempio, la regola article_by_id non coincide se il parameteo id non e' definito. Lo puoi forzare come mostrato nel Listato 9-19.

Listato 9-19 - Impostare valori di default per wildcard

article_by_id:
  url:          /article/:id
  param:        { module: article, action: read, id: 1 }

Nel Listato 9-20, il parametro display assume il valore true anche se non e' presente nella URL.

Listato 9-20 - Impostare un valore di default per un parametro di rihiesta

article_by_id:
  url:          /article/:id
  param:        { module: article, action: read, id: 1, display: true }

Se guardi attentamente, vedrai che anche article e read sono valori di default per le variabili module e action non presenti nel pattern.

TIP Puoi definire un parametro di default per tutte le regole di routing impostando il parametro di configurazione sf_routing_default. Ad esempio, se vuoi che tutte le URL abbiano per default un parametro theme impostato a default per default, aggiungi la linea sfConfig::set('sf_routing_defaults', array('theme' => 'default')); al file config.php della tua applicazione.

Velocizzare il routing usando i nomi delle regole

Gli helper dei link accettano un'etichetta invece di una coppia modulo/azione se tale etichetta e' preceduta dalla chiocciola (@), come mostrato nel Listato 9-21.

Listato 9-21 - Utilizzare label invece di modulo/azione

[php]
<?php echo link_to('my article', 'article/read?id='.$article->getId()) ?>

// puo' anche essere scritto
<?php echo link_to('my article', '@article_by_id?id='.$article->getId()) ?>

Ci sono pro e contro a tal metodo. I vantaggi sono i seguenti:

  • La formattazione di URI interne avviene molto piu' velocemente, in quanto symfony non deve cercare tutte le regole per trovare quella che corrisponde al link. In pagine in cui il numero di link e' elevato, noterai la differenza di velocita' se usi label invece di coppie modulo/azione.
  • Usare le label aiuta ad astrarre la logica dietro un'azione. Se decidi di cambiare il nome dell'azione ma non la URL, infatti, sara' sufficiente una piccola modifica del file routing.yml. Tutte le chiamate link_to() continueranno a funzionare senza ulteriori cambiamenti.
  • La logica della chiamata e' piu' evidente se utilizzi una label. Anche se i tuoi moduli e azioni possiedono nomi espliciti, spesso e' piu' evidente chiamare @display_article_by_slug di article/display.

D'altra parte, uno svantaggio e' che aggiungere nuovi link e' meno intuitivo, dato che devi sempre controllare il file routing.yml per vedere come si chiama la label.

La scelta migliore dipende dal progetto. Sulla lunga distanza, dipende da te.

TIP Durante i tuoi test (nell'ambiente dev), se vuoi controllare quale regola coincide con una richiesta, apri la sezione "logs and msgs" della debug toolbar, e cerca una linea specificando "matched route XXX." Il Capitolo 16 fornisce maggiori informazioni riguardo la modalita' di debug web.

Aggiungere un'estensione .html

Confronta queste due URL:

http://myapp.example.com/article/Finance_in_France
http://myapp.example.com/article/Finance_in_France.html

Anche se si tratta della stessa pagina, utenti e robot potrebbero vederla in maniera diversa, per via dell'estensione. La seconda URL ricorda una profonda e ben organizzata struttura di pagine statiche, esattamente il tipo di sito che i motori di ricerca sanno come indicizzare.

Per aggiungere un suffisso ad ogni URL esterna generata dal sistema di routing, cambia il valore del parametro suffix dentro settings.yml dell'applicazione, come mostrato nel Listato 9-22.

Listato 9-22 - Aggiungere un suffisso a tutte le URL, in myapp/config/settings.yml

prod:
  .settings
    suffix:         .html

Il suffisso di default e' un punto (.) che significa non aggiungere alcuna estensione.

Talvolta si rende necessario specificare un suffisso per una regola di routing unica. In tal caso, scrivi il suffisso direttamente nella linea relativa url: del file routing.yml, come mostrato nel Listato 9-23. Cosi' il suffisso globale verra' ignorato.

Listato 9-23 - Impostare un suffisso per una regola, in myapp/config/routing.yml

article_list:
  url:          /latest_articles
  param:        { module: article, action: list }

article_list_feed:
  url:          /latest_articles.rss
  param:        { module: article, action: list, type: feed }

Creare regole senza routing.yml

Come per la maggior parte dei file di configurazione, routing.yml e' la soluzione per definire regole di routing, ma non l'unica. Puoi definire regole in PHP, sia nel file config.php dell'applicazione che nello script del front controller, ma prima della chiamata a dispatch(), perche' tale metodo determina l'azione da eseguire secondo le regole di routing correnti. Definire regole in PHP ti permette di creare regole dinamiche, dipendenti dalla configurazione o dai parametri.

L'oggetto che gestisce le regole di routing e' il singleton sfRouting. Esso e' disponibile da qualsiasi parte dell'applicazione tramite sfRouting::getInstance(). Il suo metodo prependRoute() aggiunge una nuova regola prima di tutte quelle definite in routing.yml. Si aspetta quattro parametri, che sono gli stessi per le regole di routing: label, pattern, array associativo di valori di default e array associtaivo per i requisiti. Ad esempio, la definizione delle regole del Listatp 9-18 e' equivalente al codice PHP del Listato 9-24.

Novità della versione in sviluppo

NOTE La classe di routing è configurabile nel file di configurazione factories.yml (per cambiare la classe di routing di default consulta il Capitolo 17). Questo capitolo spiega la classe sfPatternRouting, che è la classe di routing di default.

Listato 9-24 - Definire una regola in PHP

[php]
sfContext::getInstance()->getRouting()->prependRoute(
  'article_by_id',                                  // Route name
  '/article/:id',                                   // Route pattern
  array('module' => 'article', 'action' => 'read'), // Default values
  array('id' => '\d+'),                             // Requirements
);

Il singleton sfRouting possiede altri metodi per la gestione manuale del routing: clearRoutes(), hasRoutes(), getRoutesByName() e cosi' via. Per maggiori informazioni in merito puoi consultare le API ([http://www.symfony-project.com/api/symfony.html]).

TIP Una volta che hai compreso a fondo i concetti presentati in questo libro, puoi aumentare la tua comprensione del framework consultando le API o, ancora meglio, i sorgenti di symfony. Non tutti i parametri ed i "trucchi" di symfony possono essere spiegati in questo libro. In ogni caso la documentazione online e' illimitata.

Gestire le route nelle azioni

Se hai bisogno di sapere informazioni sulla route corrente, ad esempio per preparare un futuro link "Indietro alla pagina XXX", dovresti usare i metodi dell'oggetto sfRouting. Le URI restituite dal metodo getCurrentInternalUri() possono venire usate in una chiamata link_to(), come mostrato nel Listato 9-25.

Listato 9-25 - Utilizzare sfRouting per avere informazioni sulla route corrente

[php]
// Se ti serve una URL come
http://myapp.example.com/article/21

$routing = sfContext::getInstance()->getRouting();

// Usa il codice seguente nell'azione in article/read
$uri = $routing->getCurrentInternalUri();
 => article/read?id=21

$uri = $routing->getCurrentInternalUri(true);
 => @article_by_id?id=21

$rule = $routing->getCurrentRouteName();
 => article_by_id

// Se ti servono semplicemente i nomi del modulo/azione corrente,
// ricorda che essi sono parametri di richiesta effettivi
$module = $this->getRequestParameter('module');
$action = $this->getRequestParameter('action');

Se hai bisogno di trasformare una URI in una URL esterna in un'azione, come url_for() fa nelle template, usa il metodo genUrl() dell'oggetto sfController, come mostrato nel Listato 9-26.

Listato 9-26 - Usare sfController per trasformare una URI interna

[php]
$uri = 'article/read?id=21';

$url = $this->getController()->genUrl($uri);
 => /article/21

$url = $this->getController()->genUrl($uri, true);
=> http://myapp.example.com/article/21

Sommario

Il routing e' un meccanismo bidirezionale pensato per permettere la formattazione di URL esterne in modo che siano piu' user-friendly. L'URL rewriting e necessaria per permettere l'omissione del nome del front controller nell'URL di una delle applicazioni di ogni progetto. Devi usare gli helper dei link ogni qualvolta tu abbia bisogno di stampare una URL in una template, se vuoi che il sistema di routing funzioni in entrambe le direzioni. Il file routing.yml configura le regole del sistema di routing ed utilizza un ordine di precedenza e requisiti. Il file settings.yml contiene impostazioni addizionali riguardanti la presenza del nome del front controller e possibili suffissi in URL esterne.