Introduciendo shortcodes en Symfony mediante tagged services

En este artículo quiero enseñaros cómo podemos crear un sistema de shortcodes para vitaminar nuestros campos de texto mediante servicios taggeados, lo cual creo que es un gran ejemplo para introducirse en el mundo de los Compiler Pass y las tags propiamente dichas.

Para los que nunca hayáis trabajado con Wordpress, los shortcodes son una forma sencilla de permitir a los usuarios menos familiarizados con la programación crear elementos complejos como por ejemplo listados de entidades, carruseles o botones.

Para ello, se les provee de ciertos códigos especiales que al escribirlos dentro de los campos de texto serán parseados para mostrar algún elemento más complejo.

Por ejemplo, podemos crearles el siguiente shortcode:

[articles ids="lista de ids"][/articles]

El cual al escribirlo en un campo de texto será transformado automágicamente en una lista de artículos con los ids indicados. Si queréis leer más sobre los shortcodes podéis hacerlo desde aquí:

Dicho esto y como decía al principio del artículo, vamos a usar las tags de los services para crear un sistema que nos permita añadir shortcodes de forma muy sencilla. ¡Vamos a ello!

Procesando shortcodes

Tal y como hace wordpress, lo que queremos es escribir en base de datos un texto con shortcodes y que, en el momento de pintarse, éstos sean procesados y convertidos al HTML correspondiente. Esto nos permitirá volver a editarlos más tarde (realmente también existe la posibilidad de guardar tanto el texto original como el procesado, pero por simplificar el artículo optaremos por esta forma).

Lo primero de todo será implementar un servicio capaz de procesar shortcodes, para lo cual, nos basaremos en el que ya trae wordpress:

De todo ese código, la función que realiza el trabajo es shortcodeProcess, la cual recibe el texto a procesar y convierte los shortcodes encontrados en el HTML correspondiente. Lo cual está muy bonito siempre y cuando sepamos los shortcodes que tenemos que buscar en el texto.

Es aquí donde entran los tagged services y el Compiler Pass encargado de pasárselos a nuestro servicio recién creado.

Localizando shortcodes en el texto

Si os habéis leido por encima el gist anterior, habréis visto que empleo la función isShortcode para comprobar si la tag encontrada es un shortcode reconocido por nuestra aplicación. Es decir, el servicio ShortcodeService posee un array de shortcodes reconocidos e indexados por nombre, de modo que cuando la función shortcodeProcess se encuentra con algo del estilo [nombre][/nombre] trata de ver si nombre es una clave válida dentro del array shortcodes.

Este array estará relleno de servicios que implementarán la interfaz ShortcodeInterface como vamos a ver a continuación.

Creando el Shortcode button

Para ilustrar un poco todo lo comentador anteriormente, vamos a crear primero un shortcode sencillo.

Puesto que todos compartirán los mismos métodos, declararemos primero la interfaz que habrán de implementar.

La interfaz define tres métodos:

  • getName , que será en nombre por el cual será indexado en el servicio ShortcodeService
  • getArgs , que devolverá los argumentos
  • apply , el cual recibirá como argumentos los atributos y el contenido del shortcode que el usuario haya escrito y devolverá el HTML correspondiente tras haber procesado el shortcode.:

Por tanto, si queremos crear un shortcode que permita al usuario escribir

[button]Esto es un botón[/button]

para generar un botón en HTML:

<button>Esto es un botón</button>

tendremos que escribir lo siguiente:

Muy fácil como veis. Ahora solo queda ver como enviar todos los shortcodes que creemos de este modo a nuestro servicio ShortcodeService

Sin tener que inyectar manualmente uno por uno en su constructor

Es decir, en vez de hacer ésto:

class ShortcodeService {
  public function __construct(ShortcodeWell $sw, $ShortcodeModal $m, more shortcodes) {
    $this->shortcodes[] = $sw;
    $this->shortcodes[] = $m; //etc etc etc
  }
}

podemos etiquetar todos los servicios Shortcode de una misma forma y recogerlos en un Compiler Pass para enviarlos de golpe a nuestro servicio, lo cual nos ahorra tener que tocar el constructor cada vez que añadamos uno nuevo (principio open-close).

Vamos a ver cómo hacerlo

Conectando nuestro shortcode con el servicio que los procesa

Para llevar a cabo el proceso que os contaba antes, lo primero de todo será definir una tag para identificar a todos nuestros servicios que implementen la interfaz ShortcodeInterface dentro del container . Para ello retocaremos la interfaz un poquito:

Y en nuestro archivo services.yaml indicaremos que nuestro servicio va etiquetado de esa forma:

App\Shortcode\ButtonShortcode:

tags:

- { name: lac.shortcode }

A continuación, añadiremos un método en nuestro ShortcodeService que permita añadir elementos al array de shortcodes:

class ShortcodeService {
  // rest of code
  public function getShortcodes(): array {
    return $this->shortcodes;
  }
  
public function addShortcode(ShortcodeInterface $shortcode) {
    $this->shortcodes[$shortcode->getName()] = $shortcode;
  }
}

Y, finalmente, la magia. Crearemos un CompilerPass para recoger todos servicios etiquetados con lac.shortcode y pasárselos al método addShortcode :

Fácil y sencillo. Ahora cada servicio etiquetado como lac.shortcode será directamente añadido al array de shortcodes de ShortcodeService y podrá ser procesado sin tener que hacer nada más.

Y de este modo ya tenéis una forma de crear shortcodes en vuestro proyecto de Symfony. Espero que os haya servido!

Pd. Si queréis leer más acerca de los CompilerPass podéis hacerlo en la documentación oficial, que como siempre es de gran ayuda: