giovedì 5 novembre 2009

Generare URL SEF in symfony

Quando si sono delineate le funzionalità essenziali di Lyra, nell'elenco era presente la creazione di URL semplificate, 'pulite' o SEF (ognuno utilizzi la terminologia che preferisce). Fino a questo momento abbiamo incontrato tre tipi di URL.

1) Prima pagina e questa ovviamente non pone problemi particolari.

http://www.example.com/

2) Vista articolo a tutta pagina. Esempio:

http://www.example.com/article/show/id/2

3) Lista articoli per etichetta. Esempio:

http://www.example.com/article/label/id/1

Queste URL non sono troppo lunghe e non presentano una lista di parametri sotto forma di query string, sono sicuramente migliori di, ad esempio:

http://www.example.com/index.php?module=article&action=show&id=2

Però credo che molti utenti avrebbero come minimo qualche dubbio a definirle vere e proprie URL SEF. Va detto che hanno il vantaggio di poter essere generate in base a rotte predefinite senza che sia necessario modificare alcun file di configurazione, se si è disposti a rinunciare a questo vantaggio si possono personalizzare le URL della propria applicazione come si preferisce.

Come esempio modificheremo la URL della vista articolo a tutta pagina in modo che risulti così:

http://www.example.com/article/titolo-articolo.html

Per prima cosa definiamo una nuova rotta. L'ordine delle definizioni è importante per cui inseriamo la nostra prima di quelle già presenti (rotte di default)

apps/frontend/config/routing.yml

article_show:
  url: /article/:slug.html
  class: sfDoctrineRoute
  options:
    type: object
    model: LyraArticle
    method: findItem
  param:
    module: article
    action: show
  requirements:
    sf_method: [get]

  • url indica il formato della URL generata dalla rotta: gli identificatori preceduti da ':' (es. :slug) sono variabili;
  • class è la classe della rotta: sfDoctrineRoute indica che questa rotta è associata ad un oggetto (record) o insieme di oggetti (collezione di record) istanze di una classe modello Doctrine;
  • options indica che la rotta è associata ad un singolo record (type è object, l'alternativa, list indicherebbe l'associazione con una collezione di record) di classe LyraArticle. findItem è il metodo del modello che servirà a reperire l'oggetto corrispondente alla rotta, nel nostro caso il record articolo da visualizzare;
  • param indica che la richiesta che corrisponde a questa rotta verrà processata dall'azione show del modulo article;
  • requirements indica che il metodo della richiesta deve essere GET.

Bisogna poi modificare i template in modo che i link agli articoli siano generati in base alla rotta appena definita.

apps/frontend/modules/article/templates/_list.php

...
  <h2 class="article-title">
    <?php echo link_to($item->getTitle(), '@article_show?slug=' . $item->getSlug())?>
  </h2>
...
  <span class="article-readmore">
    <?php echo link_to(__('LINK_READMORE'), '@article_show?slug=' . $item->getSlug(), array('title'=>$item->getTitle()))?>
  </span>
...

Alla funzione helper link_to() passiamo direttamente il nome della rotta preceduto da '@' a cui vengono accodati i valori per le parti variabili della rotta (:slug) nella stessa forma che sarebbe utilizzata per costruire una query string. Chiaramente il link risultante viene generato come indicato dal parametro url nella definizione della rotta, cioè in un formato a 'segmenti' senza alcun parametro in forma di query string.

Resta da vedere come viene individuato l'articolo da visualizzare quando viene cliccato uno dei link generati nel modo appena visto. Abbiamo detto che una rotta è associata ad un oggetto (o ad una collezione di oggetti), quindi partendo dalla rotta possiamo risalire all'oggetto corrispondente. Questo avviene nell'azione (le righe sbarrate sono quelle presenti nella versione precedente e ora rimosse)

apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
  ...
  public function executeShow(sfWebRequest $request)
  {
    $this->item = Doctrine::getTable('LyraArticle')
      ->find($request->getParameter('id'));
    $this->forward404Unless($this->item);
    $this->item = $this->getRoute()->getObject();
    ...
   }
 ...  
} // fine articleActions

Il metodo getObject() invoca il metodo definito nelle options della rotta (findItem()) e gli passa un array di parametri contenente le variabili della rotta. Nel metodo dobbiamo eseguire una query in base a questi parametri e ritornare l'oggetto oppure false: nel primo caso l'oggetto viene passato indietro all'azione ed il flusso continua; nel secondo caso nel codice di getObject() viene sollevata un'eccezione che genera un errore 404.

Proprio perché nel caso di oggetto non trovato l'errore viene gestito internamente abbiamo rimosso dall'azione la riga

$this->forward404Unless($this->item);

Se per qualsiasi ragione vogliamo continuare a gestire questa situazione di errore nel codice dell'azione, basta modificare la definizione della rotta in questo modo

article_show:
  ...
  options:
    model: LyraArticle
    type: object
    method: findItem
    allow_empty: true
...

Con l'opzione allow_empty impostata a true il metodo getObject() ritorna null in caso di oggetto non trovato senza generare internamente nessuna eccezione. Chiaramente a questo punto dovremmo reintrodurre la chiamata al metodo forward404Unless() o comunque gestire la situazione opportunamente. Il codice ad esempio dovrebbe essere modificato così:

apps/frontend/modules/article/actions/actions.class.php

class articleActions extends sfActions
{
  ...
  public function executeShow(sfWebRequest $request)
  {    
    $this->item = $this->getRoute()->getObject();
    $this->forward404Unless($this->item);
    ...
   }
 ...  
} // fine articleActions

Resta solo da implementare findItem() nel modello.

lib/model/doctrine/LyraArticleTable.class.php

class LyraArticleTable extends Doctrine_Table
{
...
  public function findItem($params = array())
  {
    if(!isset($params['slug'])) {
      return false;
    }
    $q = $this->getActiveItemsQuery();
    $q->andWhere($q->getRootAlias() .'.slug = ?', $params['slug']);
    
    return $q->fetchOne();
  }
} // fine LyraArticleTable

Nel nostro esempio l'array di parametri contiene un solo elemento, il valore del campo slug dell'articolo.

Ho inoltre aggiunto una rotta (article_label) per dare questo formato alle URL della pagina con l'elenco degli articoli per etichetta generate dal componente visto nei due articoli precedenti:

http://www.example.com/article/label/slug-etichetta

Il procedimento è del tutto analogo a quello appena visto, il codice è disponibile nel repository (revisione 18) insieme a tutto quello illustrato sopra.

Naturalmente le possibilità offerte da symfony non si fermano certo qui. Si potrebbe includere la data dell'articolo nella URL per creare un permalink in stile blog

http://www.example.com/article/2009/11/titolo-articolo.html

Oppure includere le etichette/categorie

http://www.example.com/article/javascript/jquery/articolo-su-jquery.html

Ma è prematuro continuare lo sviluppo di questa parte adesso. Il formato delle URL dei contenuti è un aspetto importante di un cms e richiederà ulteriore lavoro. Al momento va data la precedenza ad alcune parti essenziali dell'applicazione che mancano completamente, ad esempio la gestione del backend di cui non abbiamo scritto neppure una riga. Si inizierà dalla prossima volta.

Nessun commento:

Posta un commento

Nota. Solo i membri di questo blog possono postare un commento.