Extendiendo la configuración gráfica del cron en Magento

Para evitar tener que lidiar con la configuración por xml, en Magento podemos crear la configuración gráfica para los cron jobs de nuestros módulos, de manera que estamos dando mayor flexibilidad al usuario y nos evitamos riesgos que podrían ocasionarse por una mala edición de los archivos.

Normalmente las opciones que nos ofrece la configuración suelen ser suficiente.

Opciones por defecto para configurar un cron job en Magento

En otros casos, es posible que no nos alcance con sólo poder configurar una ejecución diaria, semanal o mensual. En éste esquema, nos estamos perdiendo la posibilidad de configurar la ejecución con repetición por horas o por minutos.

Para poder obtener esas opciones vamos a necesitar crear dos modelos para nuestro módulo, que serán los encargados de brindarnos esas nuevas posibilidades (y de paso vamos a arreglar otras que no funcionan desde la implementación original).

Lo primero será crear un nuevo set de opciones de frecuencia de ejecución. Para esto, podemos crear la siguiente clase (pensando que el módulo se llama Dc_Modulo): Dc_Modulo_Model_Config_Cron_Frequency, que estaría ubicada en /app/code/local/Dc/Modulo/Model/Config/Cron/Frequency.php.

La clase contendría lo siguiente.

class Dc_Modulo_Model_Config_Cron_Frequency
{
 
    protected static $_options;
 
    const CRON_MINUTELY = 'I';
    const CRON_HOURLY   = 'H';
    const CRON_DAILY    = 'D';
    const CRON_WEEKLY   = 'W';
    const CRON_MONTHLY  = 'M';
 
    public function toOptionArray()
    {
        if (!self::$_options) {
            self::$_options = array(
                array(
                    'label' => Mage::helper('modulo')->__('Minutely'),
                    'value' => self::CRON_MINUTELY,
                ),
                array(
                    'label' => Mage::helper('modulo')->__('Hourly'),
                    'value' => self::CRON_HOURLY,
                ),
                array(
                    'label' => Mage::helper('modulo')->__('Daily'),
                    'value' => self::CRON_DAILY,
                ),
                array(
                    'label' => Mage::helper('modulo')->__('Weekly'),
                    'value' => self::CRON_WEEKLY,
                ),
                array(
                    'label' => Mage::helper('modulo')->__('Monthly'),
                    'value' => self::CRON_MONTHLY,
                ),
            );
        }
        return self::$_options;
    }
}

Luego vamos a crear (o modificar) el modelo encargado de capturar los valores ingresados en la configuración para convertirlos en una expresión de tiempo cron.

Siguiendo el ejemplo, la clase podría llamarse Dc_Modulo_Model_Config_Cron_Process, la cual se encontraría en /app/code/local/Dc/Modulo/Model/Config/Cron/Process.php.

Aquí es donde vamos a insertar unas cuantas modificaciones y correcciones al modelo original ofrecido por Magento.

class Dc_Modulo_Model_Config_Cron_Process extends Mage_Core_Model_Config_Data
{
 
    const CRON_STRING_PATH = 'crontab/jobs/mi_modulo_mi_cron_job/schedule/cron_expr';
    const CRON_MODEL_PATH = 'crontab/jobs/mi_modulo_mi_cron_job/run/model';
 
    protected function _afterSave()
    {
        $time = $this->getData('groups/cronjob_execution/fields/time/value');
        $frequency = $this->getData('groups/cronjob_execution/fields/frequency/value');
 
        $frequencyMinutely = Dc_Modulo_Model_Config_Cron_Frequency::CRON_MINUTELY;
        $frequencyHourly = Dc_Modulo_Model_Config_Cron_Frequency::CRON_HOURLY;
        $frequencyDaily = Dc_Modulo_Model_Config_Cron_Frequency::CRON_DAILY;
        $frequencyWeekly = Dc_Modulo_Model_Config_Cron_Frequency::CRON_WEEKLY;
        $frequencyMonthly = Dc_Modulo_Model_Config_Cron_Frequency::CRON_MONTHLY;
 
        $cronDayOfWeek = date('N');
        $cronDayOfMonth = date('j');
 
        $minutelyValue = null;
        if ($frequency == $frequencyMinutely) {
            $minutelyValue = '*/' . intval($time[1]);
        } else {
            $minutelyValue = intval($time[1]);
        }
 
        $hourlyValue = null;
        if ($frequency == $frequencyHourly) {
            $hourlyValue = '*/' . intval($time[0]);
        } else if ($frequency == $frequencyMinutely) {
            $hourlyValue = '*';
        } else {
            $hourlyValue = intval($time[0]);
        }
 
        $cronExprArray = array(
            $minutelyValue,
            $hourlyValue,
            ($frequency == $frequencyMonthly) ? $cronDayOfMonth : '*',
            '*',
            ($frequency == $frequencyWeekly) ? $cronDayOfWeek : '*',
        );
 
        $cronExprString = join(' ', $cronExprArray);
 
        try {
            Mage::getModel('core/config_data')
                ->load(self::CRON_STRING_PATH, 'path')
                ->setValue($cronExprString)
                ->setPath(self::CRON_STRING_PATH)
                ->save();
            Mage::getModel('core/config_data')
                ->load(self::CRON_MODEL_PATH, 'path')
                ->setValue((string) Mage::getConfig()->getNode(self::CRON_MODEL_PATH))
                ->setPath(self::CRON_MODEL_PATH)
                ->save();
        } catch (Exception $e) {
            throw new Exception(Mage::helper('modulo')->__('Unable to save Cron expression'));
        }
    }
}

El paso final es indicar en la configuración de system.xml, que para éste módulo estaría ubicado en /app/code/local/Dc/Modulo/etc/, que para la configuración utilice nuestros modelos.

En éste caso, la definición de estos campos quedaría de la siguiente forma.

<time translate="label">
    <label>Start Time</label>
    <frontend_type>time</frontend_type>
    <sort_order>1</sort_order>
    <show_in_default>1</show_in_default>
    <show_in_website>0</show_in_website>
    <show_in_store>0</show_in_store>
</time>
<frequency translate="label">
    <label>Frequency</label>
    <frontend_type>select</frontend_type>
    <source_model>modulo/config_cron_frequency</source_model>
    <backend_model>modulo/config_cron_process</backend_model>
    <sort_order>2</sort_order>
    <show_in_default>1</show_in_default>
    <show_in_website>0</show_in_website>
    <show_in_store>0</show_in_store>
</frequency>

Una vez aplicados estos cambios, nuestra opción de configuración debería verse de ésta forma.

Extendiendo las posibilidades de configuración de un cron job en Magento

Ahora veamos el resultado práctico de cada una de las configuraciones que ahora podemos aplicar.

Si configuramos la ejecución por minutos.

Configuración de cron jobs para ejecución por minutos en Magento

El resultado de ésta configuración será la siguiente expresión.

+-----------+---------+----------+--------------------------------------------------------+--------------+
| config_id | scope   | scope_id | path                                                   | value        |
+-----------+---------+----------+--------------------------------------------------------+--------------+
|      1041 | default |        0 | crontab/jobs/mi_modulo_mi_cron_job/schedule/cron_expr  | */30 * * * * |
+-----------+---------+----------+--------------------------------------------------------+--------------+

Si la opción fuera por hora.

Configuración de cron jobs para ejecución por hora en Magento

Obtendríamos ésta expresión.

+-----------+---------+----------+--------------------------------------------------------+--------------+
| config_id | scope   | scope_id | path                                                   | value        |
+-----------+---------+----------+--------------------------------------------------------+--------------+
|      1041 | default |        0 | crontab/jobs/mi_modulo_mi_cron_job/schedule/cron_expr  | 0 */2 * * *  |
+-----------+---------+----------+--------------------------------------------------------+--------------+

Ahora, una de las opciones conocidas: una vez por día.

Configuración de cron jobs para ejecución por día en Magento

Nos devuelve lo que ya conocemos.

+-----------+---------+----------+--------------------------------------------------------+--------------+
| config_id | scope   | scope_id | path                                                   | value        |
+-----------+---------+----------+--------------------------------------------------------+--------------+
|      1041 | default |        0 | crontab/jobs/mi_modulo_mi_cron_job/schedule/cron_expr  | 15 3 * * *   |
+-----------+---------+----------+--------------------------------------------------------+--------------+

Para el caso de la ejecución semanal.

Configuración de cron jobs para ejecución un día por semana en Magento

Ahora si grabará correctamente el valor.

+-----------+---------+----------+--------------------------------------------------------+--------------+
| config_id | scope   | scope_id | path                                                   | value        |
+-----------+---------+----------+--------------------------------------------------------+--------------+
|      1041 | default |        0 | crontab/jobs/mi_modulo_mi_cron_job/schedule/cron_expr  | 30 4 * * 5   |
+-----------+---------+----------+--------------------------------------------------------+--------------+

El último caso es el de la ejecución una vez por mes.

Configuración de cron jobs para ejecución un día por mes en Magento

Luego de la corrección, ahora si, nos devolverá la siguiente expresión.

+-----------+---------+----------+--------------------------------------------------------+--------------+
| config_id | scope   | scope_id | path                                                   | value        |
+-----------+---------+----------+--------------------------------------------------------+--------------+
|      1041 | default |        0 | crontab/jobs/mi_modulo_mi_cron_job/schedule/cron_expr  | 45 5 1 * *   |
+-----------+---------+----------+--------------------------------------------------------+--------------+

Si bien éste cambio nos permite una mejora sustancial en la configuración de un cron job, para crear configuraciones más avanzadas tenemos sólo dos opciones:

  1. Configurar por xml la expresión de tiempo.
  2. Habilitar un campo de texto en el cual el usuario ingresará la expresión como si lo estuviera haciendo en el xml.