Más con Symfony

2.1. Preparación del proyecto: un CMS para muchos clientes

En este proyecto, una empresa imaginaria llamada Sympal Builder quiere crear un CMS para que sus clientes puedan construir sitios web como subdominios de sympalbuilder.com. En concreto, el cliente XXX puede ver su sitio web en xxx.sympalbuilder.com y hacer uso del área de administración en xxx.sympalbuilder.com/backend.php.

Nota El nombre Sympal se ha tomado prestado del proyecto Sympal creado por Jonathan Wage, que es un framework de gestores de contenidos (CMF) desarrollado con Symfony.

Los dos requerimientos básicos del proyecto son:

  • Los usuarios pueden crear páginas y especificar el título, contenido y URL de esas páginas.

  • Toda la aplicación debe construirse dentro de un único proyecto de Symfony que gestione el frontend y backend de todos los sitios de los clientes y que obtenga los datos adecuados en función del subdominio utilizado por cada cliente.

Nota Para crear esta aplicación, el servidor debe configurarse para redirigir todos los subdominios *.sympalbuilder.com al mismo directorio raíz, que es el directorio web del proyecto Symfony.

2.1.1. El esquema y los datos

La base de datos del proyecto está formada por clientes (objeto Client) y páginas (objeto Page). Cada cliente representa un sitio web accesible mediante un subdominio y puede contener varias páginas.

# config/doctrine/schema.yml
Client:
  columns:
    name:       string(255)
    subdomain:  string(50)
  indexes:
    subdomain_index:
      fields:   [subdomain]
      type:     unique

Page:
  columns:
    title:      string(255)
    slug:       string(255)
    content:    clob
    client_id:  integer
  relations:
    Client:
      alias:        Client
      foreignAlias: Pages
      onDelete:     CASCADE
  indexes:
    slug_index:
      fields:   [slug, client_id]
      type:     unique

Nota Aunque los índices de cada tabla no son obligatorios, es mejor añadirlos porque la aplicación va a realizar muchas consultas que utilizan estas columnas.

Para poder probar el funcionamiento del proyecto, añade los siguientes datos de prueba en el archivo data/fixtures/fixtures.yml:

# data/fixtures/fixtures.yml
Client:
  client_pete:
    name:      Pete's Pet Shop
    subdomain: pete
  client_pub:
    name:      City Pub and Grill
    subdomain: citypub

Page:
  page_pete_location_hours:
    title:     Location and Hours | Pete's Pet Shop
    content:   We're open Mon - Sat, 8 am - 7pm
    slug:      location
    Client:    client_pete
  page_pub_menu:
    title:     City Pub And Grill | Menu
    content:   Our menu consists of fish, Steak, salads, and more.
    slug:      menu
    Client:    client_pub

Los datos de prueba crean dos sitios web, cada uno de ellos con una página. La URL completa de cada página está formada por el valor de la columa subdomain de la tabla Client y por el valor de la columna slug del objeto Page.

http://pete.sympalbuilder.com/location
http://citypub.sympalbuilder.com/menu

2.1.2. El enrutamiento

Todas las páginas de los sitios web creados en Sympal Builder se corresponden de forma directa con un objeto de tipo Page del modelo, que define el título y el contenido que se muestran. Para asociar cada URL con su correspondiente objeto Page, se crea una nueva ruta de objetos de tipo sfDoctrineRoute y que hace uso del campo slug. El siguiente código busca automáticamente en la base de datos un objeto de tipo Page cuyo campo slug coincida con el que incluye la URL:

# apps/frontend/config/routing.yml
page_show:
  url:        /:slug
  class:      sfDoctrineRoute
  options:
    model:    Page
    type:     object
  params:
    module:   page
    action:   show

La ruta anterior asocia correctamente la página http://pete.sympalbuilder.com/location con su objeto Page. Desafortunadamente, la ruta anterior también funciona con la URL http://pete.sympalbuilder.com/menu, por lo que el menú del restaurante se mostraría en el sitio web de Peter. Por el momento, el enrutamiento no es consciente de la importancia de los subdominios de los clientes.

Para que la aplicación funcione correctamente, el enrutamiento debe ser más avanzado. El objeto Page se debe buscar tanto por el campo slug como por el campo client_id. Este último campo se puede determinar comparando el host (por ejemplo pete.sympalbuilder.com) con el valor de la columna subdomain del modelo Client. Para ello, se va a mejorar el framework de enrutamiento creando una clase propia de enrutamiento. No obstante, antes de crear esta clase será necesario repasar cómo funciona el sistema de enrutamiento.