How to implement magento cron on yii

You are viewing revision #10 of this wiki article.
This is the latest version of this article.
You may want to see the changes made in this revision.

« previous (#9)

Hi Friends, First I would to say sorry but I am not very familiar to magento so after a 1 week spend I will implement the magneto cron functionality on yii I hope it's may be some helpful.

If I am not wrong magneto cron functionality work on read every xml file but in this tutorial I implement using database.

first everyone can set in your mind cron.php run every minutes

If you really develop a magneto cron functionality please follow the step..

1) First you can create the cron.php file on your root folder and paste the below code.

<?php

if (substr(phpversion(),0,1) != '6')
 	error_reporting(E_ALL);
else
 	error_reporting(E_ALL & ~E_STRICT);  
 
set_time_limit(0);
date_default_timezone_set('Europe/Oslo');

// change the following paths if necessary
//$yii=dirname(__FILE__).'/../yii/yii1.1.13/yii.php';
$yii=dirname(__FILE__).'/../yii/yii1.1.14/yii.php';
$blib=dirname(__FILE__).'/protected/blib.php';
$config=dirname(__FILE__).'/protected/config/cron.php';
// remove the following lines when in production mode
defined('YII_DEBUG') or define('YII_DEBUG',true);
// specify how many levels of call stack should be shown in each log message
defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3);
require_once($blib);
require_once($yii);
Yii::createApplication('CConsoleApplication',$config)->run();
?>

In this file I Will change a JVWebApplication to CConsoleApplication because every cron will be run using command from.

2)Go to protected/commands directory and create the MainCommand.php file and paste the below code.

<?php
Yii::import('application.modules.admin.components.*');
class MainCommand extends CConsoleCommand {


	//protected $_resourceName;


	/**
	 * Process cron queue
	 * Geterate tasks schedule
	 * Cleanup tasks schedule
	 *
	 */

     //SystemConfig::getValue('success_history_lifetime') = 200;
	 //SystemConfig::getValue('failure_history_lifetime') = 200;
	 //SystemConfig::getValueIndex('history_cleanup_every')= 10;
	 //SystemConfig::getValue('missed_if_not_run_within') = 10;
	 //SystemConfig::getValue('generate_schedules_every') = 15;
	 //SystemConfig::getValue('schedule_ahead_for') = 50;
	public function run($args) {
		$schedules = $this->getPendingSchedules();
		$scheduleLifetime = SystemConfig::getValue('missed_if_not_run_within') * 60;
		$now = time();
		foreach ($schedules as $schedule) {
			foreach ($this->generate() as $key=>$value){
				$jobConfig = $value;
				if (!$jobConfig) {
					continue;
				}
			}
			$time = strtotime($schedule->scheduled_at);
			if ($time > $now) {
				continue;
			}

			try {

				$errorStatus = VKcron::STATUS_ERROR;
				$errorMessage =('Unknown error.');
				if ($time < $now - $scheduleLifetime) {
					$errorStatus = VKcron::STATUS_MISSED;
					$schedule->status=$errorStatus;
					$schedule->messages = 'Too late for the schedule.';
				}
				if (!$schedule->tryLockJob()) {
					// another cron started this job intermittently, so skip it
					continue;
				}

				$schedule->status = VkCron::STATUS_RUNNING;
				$schedule->executed_at = strftime('%Y-%m-%d %H:%M:%S', time());


				// this is command run..
				system('php cron.php '.$schedule->job_code, $retval);



				$schedule->status = VkCron::STATUS_SUCCESS;
				$schedule->finished_at = strftime('%Y-%m-%d %H:%M:%S', time());




			}catch (Exception $e) {
				$schedule->status = $errorStatus;
				$schedule->messages = $e->__toString();
			}
			$schedule->save();
		}

		$this->generate();
		$this->cleanup();
	}

