Hace poco que me he tenido que enfrentar al manejo de subdominios dinámicos dentro de un proyecto Symfony. Básicamente la idea se trata solucionar la necesidad de poder responder desde la misma aplicación con subdominios distintos. Esto significa que podemos responder con distintos controladores a peticiones del tipo subdominio1.midominio.com/ruta y subdominio2.midominio.com/ruta. Esta flexibilidad en las ruta nos abre distintas aplicaciones que podemos implementar en nuestro proyecto.
Aplicaciones de multi subdominios
La necesidad principal que teníamos era proporcionar un subdominio personalizado para cada cliente. Imagina un producto software que podemos ofrecer a múltiples clientes, de tal manera que la funcionalidad es común para todos ellos, sin embargo se requiere que el cliente pueda acceder a su aplicación desde nombredeliente.midominio.com.
Esto tiene grandes ventajas, ya que solo mantenemos un único código para todos los clientes. Esto nos permite, por ejemplo, poder cargar recursos distintos a cada cliente, como pueden ser hojas de estilos o imágenes, o por otro lado, podríamos incluir una configuración de parámetros y módulos activos por cliente, proporcionando al cliente solo las funcionalidades necesarias (o contratadas) para su día a día. Lo ideal sería que cada cliente tenga en base de datos almacenado el subdominio con el que “matchea” para poder proporcionar los niveles de seguridad y la parametrización adecuados.
Otra de las aplicaciones que nos ofrece es generar una versión mobile y una versión desktop totalmente diferenciada. Hay muchas webs que devuelven distinto html y funcionalidades si llamas a la url desde m.midominio.com (mobile) o desde dominio.com (desktop). Esto suele ser muy habitual en periódicos digitales.
También se me ocurre la posibilidad de tener una red de blogs. Imagina que desarrollas un sistema de blogging y quieres mantener blogs distintos dentro de la misma aplicación de tal manera que puedes tener blog1.midominio.com y blog2.midominio.com conviviendo y ofreciendo la misma funcionalidad, por que quizás solo nos interesa entre uno y otro cambiar el template, ya que la temática de los post puede ser totalmente distinta.
Manos a la Obra
A continuación vamos a ver la parte práctica de este asunto, ofreciendo los pasos que debemos de realizar para poder activar esta opción multi subdominio en Symfony.
1. Configurando el routing
Desde Symfony 2.2 se incluyó la posibilidad de poder definir un patrón de url dentro del hostname. Aquí un ejemplo para definir este host:
1 2 3 4 5 6 |
company: resource: "routing/routing.yml" host: "{subdomain}.%base_domain%" defaults: subdomain: %default_media% enviroment: dev |
El parámetro %base_domain% se puede definir en el parameters.yml con nuestro dominio base, esto nos permite poder hacer distintas instalaciones y entornos de trabajo, por ejemplo, development, staging y production. El parámetro {subdomain} nos permite poder sustituir dinámicamente el valor del subdominio.
Definiendo el VirtualHost
En este caso voy a poner un ejemplo de configuración para Apache, pero no hay problema si usais otro servidor web como nginx. Lo único que necesitamos es definir en la configuración como va a tratar el servidor web las peticiones que vengan desde este dominio, de tal manera que sepa responder a llamadas del tipo *.midominio.com
1 2 3 4 5 6 7 8 9 10 |
<VirtualHost *:80> ServerName dominio.com ServerAlias *.dominio.com DocumentRoot "/home/user/sites/site/web" <Directory "/home/user/sites/site/web"> AllowOverride All Allow from All </Directory> </VirtualHost> |
Configurando el routing
Simulamos en nuestra máquina el dns forzando el fichero /etc/hosts
1 2 |
#/etc/hosts 127.0.0.1 dominio.com subdominio1.midominio.com |
Definiendo el EventListener
Una vez tenemos configurado a nivel de infraestructura nuestra gestión del dominio, vamos a incluir un Listener en nuestra aplicación que escuche cada petición para poder identificar a través de que subdominio se está atacando la aplicación, así podemos aplicar distinto comportamiento dependiendo del propio subdominio. En nuestro caso particular, buscamos en base de datos si existe un elemento Media en nuestra base de datos, para poder inyectarlo a nivel global, y que en nuestros controladores y servicios podamos saber en que Media estamos y que configuración tiene activada.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
<?php namespace AppBundle\Event; use AppBundle\Entity\Media; use AppBundle\Entity\MediaManager; use Doctrine\ORM\EntityManager; use Symfony\Bundle\FrameworkBundle\Routing\Router; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\KernelEvents; class RequestListener implements EventSubscriberInterface { /** * @var MediaManager */ private $mediaManager; /** * @var string */ private $enviroment; /** * @var Router */ private $router; /** * @var EntityManager */ private $em; public function __construct(MediaManager $mediaManager, $enviroment, Router $router, EntityManager $em) { $this->mediaManager = $mediaManager; $this->enviroment = $enviroment; $this->router = $router; $this->em = $em; } public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); $currentHost = $request->getHttpHost(); $baseHost = $this->container->getParameter("base_domain"); $explodeElements = explode('.', $currentHost); $subdomain = $explodeElements[0]; $site = $this->em->getRepository(Media::class)->findOneBy(array('subdomain' => $subdomain)); if (!$site) { throw new NotFoundHttpException(sprintf( 'No site for host "%s", subdomain "%s"', $baseHost, $subdomain )); } if (!$site->isActive()) { throw new LogicException("Media is not active"); } $this->mediaManager->setCurrentSite($site); $this->router->getContext()->setParameter('subdomain', $subdomain); $this->router->getContext()->setParameter("enviroment", $enviroment); } public static function getSubscribedEvents() { return array( KernelEvents::REQUEST => array(array('onKernelRequest', 33)), ); } } |
Conclusión
Como has podido leer, es bastante sencillo configurar multi subdominios en nuestra aplicación Symfony. El ejemplo mostrado es muy simple, pero partiendo de este ejemplo puedes aplicarlo para las necesidades concretas de tu proyecto. Espero que el post haya sido de utilidad 🙂