Magento – How to set cronjob dynamic schedule from database in Magento 2

configurationcroncrontabmagento2module

I am working on a custom module for Magento 2. I did setup crontab there and it is working good with static schedule.

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../app/code/Magento/Cron/etc/crontab.xsd">
    <group id="default">
        <job name="tm-feed-job" instance="TM\Feed\Model\Cron" method="export">
            <schedule>* * * * * *</schedule>
        </job>
    </group>
</config>

But I need the <schedule>* * * * * *</schedule> dynamic from my database saved value. Here I need to use 3 schedule Daily Weekly Monthly

Frequency already saved in database enter image description here

How can i add dynamic schedule there?

Best Answer

Not as easy as I expected.

First you need to update your crontab.xml to setup the config path to your frequency:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../app/code/Magento/Cron/etc/crontab.xsd">
    <group id="default">
        <job name="tm-feed-job" instance="TM\Feed\Model\Cron" method="export">
            <config_path>crontab/default/jobs/tm_feed_job/schedule/cron_expr</config_path>
        </job>
    </group>
</config>

Now you need to add a field to the configuration to be able to choose the frequency so in adminhtml/system.xml :

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="vendor">
            <group id="module" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
                <label>Cron Settings</label>
                <field id="frequency" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
                    <label>Frequency</label>
                    <source_model>Magento\Cron\Model\Config\Source\Frequency</source_model>
                    <backend_model>Vendor\Module\Model\Config\Backend\Frequency</backend_model>
                </field>
                <field id="time" translate="label" type="time" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0">
                    <label>Start Time</label>
                </field>
            </group>
        </section>
    </system>
</config>

Now we need to create Vendor\Module\Model\Config\Backend\Frequency for the frequency backend model:

<?php

namespace Vendor\Module\Model\Config\Backend;

class Frequency extends \Magento\Framework\App\Config\Value
{
    /**
     * Cron string path
     */
    const CRON_STRING_PATH = 'crontab/default/jobs/tm_feed_job/schedule/cron_expr';

    /**
     * Cron model path
     */
    const CRON_MODEL_PATH = 'crontab/default/jobs/tm_feed_job/run/model';

    /**
     * @var \Magento\Framework\App\Config\ValueFactory
     */
    protected $_configValueFactory;

    /**
     * @var string
     */
    protected $_runModelPath = '';

    /**
     * @param \Magento\Framework\Model\Context $context
     * @param \Magento\Framework\Registry $registry
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $config
     * @param \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList
     * @param \Magento\Framework\App\Config\ValueFactory $configValueFactory
     * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
     * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
     * @param string $runModelPath
     * @param array $data
     */
    public function __construct(
        \Magento\Framework\Model\Context $context,
        \Magento\Framework\Registry $registry,
        \Magento\Framework\App\Config\ScopeConfigInterface $config,
        \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList,
        \Magento\Framework\App\Config\ValueFactory $configValueFactory,
        \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
        \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
        $runModelPath = '',
        array $data = []
    ) {
        $this->_runModelPath = $runModelPath;
        $this->_configValueFactory = $configValueFactory;
        parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
    }

    /**
     * {@inheritdoc}
     *
     * @return $this
     * @throws \Exception
     */
    public function afterSave()
    {
        $time = $this->getData('groups/module/fields/time/value');
        $frequency = $this->getData('groups/module/fields/frequency/value');

        $cronExprArray = [
            intval($time[1]), //Minute
            intval($time[0]), //Hour
            $frequency == \Magento\Cron\Model\Config\Source\Frequency::CRON_MONTHLY ? '1' : '*', //Day of the Month
            '*', //Month of the Year
            $frequency == \Magento\Cron\Model\Config\Source\Frequency::CRON_WEEKLY ? '1' : '*', //Day of the Week
        ];

        $cronExprString = join(' ', $cronExprArray);

        try {
            $this->_configValueFactory->create()->load(
                self::CRON_STRING_PATH,
                'path'
            )->setValue(
                $cronExprString
            )->setPath(
                self::CRON_STRING_PATH
            )->save();
            $this->_configValueFactory->create()->load(
                self::CRON_MODEL_PATH,
                'path'
            )->setValue(
                $this->_runModelPath
            )->setPath(
                self::CRON_MODEL_PATH
            )->save();
        } catch (\Exception $e) {
            throw new \Exception(__('We can\'t save the cron expression.'));
        }

        return parent::afterSave();
    }
}

This code looks tricky at first sight but it basically generates the crontab/default/jobs/tm_feed_job/schedule/cron_expr config path based on what you've chosen in the frequency dropdown.

Interesting side note finding: this is implemented for a couple of modules in M2 natively which includes currency, product alerts, backups, sitemaps. The interesting thing is where the backend model are defined for some of those modules:

  • Product Alert: Magento\Cron\Model\Config\Backend\Product\Alert
  • Sitemap: Magento\Cron\Model\Config\Backend\Sitemap

So yeah you read it right. Instead of being in their corresponding module folder, those backend models are located under the Magento\Cron module folder.

Related Topic