Enrutando

Esta es una introducción para entender los conceptos detrás del enrutado del CMF. Para la documentación de referencia por favor ve Enrutando y RoutingExtraBundle.

Concepto

Why a new Routing Mechanism?

Los CMS son sitios altamente dinámicos, donde la mayoría del contenido lo gestionan los administradores en lugar de los desarrolladores. El número de páginas disponibles fácilmente puede alcanzar miles, lo cual normalmente es multiplicado por el número de traducciones disponibles. Mejor accesibilidad y prácticas SEO, así como preferencias de usuario dictan que direcciones URL deberían ser definibles por los gestores de contenido.

El mecanismo de enrutado predefinido de Symfony2, con enfoque de archivos de configuración, no es la mejor solución para este problema, puesto que no es apropiado para manejar rutas dinámicas definidas por el usuario, ni escalan bien para un gran número de rutas.

The Solution

Para afrontar estos problemas, se desarrolló un nuevo sistema de enrutado, este tiene en cuenta las necesidades típicas de enrutado de un CMS:

  • User defined URLs;
  • Multi-site;
  • Multi-language;
  • Tree-like structure for easier management;
  • Contenido, Menú y separación de Rutas para flexibilidad adicional.

Con estos requisitos en mente, fue desarrollado el componente Routing del CMF de Symfony.

La ChainRouter

En el núcleo del CMF de Symfony el componente de enrutado es la ChainRouter. Esta se utiliza como sustituta para el sistema de enrutado predefinido de Symfony2 y, como tal, es responsable de determinar cuál Controlador manejará cada petición.

The ChainRouter works by accepting a set of prioritized routing strategies, Symfony\Component\Routing\RouterInterface implementations, commonly referred to as “Routers”. The routers are responsible for matching an incoming request to an actual Controller and, to do so, the ChainRouter iterates over the configured Routers according to their configured priority:

  • YAML
    # app/config/config.yml
    symfony_cmf_routing_extra:
        chain:
            routers_by_id:
                # enable the DynamicRouter with high priority to allow overwriting
                # configured routes with content
                symfony_cmf_routing_extra.dynamic_router: 200
    
                # activa el enrutador predefinido de symfony con
                # baja prioridad
                router.default: 100
    
  • XML
    <!-- app/config/config.xml -->
    <symfony-cmf-routing-extra:config>
        <symfony-cmf-routing-extra:chain>
            <symfony-cmf-routing-extra:routers-by-id
                id="symfony-cmf-routing-extra.dynamic-router">
                200
            </symfony-cmf-routing-extra:routers-by-id>
    
            <symfony-cmf-routing-extra:routers-by-id
                id="router.default">
                100
            </symfony-cmf-routing-extra:routers-by-id>
        </symfony-cmf-routing-extra:chain>
    </symfony-cmf-routing-extra:config>
    
  • PHP
    // app/config/config.php
    $container->loadFromExtension('symfony_cmf_routing_extra', array(
        'chain' => array(
            'routers_by_id' => array(
                'symfony_cmf_routing_extra.dynamic_router' => 200,
                'router.default'                           => 100,
            ),
        ),
    ));
    

You can also load Routers using tagged services, by using the router tag and an optional priority. Mientras más alta prioridad, más temprano será consultado tu enrutador para emparejar la ruta. Si no especificas la prioridad, tu enrutador vendrá al último. Si hay varios enrutadores con la misma prioridad, el orden entre ellos es indeterminado. El servicio etiquetado se verá como este:

  • YAML
    services:
        my_namespace.my_router:
            class: "%my_namespace.my_router_class%"
            tags:
                    - { name: router, priority: 300 }
    
  • XML
    <service id="my_namespace.my_router" class="%my_namespace.my_router_class%">
        <tag name="router" priority="300" />
        <!-- ... -->
    </service>
    
  • PHP
    $container
        ->register('my_namespace.my_router', '%my_namespace.my_router_class%')
        ->addTag('router', array('priority' => 300))
    ;
    

El sistema de enrutado del CMF de Symfony añade un nuevo DynamicRouter, el cual complementa el Router predefinido de Symfony2.

The Default Symfony2 Router

