Meetup «Los retos de la comunidad Symfony»

Introducción

Antes de nada, sería pertinente comenzar por el por qué del grupo. Recientemente nos planteamos la certificación de Symfony para los desarrolladores de nuestra empresa, y en la documentación de los pasos a seguir, vimos que desde la página de academia de Sensiolabs tenían con frecuencia cursos y talleres de preparación, en Francia, Londres y Alemania. Cuando les preguntamos sobre la posibilidad de realizar la formación aquí en España al igual que en otros países, la respuesta que obtuvimos es que no hay tanta gente que lo solicite, por lo que no se lo plantean.

Queremos conseguir que todos los desarrolladores de Symfony que lo deseemos tengamos la posibilidad de tener un espacio en el que discutir inquietudes, apoyarnos y mejorar, sin tener que esperar a grandes eventos anuales como el deSymfony o Codemotion para ello, y por esas razones, creamos el grupo de Symfony.

Un poco de contexto

El pasado jueves tuvimos la suerte de poder organizar nuestro primer meetup, «Los retos de la comunidad Symfony«, y desde aquí quiero agradecerles a todos los asistentes que nos brindaron su tiempo. Desde el principio queríamos desmarcarnos de la clásica master class, que aunque no tiene nada de malo, no se ajustaba tanto a nuestra idea de generar comunidad en torno a Symfony, queríamos romper el hielo entre los participantes (que no oyentes), y queríamos debate.

 

Los retos de la comunidad Symfony

Como adelantaba antes, queríamos que el meetup fuera algo más comunicativo, que todos los presentes aportasen su visión, mostrasen sus inquietudes y pudieran ser ayudados. Pensamos usar Lean Coffee como método para obtener, priorizar y tratar temas de conversación que todos deseáramos discutir.

Para el que desconozca cómo funciona lean coffee, está dividido en tres grandes bloques de los que hicimos uso de la siguiente manera:

  • Obtención de temas: el primer paso era saber los temas sobre los que querían hablar los participantes. Para ello, planteamos las preguntas «¿Qué quiero aprender?», «¿Qué puedo aportar?», «Dudas existenciales». Repartimos una serie de post-its sobre los que escribir los temas que quisieran tratar y que posteriormente se colocaron bajo cada pregunta en un muro. Para tener mejor visibilidad de todo, dedicamos unos instantes en agrupar los temas similares o los que directamente eran iguales.
  • Priorización: una vez teníamos todos los temas, los priorizamos para hablar sobre los que pudiéramos dentro del tiempo que teníamos disponible, repartimos entre cada uno 5 puntos que usarían para votar los temas que les resultaran más interesantes, y de esa manera, ordenarlos y detectar los prioritarios.
  • Discusión: establecimos un timebox de 5 minutos para hablar por tema, llegando a la posibilidad de prorrogarlos con una votación hasta que todos pensásemos que el tema no daba más de sí.

Temas tratados

Tras realizar los primeros dos pasos del Lean Coffee, se nos quedó el siguiente muro con los temas que trataríamos en la siguiente hora:

Muro de temas a tratar sacados en el Lean Coffee

Muro del Lean Coffee

Como podéis ver, uno de las grandes preocupaciones que asaltaban a los participantes es la gestión de los assets del frontal de nuestras aplicaciones, cómo hacemos uso de Gulp y/o Webpack Encore. Este tema se llevo gran parte del tiempo asignado del meetup.

Otro tema que tratamos fueron sobre la forma en la que cada uno realizaba la subida a producción de los assets, el debate estaba en generarlos en local y copiarlos arriba, o generarlos directamente arriba. Cada uno defendió sus ventajas e inconvenientes dependiendo de lo que acostumbra a hacer.

Siguiendo con los frontales de nuestras aplicación, surgió la visión de cómo Symfony estaba alejándose de la alimentación del frontal para centrarse en el lado back de las aplicaciones, dando paso así a los clientes desarrollados con frameworks javascripts como React, Angular o Vue. Dedicamos un rato a discutir nuestras experiencias con cada uno, cómo realizamos las comunicaciones entre cada parte, y el manejo de datos sensibles de usuarios.

El último tema que nos dio tiempo a tratar fue el de la gestión de formularios en Symfony, la validación, creación de Constrains. Cada uno expusimos cómo trabajamos con los formularios.

Se quedaron fuera temas interesantes como Certificación, los cambios que se acercan con Symfony 4 o testing, uso de servicios para la lógica de la aplicación frente a custom events.

Cierre

Una vez más, me gustaría desde aquí mandar un saludo a todas las personas que quisieron y pudieron venir, esperamos que les resultase una experiencia enriquecedora y quieran repetir en el próximo. Por otro lado, a todos los que os interese apuntaros al grupo y estar al tanto de los próximos eventos que organicemos, dejo el enlace al registro en el grupo de Meetup.

Versionado de assets con Gulp y Symfony

Uno de los grandes “problemas” que nos encontramos en Elemento115 a la hora de hacer despliegues rápidos en un corto lapso de tiempo es, precisamente, cómo mostrar estos cambios al usuario final. Me explico.

Nuestro flujo de trabajo nos permite hacer cambios rápidos de diseño y funcionalidad en la parte cliente, por lo que podemos llegar a desplegar una aplicación bastantes veces al día. ¿Nuestra némesis? La caché. Ese elemento que ayuda tanto a que nuestros desarrollos carguen a una velocidad óptima también provoca que el usuario no vea los cambios de manera inmediata.

Para solucionarlo tenemos varias opciones, como dejar los archivos servidos expiren en tiempos relativamente cortos (en nuestro caso debería ser de horas) o forzar el de nuevo la bajada haciendo que expire manualmente. Ninguna de estas opciones nos convence. Hasta Symfony 2.8 utilizábamos Assetic para la gestión de assets. Este componente estaba integrado en Symfony, era fácil de utilizar y además nos solucionaba este problema con un sistema simple, el versionado de archivos. Pero, y siempre hay un pero, Assetic pasó a estar deprecado en Symfony 2.8.

 

Gulp al rescate.

¿Cómo afrontamos esto? Fácil, nos subimos al barco de Gulp. La integración con Symfony fue sencilla y además ganamos tiempo a la hora de hacer los despliegues gracias a que Gulp resultaba ser más rápido que Assetic a la hora de procesar los assets. Pero (otra vez) perdimos la facilidad con la que versionabamos los archivos, ya que con Assetic solo era necesario cambiar el número de versión en un archivo de configuración.

No íbamos a hacer de esto un drama, así que nos pusimos manos a la obra para solucionar el tema del versionado de archivos de la manera más limpia y automatizada posible. Queríamos que, cada vez que un archivo, ya sea CSS o JS, fuera modificado, se actualizase la versión de este para servir el nuevo contenido a los usuarios de manera inmediata. Para ello integramos gulp-rev, un paquete de Gulp que se encarga de añadir un hash a los archivos que necesitamos versionar. Un ejemplo práctico con archivos Sass/Css:


gulp.task('public-clean-assets', function() {
    return gulp.src(['web/public/css/*.css', 'web/public/**/*.js'], {read: false})
        .pipe(clean({force: true}));
});

gulp.task('public-sass', function() {
    return gulp.src('app/Resources/front/public/scss/*.scss')
        .pipe(sass({includePaths: ['app/Resources/front/public/scss/modules']}))
        .pipe(gulp.dest('app/Resources/front/public/css'))
        .pipe(notify({message: 'PUBLIC SASS OK', onLast: true}));
});
gulp.task('public-styles', ['public-clean-assets', 'public-sass'], function() {
    return gulp.src('app/Resources/front/public/css/**/*.css')
        .pipe(cleancss())
        .pipe(rename({suffix: '.min'}))
	.pipe(rev())
        .pipe(gulp.dest('web/public/css'))
	.pipe(rev.manifest('web/rev-manifest.json', {
            merge: true
        }))
	.pipe(gulp.dest('.'))
        .pipe(notify({message: 'PUBLIC CSS OK', onLast: true}));
});

Aquí actúan básicamente tres tareas de Gulp.

  • Con public-clean-assets limpiamos las carpetas de assets públicos, así no acumulamos un gran numero de archivos sin utilizar.
  • public-sass se encarga de procesar todos los archivos Sass de la aplicación.
  • public-styles se encarga de:
    • “Minificar” el archivo resultante de procesar los archivos Sass.
    • Añadir el sufijo “.min” una vez minificado.
    • Añadir el hash de versión.
    • Mover el archivo resultante a la carpeta pública desde donde se sirve.
    • Añadir una entrada en el rev-manifest.json con el nombre del archivo original y el resultante.

Vale, ya tenemos los archivos listos para servir, ¿como sabe Symfony qué archivos servir?

 

Servir la versión correcta con TwigExtensions.

Vamos a aprovechar el archivo rev-manifest.json para servir los assets correctos. En el archivo rev-manifest.json tenemos, por ejemplo, el siguiente contenido:


{
  "jquery-3-2-1.min.js": "jquery-3-2-1-c9f5aeeca3.min.js",
  "jquery-ui.min.js": "jquery-ui-fdf4d9013c.min.js",
  "main.min.css": "main-6ca53126cb.min.css",
  "main.min.js": "main-32fc16b6cd.min.js"
}

Aquí tenemos la relación entre el archivo que queremos utilizar (main.min.css por ejemplo) y el nombre versionado del archivo (main-32fc16b6cd.min.js). Para hacerlo de manera automática vamos a crear una extensión de Twig que realice este proceso por nosotros:



namespace AppBundle\Twig; 

/** 
 * AssetVersionExtension 
 */ 
class AssetVersionExtension extends \Twig_Extension 
{ 
    private $rootDir; 

    /** 
     * Constructor 
     * 
     * @param string $rootDir
     * @param string $cssDir
     * @param string $jsDir 
     * @param string $pluginsDir 
     */ 
    public function __construct($rootDir, $cssDir, $jsDir, $pluginsDir)
    { 
        $this->rootDir = $rootDir;
        $this->css = $cssDir;
        $this->js = $jsDir;
        $this->plugins = $pluginsDir;
    }

    /**
     * @return array
     */
    public function getFilters()
    {
        return array(
            new \Twig_SimpleFilter('assetVersion', [$this, 'getAssetVersion']),
        );
    }

    /**
     * Retrives the hashed version of the file
     *
     * @param string $filename
     * @param string $fileType  Should match js or css property
     * @param string $subfolder If the file is in a subdirectory it must by especified
     *
     * @return string
     */
    public function getAssetVersion($filename, $fileType, $subfolder = '')
    {
        $manifestPath = $this->rootDir . '/../web/rev-manifest.json';
        if (!file_exists($manifestPath)) {
            throw new \Exception(sprintf('Cannot find manifest file: "%s"', $manifestPath));
        }

        $paths = json_decode(file_get_contents($manifestPath), true);

        if (!isset($paths[$filename])) {
            throw new \Exception(sprintf('There is no file "%s" in the version manifest!', $filename));
        }

        return $this->{$fileType} . $subfolder .  $paths[$filename];
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'asset_version_extension';
    }
}

Declaración del servicio en services.yml:


...
app.twig.assets.version.extension:
    class: AppBundle\Twig\AssetVersionExtension
    arguments: ['%kernel.root_dir%', 'public/css/', 'public/js/', 'public/js/vendor/']
    tags:
        - { name: twig.extension }
...

¿Como funciona la extensión? Básicamente se le pasa como parámetros el nombre original del archivo y el tipo de archivo. Con esto, se encarga de leer desde el rev-manifest.json cual es el archivo correcto a utilizar.

Un ejemplo de uso:


...
< link rel="stylesheet" href="{{ asset('main.min.css'|assetVersion('css')) }}" />
...

Con esto, ya tenemos automatizado el versionado de los assets, además de servir siempre el archivo correspondiente sin necesidad de cambiar ningún tipo de configuración en los despliegues.

Un apunte sobre Gulp y la «sincronicidad».

Gulp, al ser una herramienta escrita puramente en JavaScript, ejecuta las tareas de manera completamente asíncrona. Debido a este comportamiento, es posible que intente hacer un versionado de archivos que aún no existen, por ejemplo cuando aún no se ha terminado de procesar los ficheros Sass.

Esto es fácil de corregir, podemos «forzar» a que Gulp actúe de manera síncrona. Tan solo es necesario pasar como segundo parámetro a la función gulp.task que lo requiera un array de tareas a las que debe esperar antes de ejecutarse. En el caso de la tarea public-styles, esta no se ejecuta hasta que public-clean-assets y public-sass han terminado.

DeSymfony 2017

RUMBO A DESYMFONY

Justo hace una semana nos preparábamos y emprendíamos viaje a Castellón con la intención de estar presentes un año más en el evento de deSymfony. Conferencias que cubren problemas, necesidades y metodologías del presente, y que nos ayudan a estar en la actualidad del desarrollo de software con este framework.

LAS CHARLAS

Horario y título de las charlas de DeSymfony

Programa de DeSymfony

Todas las charlas mantuvieron un nivel alto, y fueron muy interesantes y variadas, pero por no desarrollar un post eterno, haremos hincapié en un par de ellas: Symfony 4 y Sylius. Aunque tenemos intención de realizar otro post (o varios) entrando más en detalle.

SYMFONY 4 Y FLEX

Comenzamos el evento con una charla que nos sitúa en la evolución del framework en el futuro inmediato, y se aproximan curvas. El encargado, nada más y nada menos que Javier Eguiluz.

Vimos como Symfony sigue siendo una tendencia en el mundo del desarrollo web, y sus intenciones de serlo durante otros 5 años más. Para ello, se modificará la estructura para ajustarse a micro aplicaciones (no fuertemente ligadas a aplicaciones WEB) y se facilitará la instalación y configuración de bundles gracias a Symfony Flex. Ah! Será requisito PHP 7.1 y saldrá en Noviembre.

El paso a micro framework conlleva a la reducción del código en un 70%, y en igual cantidad, el número de ficheros en vendor. ¿Y Silex? El hueco que llenaba Silex será ocupado por Symfony, capaz de realizar la misma labor. ¿Significa el fin de Silex? Así nos lo confirmó Javier con un rotundo: «Silex está muerto».

Os recomendamos echar un vistazo por las diapositivas de esta primera charla, pues es muy interesante todos los puntos que aborda.

A lo largo de los dos días vimos metodologías, uso de bundles para realizar APIs seguras, o para serializar datos. Nos mostraron que las aplicaciones de consola también pueden ser atractivas y el proceso para empaquetar nuestros propios comandos. Las batallas que han librado para convertir código legacy y aplicaciones monolito a micro servicios más mantenibles. Vimos cómo podemos disminuir el tiempo de carga de nuestras aplicaciones para servir el contenido lo más rápidamente al usuario.

SYLIUS

eCommerce framework for tailored solutions

Asier Marqués nos mostró las bondades de un conjunto de bundles que facilitan el desarrollo de aplicaciones de comercio electrónico con Symfony: Sylius. Y también nos mostró lo preparados que estaban esos bundles para ser usados de manera independiente al resto.

Nos pueden ser de gran utilidad para los siguientes aspectos:

Gestión de pagos (PayumBundle).

Gestión de estados (WinzouStateMachineBundle).

Gestión de entidades de nuestra aplicación y facilidad de crear una API sencilla (SyliusResourceBundle).

Para la gestión del front y temas de Sylius (GridBundle y SyliusThemingBundle).

Por último, trató cómo se mantiene la calidad y testing de Sylius, a través de Behat.

TERMINANDO…

Para finalizar, queremos agradecer otro año más su esfuerzo a los organizadores, que han realizado una labor titánica para juntar a una comunidad que se muestra viva y creciente.

Agradecer también a los ponentes, pues es de respetar el valor que muestran al subirse delante de todos nosotros y por compartir sus conocimientos para que los demás podamos mejorar.

Y cómo no, una mención especial a los patrocinadores, que gracias a su granito de arena eventos así son posibles.

Nos vemos el año que viene.

Cargar configuración del CKEDITOR en Symfony

La semana pasada, durante la fase de test de un proyecto que estamos desarrollando, un cliente nos dijo que estaba teniendo problemas para introducir contenidos porque estaba fallando el editor WYSIWYG que estábamos usando, el CKEDITOR integrado en Symfony. Como siempre, los desarrolladores dijimos que «a nosotros nos funcionaba«, y es que era cierto… hasta que dejó de serlo. De forma aparentemente aleatoria, a veces se permitía la introducción de contenidos con normalidad y otras el proceso no se llegaba a realizar por completo: desaparecía el TEXTAREA asociado pero el editor no llegaba a aparecer en su lugar.

El ordenador del desarrollador como centro del universo

En este punto hay que decir que nuestras necesidades iban un poco más allá de las de por defecto, ya que teníamos que limitar la cantidad de encabezados que podía introducir el usuario y al código HTML al que equivalían unas vez renderizados. Además, en la carga inicial de Symfony no conseguíamos que apareciese la configuración deseada (botones de la barra de herramientas y skin), así que terminamos llegando a este código:


var WYSIWYG = {
	init : function() {
		CKEDITOR.config.format_tags = 'p;h1;h2;h3';
		CKEDITOR.config.format_h1 = { element: 'h3' };
		CKEDITOR.config.format_h2 = { element: 'h4' };
		CKEDITOR.config.format_h3 = { element: 'h5' };
		editors = CKEDITOR.instances;
		if (editors) {
			for (var editor in editors) {
				config = editors[editor].config;
				editors[editor].destroy(true);
				if ($('#' + editor).length > 0) {
					CKEDITOR.inline(editor, config);
				}
			}
		}
	}
};

En donde lo que hacíamos era:

  • Modificábamos los parámetros de la configuración general del CKEDITOR que necesitábamos (format_tags, format_h1, format_h2 y format_h3).
  • Recogíamos todas las instancias del CKEDITOR
  • Para cada una de ellas, almacenábamos su configuración, destruíamos la instancia y la volvíamos a generar, esta vez invocando nosotros al constructor inline.

Este método, según vimos en las sagradas escrituras de Stackoverflow, es muy común cuando se quiere modificar de forma dinámica la configuración del CKEDITOR. Provocaba un efecto de parpadeo algo extraño, ya que primero se invocaba desde el Symfony, y una vez creado se volvía a hacer lo mismo desde el JavaScript, por lo que había unos breves instantes en que el el TEXTAREA se quedaba sin CKEDITOR asociado, pero nos pareció una consecuencia aceptable si lográbamos hacerlo funcionar como queríamos.

Pero entonces ocurrió que esos breves instantes se convirtieron en eternidades, y por más que esperásemos, había veces en que esa segunda creación del CKEDITOR nunca llegaba, y resultaba imposible por tanto introducir contenidos en ese campo. Intuíamos que, al carecer de patrón de aparición aparente, se debía a desajustes en los procesos de carga del JavaScript de Symfony y el nuestro, así que nos pusimos a indagar hasta que finalmente dimos con la solución:

Al añadir el campo en el formulario de Symfony, hacerlo añadiendo un auto_inline a false:


->add('text', CKEditorType::class,
    array(
        'required' => false,
        'auto_inline' => false,
        'inline' => true,
    )
)

Y en el config.yml, cargar la configuración que antes metíamos por JavaScript


page_manual:
            entities: false
            allowedContent: true
            format_tags: "p;h1;h2;h3"
            format_h1: { element: 'h3' }
            format_h2: { element: 'h4' }
            format_h3: { element: 'h5' }
            toolbar: [ ['WidgetTemplateMenu', 'BootstrapTabs'],['Bold', 'Italic', 'Underline', 'Strike'],['Format'],['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'],['NumberedList', 'BulletedList'],['Link', 'Unlink', 'Anchor'],['Image', 'PageBreak'],['PasteText', 'PasteFromWord', '-', 'Undo', 'Redo'],['Find', 'Replace', '-', 'SelectAll'],['ShowBlocks'] ]
            uiColor: "#ffffff"

El resultado es que ahora la configuración correcta se carga inicialmente desde el componente de Symfony, y el JavaScript se utiliza… para nada, ya no nos hace falta: un código más simple, eficaz y fiable.

Esperamos que esta solución le pueda servir a alguien y le ahorre un rato del camino que tuvimos que recorrer hasta llegar a esta concusión 🙂

Optimizando en MySQL, un elegante caso de uso de SUM con CASE

En uno de nuestros proyectos necesitamos poder estudiar el comportamiento de los usuarios. Así, tenemos almacenados en base de datos ciertos logs de actividad y por cada acción que realizan que queremos monitorizar guardamos una línea de log.

Para dar un poco de contexto, tenemos dos tablas en una base de datos MySQL: los logs se guardan en una tabla (datalog), en la que la mayor parte de líneas indican únicamente que se ha realizado una acción, pero en otras se indica un valor escalar: tiempo, cantidad asociada. Tenemos también una tabla (items) con la información complementaria sobre el elemento que se está monitorizando: nombre, identificador, categoría…

Leer más

Symfony 2: Form Type y Prototypes

La gracia de un framework es poderte abstraer de muchos problemas complejos, como por ejemplo dar una manera sencilla de administrar los contenidos. Symfony2 nos provee de una potente herramienta de gestión formularios: su creación, su validación, su populación… y la pieza más importante en relaciones 1 a N: los Form Prototypes. Cuando realizamos un proyecto, […]