4

In Magento 1, instead of declaring the cron expression directly like this:

<crontab> 
    <jobs> 
        <vendor_module_cron>
            <schedule>*/30 * * * *</schedule>
            <run>
                <model>module/observer::runCron</model>
            </run>
        </vendor_module_cron>
    </jobs> 
</crontab>

you could setup your crontab like this in the config.xml:

<crontab> 
    <jobs> 
        <vendor_module_cron>
            <schedule>
                <config_path>vendor/module/cron_expr</config_path>
            </schedule>
            <run>
                <model>module/observer::runCron</model>
            </run>
        </vendor_module_cron>
    </jobs> 
</crontab>

Where Magento would use Mage::getStoreConfig((string)$jobConfig->schedule->config_path) to get the cron schedule expression.

This way you could dynamically change the schedule from the backend without having to modify the config.xml file directly.

How can we achieve this in Magento 2 ? Is it still possible ?

3 Answers 3

11

Following ProductAlerts in M2:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="default">
        <job name="catalog_product_alert" instance="Magento\ProductAlert\Model\Observer" method="process">
            <config_path>crontab/default/jobs/catalog_product_alert/schedule/cron_expr</config_path>
        </job>
    </group>
</config>

You should be able to define something similarly:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="default">
        <job name="custom_job_name" instance="Namespace\Module\Model\Observer" method="process">
            <config_path>namespace/module/schedule/cron_expr</config_path>
        </job>
    </group>
</config>

You could leverage the way they interpret the expression through multiple fields but it shouldn't be required, such as:

<group id="productalert_cron" translate="label" type="text" sortOrder="260" showInDefault="1" showInWebsite="0" showInStore="0">
    <label>Product Alerts Run 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>Magento\Cron\Model\Config\Backend\Product\Alert</backend_model>
    </field>
    <field id="time" translate="label" type="time" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0">
        <label>Start Time</label>
    </field>

This would make the input user-friendly (so-to-speak) but is not required, a simple text field that allows you to enter an expression like 7 7 * * * would be fine as well, I would just put some validation on that field to make sure the expression is correct.

2
  • When does it work this save status to cron_schedule like normal cron? Oct 30, 2020 at 2:53
  • @DavidDuong since the cronjob itself makes reference to the config path, this would be happening automatically as long as your field is accepting the expression only. Otherwise you'll need to handle an afterSave plugin to translate your fields to a valid expression (similar to the answer below for ProductAlerts module). Dec 17, 2020 at 16:03
3

You can achieve this same as magento 1

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="default">
        <job name="my_cron" instance="Namespace\Modulename\Observer\Cron" method="execute">
            <config_path>section-id/group-id/field-id</config_path>
        </job>
    </group>
</config>

See code vendor\magento\module-product-alert\etc\crontab.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="default">
        <job name="catalog_product_alert" instance="Magento\ProductAlert\Model\Observer" method="process">
            <config_path>crontab/default/jobs/catalog_product_alert/schedule/cron_expr</config_path>
        </job>
    </group>
</config>

It is a config path crontab/default/jobs/catalog_product_alert/schedule/cron_expr it has time schedule value if you see in database core_config_data

vendor\magento\module-cron\Model\Config\Backend\Product\Alert.php