A pesar de que reemplaza el mecanismo de enrutado predefinido, el Routing del CMF de Symfony te permite seguir utilizando el sistema existente. De hecho, el enrutado predefinido está habilitado por omisión, así que puedes seguir utilizando las rutas que declaraste en tus archivos de configuración, o como fueron declaradas por otros paquetes.

El DynamicRouter

Este enrutador puede cargar instancias de Ruta dinámicamente desde un determinado proveedor. Luego usa un proceso para emparejar la petición entrante a una Ruta específica, la cual en turno suele determinar a qué Controlador enviar la petición.

La configuración predefinida del paquete declara que el DynamicRouter está inhabilitado por omisión. Para activarlo, sólo añade lo siguiente a tu archivo de configuración:

  • YAML
    # app/config/config.yml
    symfony_cmf_routing_extra:
        dynamic:
            enabled: true
    
  • XML
    <!-- app/config/config.xml -->
    <symfony-cmf-routing-extra:config>
        <symfony-cmf-routing-extra:dynamic enabled="true" />
    </symfony-cmf-routing-extra:config>
    
  • PHP
    // app/config/config.php
    $container->loadFromExtension('symfony_cmf_routing_extra', array(
        'dynamic' => array(
            'enabled' => true,
        ),
    ));
    

Esta es la configuración mínima requirida para cargar el DynamicRouter como servicio, esta lo capacita para realizar cualquier enrutamiento. De hecho, cuándo exploras las páginas predefinidas que vienen con la EE del CMF de Symfony, el DynamicRouter es el que empareja tus peticiones con los Controladores y Plantillas.

Getting the Route Object

Puedes configurar el proveedor a usar para adaptarlo a las necesidades de cada implementación, este debe implementar la RouteProviderInterface. Como parte de este paquete, se proporciona una implementación para el PHPCR-ODM, pero fácilmente puedes crear una propia, puesto que el Enrutador es de almacenamiento agnóstico. El proveedor predefinido carga la ruta como el camino en la petición y todas las rutas padre permiten que algunos segmentos de la ruta sean parámetros.

Para información más detallada sobre esta implementación y cómo la puedes personalizar o extender, consulta el RoutingExtraBundle.

El DynamicRouter es capaz de emparejar la petición entrante con un objeto Ruta del proveedor subyacente. Los detalles sobre cómo se lleva a cabo el proceso de emparejamiento se pueden encontrar en Enrutando.

Nota

Para hacer que el proveedor de rutas encuentre rutas, también necesitas proporcionar los datos en tu almacenamiento. Con PHPCR-ODM, esto se hace a través de la interfaz de admininistración (ve más adelante) o con accesorios.

No obstante, antes de poder explicar cómo hacerlo, necesitas entender cómo trabaja el DynamicRouter. An example will come later in this document.

Consiguiendo el controlador y la plantilla

Una Ruta necesita especificar cuál Controlador debería manejar una Petición específica. El DynamicRouter utiliza uno de varios métodos posibles para determinarlo (por orden de precedencia):

  • Explicito: The stored Route document itself can explicitly declare the target Controller by specifying the ‘_controller’ value in getRouteDefaults().
  • Por alias: the Route returns a ‘type’ value in getRouteDefaults(), which is then matched against the provided configuration from config.yml
  • By class: requires the Route instance to implement RouteObjectInterface and return an object for getRouteContent(). The returned class type is then matched against the provided configuration from config.yml.
  • Predefinido: Si está configurado, utilizará un controlador predefinido.

Apart from this, the DynamicRouter is also capable of dynamically specifying which Template will be used, in a similar way to the one used to determine the Controller (in order of precedence):

  • Explicito: The stored Route document itself can explicitly declare the target Template in getRouteDefaults().
  • Por clase: requires the Route instance to implement RouteObjectInterface and return an object for getRouteContent(). The returned class type is then matched against the provided configuration from config.yml.

