PHP Shared file reader/writer

Recently i came accross a simple problem : how to perform a read/write on a file with a shared system.

For example : let’s imagine a JSON system stored inside a file, this JSON system needs to be updated. Of course, it’s quite easy to understand that if two people are writing the file at the same time (updating the same JSON at the same time), there is a risk to get a writing problem. And this can result to an unreadable JSON.

So I decide to create a basic class to perform like file_get_contents and file_put_contents, but with a lock system between the two commands, by this way, you can read the file, perform modification on it, and store the modified version, while another PHP thread will wait system to finish…

Of course, you should take care a lot of the lock release, to be sure to never have locked resources which can lead to a strong problem.

Here is the class I used to manage this lock, i use the usual fopen and fclose, creating a lock using flock :

<?php
/**
 * sharedFile class
 * @class			sharedFile
 * @file			shared/sharedFile.class.php
 * @brief			This class use read/write file with a lock state
 * @version			0.1
 * @date			2012-06-28
 * @copyright		OpenSource : LGPLv3
 *
 * This class use read/write file with a lock state
 */
class sharedFile{
	private $file;
	private $filename;
	private $fileExist;
	private $locked;

	/**
	 * Constructor
	 * @param string $file The file to read
	 */
	public function __construct($file){
		$this->locked = false;
		$this->filename = $file;
		$this->fileExist = file_exists($file);

		//Trying to create file
		if($this->fileExist === false){
			touch($file);
		}

		$this->file = @fopen($file, "rb+");
		if($this->file !== false){
			$this->locked = flock($this->file, LOCK_EX);
		}
	}

	/**
	 * Destructor : Perform a lock release if needed
	*/
	public function __destruct(){
		$this->close();
	}

	/**
	 * Get the exist state of the file
	 * @return boolean True if the file exist, false else
	*/
	public function exists(){
		return $this->fileExist;
	}

	/**
	 * Get the filename of the current watched file
	 * @return string The filename
	*/
	public function getFilename(){
		return $this->filename;
	}

	/**
	 * Get the file content (alias of read function)
	 * @return mixed False if there is an open or locked error, a string if the content was fully readed
	*/
	public function get(){
		return $this->read();
	}

	/**
	 * Get the file content
	 * @return mixed False if there is an open or locked error, a string if the content was fully readed
	*/
	public function read(){
		if($this->file === false && $this->locked !== true){
			return false;
		}

		//Start from beginning
		fseek($this->file, 0);
		$result = "";
		//Read data
		while(!feof($this->file)){
			$result .= fgets($this->file, 4096);
		}
		return $result;
	}

	/**
	 * Set the file content (alias of write function)
	 * @param string $data The data to store into file
	 * @return boolean True if the data where saved, false else.
	*/
	public function set($data){
		return $this->write($data);
	}

	/**
	 * Set the file content
	 * @param string $data The data to store into file
	 * @return boolean True if the data where saved, false else.
	*/
	public function write($data){
		if($this->file === false  && $this->locked !== true){
			return false;
		}

		//Clearing content and go back to first characters
		ftruncate($this->file, 0);
		rewind($this->file);

		//Save data (in UTF8 if possible)
		if(!mb_detect_encoding($data, "UTF-8", true)){
			fwrite($this->file, utf8_encode($data));
		}else{
			fwrite($this->file, $data);
		}
		fflush($this->file);
		return true;
	}

	/**
	 * Write data to file, and close, send back the write state result
	 * @param string $data The data to store into file
	 * @return boolean True if the data where saved, false else.
	*/
	public function writeClose($data){
		$tmp = $this->write($data);
		$this->close();
		return $tmp;
	}

	/**
	 * Get back the lock state of the file
	 * @return boolean True the file is locked, false if the lock failed
	*/
	public function isLocked(){
		return $this->locked;
	}

	/**
	 * Close the openned file and remove lock
	*/
	public function close(){
		if($this->locked === true){
			flock($this->file, LOCK_UN);
		}
		$this->locked = false;
		if($this->file !== false){	
			fclose($this->file);
		}
	}
}
?>

To use this class, simply use code below :

$test = new sharedFile("/tmp/test.txt");

//Read the whole file
$fileContent = $test->read();

//Writing into file
$test->write("hello I'm writing content");
$test->close();

//OR : write and close directly the file (the close release the lock)
$test->writeClose("hello I'm writing content");

You must use this script, everytime you need to do have shared lock, because flock will not work if another process does not take care of it. So this class must be the only one used for files you want to manage with lock (or at least you must use also flock to not have sharing problem)

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :