jueves, 14 de noviembre de 2013

Scheduled tasks in Symfony2: Symfony commands and Cron

Sometimes we need some tasks to be executed regularly. We could, for instance, want to send every midnight an email to every user who hasn't logged into our app for three days in order to provide him with a report of unseen notifications.  To get jobs like this done in a Symfony app we need two tools: Symfony commands and a task scheduler such as Cron in UNIX systems.

First of all we need to create a Symfony command which will execute the required functionality. We will create a folder named Command inside our Bundle (It will be on the same level as Controller or Resources folders). Then we create the command class, whose name must end with "Command". In this case I'll name it MyCommand.



As you can see MyCommand class extends ContainerAwareCommand class so that we can use the Service Container just by doing $this->getContainer(). This will give us access to any registered service, such as Doctrine Entity Manager or SwiftMailer.

As far as class methods are concerned, we have two methods: configure and execute. In configure method we will configure our command. We give it a name, in this case devness:my_command and arguments if necessary. Arguments can be required or optional, depending on the second argument of addArgument method (InputArgument::OPTIONAL or InputArgument::REQUIRED)

Execute is the method in charge of doing the hard work. As mentioned before we can get any service through the service container. Inside this method we could, for example, check for users who hadn't logged in for some time using Doctrine and then send them an e-mail. Now we have our Symfony command, so we will run it.

  • php app/console devness:mycommand --my_argument=value

  • Our argument is optional so we could omit it:
    • php app/console devness:mycommand

    Does it work? Perfect. Now we need this task to be run at some point in time, or regularly (every five minutes, every day, etc.). We will use Cron to schedule this task. You only will be able to go ahead on this tutorial if you run your Symfony app in a UNIX environment such as Linux or Mac OS X. It's not the purpose of this post to give a master class about Cron (I'm including a good reference at the end of the post). In short, we have a plain text file named crontab which we can edit by executing the following command:
    • crontab -e
    In my laptop this command opens the file using Vim. I don't really like Vim much (to be honest I don't even understand it, I should give it a try) so I execute it this way (nano editor):
    • EDITOR=nano crontab -e

    If we want our Symfony command to be run every minute we will ad the following line to the crontab:
    • * * * * * php /Path/to/SymfonyApp/app/console devness:mycommand --myargument=value

    Or If we wanted to execute it every two hours we could add this instead:
    • * */2 * * * php /Path/to/SymfonyApp/app/console devness:mycommand --myargument=value

    So that's it! We already have our Symfony command up and running. You can get some additional info on crontab format following this link

    Ejecutar tareas programadas en Symfony 2 usando comandos Symfony y Cron

    A veces, durante el desarrollo de una aplicación, necesitamos que se ejecute algún tipo de acción cada cierto periodo de tiempo. Por ejemplo podemos querer enviar un correo electrónico a cada usuario que lleve más de 3 días sin hacer login en la aplicación con un resumen de las notificaciones que tiene pendientes de leer con el fin de incitarle a seguir usando la aplicación. Otro ejemplo del uso de las tareas programadas podría ser una aplicación en la que el usuario crea eventos y los gestiona. Sería interesante que cuando un evento finaliza (tiene una fecha/hora de finalización) se envíe un correo a su creador con un resumen de cómo ha ido el evento: asistentes, recaudación, etc. Para llevar esto a cabo tenemos que hacer uso de los comandos Symfony por una parte y por otra del administrador de tareas programadas de Unix (Cron).

    En primer lugar necesitamos crear el comando symfony que ejecute la funcionalidad que deseamos. Para ello creamos una carpeta llamada Command en nuestro Bundle (al mismo nivel que Controller o Resources) y le pondremos nombre a nuestra clase. El nombre siempre tiene que terminar en Command o no funcionará. Le vamos a llamar MyCommand.

    Como podéis ver la clase extiende de ContainerAwareCommand y tiene dos métodos: configure y execute. Extendemos de ContainerAwareCommand ya que de esta manera tenemos acceso al Service Container simplemente haciendo $this->getContainer(). Esto nos servirá para acceder a servicios muy comunes como el Entity Manager o el Mailer.

    En cuanto a los métodos en configure definimos el nombre del comando, en nuestro caso devness:my_command (luego explicaré para qué sirve) Como podéis observar en el ejemplo he incluido un argumento llamado "my_argument" de tipo opcional. Si usamos la constante InputArgument::REQUIRED en lugar de InputArgument::OPTIONAL el argumento será obligatorio.

    En el método execute es donde llevamos a cabo la tarea. En el código podéis ver algunas cosas típicas que pueden hacerse. Si se tratase del ejemplo que he dado en la introducción sobre enviar un correo-resumen de notificaciones a los usuarios que llevan días sin entrar aquí sería donde accederíamos a la base de datos para averiguar qué usuarios reunen los requisitos para recibir dicho correo y nos encargaríamos de enviarlo. Ahora que ya tenemos nuestro comando nos vamos al directorio donde tenemos nuestro proyecto symfony y lo ejecutamos:

    • php app/console devness:mycommand --my_argument=valor

    Como en nuestro ejemplo el argumento es opcional podríamos omitirlo:
    • php app/console devness:mycommand

    Funciona? Perfecto. Ahora necesitamos que esta tarea se ejecute en algún momento determinado del tiempo, normalmente de manera periódica. Para ello usaremos Cron (En sistemas UNIX como Linux o Mac OS X). En este post no pretendo explicar como funciona Cron, ya que con usar un poco a nuestro amigo Google podemos descubrir como funciona. En resumidas cuentas tenemos un archivo de texto llamado crontab que podemos editar en el servidor donde corre la aplicación Symfony mediante el siguiente comando:
    • crontab -e

    En mi ordenador este comando me abre el crontab con el editor Vim. Yo prefiero nano, por lo que yo lo ejecuto así:
    • EDITOR=nano crontab -e

    Si queremos que nuestra tarea se ejecute cada minuto escribiríamos la siguiente línea en nuestro crontab
    • * * * * * php /Ruta/Absoluta/Hasta/Proyecto/Symfony/app/console devness:mycommand --myargument=valor

    Si quisiéramos que se ejecutase cada 2 horas escribiríamos esto:
    • * */2 * * * php /Ruta/Absoluta/Hasta/Proyecto/Symfony/app/console devness:mycommand --myargument=valor

    Y ya está! ya tenemos nuestro comando Symfony ejecutándose temporalmente como nosotros queríamos. Podéis encontrar más ejemplos y información sobre el formato del crontab haciendo click aquí


    sábado, 9 de noviembre de 2013

    Using form inheritance in Symfony 2

    During the development of a web app it's fairly common to need forms sharing most of their fields. For example we often need a "new" and an "edit" form for some of the entities in our app. Normally we only need to add or remove some fields to get the new form and we don't want to replicate code as it will make our app harder to maintain and more error-prone.

    Let's say we have an Event entity and we need two forms: an EventNew form to create a new Event and another form that we will call EventEdit in order to edit an Event entity. We want our New form to have the following fields:

    • Name
    • Category
    • Subcategory
    • Datetime

    On the other hand, we want the EventEdit form to have the same fields except for Datetime (for some reason we don't want the user to be able to change the Datetime of the event once it's created). In addition, we have the Edit form to have a textarea field named "description" (we didn't want to annoy the user requiring a description for the event when it's created). So the list of fields for the edit form would be as follows:
    • Name
    • Category
    • Subcategory
    • Description

    We have at least two ways to solve this problem. We can create a BaseEventType class including the common fields for both forms and then inherit from it adding/removing fields as needed or we can create just two forms (the "new" one and the "edit" one) and make the EventEditType inherit from EventNewType. If you're sure you won't need more Event forms in the future you can just stick to option two. If we take option one we should create a class named BaseEventType containing all the common fields:


    Now we need an EventNewType for our new event form and we'll add the datetime field. Note that we need to call to the method buildForm of parent class (BaseEventType, which we extend).


    Last but not least we create our EventEditType and we already have our edit event form:

    If we decided to take option 2 we should create a EventNewType including all 4 fields (name, category, subcategory, datetime). Then we would create EventEditType inheriting from EventNewType and add/remove fields as necessary:




    Usando la herencia de formularios en Symfony2

    Durante el desarrollo de una aplicación web en Symfony 2 es bastante común encontrarnos con la necesidad de tener varios formularios similares entre sí. Por ejemplo es muy común necesitar un formulario para crear una nueva entidad y otro para editar dicha entidad. Normalmente la diferencia entre uno y otro no es más que añadir o eliminar algunos campos y no deseamos tener que copiar prácticamente todo el código del formulario ya que eso haría nuestro código menos mantenible y nuestra aplicación más propensa a errores.

    Digamos que tenemos una entidad Event y necesitamos dos formularios: uno al que llamaremos EventNew y que sirve para crear un nuevo evento y otro llamado EventEdit que servirá para editar un evento. Queremos que el formulario para crear un nuevo evento tenga los siguientes campos:

    • Name
    • Category
    • Subcategory
    • Datetime

    Por otra parte queremos que el formulario de edición tenga los mismos campos excepto la fecha/hora del evento (por alguna razón no deseamos que este dato pueda cambiarse una vez creado el evento). Además queremos que nuestro formulario de edición incluya un campo description, ya que hemos decidido que en el formulario de nuevo evento no lo queremos para agilizar el proceso de creación de un nuevo evento. Así pues la lista de campos del formulario de edición sería la siguiente::
    • Name
    • Category
    • Subcategory
    • Description

    Tenemos como mínimo dos maneras de resolver este problema. Podemos crear una clase llamada BaseEventType de la cual heredarán EventNewType y EventEditType, la cual contendrá los campos que son comunes a ambos formularios (nombre, categoría y subcategoría). La segunda opción es simplemente hacer que EventEditType herede de EventNewType (el cual contendrá sus 4 campos descritos anteriormente) y añadir o eliminar los campos necesarios. Probablemente la opción 1 es más adecuada de cara a ampliar en un futuro con más formularios. Si estás seguro de que solamente vas a necesitar estos dos la opción 2 es más rápida. Si escogemos la primera opción la clase BaseEventType quedaría así:


    Ahora necesitamos la clase EventNewType que heredará de la Base y añade el campo datetime. Fíjate como llamamos en buildForm al método buildForm de la clase padre (BaseEventType) con el fin de heredar los campos que hemos incluido en el base:


    Por último creamos la clase EventEditType:

    Si optamos por la segunda opción planteada anteriormente crearíamos la clase EventNewType incluyendo los cuatro campos (name, category, subcategory, datetime). Luego crearíamos la clase EventEditType heredando de EventNewType. Eliminaríamos el campo datetime y añadiríamos el campo description empleando los metodos add y remove de FormBuilderInterface: