Creative Commons License
This work is licensed under a
Creative Commons
Attribution-Share Alike 3.0
Unported License.

Master Symfony2 fundamentals

Be trained by SensioLabs experts (2 to 6 day sessions -- French or English).
trainings.sensiolabs.com

Symfony hosting done right

ServerGrove, outstanding support at the right price for your Symfony hosting needs.
servergrove.com

Discover the SensioLabs Support

Access to the SensioLabs Competency Center for an exclusive and tailor-made support on Symfony
sensiolabs.com

Le rotte

URL ben realizzati sono una cosa assolutamente da avere per qualsiasi applicazione web seria. Questo significa lasciarsi alle spalle URL del tipo index.php?article_id=57 in favore di qualcosa come /read/intro-to-symfony.

Avere flessibilità è ancora più importante. Che cosa succede se è necessario modificare l'URL di una pagina da /blog a /news? Quanti collegamenti bisogna cercare e aggiornare per realizzare la modifica? Se si stanno utilizzando le rotte di Symfony la modifica è semplice.

Le rotte di Symfony2 consentono di definire URL creativi che possono essere mappati in differenti aree dell'applicazione. Entro la fine del capitolo, si sarà in grado di:

  • Creare rotte complesse che mappano i controllori
  • Generare URL all'interno di template e controllori
  • Caricare le risorse delle rotte dai bundle (o da altre parti)
  • Eseguire il debug delle rotte

Le rotte in azione

Una rotta è una mappatura tra uno schema di URL e un controllore. Per esempio, supponiamo che si voglia gestire un qualsiasi URL tipo /blog/my-post o /blog/all-about-symfony e inviarlo a un controllore che cerchi e visualizzi quel post del blog. La rotta è semplice:

  • YAML
    1
    2
    3
    4
    # app/config/routing.yml
    blog_show:
        path:      /blog/{slug}
        defaults:  { _controller: AcmeBlogBundle:Blog:show }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <!-- app/config/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog_show" path="/blog/{slug}">
            <default key="_controller">AcmeBlogBundle:Blog:show</default>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // app/config/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('blog_show', new Route('/blog/{slug}', array(
        '_controller' => 'AcmeBlogBundle:Blog:show',
    )));
    
    return $collection;
    

New in version 2.2: L'opzione path è nuova in Symfony2.2, nelle precedenti versioni veniva usata l'opzione pattern.

Lo schema definito dalla rotta blog_show si comporta come /blog/*, dove al carattere jolly viene dato il nome slug. Per l'URL /blog/my-blog-post, la variabile slug ottiene il valore my-blog-post, che è disponibile per l'utilizzo nel controllore (proseguire nella lettura).

Il parametro _controller è una chiave speciale che dice a Symfony quale controllore dovrebbe essere eseguito quando un URL corrisponde a questa rotta. La stringa _controller è detta nome logico. Segue un pattern che punta a uno specifico metodo di una classe PHP:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// src/Acme/BlogBundle/Controller/BlogController.php
namespace Acme\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BlogController extends Controller
{
    public function showAction($slug)
    {
        // usare la variabile $slug per interrogare la base dati
        $blog = ...;

        return $this->render('AcmeBlogBundle:Blog:show.html.twig', array(
            'blog' => $blog,
        ));
    }
}

Congratulazioni! Si è appena creata la prima rotta, collegandola ad un controllore. Ora, quando si visita /blog/my-post, verrà eseguito il controllore showAction e la variabile $slug avrà valore my-post.

Questo è l'obiettivo delle rotte di Symfony2: mappare l'URL di una richiesta in un controllore. Lungo la strada, si impareranno tutti i trucchi per mappare facilmente anche gli URL più complessi.

Le rotte: funzionamento interno

Quando all'applicazione viene fatta una richiesta, questa contiene un indirizzo alla esatta "risorsa" che il client sta richiedendo. Questo indirizzo è chiamato URL, (o URI) e potrebbe essere /contact, /blog/read-me, o qualunque altra cosa. Prendere ad esempio la seguente richiesta HTTP:

1
GET /blog/my-blog-post

L'obiettivo del sistema delle rotte di Symfony2 è quello di analizzare questo URL e determinare quale controller dovrebbe essere eseguito. L'intero processo è il seguente:

  1. La richiesta è gestita dal front controller di Symfony2 (ad esempio app.php);
  2. Il nucleo di Symfony2 (ad es. il kernel) chiede al router di ispezionare la richiesta;
  3. Il router verifica la corrispondenza dell'URL in arrivo con una specifica rotta e restituisce informazioni sulla rotta, tra le quali il controllore che deve essere eseguito;
  4. Il kernel di Symfony2 esegue il controllore, che alla fine restituisce un oggetto Response.
flusso della richiesta di Symfony2

Lo strato delle rotte è uno strumento che traduce l'URL in ingresso in uno specifico controllore da eseguire.

Creazione delle rotte

Symfony carica tutte le rotte per l'applicazione da un singolo file con la configurazione delle rotte. Il file generalmente è app/config/routing.yml, ma può essere configurato per essere qualunque cosa (compreso un file XML o PHP) tramite il file di configurazione dell'applicazione:

  • YAML
    1
    2
    3
    4
    # app/config/config.yml
    framework:
        # ...
        router:        { resource: "%kernel.root_dir%/config/routing.yml" }
    
  • XML
    <!-- app/config/config.xml -->
    <framework:config ...>
        <!-- ... -->
        <framework:router resource="%kernel.root_dir%/config/routing.xml" />
    </framework:config>
  • PHP
    1
    2
    3
    4
    5
    // app/config/config.php
    $container->loadFromExtension('framework', array(
        // ...
        'router'        => array('resource' => '%kernel.root_dir%/config/routing.php'),
    ));
    

Tip

Anche se tutte le rotte sono caricate da un singolo file, è una pratica comune includere ulteriori risorse di rotte all'interno del file. Per farlo, basta indicare nel file di routing principale quale file esterni debbano essere inclusi. Vedere la sezione Includere risorse esterne per le rotte per maggiori informazioni.

Configurazione di base delle rotte

Definire una rotta è semplice e una tipica applicazione avrà molte rotte. Una rotta di base è costituita da due parti: il pattern da confrontare e un array defaults:

  • YAML
    1
    2
    3
    _welcome:
        path:      /
        defaults:  { _controller: AcmeDemoBundle:Main:homepage }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="_welcome" path="/">
            <default key="_controller">AcmeDemoBundle:Main:homepage</default>
        </route>
    
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('_welcome', new Route('/', array(
        '_controller' => 'AcmeDemoBundle:Main:homepage',
    )));
    
    return $collection;
    

Questa rotta corrisponde alla homepage (/) e la mappa nel controllore AcmeDemoBundle:Main:homepage. La stringa _controller è tradotta da Symfony2 in una funzione PHP effettiva, ed eseguita. Questo processo verrà spiegato a breve nella sezione Schema per il nome dei controllori.

Rotte con segnaposti

Naturalmente il sistema delle rotte supporta rotte molto più interessanti. Molte rotte conterranno uno o più segnaposto "jolly":

  • YAML
    1
    2
    3
    blog_show:
        path:      /blog/{slug}
        defaults:  { _controller: AcmeBlogBundle:Blog:show }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog_show" path="/blog/{slug}">
            <default key="_controller">AcmeBlogBundle:Blog:show</default>
        </route>
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('blog_show', new Route('/blog/{slug}', array(
        '_controller' => 'AcmeBlogBundle:Blog:show',
    )));
    
    return $collection;
    

Lo schema verrà soddisfatto da qualsiasi cosa del tipo /blog/*. Meglio ancora, il valore corrispondente il segnaposto {slug} sarà disponibile all'interno del controllore. In altre parole, se l'URL è /blog/hello-world, una variabile $slug, con un valore hello-world, sarà disponibile nel controllore. Questo può essere usato, ad esempio, per caricare il post sul blog che verifica questa stringa.

Tuttavia lo schema non deve corrispondere semplicemente a /blog. Questo perché, per impostazione predefinita, tutti i segnaposto sono obbligatori. Questo comportamento può essere cambiato aggiungendo un valore segnaposto all'array defaults.

Segnaposto obbligatori e opzionali

Per rendere le cose più eccitanti, aggiungere una nuova rotta che visualizza un elenco di tutti i post disponibili del blog per questa applicazione immaginaria di blog:

  • YAML
    1
    2
    3
    blog:
        path:      /blog
        defaults:  { _controller: AcmeBlogBundle:Blog:index }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog" path="/blog">
            <default key="_controller">AcmeBlogBundle:Blog:index</default>
        </route>
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('blog', new Route('/blog', array(
        '_controller' => 'AcmeBlogBundle:Blog:index',
    )));
    
    return $collection;
    

Finora, questa rotta è la più semplice possibile: non contiene segnaposto e corrisponde solo all'esatto URL /blog. Ma cosa succede se si ha bisogno di questa rotta per supportare l'impaginazione, dove /blog/2 visualizza la seconda pagina dell'elenco post del blog? Bisogna aggiornare la rotta per avere un nuovo segnaposto {page}:

  • YAML
    1
    2
    3
    blog:
        path:      /blog/{page}
        defaults:  { _controller: AcmeBlogBundle:Blog:index }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog" path="/blog/{page}">
            <default key="_controller">AcmeBlogBundle:Blog:index</default>
        </route>
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('blog', new Route('/blog/{page}', array(
        '_controller' => 'AcmeBlogBundle:Blog:index',
    )));
    
    return $collection;
    

Come il precedente segnaposto {slug}, il valore che verifica {page} sarà disponibile all'interno del controllore. Il suo valore può essere usato per determinare quale insieme di post del blog devono essere visualizzati per una data pagina.

Un attimo però! Dal momento che i segnaposto per impostazione predefinita sono obbligatori, questa rotta non avrà più corrispondenza con il semplice /blog. Invece, per vedere la pagina 1 del blog, si avrà bisogno di utilizzare l'URL /blog/1! Dal momento che non c'è soluzione per una complessa applicazione web, modificare la rotta per rendere il parametro {page} opzionale. Questo si fa includendolo nella collezione defaults:

  • YAML
    1
    2
    3
    blog:
        path:      /blog/{page}
        defaults:  { _controller: AcmeBlogBundle:Blog:index, page: 1 }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog" path="/blog/{page}">
            <default key="_controller">AcmeBlogBundle:Blog:index</default>
            <default key="page">1</default>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('blog', new Route('/blog/{page}', array(
        '_controller' => 'AcmeBlogBundle:Blog:index',
        'page' => 1,
    )));
    
    return $collection;
    

Aggiungendo page alla chiave defaults, il segnaposto {page} non è più obbligatorio. L'URL /blog corrisponderà a questa rotta e il valore del parametro page verrà impostato a 1. Anche l'URL /blog/2 avrà corrispondenza, dando al parametro page il valore 2. Perfetto.

/blog {page} = 1
/blog/1 {page} = 1
/blog/2 {page} = 2

Tip

Le rotte con parametri facoltativi alla fine non avranno corrispondenza da richieste con barra finale (p.e. /blog/ non corrisponderà, /blog invece sì).

Aggiungere requisiti

Si dia uno sguardo veloce alle rotte che sono state create finora:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    blog:
        path:      /blog/{page}
        defaults:  { _controller: AcmeBlogBundle:Blog:index, page: 1 }
    
    blog_show:
        path:      /blog/{slug}
        defaults:  { _controller: AcmeBlogBundle:Blog:show }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog" path="/blog/{page}">
            <default key="_controller">AcmeBlogBundle:Blog:index</default>
            <default key="page">1</default>
        </route>
    
        <route id="blog_show" path="/blog/{slug}">
            <default key="_controller">AcmeBlogBundle:Blog:show</default>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('blog', new Route('/blog/{page}', array(
        '_controller' => 'AcmeBlogBundle:Blog:index',
        'page' => 1,
    )));
    
    $collection->add('blog_show', new Route('/blog/{show}', array(
        '_controller' => 'AcmeBlogBundle:Blog:show',
    )));
    
    return $collection;
    

Si riesce a individuare il problema? Notare che entrambe le rotte hanno schemi che verificano URL del tipo /blog/*. Il router di Symfony sceglie sempre la prima rotta corrispondente che trova. In altre parole, la rotta blog_show non sarà mai trovata. Invece, un URL del tipo /blog/my-blog-post verrà abbinato alla prima rotta (blog) restituendo il valore senza senso my-blog-post per il parametro {page}.

URL rotta parametri
/blog/2 blog {page} = 2
/blog/my-blog-post blog {page} = my-blog-post

La risposta al problema è aggiungere rotte obbligatorie. Le rotte in questo esempio potrebbero funzionare perfettamente se lo schema /blog/{page} fosse verificato solo per gli URL dove {page} fosse un numero intero. Fortunatamente, i requisiti possono essere scritti tramite espressioni regolari e aggiunti per ogni parametro. Per esempio:

  • YAML
    1
    2
    3
    4
    5
    blog:
        path:      /blog/{page}
        defaults:  { _controller: AcmeBlogBundle:Blog:index, page: 1 }
        requirements:
            page:  \d+
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog" path="/blog/{page}">
            <default key="_controller">AcmeBlogBundle:Blog:index</default>
            <default key="page">1</default>
            <requirement key="page">\d+</requirement>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('blog', new Route('/blog/{page}', array(
        '_controller' => 'AcmeBlogBundle:Blog:index',
        'page' => 1,
    ), array(
        'page' => '\d+',
    )));
    
    return $collection;
    

Il requisito \d+ è una espressione regolare che dice che il valore del parametro {page} deve essere una cifra (cioè un numero). La rotta blog sarà comunque abbinata a un URL del tipo /blog/2 (perché 2 è un numero), ma non sarà più abbinata a un URL tipo /blog/my-blog-post (perché my-blog-post non è un numero).

Come risultato, un URL tipo /blog/my-blog-post ora verrà correttamente abbinato alla rotta blog_show.

URL rotta parametri
/blog/2 blog {page} = 2
/blog/my-blog-post blog_show {slug} = my-blog-post

Il significato di tutto questo è che l'ordine delle rotte è molto importante. Se la rotta blog_show fosse stata collocata sopra la rotta blog, l'URL /blog/2 sarebbe stato abbinato a blog_show invece di blog perché il parametro {slug} di blog_show non ha requisiti. Utilizzando l'ordinamento appropriato e dei requisiti intelligenti, si può realizzare qualsiasi cosa.

Poiché i requisiti dei parametri sono espressioni regolari, la complessità e la flessibilità di ogni requisito dipende da come li si scrive. Si supponga che la pagina iniziale dell'applicazione sia disponibile in due diverse lingue, in base all'URL:

  • YAML
    1
    2
    3
    4
    5
    homepage:
        path:      /{culture}
        defaults:  { _controller: AcmeDemoBundle:Main:homepage, culture: en }
        requirements:
            culture:  en|fr
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="homepage" path="/{culture}">
            <default key="_controller">AcmeDemoBundle:Main:homepage</default>
            <default key="culture">en</default>
            <requirement key="culture">en|fr</requirement>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('homepage', new Route('/{culture}', array(
        '_controller' => 'AcmeDemoBundle:Main:homepage',
        'culture' => 'en',
    ), array(
        'culture' => 'en|fr',
    )));
    
    return $collection;
    

Per le richieste in entrata, la porzione {culture} dell'URL viene controllata tramite l'espressione regolare (en|fr).

/ {culture} = en
/en {culture} = en
/fr {culture} = fr
/es non si abbina a questa rotta

Aggiungere requisiti al metodo HTTP

In aggiunta agli URL, si può anche verificare il metodo della richiesta entrante (ad esempio GET, HEAD, POST, PUT, DELETE). Si supponga di avere un form contatti con due controllori: uno per visualizzare il form (su una richiesta GET) e uno per l'elaborazione del form dopo che è stato inviato (su una richiesta POST). Questo può essere realizzato con la seguente configurazione per le rotte:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    contact:
        path:     /contact
        defaults: { _controller: AcmeDemoBundle:Main:contact }
        methods:  [GET]
    
    contact_process:
        path:     /contact
        defaults: { _controller: AcmeDemoBundle:Main:contactProcess }
        methods:  [POST]
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="contact" path="/contact" methods="GET">
            <default key="_controller">AcmeDemoBundle:Main:contact</default>
        </route>
    
        <route id="contact_process" path="/contact" methods="POST">
            <default key="_controller">AcmeDemoBundle:Main:contactProcess</default>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('contact', new Route('/contact', array(
        '_controller' => 'AcmeDemoBundle:Main:contact',
    ), array(), array(), '', array(), array('GET')));
    
    $collection->add('contact_process', new Route('/contact', array(
        '_controller' => 'AcmeDemoBundle:Main:contactProcess',
    ), array(), array(), '', array(), array('POST')));
    
    return $collection;
    

New in version L'opzione: methods è stata aggiunta in Symfony2.2. Usare il requisito _method in versioni precedenti.

Nonostante il fatto che queste due rotte abbiano schemi identici (/contact), la prima rotta corrisponderà solo a richieste GET e la seconda rotta corrisponderà solo a richieste POST. Questo significa che è possibile visualizzare il form e inviarlo utilizzando lo stesso URL ma controllori distinti per le due azioni.

Note

Se non viene specificato alcune metodo, la rotta verrà abbinata a tutti i metodi.

Aggiungere un host

New in version 2.2: Il supporto per gli host è stato aggiunto in Symfony 2.2

Si può anche far corrispondere un host HTTP della richiesta in arrivo. Per maggiori informazioni, vedere Corrispondere una rotta in base all'host nella documentazione del componente Routing.

Esempio di rotte avanzate

A questo punto, si ha tutto il necessario per creare una complessa struttura di rotte in Symfony. Quello che segue è un esempio di quanto flessibile può essere il sistema delle rotte:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    article_show:
      path:     /articles/{culture}/{year}/{title}.{_format}
      defaults: { _controller: AcmeDemoBundle:Article:show, _format: html }
      requirements:
          culture:  en|fr
          _format:  html|rss
          year:     \d+
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="article_show" path="/articles/{culture}/{year}/{title}.{_format}">
            <default key="_controller">AcmeDemoBundle:Article:show</default>
            <default key="_format">html</default>
            <requirement key="culture">en|fr</requirement>
            <requirement key="_format">html|rss</requirement>
            <requirement key="year">\d+</requirement>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('homepage', new Route('/articles/{culture}/{year}/{title}.{_format}', array(
        '_controller' => 'AcmeDemoBundle:Article:show',
        '_format' => 'html',
    ), array(
        'culture' => 'en|fr',
        '_format' => 'html|rss',
        'year' => '\d+',
    )));
    
    return $collection;
    

Come si sarà visto, questa rotta verrà soddisfatta solo quando la porzione {culture} dell'URL è en o fr e se {year} è un numero. Questa rotta mostra anche come sia possibile utilizzare un punto tra i segnaposto al posto di una barra. Gli URL corrispondenti a questa rotta potrebbero essere del tipo:

  • /articles/en/2010/my-post
  • /articles/fr/2010/my-post.rss

Questo esempio mette in evidenza lo speciale parametro per le rotte _format. Quando si utilizza questo parametro, il valore cercato diventa il "formato della richiesta" dell'oggetto Request. In definitiva, il formato della richiesta è usato per cose tipo impostare il Content-Type della risposta (per esempio una richiesta di formato json si traduce in un Content-Type con valore application/json). Può essere utilizzato anche nel controllore per rendere un template diverso per ciascun valore di _format. Il parametro _format è un modo molto potente per rendere lo stesso contenuto in formati diversi.

Note

A volte si desidera che alcune parti delle rotte siano configurabili in modo globale. Symfony2.1 fornisce un modo per poterlo fare, sfruttando i parametri del contenitore di servizi. Si può approfondire in "Come usare i parametri del contenitore di servizi nelle rotte.

Parametri speciali per le rotte

Come si è visto, ogni parametro della rotta o valore predefinito è disponibile come parametro nel metodo del controllore. Inoltre, ci sono tre parametri speciali: ciascuno aggiunge una funzionalità all'interno dell'applicazione:

  • _controller: Come si è visto, questo parametro viene utilizzato per determinare quale controllore viene eseguito quando viene trovata la rotta;
  • _format: Utilizzato per impostare il formato della richiesta (per saperne di più);
  • _locale: Utilizzato per impostare il locale sulla richiesta (per saperne di più);

Tip

Se si usa il parametro _locale in una rotta, il valore sarà memorizzato nella sessione, in modo che le richieste successive lo mantengano.

Schema per il nome dei controllori

Ogni rotta deve avere un parametro _controller, che determina quale controllore dovrebbe essere eseguito quando si accoppia la rotta. Questo parametro utilizza un semplice schema stringa, chiamato nome logico del controllore, che Symfony mappa in uno specifico metodo PHP di una certa classe. Lo schema ha tre parti, ciascuna separata da due punti:

bundle:controllore:azione

Per esempio, se _controller ha valore AcmeBlogBundle:Blog:show significa:

Bundle Classe del controllore Nome del metodo
AcmeBlogBundle BlogController showAction

Il controllore potrebbe essere simile a questo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// src/Acme/BlogBundle/Controller/BlogController.php
namespace Acme\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BlogController extends Controller
{
    public function showAction($slug)
    {
        // ...
    }
}

Si noti che Symfony aggiunge la stringa Controller al nome della classe (Blog => BlogController) e Action al nome del metodo (show => showAction).

Si potrebbe anche fare riferimento a questo controllore con il nome completo di classe e metodo: Acme\BlogBundle\Controller\BlogController::showAction. Ma seguendo alcune semplici convenzioni, il nome logico è più conciso e permette una maggiore flessibilità.

Note

Oltre all'utilizzo del nome logico o il nome completo della classe, Symfony supporta un terzo modo per fare riferimento a un controllore. Questo metodo utilizza solo un separatore due punti (ad esempio nome_servizio:indexAction) e fa riferimento al controllore come un servizio (vedere Definire i controllori come servizi).

Parametri delle rotte e parametri del controllore

I parametri delle rotte (ad esempio {slug}) sono particolarmente importanti perché ciascuno è reso disponibile come parametro al metodo del controllore:

1
2
3
4
public function showAction($slug)
{
  // ...
}

In realtà, l'intera collezione defaults viene unita con i valori del parametro per formare un singolo array. Ogni chiave di questo array è disponibile come parametro sul controllore.

In altre parole, per ogni parametro del metodo del controllore, Symfony cerca per un parametro della rotta con quel nome e assegna il suo valore a tale parametro. Nell'esempio avanzato di cui sopra, qualsiasi combinazioni (in qualsiasi ordine) delle seguenti variabili potrebbe essere usati come parametri per il metodo showAction():

  • $culture
  • $year
  • $title
  • $_format
  • $_controller

Dal momento che il segnaposto e la collezione defaults vengono uniti insieme, è disponibile anche la variabile $_controller. Per una trattazione più dettagliata, vedere I parametri delle rotte come parametri del controllore.

Tip

È inoltre possibile utilizzare una variabile speciale $_route, che è impostata sul nome della rotta che è stata abbinata.

Includere risorse esterne per le rotte

Tutte le rotte vengono caricate attraverso un singolo file di configurazione, generalmente app/config/routing.yml (vedere Creazione delle rotte sopra). In genere, però, si desidera caricare le rotte da altri posti, come un file di rotte presente all'interno di un bundle. Questo può essere fatto "importando" il file:

  • YAML
    1
    2
    3
    # app/config/routing.yml
    acme_hello:
        resource: "@AcmeHelloBundle/Resources/config/routing.yml"
    
  • XML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- app/config/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <import resource="@AcmeHelloBundle/Resources/config/routing.xml" />
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    // app/config/routing.php
    use Symfony\Component\Routing\RouteCollection;
    
    $collection = new RouteCollection();
    $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php"));
    
    return $collection;
    

Note

Quando si importano le risorse in formato YAML, la chiave (ad esempio acme_hello) non ha senso. Basta essere sicuri che sia unica, in modo che nessun'altra linea la sovrascriva.

La chiave resource carica la data risorsa di rotte. In questo esempio la risorsa è il percorso completo di un file, dove la sintassi scorciatoia @AcmeHelloBundle viene risolta con il percorso del bundle. Il file importato potrebbe essere tipo questo:

  • YAML
    1
    2
    3
    4
     # src/Acme/HelloBundle/Resources/config/routing.yml
    acme_hello:
         path:     /hello/{name}
         defaults: { _controller: AcmeHelloBundle:Hello:index }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <!-- src/Acme/HelloBundle/Resources/config/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="acme_hello" path="/hello/{name}">
            <default key="_controller">AcmeHelloBundle:Hello:index</default>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // src/Acme/HelloBundle/Resources/config/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('acme_hello', new Route('/hello/{name}', array(
        '_controller' => 'AcmeHelloBundle:Hello:index',
    )));
    
    return $collection;
    

Le rotte di questo file sono analizzate e caricate nello stesso modo del file principale delle rotte.

Prefissare le rotte importate

Si può anche scegliere di fornire un "prefisso" per le rotte importate. Per esempio, si supponga di volere che la rotta acme_hello abbia uno schema finale con /admin/hello/{name} invece di /hello/{name}:

  • YAML
    1
    2
    3
    4
    # app/config/routing.yml
    acme_hello:
        resource: "@AcmeHelloBundle/Resources/config/routing.yml"
        prefix:   /admin
    
  • XML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- app/config/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <import resource="@AcmeHelloBundle/Resources/config/routing.xml" prefix="/admin" />
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    // app/config/routing.php
    use Symfony\Component\Routing\RouteCollection;
    
    $collection = new RouteCollection();
    $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php"), '/admin');
    
    return $collection;
    

La stringa /admin ora verrà preposta allo schema di ogni rotta caricata dalla nuova risorsa delle rotte.

Tip

Si possono anche definire le rotte tramite annotazioni. Vedere la documentazione di FrameworkExtraBundle per scoprire come.

Espressioni regolari per gli host nelle rotte importate

New in version 2.2: Il supporto per gli host è stato aggiunto in Symfony 2.2

Si può impostare un'espressione regolare sull'host nelle rotte importate. Per maggiori informazioni, vedere Aggiungere un'espressione regolare.

Visualizzare e fare il debug delle rotte

L'aggiunta e la personalizzazione di rotte è utile, ma lo è anche essere in grado di visualizzare e recuperare informazioni dettagliate sulle rotte. Il modo migliore per vedere tutte le rotte dell'applicazione è tramite il comando di console router:debug. Eseguire il comando scrivendo il codice seguente dalla cartella radice del progetto

1
php app/console router:debug

Il comando visualizzerà un utile elenco di tutte le rotte configurate nell'applicazione:

1
2
3
4
5
6
homepage              ANY       /
contact               GET       /contact
contact_process       POST      /contact
article_show          ANY       /articles/{culture}/{year}/{title}.{_format}
blog                  ANY       /blog/{page}
blog_show             ANY       /blog/{slug}

Inoltre è possibile ottenere informazioni molto specifiche su una singola rotta mettendo il nome della rotta dopo il comando:

1
$ php app/console router:debug article_show

New in version 2.1: Il comando router:match è stato aggiunto in Symfony 2.1

Si può verificare quale rotta, se esiste, corrisponda a un percorso, usando il comando router:match:

1
2
$ php app/console router:match /articles/en/2012/article.rss
Route "article_show" matches

Generazione degli URL

Il sistema delle rotte dovrebbe anche essere usato per generare gli URL. In realtà, il routing è un sistema bidirezionale: mappa l'URL in un controllore + parametri e una rotta + parametri di nuovo in un URL. I metodi match() e generate() formano questo sistema bidirezionale. Si prenda la rotta dell'esempio precedente blog_show:

1
2
3
4
5
6
7
8
$params = $this->get('router')->match('/blog/my-blog-post');
// array(
//     'slug' => 'my-blog-post',
//     '_controller' => 'AcmeBlogBundle:Blog:show',
// )

$uri = $this->get('router')->generate('blog_show', array('slug' => 'my-blog-post'));
// /blog/my-blog-post

Per generare un URL, è necessario specificare il nome della rotta (ad esempio blog_show) ed eventuali caratteri jolly (ad esempio slug = my-blog-post) usati nello schema per questa rotta. Con queste informazioni, qualsiasi URL può essere generata facilmente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class MainController extends Controller
{
    public function showAction($slug)
    {
        // ...

        $url = $this->generateUrl(
            'blog_show',
            array('slug' => 'my-blog-post')
        );
    }
}

Note

In controllori che estendono la classe base di Symfony Controller, si può usare il metodo generateUrl(), che richiama il metodo generate() del servizio router.

In una delle prossime sezioni, si imparerà a generare URL dall'interno di un template.

Tip

Se la propria applicazione usa richieste AJAX, si potrebbe voler generare URL in JavaScript, che siano basate sulla propria configurazione delle rotte. Usando FOSJsRoutingBundle, lo si può fare:

1
2
3
4
var url = Routing.generate(
    'blog_show',
    {"slug": 'my-blog-post'}
);

Per ultetiori informazioni, vedere la documentazione del bundle.

Generare URL assoluti

Per impostazione predefinita, il router genera URL relativi (ad esempio /blog). Per generare un URL assoluto, è sufficiente passare true come terzo parametro del metodo generate():

1
2
$router->generate('blog_show', array('slug' => 'my-blog-post'), true);
// http://www.example.com/blog/my-blog-post

Note

L'host che viene usato quando si genera un URL assoluto è l'host dell'oggetto Request corrente. Questo viene rilevato automaticamente in base alle informazioni sul server fornite da PHP. Quando si generano URL assolute per script che devono essere eseguiti da riga di comando, sarà necessario impostare manualmente l'host desiderato sull'oggetto RequestContext:

1
$router->getContext()->setHost('www.example.com');

Generare URL con query string

Il metodo generate accetta un array di valori jolly per generare l'URI. Ma se si passano quelli extra, saranno aggiunti all'URI come query string:

1
2
$router->generate('blog', array('page' => 2, 'category' => 'Symfony'));
// /blog/2?category=Symfony

Generare URL da un template

Il luogo più comune per generare un URL è all'interno di un template quando si creano i collegamenti tra le varie pagine dell'applicazione. Questo viene fatto esattamente come prima, ma utilizzando una funzione helper per i template:

  • Twig
    1
    2
    3
    <a href="{{ path('blog_show', { 'slug': 'my-blog-post' }) }}">
      Read this blog post.
    </a>
    
  • PHP
    1
    2
    3
    <a href="<?php echo $view['router']->generate('blog_show', array('slug' => 'my-blog-post')) ?>">
        Read this blog post.
    </a>
    

Possono anche essere generati URL assoluti.

  • Twig
    1
    2
    3
    <a href="{{ url('blog_show', {'slug': 'my-blog-post'}) }}">
      Read this blog post.
    </a>
    
  • PHP
    1
    2
    3
    <a href="<?php echo $view['router']->generate('blog_show', array('slug' => 'my-blog-post'), true) ?>">
        Read this blog post.
    </a>
    

Riassunto

Il routing è un sistema per mappare l'URL delle richieste in arrivo in una funzione controllore che dovrebbe essere chiamata a processare la richiesta. Il tutto permette sia di creare URL "belle" che di mantenere la funzionalità dell'applicazione disaccoppiata da questi URL. Il routing è un meccanismo bidirezionale, nel senso che dovrebbe anche essere utilizzato per generare gli URL.