in afterSave function

 const CRON_STRING_PATH = 'crontab/default/jobs/catalog_product_alert/schedule/cron_expr';

    public function afterSave()
    {
        $time = $this->getData('groups/productalert_cron/fields/time/value');
        $frequency = $this->getData('groups/productalert_cron/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();
    }

In above function set that config_path value from store > Configuration > Catalog > catalog > Product Alerts Run Settings

0

It's kinda simple.
I got this from module Mageplaza\ImageOptimizer:
You only need to load config_path in $this->_configValueFactory, and magento will automatic run the cron for you.

app/code/Mageplaza/ImageOptimizer/etc/adminhtml/system.xml:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="mpimageoptimizer" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0">
            <group id="cron_job" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0">
                <label>Cron Job</label>
                <field id="scan_schedule" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1">
                    <label>Scan Schedule</label>
                    <backend_model>Mageplaza\ImageOptimizer\Model\Config\Backend\Cron\Schedule</backend_model>
                </field>
            </group>
        </section>
    </system>
</config>

app/code/Mageplaza/ImageOptimizer/Model/Config/Backend/Cron/Schedule.php:

<?php

namespace Mageplaza\ImageOptimizer\Model\Config\Backend\Cron;

use Exception;
use Magento\Framework\App\Cache\TypeListInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Config\Value;
use Magento\Framework\App\Config\ValueFactory;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Message\ManagerInterface;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Registry;

/**
 * Class Schedule
 * @package Mageplaza\ImageOptimizer\Model\Config\Backend\Cron
 */
class Schedule extends Value
{
    /**
     * Cron scan path
     */
    const CRON_SCAN_PATH = 'crontab/default/jobs/mpimageoptimizer_cronjob_scan/schedule/cron_expr';

    /**
     * @var ValueFactory
     */
    protected $_configValueFactory;

    /**
     * @var ManagerInterface
     */
    protected $messageManager;

    /**
     * Schedule constructor.
     *
     * @param Context $context
     * @param Registry $registry
     * @param ScopeConfigInterface $config
     * @param TypeListInterface $cacheTypeList
     * @param ValueFactory $configValueFactory
     * @param ManagerInterface $messageManager
     * @param AbstractResource|null $resource
     * @param AbstractDb|null $resourceCollection
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $registry,
        ScopeConfigInterface $config,
        TypeListInterface $cacheTypeList,
        ValueFactory $configValueFactory,
        ManagerInterface $messageManager,
        AbstractResource $resource = null,
        AbstractDb $resourceCollection = null,
        $runModelPath = '',
        array $data = []
    ) {
        $this->_configValueFactory = $configValueFactory;
        $this->messageManager      = $messageManager;

        parent::__construct(
            $context,
            $registry,
            $config,
            $cacheTypeList,
            $resource,
            $resourceCollection,
            $data
        );
    }

    /**
     * @return Value
     */
    public function afterSave()
    {
        $enableScan       = $this->getData('groups/cron_job/fields/enabled_scan/value');
        $scanSchedule     = $this->getData('groups/cron_job/fields/scan_schedule/value');

        if ($enableScan) {
            try {
                $this->_configValueFactory->create()->load(
                    self::CRON_SCAN_PATH,
                    'path'
                )->setValue(
                    $scanSchedule
                )->setPath(
                    self::CRON_SCAN_PATH
                )->save();
            } catch (Exception $e) {
                $this->messageManager->addErrorMessage(__('We can\'t save the cron expression. %1', $e->getMessage()));
            }
        }

        return parent::afterSave();
    }
}

app/code/Mageplaza/ImageOptimizer/etc/crontab.xml:

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="default">
        <job name="mpimageoptimizer_cronjob_scan" instance="Mageplaza\ImageOptimizer\Cron\Scan" method="execute">
            <config_path>crontab/default/jobs/mpimageoptimizer_cronjob_scan/schedule/cron_expr</config_path>
        </job>
    </group>
</config>

app/code/Mageplaza/ImageOptimizer/Cron/Scan.php:

<?php

namespace Mageplaza\ImageOptimizer\Cron;

use Exception;
use Psr\Log\LoggerInterface;

/**
 * Class Scan
 * @package Mageplaza\ImageOptimizer\Cron
 */
class Scan
{
    /**
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * Scan constructor.
     *
     * @param LoggerInterface $logger
     */
    public function __construct(
        LoggerInterface $logger
    ) {
        $this->logger = $logger;
    }

    /**
     * @return $this
     */
    public function execute()
    {
        try {
            // do something
        } catch (Exception  $e) {
            $this->logger->critical($e->getMessage());
        }

        return $this;
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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