	public function generate(){


		/**
		 *	find a highest date 
		 */

		$criteria = new CDbCriteria;
		$criteria->select = '*';
		$criteria->order = "scheduled_at desc";
		$criteria->limit = "1";
		$resultSet    =    VkCron::model()->find($criteria);

		if(!empty($lastRun1->scheduled_at)){
			$lastRun = $resultSet->scheduled_at;
		}else{
			$lastRun = date('Y-m-d H:i:s',time());
		}
		/*end*/


		if ($lastRun > time() - SystemConfig::getValue('generate_schedules_every') * 60) {
			return $this;
		}

		$exists = array();
		foreach($this->getPendingSchedules() as $value){
			$exists[$value->job_code.'/'.$value->scheduled_at] = 1;
			//$config[$value->job_code] = array('schedule'=>array('cron_expr'=>'*/5 * * * *'));
		}
		$vk_Web= VkWebshopSetting::model()->findAll('cron_exp IS NOT NULL');
		foreach ($vk_Web as $da){
			$va=explode(',',$da->display_value);
			if($va[1] == 'Yes'){
				$config[$da->cron_exp]=  array('schedule'=>array('cron_expr'=>$va[0]));
				$this->_generateJobs($config, $exists);
			}else{
				/*existing record set ignore*/
				$find_cron = VkCron::model()->findAll("job_code='".$da->cron_exp."'");
				$result = VkCron::model()->updateAll(array( 'status' => VkCron::STATUS_IGNORE,'messages'=>'This job has been ignore'), "job_code='".$da->cron_exp."'");
			}


		}
		return $config;
	}


	public function _generateJobs($config, $exists){
		$scheduleAheadFor = SystemConfig::getValue('schedule_ahead_for')*60;
		$schedule = new VkCron();

		foreach ($config as $jobCode => $jobConfig) {
			$cronExpr = null;
			if (empty($cronExpr) && $jobConfig['schedule']['cron_expr']) {
				$cronExpr = (string)$jobConfig['schedule']['cron_expr'];

			}

			if (!$cronExpr) {
				continue;
			}

			$now = time();
			$timeAhead = $now + $scheduleAheadFor;
			$schedule->setJobCode($jobCode);
			$schedule->setCronExpr($cronExpr);
			$schedule->setStatus(VKcron::STATUS_PENDING);
			for ($time = $now; $time < $timeAhead; $time += 60) {
				$ts = strftime('%Y-%m-%d %H:%M:00', $time);
				if (!empty($exists[$jobCode.'/'.$ts])) {
					// already scheduled
					continue;
				}
				if (!$schedule->trySchedule($time)) {
					// time does not match cron expression
					continue;
				}

				$model= new VkCron();
				$model->job_code=$schedule->getData('job_code');
				$model->status=$schedule->getData('status');
				$model->created_at=$schedule->getData('created_at');
				$model->scheduled_at=$schedule->getData('scheduled_at');
				$model->save(false);

			}

		}
		return $this;

	}

	public function getPendingSchedules()
	{
		$pendingSchedules=VkCron::model()->findAll("status='".VKcron::STATUS_PENDING."'");
		return $pendingSchedules;
	}
	public function cleanup(){
		
		
		/*find a lowest date */
			$criteria = new CDbCriteria;
			$criteria->select = '*';
			$criteria->order = "scheduled_at ASC";
			$criteria->limit = "1";
			$resultSet    =    VkCron::model()->find($criteria);
			
			
			if(!empty($resultSet->scheduled_at)){
				$lastCleanup = date('Y-m-d H:i:s', strtotime($resultSet->scheduled_at) - 100);
			}else{
				$lastCleanup = date('Y-m-d H:i:s',time());
			}
		/*end*/


		if ($lastCleanup > time() - SystemConfig::getValueIndex('history_cleanup_every')*60) {
			return $this;
		}
		$history=VkCron::model()->findAll("status IN ('".VKcron::STATUS_SUCCESS."', '".VKcron::STATUS_MISSED."','".VKcron::STATUS_ERROR."')");
		$historyLifetimes = array(
			VKcron::STATUS_SUCCESS => SystemConfig::getValue('success_history_lifetime')*60,
			VKcron::STATUS_MISSED => SystemConfig::getValue('failure_history_lifetime')*60,
			VKcron::STATUS_ERROR => SystemConfig::getValue('failure_history_lifetime')*60,
		);
		$now = time();
		foreach ($history as $record) {
			if (strtotime($record->executed_at) < $now - $historyLifetimes[$record->status]) {
				VkCron::model()->deleteAll();
			}
		}

	}

}
?>

So In this file I will created implement a all cron command run dynamically and create next scheduled generated.

My client requirement..

for ex:

If my command like import product 15 , import customer 2 etc.. so what excetlly this command

=> If I run this command on commnadForm

php cron.php import product 15 

so fetch the all product data from magneto site to yii and insert the yii table.

3) whatever your command you can all command inserting on single table and set the default cron time.