Aquí tienes un ejemplo sobre cómo configurar las opciones mencionadas anteriormente:

  • YAML
    # app/config/config.yml
    symfony_cmf_routing_extra:
        dynamic:
            generic_controller: symfony_cmf_content.controller:indexAction
            controllers_by_type:
                editablestatic: sandbox_main.controller:indexAction
            controllers_by_class:
                Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent: symfony_cmf_content.controller::indexAction
            templates_by_class:
                Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent: SymfonyCmfContentBundle:StaticContent:index.html.twig
    
  • XML
    <!-- app/config/config.xml -->
    <symfony-cmf-routing-extra:config>
        <symfony-cmf-routing-extra:dynamic
            generic-controller="symfony_cmf_content.controllerindexAction"
        >
            <symfony-cmf-routing-extra:controllers-by-type
                type="editablestatic"
            >
                sandbox_main.controller:indexAction
            </symfony-cmf-routing-extra:controllers-by-type>
    
            <symfony-cmf-routing-extra:controllers-by-class
                class="Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent"
            >
                symfony_cmf_content.controller::indexAction
            </symfony-cmf-routing-extra:controllers-by-class>
    
            <symfony-cmf-routing-extra:templates-by-class
                alias="Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent"
            >
                SymfonyCmfContentBundle:StaticContent:index.html.twig
            </symfony-cmf-routing-extra:templates-by-class>
        </symfony-cmf-routing-extra:dynamic>
    </symfony-cmf-routing-extra:config>
    
  • PHP
    // app/config/config.php
    $container->loadFromExtension('symfony_cmf_routing_extra', array(
        'dynamic' => array(
            'generic_controller' => 'symfony_cmf_content.controller:indexAction',
            'controllers_by_type' => array(
                'editablestatic' => 'sandbox_main.controller:indexAction',
            ),
            'controllers_by_class' => array(
                'Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent' => 'symfony_cmf_content.controller::indexAction',
            ),
            'templates_by_class' => array(
                'Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent' => 'SymfonyCmfContentBundle:StaticContent:index.html.twig',
            ),
        ),
    ));
    

Ten en cuenta que enabled: true ya no está presente. It’s only required if no other configuration parameter is provided. The router is automatically enabled as soon as you add any other configuration to the dynamic entry.

Nota

Internamente, el componente Routing asocia estas opciones de configuración a varias instancias de la RouteEnhancerInterface. El alcance real de estos potenciadores es mucho mayor, y puedes encontrar más información sobre ellos en la página Enrutando de la documentación.

Linking a Route with a Model Instance

Dependiendo de la lógica de tu aplicación, una URL solicitada puede tener una instancia del modelo asociado en la base de datos. Estas Rutas pueden implementar la RouteObjectInterface, y opcionalmente regresar una instancia del modelo, esta automáticamente será pasada al Controlador como la variable $contentDocument, si la declaras como parámetro.

Ten en cuenta que una Ruta puede implementar la interfaz mencionada anteriormente pero todavía no regresar ninguna instancia del modelo, en cuyo caso ningún objeto asociado será proporcionado.

Además, las Rutas que implementen esta interfaz también pueden tener un nombre de Ruta personalizado, en vez del predefinido compatible con el núcleo de Symfony, y pueden contener cualquier carácter. Esto te permite, por ejemplo, poner una ruta como el nombre de la ruta.

Redirecciones

Puedes construir redirecciones implementando la RedirectRouteInterface. Si estás utilizando el proveedor de ruta PHPCR-ODM predefinido, uno listo para usar la implementación es proporcionado en el Documento RedirectRoute. Este puede redirigir o bien a una URI absoluta, a una Ruta nombrada que puede generar cualquier Enrutador en la cadena o a otro objeto Ruta conocido por el proveedor de rutas. La redirección real es manejada por un Controlador específico, el cual puedes configurar como gustes:

  • YAML
    # app/config/config.yml
    symfony_cmf_routing_extra:
        controllers_by_class:
            Symfony\Cmf\Component\Routing\RedirectRouteInterface:  symfony_cmf_routing_extra.redirect_controller:redirectAction
    
  • XML
    <!-- app/config/config.xml -->
    <symfony-cmf-routing-extra:config>
        <symfony-cmf-routing-extra:controllers-by-class
            class="Symfony\Cmf\Component\Routing\RedirectRouteInterface">
            symfony_cmf_routing_extra.redirect_controller:redirectAction
        </symfony-cmf-routing-extra:controllers-by-class>
    </symfony-cmf-routing-extra:config>
    
  • PHP
    // app/config/config.php
    $container->loadFromExtension('symfony_cmf_routing_extra', array(
        'controllers_by_class' => array(
            'Symfony\Cmf\Component\Routing\RedirectRouteInterface' => 'symfony_cmf_routing_extra.redirect_controller:redirectAction',
        ),
    ));
    

