Location: PHPKode > scripts > Script Alone > ScriptAlone.php
<?php

/**
 * 
 * @desc Class for limiting running only one instance of some script
 * @see http://code.google.com/p/scriptalone
 * @author Barbushin Sergey http://www.linkedin.com/in/barbushin
 * 
 */
class ScriptAlone {

	protected $processUid;

	protected $startedTime;

	protected $workedTime;

	protected $expireTime;

	protected $withoutNotifyLifetime;
	
	protected $scriptLifetime;

	protected $stateFilepath;

	protected $stopOnReadyToStop;
	
	protected $scriptWasStopped;
	
	public function __construct($stateFilepath, $withoutNotifyLifetime = 60, $scriptLifetime = null, $stopOnReadyToStop = false) {
		
		if(!$withoutNotifyLifetime) {
			$withoutNotifyLifetime = 60*60*24*365*100; // 100 years
		}
		
		if($scriptLifetime < $withoutNotifyLifetime) {
			throw new Exception('Value of argument "scriptLifetime" cannot be less than value of "withoutNotifyLifetime"');
		}
		
		$this->stateFilepath = $stateFilepath;
		$this->withoutNotifyLifetime = $withoutNotifyLifetime;
		$this->scriptLifetime = $scriptLifetime;
		$this->stopOnReadyToStop = $stopOnReadyToStop;
		$this->processUid = time() . mt_rand();
		$this->startedTime = time();
		
		if ($this->isAnotherNotExpiredScriptInstance()) {
			$this->stop('Another script instance still not expired');
		}
		
		$this->notifyItWorks(false);
	}

	/**************************************************************
	 STATE STORAGE
	 **************************************************************/
	
	protected function stateVarsToString(array $vars) {
		$string = '';
		foreach ($vars as $title => $value) {
			$string .= "$title: $value\n";
		}
		return rtrim($string, "\n");
	}

	protected function stateStringToVars($string) {
		$vars = array();
		if (preg_match_all('/([\w]+?)\:\s*(.+)/', $string, $m)) {
			foreach ($m[1] as $i => $title) {
				$vars[$title] = $m[2][$i];
			}
		}
		return $vars;
	}

	protected function saveState() {
		$stateString = $this->stateVarsToString(array(
			'PID' => $this->processUid, 
			'Started' => date('Y-m-d H:i:s', $this->startedTime), 
			'Worked' => date('Y-m-d H:i:s', $this->workedTime), 
			'Expire' => date('Y-m-d H:i:s', $this->expireTime)));		
		
		if (!file_put_contents($this->stateFilepath, $stateString)) {
			throw new Exception('Saving state was failed, cannot write file '.$this->stateFilepath);
		}
	}

	protected function readState(&$processUid, &$startedTime, &$workedTime, &$expireTime) {
		clearstatcache();
		if (is_file($this->stateFilepath)) {
			$state = $this->stateStringToVars(file_get_contents($this->stateFilepath));
			$processUid = isset($state['PID']) ? $state['PID'] : null;
			$startedTime = isset($state['Started']) ? strtotime($state['Started']) : null;
			$workedTime = isset($state['Worked']) ? strtotime($state['Worked']) : null;
			$expireTime = isset($state['Expire']) ? strtotime($state['Expire']) : null;
			return $state;
		}
		return false;
	}

	/**************************************************************
	 STATES
	 **************************************************************/
	
	public function isAnotherNotExpiredScriptInstance() {
		if ($state = $this->readState($processUid, $startedTime, $workedTime, $expireTime)) {
			return $processUid != $this->processUid && $expireTime > time();
		}
		return false;
	}

	public function isAnotherScriptInstanceStarted() {
		if ($this->readState($processUid, $startedTime, $workedTime, $expireTime)) {
			return $processUid != $this->processUid;
		}
		return false;
	}
	
	public function isReadyToStop() {
		return $this->scriptLifetime && ($this->startedTime + $this->scriptLifetime - time()) < $this->withoutNotifyLifetime;
	}

	public function isScriptStopped() {
		return is_file($this->stateFilepath . '.stop');
	}

	/**************************************************************
	 ACTIONS
	 **************************************************************/
	
	public function notifyItWorks($checkAnotherScriptInstanceStarted = true) {
		$this->workedTime = time();
		$this->expireTime = $this->workedTime + $this->withoutNotifyLifetime;
		
		if ($this->isScriptStopped()) {
			$this->unlinkStateFilepath();
			$this->stop('Script is stoped by stop-file');
		}
		elseif ($checkAnotherScriptInstanceStarted && $this->isAnotherScriptInstanceStarted()) {
			$this->stop('Another script instance was started');
		}
		elseif($this->isReadyToStop() && $this->stopOnReadyToStop) {
			$this->unlinkStateFilepath();
			$this->stop('Script lifetime is over');
		}
		
		set_time_limit($this->withoutNotifyLifetime);
		
		$this->saveState();
	}
	
	
	
	protected function stop($message) {
		$this->scriptWasStopped = true;
		throw new ScriptAlone_Stopped($message);
	}

	protected function unlinkStateFilepath() {
		if (is_file($this->stateFilepath)) {
			unlink($this->stateFilepath);
		}
	}

	public function __destruct() {
		if (!$this->scriptWasStopped) {
			$this->unlinkStateFilepath();
		}
	}
}

class ScriptAlone_Stopped extends Exception {
}
Return current item: Script Alone