Tuesday, January 21, 2014

Magento Configurable Cron

Cron in Magento is fairly easy to configure. A few lines in config.xml, a method and it’s done. But what happens if you want to spice it up a bit and create a schedule-configurable cron? Fortunately for us, Magento has that already included in the system and it’s pretty easy to implement.
Cron itself is more or less straightforward. As seen in the code below, it is defined in a config.xml file with two main parts: schedule and a method to be ran. A sample below shows a cron job named “my_cron” that runs “doSomething” method inside the observer file every five minutes. If you’re not familiar with the cron schedule format, there’s a bunch of the articles and cron generators on the internet.
<config>
    <crontab>
        <jobs>
            <my_cron>
                <schedule>
                    <cron_expr>*/5 * * * *</cron_expr>
                </schedule>
                <run>
                    <model>mymodule/observer::doSomething</model>
                </run>
            </my_cron>
        </jobs>
    </crontab>
</config>

The thing about this is that the schedule cannot be configured…yet. Inside the Mage_Cron_Model_Observer class, which generates cron jobs, there’s a “generate” method that also checks for the values inside core_config_data table in the same way as it does for config.xml files. Having that in mind, we could save the path to the cron schedule and set any (valid) value we want. So let’s get to work!

Admin configuration

As we want to have schedule-configurable cron we have to have an admin configuration for that. As you can make it whatever and however you want, I’m not going to do the miracles here but I’ll use what Magento already offers to us.
Inside the system.xml will be time (hour, minute and second) and frequency selectors (daily, weekly and monthly) that already come packed within Magento. All the coding that has to be done is to create our own backend model that will format and save the values to path we want. In this case  it’s will be in the mymodule/adminhtml_system_config_backend_mymodel_cron.
<config>
    <sections>
        <catalog>
            <groups>
                <configurable_cron translate="label">
                    <label>Cron Schedule</label>
                    <sort_order>100</sort_order>
                    <show_in_default>1</show_in_default>
                    <show_in_website>0</show_in_website>
                    <show_in_store>0</show_in_store>
                    <fields>
                        <time translate="label">
                            <label>Start Time</label>
                            <frontend_type>time</frontend_type>
                            <sort_order>10</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>adminhtml/system_config_source_cron_frequency</source_model>
                            <backend_model>mymodule/adminhtml_system_config_backend_mymodel_cron</backend_model>
                            <sort_order>20</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>0</show_in_website>
                            <show_in_store>0</show_in_store>
                        </frequency>
                    </fields>
                </configurable_cron>
            </groups>
        </catalog>
    </sections>
</config>

Backend model

The frontend types in the admin configuration don’t save the inputs in the way the cron generator can read them. Because of that we have to have a backend model that fill format those inputs the way we want.
From the sample code above on how the regular cron job is set in the config.xml, the same paths should be used in the core_config_data table that we’re going to deal with. From the code above, the path to our schedule would be “crontab/jobs/my_cron/schedule/cron_expr”. The code below formats the inputs and saves the value to the table.
class Inchoo_MyModule_Model_Adminhtml_System_Config_Backend_MyModel_Cron extends Mage_Core_Model_Config_Data
{
    const CRON_STRING_PATH = ‘crontab/jobs/my_cron/schedule/cron_expr';
    protected function _afterSave()
    {
        $time = $this->getData('groups/configurable_cron/fields/time/value');
        $frequencyDaily = Mage_Adminhtml_Model_System_Config_Source_Cron_Frequency::CRON_DAILY;
        $frequencyWeekly = Mage_Adminhtml_Model_System_Config_Source_Cron_Frequency::CRON_WEEKLY;
        $frequencyMonthly = Mage_Adminhtml_Model_System_Config_Source_Cron_Frequency::CRON_MONTHLY;
        $cronDayOfWeek = date('N');
        $cronExprArray = array(
            intval($time[1]),                                   # Minute
            intval($time[0]),                                   # Hour
            (frequency == $frequencyMonthly) ? '1' : '*',       # Day of the Month
            '*',                                                # Month of the Year
            (frequency == $frequencyWeekly) ? '1' : '*',        # Day of the Week
        );
        $cronExprString = join(' ', $cronExprArray);
        try {
            Mage::getModel('core/config_data')
                ->load(self::CRON_STRING_PATH, 'path')
                ->setValue($cronExprString)
                ->setPath(self::CRON_STRING_PATH)
                ->save();
        }
        catch (Exception $e) {
            throw new Exception(Mage::helper('cron')->__('Unable to save the cron expression.'));
        }
    }
}
Cron generator goes through the cron jobs and takes only the ones with the schedule defined. To have the schedule read from the config table, the schedule node has to be removed from the config.xml and only have the model defined, as shown below.
<config>
    <crontab>
        <jobs>
            <my_cron>
                <run>
                    <model>mymodule/observer::doSomething</model>
                </run>
            </my_cron>
        </jobs>
    </crontab>
</config>

What happens in the background? Cron generator runs through the config.xml files and config table, looks for the cron schedules and populates the cron schedule table. At this point models are irrelevant. All the cron jobs that have no schedule defined will be skipped. Another method, Cron dispatch, reads the cron jobs from the cron schedule table, collects the methods from both config.xml files and the config table and runs them.

Observer

In the observer will be the method that will be ran by the cron. What code comes here is up to you.

class Inchoo_MyModule_Model_Observer
{
    public function doSomething()
    {
    // do something
    }
}

This is just one example of using cron schedules. There’s plenty of room for modifications and improvements depending on your needs after you get the point how this works.

No comments:

Post a Comment