7

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?

2 Answers 2

11

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.

4
  • thank you for the note - but wanted to ask what is runModel doing here. Pelase explain
    – Abid Malik
    Commented Oct 11, 2019 at 12:08
  • You always come up with a good solution. +1 :) Commented Oct 28, 2019 at 6:11
  • What the use of runModelPath ? Is it required to add this?
    – DJ Dev
    Commented Dec 27, 2019 at 11:36
  • its not working in magento 2.3.3. cron not run as per time set in configuration. my cron run at 00:01 everyday but i have set time 10:30 and Frequency "Daily".
    – nil108
    Commented Jan 22, 2020 at 12:04
0

When looking at \Magento\Cron\Observer\ProcessCronQueueObserver::_generateJobs it would suffice to create an varchar input at the specified config key for your respective <config_path> value to be read from core_config_data.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.