Giorno 5: Il routing
Se avete portato a termine il giorno 4, dovreste avere preso confidenza con il pattern MVC e dovrebbe sembrarvi un modo più naturale di scrivere codice. Investite un po' di tempo in più per conoscerlo meglio e non tornerete più indietro. Parlando di cose pratiche, ieri abbiamo personalizzato le pagine di Jobeet e allo stesso tempo abbiamo rivisto alcuni concetti di symfony come il layout, gli helper e gli slot.
Oggi ci addentreremo nel magnifico mondo del framework del routing di symfony.
~URL~
Se cliccate su un'offerta di lavoro sulla homepage di Jobeet, l'URL apparirà come il seguente: /job/show/id/1
. Se avete già sviluppato altri siti in PHP, sarete abituati a URL del tipo /job.php?id=1
. Come fa symfony a farli funzionare? Come fa symfony a decidere l'azione da chiamare basandosi su quest'URL? Perché l'id
dell'offerta di lavoro viene recuperata con
$request->getParameter('id')
? Oggi risponderemo a tutte queste domande.
Prima però parliamo un po' degli URL e di cosa siano esattamente. In un contesto web, un URL è l'identificatore univoco di una risorsa. Quando visitate un URL, chiedete al browser di utilizzare una risorsa identificata da quell'URL. Perciò, siccome l'URL è l'interconnessione tra il sito web e l'utente, deve comunicare significative informazioni riguardo alla risorsa che identifica. Ma gli URL "tradizionali" non descrivono davvero una risorsa, essi mostrano la struttura interna di un'applicazione. All'utente non interessa che il sito sia realizzato in PHP o che una particolare offerta di lavoro abbia un certo identificatore sul database. Mostrare il funzionamento interno dell'applicazione è piuttosto dannoso in termini di ~sicurezza|Sicurezza~: cosa succederebbe se l'utente provasse ad accedere a risorse per cui non ha il permesso? Sicuramente lo sviluppatore dovrebbe metterle in sicurezza in modo appropriato, ma è sempre meglio nascondere informazioni sensibili.
Gli URL sono molto importanti in symfony, a tal punto da avere un intero framework dedicato alla loro gestione: il framework del ~routing|Routing~ . Il routing si occupa della gestione degli URI interni e URL esterni. Quando arriva una richiesta, il routing processa l'URL e lo converte in un URI interno.
Abbiamo già visto un URI interno della pagina di un'offerta di lavoro nel template indexSuccess.php
:
'job/show?id='.$job->getId()
L'~helper url_for()
|url_for()
~ converte l'URI interno in un vero e proprio URL:
/job/show/id/1
L'URI interno è costituito da molte parti: job
è il modulo, show
è l'azione e la query string aggiunge i parametri da passare all'azione. Lo schema generico per un URI interno è il seguente:
MODULO/AZIONE?chiave=valore&chiave_1=valore_1&...
Visto che il routing di symfony è un processo a due vie, si possono cambiare gli URL senza cambiarne l'implementazione tecnica. Questo è uno dei vantaggi principali del design pattern front-controller.
Configurazione del routing
La mappatura tra gli URI interni e gli URL esterni è stabilita nel file di configurazione ~routing.yml
~:
Il file routing.yml
descrive le rotte. Una rotta ha un nome (homepage
), uno schema (/:module/:action/*
) e alcuni parametri (sotto la chiave param
).
Quando arriva una richiesta, il routing cerca uno schema corrispondente per l'URL passato. La prima rotta corrispondente vince, perciò l'ordine nel file routing.yml
è importante. Diamo uno sguardo ad alcuni esempi per comprendere meglio come funziona tutto questo.
Quando si richiede l'homepage di Jobeet, che contiene l'URL /job
, la prima rotta corrispondente è default_index
. In uno schema, una parola ~preceduta|Prefisso~ dai due punti (:
) è una variabile, quindi /:module
significa: individua una /
seguita da qualcosa. Nel nostro esempio, la variabile module
ha come valore job
. Questo valore può essere recuperato con $request->getParameter('module')
. Questa rotta definisce inoltre un valore di default per la variabile action
. Quindi, per tutti gli URL corrispondenti a questa rotta, la richiesta avrà anche un parametro action
con index
come valore.
Se si richiede la pagina /job/show/id/1
, symfony identificherà la corrispondenza dell'ultimo schema: /:module/:action/*
. In uno schema, l'asterisco (*
) corrisponde a un insieme di coppie variabile/valore separate da slash (/
):
Parametro richiesto | Valore |
---|---|
module | job |
action | show |
id | 1 |
Le viariabili ~
module|Modulo
~ e ~action|Azione
~ sono speciali, visto che sono utilizzate da symfony per determinare quale azione eseguire.
L'URL /job/show/id/1
può essere creato da un template utilizzando la seguente chiamata all'helper url_for()
:
url_for('job/show?id='.$job->getId())
Potete utilizzare anche il nome della rotta aggiungendo il prefisso @
:
url_for('@default?module=job&action=show&id='.$job->getId())
Le chiamate sono equivalenti, ma l'ultima è più efficiente, visto che il sistema di routing non deve analizzare tutte le rotte per trovare quella con la migliore corrispondenza ed è meno legata all'implementazione (i nomi del modulo e dell' azione non sono presenti nell'URI interno).
Personalizzazioni delle rotte
Per ora, quando si richiede l'URL /
nel browser, si ottiene la pagina predefinita di congratulazioni di symfony. Questo perché l'URL corrisponde alla ~rotta|Rotta~ homepage
. Ma ha senso cambiarla per renderla l'homepage di Jobeet. Per fare la modifica, cambiamo la variabile module
della rotta homepage
in job
:
# apps/frontend/config/routing.yml homepage: url: / param: { module: job, action: index }
Possiamo ora cambiare il link del logo di Jobeet nel layout per fargli usare la rotta homepage
:
<h1> <a href="<?php echo url_for('@homepage') ?>"> <img src="/images/jobeet.jpg" alt="Jobeet Job Board" /> </a> </h1>
È stato facile!
Quando si aggiorna la configurazione delle rotte, le modifiche vengono immediatamente prese in considerazione nell'ambiente di sviluppo. Ma per farle funzionare anche nell'ambiente di produzione, occorre pulire la cache utilizzando il task
cache:clear
.
Per qualcosa di un po' più complesso, cambiamo l'URL della pagina dei lavori in qualcosa di più sensato:
/job/sensio-labs/paris-france/1/web-developer
Senza sapere niente su Jobeet e senza guardare la pagina, si può capire dall'URL che Sensio Labs sta cercando uno sviluppatore web per lavorare a Parigi, in Francia.
Gli URL carini sono importanti, perché portano informazioni all'utente. Sono anche utili quando si copia e incolla un URL in una email o per ottimizzare il sito per i motori di ricerca.
Il seguente schema corrisponde a questo URL:
/job/:company/:location/:id/:position
Modifichiamo il file routing.yml
e la rotta job
all'inizio del file:
Se si aggiorna l'homepage di Jobeet, i link ai lavori non sono cambiati. Questo perché per rigenerare una rotta, bisogna passare le variabili richieste. Quindi occorre cambiare la chiamata a url_for()
in indexSuccess.php
:
url_for('job/show?id='.$job->getId().'&company='.$job->getCompany(). '&location='.$job->getLocation().'&position='.$job->getPosition())
Un ~URI interno~ può anche essere espresso come array:
url_for(array( 'module' => 'job', 'action' => 'show', 'id' => $job->getId(), 'company' => $job->getCompany(), 'location' => $job->getLocation(), 'position' => $job->getPosition(), ))
Requisiti
Durante il primo giorno di tutorial, abbiamo parlato della ~validazione|Validazione~ e della gestione degli errori per buone ragioni. Il sistema delle rotte ha una capacità di validazione intrinseca. Ogni schema di variabili può essere validato da un'espressione regolare definita usando la voce ~requirements|Requirements
~ nella definizione della ~rotta|Rotta~:
La voce requirements
forza id
a essere un valore numerico. Altrimenti, la rotta non corrisponderà.
Classi di rotte
Ogni rotta definita in ~routing.yml
~ viene internamente convertita in un oggetto della classe sfRoute
. Questa classe può essere cambiata definendo una voce class
nella definizione della rotta. Se avete familiarità col protocollo ~HTTP~, forse saprete che definisce diversi "metodi", come ~GET
~, ~POST
~, ~HEAD
~, ~DELETE
~ e ~PUT
~. I primi tre sono supportati da tutti i browser, mentre gli altri due no.
Per restringere una rotta a corrispondere solo ad alcuni metodi di richiesta, si può cambiare la classe della rotta in sfRequestRoute
ed aggiungere un requisito per la variabile virtuale sf_method
:
Richiedere a una rotta di corrispondere solo ad alcuni ~metodi HTTP~ non è esattamente equivalente a usare
sfWebRequest::isMethod()
nell'azione. Questo perché il routing continuerà a cercare una rotta corrispondente se il metodo non corrisponde a quello atteso.
Classi di oggetti rotta
Il nuovo URI interno per un lavoro è un po' lungo e difficile da scrivere (url_for('job/show?id='.$job->getId().'&company='.$job->getCompany().'&location='.$job->getLocation().'&position='.$job->getPosition())
), ma come abbiamo appena imparato nella sezione precedente, la classe della rotta può essere cambiata. Per la rotta job_show_user
, è meglio usare ~sfPropelRoute
~, essendo la classe ottimizzata per rotte che rappresentano oggetti ##ORM## o insiemi di oggetti ##ORM##:
La voce options
personalizza il comportamento della rotta. Qui, l'opzione model
definisce la classe del modello di ##ORM## (JobeetJob
) relativa alla rotta e l'opzione type
impone che questa rotta sia legata a un oggetto (si può anche usare list
, se una rotta rappresenta un insieme di oggetti).
La rotta job_show_user
ora sa della sua relazione con JobeetJob
e quindi possiamo semplificare la chiamata a ~url_for()
~:
url_for(array('sf_route' => 'job_show_user', 'sf_subject' => $job))
oppure solo:
url_for('job_show_user', $job)
Il primo esempio è utile quando si devono passare più parametri oltre all'oggetto.
Questo funziona perché tutte le variabili nella rotta hanno un corrispondente metodo di accesso nella classe JobeetJob
(ad esempio, la variabile di rotta company
è sostituita con il valore digetCompany()
).
Guardando gli URL generati, non sono proprio come li avremmo voluti:
http://www.jobeet.com.localhost/frontend_dev.php/job/Sensio+Labs/Paris%2C+France/1/Web+Developer
Dobbiamo applicare uno "~slug|Slug~" ai valori delle colonne, sostituendo tutti i caratteri non ASCII con un -
. Apriamo il file JobeetJob
e aggiungiamo i seguenti metodi alla classe:
// lib/model/JobeetJob.php // lib/model/doctrine/JobeetJob.class.php public function getCompanySlug() { return Jobeet::slugify($this->getCompany()); }
public function getPositionSlug()
{
return Jobeet::slugify($this->getPosition());
}
public function getLocationSlug()
{
return Jobeet::slugify($this->getLocation());
}
Quindi, creiamo il file lib/Jobeet.class.php
e vi inseriamo il metodo slugify
:
// lib/Jobeet.class.php class Jobeet { static public function slugify($text) { // sostituisce tutto tranne lettere e punti con - $text = preg_replace('/\W+/', '-', $text); // cancella gli spazi e converte in minuscolo $text = strtolower(trim($text, '-')); return $text; } }
In questo tutorial, non mostriamo mai l'istruzione di apertura
<?php
negli esempi che contengono solo codice PHP, in modo da ottimizzare lo spazio e salvare un po' di alberi. Ovviamente dovete ricordare sempre di aggiungerla quando create un nuovo file PHP. Al contrario non va messa nei file dei template.
Abbiamo definito tre metodi di accesso "virtuali": getCompanySlug()
, getPositionSlug()
e getLocationSlug()
. Essi restituiscono i valori delle rispettive colonne, dopo aver applicato il metodo slugify()
. Ora possiamo sostituire i veri nomi delle colonne con i loro corrispettivi virtuali nella rotta job_show_user
:
Ora abbiamo gli URL che ci aspettavamo:
http://www.jobeet.com.localhost/frontend_dev.php/job/sensio-labs/paris-france/1/web-developer
Ma siamo solo a metà strada. La rotta può generare un URL basato su un oggetto, ma può anche trovare l'oggetto relativo a un URL dato. L'oggetto relativo può essere recuperato col metodo getObject()
dell'oggetto rotta. Quando una richiesta in arrivo viene esaminata, il routing memorizza la rotta corrispondente in modo che possa essere usata nelle azioni. Quindi, cambiamo il metodo executeShow()
per usare l'oggetto rotta per recuperare l'oggetto Jobeet
:
class jobActions extends sfActions { public function executeShow(sfWebRequest $request) { $this->job = $this->getRoute()->getObject(); $this->forward404Unless($this->job); } // ... }
Se si prova a recuperare un lavoro per una id
sconosciuta, si otterrà una pagina di errore 404, ma il messaggio di errore è cambiato:
Questo perché l'~errore 404|Errore 404~ è stato lanciato automaticamente dal metodo getRoute()
. Quindi possiamo semplificare ulteriormente il metodo executeShow
:
class jobActions extends sfActions { public function executeShow(sfWebRequest $request) { $this->job = $this->getRoute()->getObject(); } // ... }
Se non si vuole che la rotta generi un errore 404, si può impostare l'opzione di routing
allow_empty
atrue
.
L'oggetto collegato a una rotta è caricato in modo pigro. Viene recuperato dal database solo se si richiama il metodo
getRoute()
.
Il routing nelle azioni e nei template
In un template, l'helper url_for()
converte un URI interno in un URL esterno. Alcuni altri helper di symfony accettano anche un URI interno come parametro, come l'helper ~link_to()
~, che genera un tag <a>
:
<?php echo link_to($job->getPosition(), 'job_show_user', $job) ?>
Genera il seguente codice HTML:
<a href="/job/sensio-labs/paris-france/1/web-developer">Web Developer</a>
Sia url_for()
che link_to()
possono generare anche URL assolute:
url_for('job_show_user', $job, true); link_to($job->getPosition(), 'job_show_user', $job, true);
Se si vuole generare un URL da un'azione, si può usare il metodo generateUrl()
:
$this->redirect($this->generateUrl('job_show_user', $job));
La famiglia dei metodi "~redirect|Rinvio~"
Ieri abbiamo parlato dei metodi "forward". Tali metodi redirigono la richiesta corrente a un'altra azione senza passare dal browser.
I metodi "redirect" redirigono l'utente a un altro URL. Come per i "forward", si può usare il metodo
redirect()
, o i metodi scorciatoiaredirectIf()
eredirectUnless()
.
Classi di raggruppamento delle rotte
Per il modulo job
, abbiamo già personalizzato la rotta dell'azione show
, ma gli URL per gli altri metodi (index
, new
, edit
, create
, update
, e delete
) sono ancora gestiti dalla rotta default
:
---
default:
url: /
La rotta default
è un ottimo modo per iniziare a scrivere codice senza definire troppe rotta. Ma siccome tale rotta agisce come "acchiappa-tutto", non può essere configurata per necessità specifiche.
Siccome tutte le azioni job
sono legate alla classe del modello JobeetJob
, possiamo definire facilmente una rotta personalizzata ~sfPropelRoute
~ per ciascuno, come abbiamo già fatto per l'azione show
. Ma siccome il modulo job
definisce le classiche sette azioni possibili per un modello, possiamo anche usare la classe ~sfPropelRouteCollection
~. Apriamo il file routing.yml
e modifichiamolo come segue:
La rotta job
qui sopra è una scorciatoia che genera automaticamente le seguenti sette rotte sfPropelRoute
:
Alcune rotte generate da
sfPropelRouteCollection
hanno lo stesso ~URL~. Il routing è comunque in grado di usarle perché hanno differenti ~metodi HTTP~ come richiesta.
Le rotte job_delete
e job_update
richiedono ~metodi HTTP~ che non sono supportati dai browser (rispettivamente ~DELETE
~ e ~PUT
~). Ma funzionano perché symfony li simula. Apriamo il template _form.php
per vedere un esempio:
// apps/frontend/modules/job/templates/_form.php <form action="..." ...> <?php if (!$form->getObject()->isNew()): ?> <input type="hidden" name="sf_method" value="PUT" /> <?php endif; ?> <?php echo link_to( 'Delete', 'job/delete?id='.$form->getObject()->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?') ) ?>
Tutti gli helper di symfony possono essere istruiti per simulare qualsiasi metodo HTTP si vuole, passando il parametro speciale sf_method
.
symfony ha altri parametri speciali simili a
sf_method
, tutti che iniziano col ~prefisso|Prefisso~sf_
. Nelle rotte generate viste sopra, se ne può vedere un altro:sf_format
, che sarà spiegato in un giorno successivo.
Debug delle rotte
Quando si usa un insieme di rotte, a volte è utile elencare le rotte generate. Il task app:routes
mostra tutte le rotte per una data applicazione:
$ php symfony app:routes frontend
Si possono ottenere anche molte informazioni di ~debug|Debug~ per una rotta passando il suo nome come parametro aggiuntivo:
$ php symfony app:routes frontend job_edit
Rotte di default
È una buona pratica definire ~rotte|Rotta~ per tutti i proprio URL. Siccome la rotta job
definisce tutte le rotte necessarie per descrivere l'applicazione Jobeet, si possono rimuovere o commentare le rotte di default nel file di configurazione routing.yml
:
L'applicazione Jobeet deve funzionare come prima.
A domani
Questo giorno è stato riempito di nuove informazioni. Abbiamo imparato come usare il framework del routing di syfmony e come disaccoppiare le nostre URL dall' implementazione tecnica.
Nel tutorial di domani non introdurremo nuovi concetti, ma spenderemo un po' di tempo per approfondire quello che abbiamo visto finora.
ORM
インデックス
Document Index
関連ページリスト
Related Pages
- Giorno 1: Impostare il progetto
- Giorno 2: Il progetto
- Giorno 3: Il ~Modello dei dati~
- Giorno 4: Il controllore e la vista
- Giorno 5: Il routing
- Giorno 6: Di più sul Modello
- Giorno 7: Giocare con la pagina delle categorie
- Giorno 8: I test unitari
- Giorno 9: I test funzionali
- Giorno 10: Form
- Giorno 11: Testare i Form
- Giorno 12: Admin Generator
- Giorno 13: L'utente
- Giorno 14: Feed
- Giorno 15: Web Service
- Giorno 16: Inviare ~email|Email~
- Giorno 17: Ricerca
- Giorno 18: ~AJAX~
- Giorno 19: Internazionalizzazione e Localizzazione
- Giorno 20: I plugin
- Giorno 21: La Cache
- Giorno 22: Il rilascio
- Giorno 23: Un altro sguardo a symfony
- Appendice B - Licenza
- Riconoscimenti
日本語ドキュメント
Japanese Documents
- 2011/01/18 Chapter 17 - Extending Symfony
- 2011/01/18 The generator.yml Configuration File
- 2011/01/18 Les tâches
- 2011/01/18 Emails
- 2010/11/26 blogチュートリアル(8) ビューの作成