Generar URL y enlaces en Drupal es mas sencillo de lo que parece, antiguamente había muchas maneras de diferentes de realizar esta tarea, pero por suerte se dieron cuenta y lo unificaron todo, agrupándolo en básicamente 2 objetos.
En esencia, solo se utilizan 2 objetos, y alguno adicional si contamos las entidades o el objeto Request de Symfony, pero nada más, y esos dos objetos principales nos proporcionan todas las herramientas necesarias.
En los siguientes apartados vamos a ver uno por uno los objetos, como utilizarlos y como interactuar con ellos para lograr lo que necesitamos.
El objeto Request
Como sabréis, Drupal utiliza Symfony, y es que es el framework quien es el responsable del procesamiento inicial, lo cual implica inicializar la petición con el objeto \Symfony\Component\HttpFoundation\Request.
Este objeto es un buen punto de partida y vamos a repasar algunas de las funciones que nos pueden resultar útiles, pero pasa saberlo todo, lo mas recomendable es ir a la documentación oficial.
Lo primero que necesitamos sabes, es como obtenerlo, de manera estatica podemos hacerlo de la siguiente manera:
/** @var \Symfony\Component\HttpFoundation\Request $request */
$request = \Drupal::request();
Y si utilizamos la injeccion de dependencias, podemos utilizar el servicio “request_stack” para obtener la petición:
/** @var \Symfony\Component\HttpFoundation\Request $request */
$request = \Drupal::service('request_stack')->getCurrentRequest();
Una vez tenemos el objeto, podemos obtener información de él, como por ejemplo el dominio con el método getHost():
$host = $request->getHost();
// www.escueladrupal.com.
Si adicionalmente necesitamos el puerto en el dominio, podemos utilizar getHttpHost(), el cual básicamente llama al método getHost() y le añade el puerto desde el método getPort(), si es uno que no sea 80 (http) o 443 (https).
$host = $request->getHttpHost();
// www.escueladrupal.com:8080
Si necesitamos el protocolo, podemos obtenerlo con el método getScheme():
$request->getScheme();
// https
Si necesitamos todo junto, podemos utilizar otro método que nos devuelve el protocolo junto al dominio (y los puertos si no son 80 ni 443):
$fullUrl = $request->getSchemeAndHttpHost();
// https://www.escueladrupal.com:8080
Si necesitamos obtener la ruta también tenemos un método para ello, el valor devuelto depende de donde nos encontremos, si estamos en “https://www.escueladrupal.com/blog” nos devolverá “/blog”
$requestUri = $request->getRequestUri();
// /blog
Debemos tener en cuenta, que si estamos en la pagina principal únicamente nos devolverá “/” y si el sitio es multi-idioma con el idioma como prefijo de la URL, también nos devolverá el código de idioma.
Para finalizar con el objeto Request, vamos a ver como podemos obtener los parámetros, y en este caso no se obtienen con un método directamente, además, son algo especiales y lo mejor es consultar la documentación oficial para ver todo lo que ofrecen, en este caso solo voy a mostrar 2 métodos.
Vamos a partir como ejemplo con la siguiente URL https://www.escueladrupal.com/blog?parametro=123
$queryParameters = $request->query->all();
// ["parametro" => "123"]
$parameter = $request->query->get(“parametro”);
// “123”
Tiene un funcionamiento algo especial cuando se envían varios parámetros como array o separados por símbolos de suma (+) o guión (-), y es que en esos casos agrupa todo en un solo array.
El objeto URL:
Primero vamos a ver la generación de las URL y para ello se utiliza la clase \Drupal\Core\Url. Debemos saber que no es un servicio, de modo que hay que instanciarla o usar directamente sus métodos de manera estática.
Lo primero que tenemos que hacer es indicar que vamos a usar la clase:
use \Drupal\Core\Url;
Ahora tenemos dos opciones, instanciarla directamente o utilizar algún método estático, primero vamos a ver como podemos hacerlo directamente, pero para ello necesitaremos una ruta:
$url = new Url('user.logout');
En este ejemplo estoy usando la ruta user.logout, el cual está en el archivo user.routing.yml.
Aunque he comentado que se necesita una ruta, Drupal proporciona algunas rutas especiales que son usadas internamente:
- <front> Esta se utiliza para obtener directamente la url de la página principal.
- <none> Esta es similar al <front>, con la diferencia de que siempre apuntara a la raíz del sitio.
- <current> Como el mismo nombre indica, nos devolverá la url de la página actual.
- <nolink> Se utiliza cuando el usuario debe insertar un enlace, pero no quiere hacerlo, por ejemplo, en puntos de menú desplegables donde no se quiere que el punto raíz tenga una URL.
- <button> Similar a <nolink> pero siempre mostrara el texto del enlace.
Veamos un par de ejemplos, primero instanciando directamente la clase:
$url = new Url('<current>');
Y con el método estatico fromRoute:
$url = Url::fromRoute('<current>');
En ambos casos obtendremos el mismo resultado.
Como podemos suponer por el nombre del método, crea una URL desde una ruta, y aunque hemos utilizado una de las rutas especiales, también podemos utilizar una ruta normal:
$url = Url::fromRoute('user.logout');
Seguramente, principalmente se utilicen rutas para generar las URL, pero también existen otras opciones para poder crearlas.
Por ejemplo, desde la misma petición con el método createFromRequest().
$url = Url::createFromRequest($request);
O si queremos crearla desde una URL externa, en ese caso, tambien hay un método (un poco especial) llamado fromUri():
$url = Url::fromUri('https://www.escueladrupal.com/');
Y aunque en el ejemplo le hemos pasado el dominio completo, podemos pasarle cualquier URI, en este caso veamos un ejemplo, para indicarle que es una URI interna:
$url = Url::fromUri('internal:/user/logout');
Acabamos de ver que se le puede pasar un parámetro llamado “internal”, y existen más, como uno para las rutas, por si queremos crearla desde una ruta, aunque ya existe un método para ello.
$url = Url::fromUri('route:user.logout');
Nota: Hay que tener cuidado al utilizar “internal” o “route”, y es que Drupal creara el objeto URL aunque no existan o sean inválidos, y lanzara una excepción si se intenta utilizar.
Si queremos crear una URL sobre una ruta o URL interna que no existe, podemos utilizar “base”.
$url = Url::fromUri('base:/ruta/aleatoria/escuela');
De esta manera, Drupal no lanzará una excepción si no existe la URI.
Lo que hemos visto hasta ahora lanza excepciones y puede causar problemas, pero existe un método el cual nos permite crear un objeto URL sin problemas de que exista la ruta o no sea válido el parámetro, pero con alguna limitación, y es que tiene que comenzar con uno de estos caracteres: ‘/’, ‘?’, o ‘#’). Está pensado para cuando no se tiene control sobre lo que se le envía, como que un usuario lo haya escrito:
$url = Url::fromUserInput('/algunta/ruta');
Finalmente, nos falta un método, el cual es fromRouteMatch(), este requiere un objeto \Drupal\Core\Routing\RouteMatch como parámetro. Este es un objeto especia que representa una ruta, en lugar de cargar la ruta.
Para usarlo primero necesitamos obtener el objeto RouteMatch y esto se hace desde la request:
$request = \Drupal::request();
$route_match = RouteMatch::createFromRequest($request);
$url = \Drupal\Core\Url::fromRouteMatch($route_match);
Y listo, de esa manera podemos generar la URL.
Existen otros métodos para crear URL, pero estos son los 4 principales y los que se deberían utilizar.
Añadiendo parámetros:
Todo lo que hemos visto hasta ahora, ha sido crear el objeto directamente, pero, ¿qué sucede con las rutas que necesitan parámetros? ¿O si queremos añadir algún query parameter? Pues existe una solución.
Vamos a ver un ejemplo, de como crear un objeto URL con la ruta de un nodo, el cual necesita la ID del nodo como parámetro:
$url = new Url('entity.node.canonical', ['node' => 1]);
Lo mismo puede aplicarse para pasarle query parameters:
$url = new Url('entity.node.canonical', ['node' => 1], ['query' => ['parametro' => '123']]);
// /node/1?parametro=123
Y esto mismo se puede aplicar al método fromRoute:
$url = Url::fromRoute('entity.node.canonical', ['node' => 1], ['query' => ['parametro' => '123']);
O desde una URI con el método fromUri():
$url = Url::fromUri('internal:/user/logout', ['query' => ['parametro' => '123']]);
Utilizando el objeto URL:
Llegados a este punto, ya sabemos todo lo necesario para poder crear nuestros objetos URL, pero ahora necesitamos saber cómo utilizarlos.
Existen 3 métodos que pueden “llevar” un objeto URL a un estado “utilizable”, veamos uno por uno esos métodos:
El primero es toString(), y como os imaginareis por el nombre, permite transformar un objeto en un string:
$url = Url::fromUri('route:user.logout');
$string = $url->toString();
// /user/logout
Si usamos este método con la ruta de una entidad y esta dispone de un alias, lo que nos devolverá será el alias de la entidad:
$url = Url::fromRoute('entity.node.canonical', ['node' => 1]);
$string = $url->toString();
// /alias/del/nodo
Otro de los métodos, es para obtener la ruta:
$url = Url::fromUri('route:user.logout');
$uriString = $url->toUriString();
// route:user.logout
Pero claro, es posible que necesitemos crear un render array desde el objeto URL, pues esto también es posible, y de una manera muy sencilla, con un método:
$url = Url::fromUri('route:user.logout');
$renderArray = $url->toRenderArray();
También existen varios métodos para poder modificar el resultado del objeto, veamos algunos.
El primero, es muy útil, ya que nos permite hacer que la URL sea absoluta, lo primero es crear el objeto URL y después llamar al metodo setAbsolute:
$url = Url::fromUri('user.logout');
$url->setAbsolute();
$string = $url->toString();
// https://www.escueladrupal.com/user/logout
Si utilizamos rutas que requieran parámetros con el metodo fromUri(), necesitamos enviarle el parámetro de alguna manera, ya que fromUri() no nos permite hacerlo, pero existe un metodo para ello:
$url = Url::fromUri('route:entity.node.canonical');
$url->setRouteParameter('node', 1);
Y al igual que para los parámetros, existe un metodo llamado setOptions(), el cual nos sirve para indicarle las opciones, como por ejemplo el query parameter:
$url = Url::fromUri('route:entity.node.canonical');
$url->setRouteParameter('node', 1);
$url->setOptions(['query' => ['parametro' => 123]]);
El objeto Link
Todo lo que hemos visto hasta ahora, es para el objeto URL, pero si necesitamos de alguna manera representar esa URL en HTML, tenemos que utilizar otra clase llamada \Drupal\Core\Link, la cual se utiliza para generar links. Al igual que la URL, esta clase se usa principalmente se manera estática.
Lo primero es añadir el ‘use’ para poder utilizarla:
use \Drupal\Core\Link;
Ahora ya podemos comenzar a utilizarlo, el primer ejemplo es instanciando directamente el objeto, lo cual no es muy común:
Nota: en todos estos ejemplos $url corresponde a un objeto de la clase \Drupal\Core\Url.
$link = new Link('Link', $url);
Lo que estamos haciendo es crear un enlace con la etiqueta <a> la cual tendrá en el atributo href lo generado por el objeto URL y el texto Link.
Veamos ahora, como hacer lo mismo, pero con un método estático:
$link = Link::fromTextAndUrl(t('Link'), $url);
Y ahora estamos haciendo lo mismo, pero llamando al método fromTextAndUrl(), y además dando la opción de traducir ‘Link’ al haberlo metido dentro de la función t().
Existe una manera de ahorrarnos la creación del objeto URL para utilizar la clase Link, y es que Link ya creara por nosotros la URL si utilizamos el metodo createFromRoute():
$link = Link::createFromRoute(t('Link'), 'user.logout');
Y también podemos pasarle parámetros, no solo la ruta:
$link = Link::createFromRoute(t('Link'), 'entity.node.canonical', ['node' => 1], ['parametro' => '123']);
Utilizando el objeto Link:
La tarea principal del objeto Link es generar HTML utilizando el texto proporcionado y la URL o ruta.
Para mostrarlo como html podemos hacer lo siguiente:
$url = Url::fromUri('route:user.logout');
$link = new Link('Salir', $url);
$string = $link->toString();
$string->getGeneratedLink();
// <a href="/user/logout">Salir</a>
Adicionalmente, podemos transformarlo en un render array:
$url = Url::fromUri('route:user.logout');
$link = new Link('Salir', $url);
$renderable = $link->toRenderable();
Y de esa manera, podemos tener el link como un render array.
Y claro, al tener un render array podemos ya hacer con el lo que queramos dentro de lo que son los render array, como por ejemplo añadirle atributos:
$url = Url::fromUri('route:user.logout');
$link = new Link('Salir', $url);
$renderable = $link->toRenderable();
$build['link'] = $renderable;
$build['link']['#attributes'] = ['class' => ['clase-css']];
Pero también podemos pasarle los atributos de otra manera, y es a través del objeto URL:
$link_options = [
'attributes' => [
'class' => [
'clase-css',
],
],
];
$url = Url::fromUri('route:user.logout');
$url->setOptions($link_options);
$link = new Link('Salir', $url);
$renderable = $link->toRenderable();
Y el resultado será el mismo, un link con la clase css ‘clase-css’.
El servicio Path Alias Manager
El servicio path_alias.manager nos ofrece unos metodos muy útiles cuando queremos convertir un alias a una ruta interna o viceversa, vamos a verlo con un ejemplo muy sencillo.
Pero primer, tenemos que saber como obtenerlo, y al ser un servicio, es muy sencillo:
/** @var \Drupal\path_alias\AliasManager $aliasManager */
$aliasManager = \Drupal::service('path_alias.manager');
Con ese servicio, ahora podemos convertir un alias a una ruta interna coin el método getPathByAlias().
$path = $aliasManager->getPathByAlias('/alias/de/un/nodo');
// /node/1
También es posible realizar el proceso inverso, desde una ruta interna obtener el alias.
$alias = $aliasManager->getAliasByPath('/node/1');
// /alias/de/un/nodo
Si utilizamos las clases Url y Link realmente no haremos mucho uso del servicio ya que se encargan de obtener los alias, pero siempre está bien tenerlo para poder utilizarlo en caso de necesidad.
El servicio CurrentRouteMatch
Ya hemos visto que se puede utilizar un objeto llamado RouteMatch en un ejemplo anterior, pues hay servicio que nos permite facilitarnos un poco la vida para poder obtener la ruta y generar también URL desde el.
Primero veamos como podemos obtener la ruta:
/** @var \Drupal\Core\Routing\CurrentRouteMatch $currentRouteMatchService */
$currentRouteMatchService = \Drupal::service('current_route_match');
$currentRouteMatchService->getRouteName();
Y listo, con el metodo getRouteName podremos tener la ruta actual.
Si recordáis el ejemplo de la URL utilizábamos el método fromRouteMatch(), en este caso no podemos usarlo directamente, porque necesita un objeto RouteMatch, pero podemos obtenerlo con un método de manera muy sencilla (el método realmente le pasa la Request a la clase RouteMatch como hemos hecho antes).
/** @var \Drupal\Core\Routing\CurrentRouteMatch $currentRouteMatchService */
$currentRouteMatchService = \Drupal::service('current_route_match');
$route = Url::fromRouteMatch($currentRouteMatchService->getCurrentRouteMatch());
Otras maneras de mostrar links:
Aunque ya hemos visto múltiples maneras de poder crear URL, existen más, y nos ahorran bastante trabajo en algunos casos.
Url y Links en entidades:
Las entidades de contenido nos proporcionan un par de métodos muy útiles que nos evitan estar obteniendo la ruta, ID, etiqueta… simplemente debemos cargar la entidad y obtener la URL o el Link.
Nota: en este ejemplo, se utiliza Node::load() para ahorrar espacio, no lo useis.
$node = Node::load(1);
$url = $node->toUrl();
$link = $node->toLink();
Y listo, ahí tenemos los objetos url y link listos para su uso.
Enlaces en textos traducidos:
Cuando usamos la función o metodo t() para traducir, podemos poner enlaces a sitios, lo ideal es que sean dinámicos utilizando las funciones que nos proporciona la función o metodo t():
$url = \Drupal\Core\Url::fromRoute('entity.node.canonical', ['node' => 1]);
$string = t('Enlace a <a href="@url">entidad</a>', ['@url' => $url->toString()]);
Enlaces en twig:
Hemos visto muchas opciones, pero todo lo visto hasta ahora ha sido en PHP, y llegamos a Twig, donde el código PHP no nos sirve y tenemos que buscar otra alternativa.
Aunque realmente, no difiere mucho, porque Twig tiene acceso al objeto Url, de modo que podemos usarlo directamente, y es que Drupal llamara al metodo toString del objeto URL al intentar renderizarlo:
<a href="{{url('user.logout'}}"> {{ 'Salir'|t }} </a>
Y lo mismo podemos hacer para pasarle parámetros:
<a href="{{url('entity.node.canonical', {'node': node.id() }) }}"> {{ 'Enlace a nodo'|t }} </a>
Conclusion:
Aunque parezca muy complicado, todo se basa en un par de objetos, Url y Link. Con ellos dos podremos generar cualquier link o url que necesitemos. Y una vez que entendemos como funcionan y hayamos echo un par de pruebas con ellos, rápidamente seremos capaces de adaptarnos y conseguir cualquier cosa.
Me metí en la aventura de Drupal con la versión 6, y aquí estoy, 10 años después, escribiendo articulos y haciendo videos sobre Drupal, quien me lo iba a decir. Aunque he probado otros framworks y cms, me quedo con Drupal de lejos, pero Symfony y Django estan entre mis favoritos. Aficionado a la montaña, la bicicleta, y el comer, de eso que no falte.