Otros 10 trucos de Symfony

1. Incluir directorios para ser procesados por Babel

Puede ser que cuando trabajamos con Webpack Encore veces necesitemos forzar que Babel procese determinadas carpetas que se encuentran en nuestro directorio node_modules , algo que por defecto es excluido.

Esto es “tan sencillo” como incluir la siguiente directiva en nuestro archivo webpack.config.js :

Encore.configureBabel(function(babelConfig) {
}, {
include_node_modules: ['bootstrap']
})

Lo pongo entre comillas porque en el caso de que estemos empleando el archivo .babelrc para configurar la librería Babel, este truco no nos funcionará y, de momento, no he encontrado otra forma:

2. Protección automática contra buscadores

Con la llegada de Symfony 4.3, la cabecera X-Robots-Tag: noindex será incluida en todas las respuestas cuando estemos trabajando en desarrollo, es decir, cuando no hayamos configurado el entorno como prod . Esto nos ahorrará algún que otro susto y, ahorrarnos en algunos casos, la necesidad de proteger la web para evitar que los buscadores la indexen. Muy útil.

3. Constraint UniqueEntity

Si queremos validar que el campo de una determinada entidad no está repetido, podemos realizar dicha comprobación mediante la constraint UniqueEntity , algo bastante útil si por ejemplo queremos asegurarnos de que un email no se repite entre varios usuarios:

// src/Entity/Person.php
namespace App\Entity;

use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @UniqueEntity(fields={"email"}, message="Esa cuenta de correo ya está en uso")
*/
class Person {
protected $email;
}

4. Una forma más sencilla de lanzar eventos

Con Symfony 4.3, lanzar eventos es mucho más sencillo pues pasamos de esta sintaxis:

$dispatcher->dispatch(MyEvent::EVENT_NAME, $myEvent);

a ésta:

$dispatcher->dispatch($myEvent);

¿Y cómo es posible ésto? Gracias a que ahora los EventSubscriber son capaces de escuchar eventos atendiendo al FQCN de la clase del evento, de modo que, a partir de ahora, si queremos suscribirnos a un evento, por ejemplo de la clase MyEvent , lo haremos del siguiente modo:

class MyEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
// Before
return [
MyEvent::EVENT_NAME => 'onMyEvent',
];

// After
return [
MyEvent::class => 'onMyEvent',
];
}

// ...
}

5. Evitar que un usuario logueado acceda a la pantalla de login

Esta solución me ha gustado especialmente por lo limpia que queda. Lo único que necesitaremos es asegurarnos de que tenemos el componente ExpressionLanguage instalado:

Si es así, en nuestro archivo security.yaml podremos escribir lo siguiente:

security:
access_control:
- { path: ^/login$, allow_if: "not has_role('ROLE_USER')" }
- { path: ^/resetting, allow_if: "not has_role('ROLE_USER')" }

6. Evitar el valor null en los checkboxes no marcados

Este es la verdad un caso bastante interesante y que nos permite volver a trabajar con los FormEvent de los cuales ya os hablé el otro día:

Imaginad que tenemos un formulario con el siguiente campo:

->add('terms', CheckboxType::class)

Como sabréis, cuando un checkbox no es marcado por el usuario, éste no es recogido por la variable $_POST , lo cual provoca que recibamos null en vez de false como cabría esperar, algo que según en qué situaciones no es deseable.

Aunque existen diversas soluciones, la verdad es que ésta me parece bastante didáctica ya que vuelve a recurrir a los FormEvent:

$builder->addEventListener(

FormEvents::POST_SUBMIT,
  function(FormEvent $e) {
    $contactInfo = $e->getData();
    $form = $e->getForm();
    $terms = $_POST[$form->getName()]['terms'] ?? false;
    $contactInfo->setTerms($terms);
});

7. Testear con Panther

Si desarrolláis siguiendo TDD o alguna otra metodología similar, seguramente escribáis tests funcionales extendiendo de la clase WebTestCase lo cual nos permitirá obtener un cliente mediante el método static::createClient . Sin embargo, esta clase tiene alguna limitación como, por ejemplo, que no envía requests o es incapaz de ejecutar código javascript sobre la respuesta recibida.

Como alternativa podemos emplear la librería Panther la cual nos permite realizar tests end-to-end con navegadores reales y que se integra perfectamente con las herramientas de tests de las que Symfony provee:

Más adelante prepararé un artículo específico sobre esta librería pero adelantaros que algunas de sus características, como la posibilidad de esperar a que cargue cierto contenido o la ya mencionada capacidad para ejecutar Javascript la hacen una alternativa muy muy interesante.

8. Definir bindings for defecto

Con la aparición de Symfony 3.4 y la llegada del autowire y autoconfigure, la creación de servicios y la inyección en ellos de dependencias se simplificó enormemente. Sin embargo, todavía tendremos que especificar “a mano” aquellos parámetros que no sean servicios propiamente dichos.

Es decir, si tenemos el siguiente constructor:

namespace App/Service
class MyService {
public function __construct(string $projectDir) {

Tendremos que añadir a nuestro archivo services.yaml lo siguiente:

App/Service/MyService:
arguments:
$projectDir: '%kernel.project_dir%'

Sin embargo, existe una forma de ahorrarnos tener que escribir esto cada vez que vayamos a inyectar el parámetro kernel.project_dir y es definiendo un binding por defecto:

# services.yaml
services:
_defaults:
bind:
$projectDir: '%kernel.project_dir%'

De este modo, cada vez que Symfony detecte un argumento en el constructor de un servicio con el nombre $projectDir , inyectará el parámetro kernel.project_dir automáticamente.

9. Sobrescribir servicios de forma rápida

Muchas veces nos vemos en la necesidad de sobreescribir alguna clase de un bundle de terceros para añadir funcionalidad propia que de otro modo no sería posible.

Una de las alternativas más habituales (y aunque parezca lo contrario) más sencillas, es recurrir a un decorador:

Sin embargo, muchas veces la clase del servicio viene definida por el valor de un determinado parámetro creado por el bundle (algo muy habitual, por ejemplo, en los bundles de Friend of Symfony como podéis ver en este enlace), de modo que también nos serviría lo siguiente:

parameters:
parameter_defined_by_the_bundle: App/Service/MyClass

10. Filtrar archivos con el componente Finder

Seguramente más de una vez os haya tocado trastear con ciertos archivos dentro del servidor desde Symfony y el componente Finder suele ser de gran ayuda para este tipo de situaciones.

Una de sus características más útiles es la posibilidad de filtrar los archivos dentro de una carpeta con apenas un par de líneas de código:

$finder = new Finder();
$finder->date('since 1 secs ago');
$files = $finder->files()->in($someFolder');

Como veis, muy muy útil a la vez que sencillo.

Bonus track

Como este no es el primer artículo de este tipo que preparo, os dejo un par de enlaces por si queréis seguir “descubriendo” cosas sobre Symfony. ¡Espero que os sean útiles!

https://medium.com/@ger86/10-bundles-que-deber%C3%ADas-conocer-si-vas-a-trabajar-con-symfony-b2eb3646fe7d