CREATE TABLE `vk_webshop_setting` (
	`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	`display_value` TEXT NULL,
	`cron_exp` TEXT NULL,
	`status` TINYINT(1) NULL DEFAULT '0' COMMENT 'Status',
	`position` INT(11) NULL DEFAULT '0' COMMENT 'Position',
	PRIMARY KEY (`id`),

	)
	COLLATE='utf8_general_ci'
	ENGINE=InnoDB
	AUTO_INCREMENT=1;
In this table insert the default cron time and command like 

for ex:

If I run import product 15 command scheduled every 5 minutes so in this table


INSERT INTO `vk_default_cron_setting` (`id`, `display_value`, `cron_exp`, `status`,`position`) VALUES (1,'*/5 * * * *,Yes', 'import notification 15', 1, ,0);

This is the my default cron table and next scheduled generate using this  */5 * * * * (display_value)

4) Next step you can create the next scheduled.

=>first create the table same as magento vk_cron table.


CREATE TABLE `vk_cron` (
		`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
		`job_code` VARCHAR(255) NULL DEFAULT NULL COMMENT 'Job Code',
		`status` ENUM('pending','running','sucess','missed','error','ignore') NOT NULL DEFAULT 'pending' COMMENT 'Status',
		`messages` TEXT NULL COMMENT 'Messages',
		`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Created At',
		`scheduled_at` TIMESTAMP NULL DEFAULT NULL COMMENT 'Scheduled At',
		`executed_at` TIMESTAMP NULL DEFAULT NULL COMMENT 'Executed At',
		`finished_at` TIMESTAMP NULL DEFAULT NULL COMMENT 'Finished At',
		PRIMARY KEY (`id`),
		INDEX `id` (`id`)
	)
	COLLATE='utf8_general_ci'
	ENGINE=InnoDB
	AUTO_INCREMENT=1;

so create the VKCron.php model

<?php

Yii::import('application.models._base.BaseVkCron');

class VkCron extends BaseVkCron
{
	const STATUS_PENDING = 'pending';
	const STATUS_RUNNING = 'running';
	const STATUS_SUCCESS = 'sucess';
	const STATUS_MISSED = 'missed';
	const STATUS_ERROR = 'error';
	const STATUS_IGNORE = 'ignore';

	protected $_status = NULL;
	protected $_job_code = NULL;
	protected $_cron_expr_arr = NULL;
	protected $_created_at = NULL;
	protected $_executed_at = NULL;



	protected  $data = array();

	public static function model($className=__CLASS__) {
		return parent::model($className);
	}


	public function getData($key = null){
		if(trim($key)) {
			if(isset($this->data[$key])){
				return $this->data[$key];
			} else {
				return null;
			}
		}
		return $this->data;

	}

	public function setStatus($data)
	{
		$this->data['status'] = $data;
		return $this;
	}

	public function setJobCode($data)
	{

		$this->data['job_code'] = $data;
		return $this;
	}


	public function setCronExpr($data)
	{
		$this->data['cron_expr_arr'] = preg_split('#\s+#', $data, null, PREG_SPLIT_NO_EMPTY);
		if (sizeof($this->data['cron_expr_arr'])<5 || sizeof($this->data['cron_expr_arr'])>6) {
			echo "Invalid cron expression";
			Yii::app()->end();
		}
		return $this;

	}

	public function setCreatedAt($data)
	{
		$this->data['created_at'] = $data;
		return $this;
	}
	public function setScheduledAt($data)
	{
		$this->data['scheduled_at'] = $data;
		return $this;
	}




	public function trySchedule($time)
	{
		$data= date('Y-m-d H:i:s',$time);

		$e = $this->data['cron_expr_arr'];
		if (!$e || !$time) {
			return false;
		}
		if (!is_numeric($time)) {
			$time = strtotime($time);
		}

		$data1= explode(' ',$data);

		$tm = $data1[1];
		$currnet_time=explode(":",$tm);

		$unix = $data1[0];
		$currnt_date=explode("-",$unix);

		$day_of_week = date('N', strtotime(date('l')));

		$d = array(
					'seconds'=>$currnet_time[2],
					'minutes'=>ltrim($currnet_time[1],'0'),
					'hours'=>ltrim($currnet_time[0],'0'),
					'mday'=>ltrim($currnt_date[2],'0'),
					'wday'=>$day_of_week,
					'mon'=>ltrim($currnt_date[1],'0'),
					'year'=>$currnt_date[0],
					'yday'=>date('j',strtotime("-1 days")),
					'weekday'=>date('l'),
					'month'=>date('F'),
					//'0'=>date('Y-m-d H:i:s',$time),
					'0'=>$time,
		);


		$match = $this->matchCronExpression($e[0], $d['minutes'])
		&& $this->matchCronExpression($e[1], $d['hours'])
		&& $this->matchCronExpression($e[2], $d['mday'])
		&& $this->matchCronExpression($e[3], $d['mon'])
		&& $this->matchCronExpression($e[4], $d['wday']);
		if ($match) {
			$this->setCreatedAt(strftime('%Y-%m-%d %H:%M:%S', time()));
			$this->setScheduledAt(strftime('%Y-%m-%d %H:%M', $time));
		}
		return $match;
	}
	public function matchCronExpression($expr, $num)
	{
		//handle ALL match
		if ($expr==='*') {
			return true;
		}

		// handle multiple options
		if (strpos($expr,',')!==false) {
			foreach (explode(',',$expr) as $e) {
				if ($this->matchCronExpression($e, $num)) {
					return true;
				}
			}
			return false;
		}

		// handle modulus
		if (strpos($expr,'/')!==false) {
			$e = explode('/', $expr);
			if (sizeof($e)!==2) {
				echo "Invalid cron expression, expecting 'match/modulus':'.$expr.'";
				Yii::app()->end();

			}
			if (!is_numeric($e[1])) {
				echo "Invalid cron expression, expecting numeric modulus:'.$expr.'";
				Yii::app()->end();
			}
			$expr = $e[0];
			$mod = $e[1];
		} else {
			$mod = 1;
		}

		// handle all match by modulus
		if ($expr==='*') {
			$from = 0;
			$to = 60;
		}
		// handle range
		elseif (strpos($expr,'-')!==false) {
			$e = explode('-', $expr);
			if (sizeof($e)!==2) {
				echo "Invalid cron expression, expecting 'from-to' structure:'.$expr.'";
				Yii::app()->end();

			}


			$from = $this->getNumeric($e[0]);
			$to = $this->getNumeric($e[1]);
		}
		// handle regular token
		else {
			$from = $this->getNumeric($expr);
			$to = $from;
		}

		if ($from===false || $to===false) {
			echo "Invalid cron expression:'.$expr.'";
		}
		//echo $from .'to: '. $to .'Num: '.$num ; die;
		return ($num>=$from) && ($num<=$to) && ($num%$mod===0);
	}


	public function getNumeric($value)
	{


		static $data = array(
            'jan'=>1,
            'feb'=>2,
            'mar'=>3,
            'apr'=>4,
            'may'=>5,
            'jun'=>6,
            'jul'=>7,
            'aug'=>8,
            'sep'=>9,
            'oct'=>10,
            'nov'=>11,
            'dec'=>12,

            'sun'=>0,
            'mon'=>1,
            'tue'=>2,
            'wed'=>3,
            'thu'=>4,
            'fri'=>5,
            'sat'=>6,
		);



		if (is_numeric($value)) {
			return $value;
		}

		if (is_string($value)) {
			$value = strtolower(substr($value,0,3));
			if (isset($data[$value])) {
				return $data[$value];
			}
		}

		return false;
	}

	public function tryLockJob(){
		return $this->trySetJobStatusAtomic($this->id, VkCron::STATUS_RUNNING,VkCron::STATUS_PENDING);
	}

	public function trySetJobStatusAtomic($scheduleId, $newStatus, $currentStatus){
		$result = VkCron::model()->updateAll(array( 'status' => $newStatus), "id='".$scheduleId."' AND status='".$currentStatus."' ");
		if($result == 1){
			return true;
		}
		return false;
	}






}
when I was run command php cron.php main then next scheduled will be generated on db like


INSERT INTO `vk_cron` (`id`, `job_code`, `status`, `messages`, `created_at`, `scheduled_at`, `executed_at`, `finished_at`) VALUES (1, 'import productReview 15', 'pending', NULL, '2014-01-23 10:19:18', '2014-01-23 11:08:00', NULL, NULL);
	
In default magneto table set a 5 status and I will be add one more status ignore why I add this because when I was create the 
scheduled and after stop the cron so current execute scheduled will be set the ignore.

I hope it's easliy understand..

I will be added in feature.

0 0
3 followers
Viewed: 20 717 times
Version: Unknown (update)
Category: How-tos
Written by: Ankit Modi
Last updated by: CeBe
Created on: Feb 6, 2014
Last updated: 10 years ago
Update Article

Revisions

View all history

Related Articles