Nota

The actual configuration for this association exists as a service, not as part of a config.yml file. Como se explicó antes, puedes utilizar cualquier enfoque.

URL Generation

El componente de enrutado del CMF de Symfony utiliza los componentes predefinidos de Symfony2 para manejar la generación de rutas, así que puedes utilizar los métodos predefinidos para generar tus url, con unas cuantas posibilidades añadidas:

  • Pass either an implementation of RouteObjectInterface or a RouteAwareInterface as name parameter
  • Or supply an implementation of ContentRepositoryInterface and the id of the model instance as parameter content_id

The route generation handles locales as well, see ContentAwareGenerator y regiones.

The PHPCR-ODM Route Document

Como se mencionó arriba, puedes utilizar cualquier proveedor de rutas. El ejemplo en esta sección aplica si utilizas el proveedor de rutas PHPCR-ODM predefinido.

Todas las rutas están localizadas bajo una ruta configurada como la raíz, por ejemplo '/cms/rutas'. A new route can be created in PHP code as follows:

use Symfony\Cmf\Bundle\RoutingExtraBundle\Document\Route;

$route = new Route;
$route->setParent($dm->find(null, '/routes'));
$route->setName('projects');

// enlaza un contenido a la ruta
$content = new Content('my content');
$route->setRouteContent($content);

// ahora configura algún parámetro, no olvides la barra inclinada inicial si
// quieres /proyectos/{id} y no /proyectos{id}
$route->setVariablePattern('/{id}');
$route->setRequirement('id', '\d+');
$route->setDefault('id', 1);

This will give you a document that matches the URL /projects/<number> but also /projects as there is a default for the id parameter.

Your controller can expect the $id parameter as well as the $contentDocument as we set a content on the route. The content could be used to define an intro section that is the same for each project or other shared data. If you don’t need content, you can just not set it in the document.

For more details, see the route document section in the RoutingExtraBundle documentation.

Integrando con SonataAdmin

Si en la sección require del archivo composer.json añades el sonata-project/doctrine-phpcr-admin-bundle, los documentos de ruta serán expuestos en el SonataDoctrinePhpcrAdminBundle. Para instrucciones sobre cómo configurar este paquete ve SonataDoctrinePhpcrAdminBundle.

De manera predefinida, use_sonata_admin se pone automáticamente basándose en si SonataDoctrinePhpcrAdminBundle está disponible pero lo puedes desactivar explícitamente si no tienes habilitado sonata, o activarlo explícitamente para conseguir un error si sonata se vuelve inasequible.

Tienes un par de opciones de configuración para la administración. El content_basepath apunta a la raíz de tus documentos de contenido.

  • YAML
    # app/config/config.yml
    symfony_cmf_routing_extra:
        use_sonata_admin: auto # usa true/false para forzar el uso / no uso de
                               # la administración de sonata
        content_basepath: ~ # usado con la administración de sonata para
                            # manejar el contenido, por omisión a
                            # symfony_cmf_core.content_basepath
    
  • XML
    <!-- app/config/config.xml -->
    <symfony-cmf-routing-extra:config
        use-sonata-admin="auto"
        content-basepath="null"
    />
    
  • PHP
    // app/config/config.php
    $container->loadFromExtension('symfony_cmf_routing_extra', array(
        'use_sonata_admin' => 'auto',
        'content_basepath' => null,
    ));
    

Términos del tipo Form

El paquete define un tipo form que puedes utilizar para la clásica casilla de verificación «aceptar términos» donde colocas las url en la etiqueta. Simply specify symfony_cmf_routing_extra_terms_form_type as the form type name and specify a label and an array with content_ids in the options:

$form->add('terms', 'symfony_cmf_routing_extra_terms_form_type', array(
    'label' => 'I have seen the <a href="%team%">Team</a> and <a href="%more%">More</a> pages ...',
    'content_ids' => array(
        '%team%' => '/cms/content/static/team',
        '%more%' => '/cms/content/static/more'
    ),
));

El tipo form automáticamente genera las rutas para el contenido especificado y pasa las rutas al ayudante trans de Twig para sustituirlas en la etiqueta.

Further Notes

Para más información sobre el componente Routing del CMF de Symfony, por favor, consulta:

Bifúrcame en GitHub