How to make improve this simple counter on php - php

I make this simple counter
$now = date ("d");
$filename = $now .".txt";
$lastcount="";
if (file_exists($filename))
{
if (time()-filemtime($filename) > 2 * 86400) {
} else {
$lastcount=strval(intval (file_get_contents($filename))+1);
}
}
file_put_contents($filename, $lastcount);
Basically it reads a file, then add 1, then rewrite
The problem is between the time I read the file, and writing it back, another copy of the program may read the file and write it.
So how do I make that atomic?
I also want to ensure that the whole script won't "crash" because of this locking.
So how to improve this counter?

You can lock file using flock. Use exclusive locking to write to file safely:
$fp = fopen($filename, "rw");
if (flock($fp, LOCK_EX)) {
// write here
// ...
// release the file
flock($fp, LOCK_UN);
} else {
// can't use it yet. Wait a little.
}

Related

Shared access: How to fix "fread(): Length parameter must be greater than 0"?

When I run this function on multiple scripts one script generated warning:
fread(): Length parameter must be greater than 0
function test($n){
echo "<h4>$n at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
$fp = fopen("$n.txt", "r");
$s = fread($fp, filesize("$n.txt") );
fclose($fp);
$fp = fopen("$n.txt", "w");
$s = $_SERVER['HTTP_USER_AGENT'].' '.time();
if (flock($fp, LOCK_EX)) { // acquire an exclusive lock
fwrite($fp, $s);
// fflush($fp);// flush output before releasing the lock
flock($fp, LOCK_UN); // release the lock
} else {
echo "Couldn't get the lock!";
}
}
}
I try to write reading of the file for multiple users, but only one user can write the file. I know that when I use fwrite with flock - LOC_EX, next scripts must wait till the write is finished. But here it seems like filesize doesn't wait till the write operation is finished. My opinion is that it tries to reach the file when the file size is 0, and as a result this produces the problem: 0 bytes will be read from the file, when it is written by original script.
Is it possible to fix this for fread function?
Purpose of this script is to test fread with some limit and to check the data which I read later, if the data are really written when I did not used fflush.
function test($n){
echo "<h4>$n at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
$start = microtime(true);
$fp = fopen("$n.txt", "r");
if(filesize($n.txt) > 0)
{
$s = fread($fp, filesize($n.txt) );
fclose($fp);
$fp = fopen("$n.txt", "w");
$s = $_SERVER['HTTP_USER_AGENT'].' '.time();
if (flock($fp, LOCK_EX)) { // acquire an exclusive lock
fwrite($fp, $s);
// fflush($fp);// flush output before releasing the lock
flock($fp, LOCK_UN); // release the lock
} else {
echo "Couldn't get the lock!";
}
}
else
{
echo "Filesize must be greater than 0";
}
}
}
please change $s variables name its use same things two time
$fp = fopen("$n.txt", "r");
$s = fread($fp, filesize("$n.txt") );
fclose($fp);
The error occurs in the middle line of the above three lines.
Firstly, these three lines could be rewritten into a single line as follows:
$s = file_get_contents("$n.txt");
However, these isn't necessary, as these three lines are entirely redundant in your code. They don't do anything useful.
What they do is open a file, store its contents to $s and then close it.
But you are then immediately setting $s to a different value, thus throwing away the previous value, and making it pointless to have read it from the file in the first place.
If you need to keep the original contents of the file, then use file_get_contents() and make sure you don't overwrite the contents of the variable.
If you don't need the original contents of the file, then just delete those three lines from your code.
Incidentally, this error highlights a couple of good coding practices that you should take on board: Firstly, never re-use a variable for two different things, and secondly always give your variables (and functions) good names. $s is not a good name; $previousFileContents would be a better name; it would have made the error much more obvious.

simple php page session visit counter not working

I am having this strange issue and can't figure it out.
On some websites I have this script works perfect... same code, same server settings...
With php, there is a simple page view hit counter that stores locally in a txt file.
Then I echo out the value on the footer copyright area of my websites to give the client a quick statistic... its pretty cool how fast it grows.
Anyway.. i have a client corner grill ny . com (seo purposes I added spaces )
On that website.. its been working great for years.
Now another website and a bunch more.. for example... savianos . com
This breaks.. and the text value is blank.
This is the counter.php code
<?php
session_start();
$counter_name = "counter/hits.txt";
//Check if a text file exists. If not create one and initialize it to zero.
if (!file_exists($counter_name)) {
$f = fopen($counter_name, "w");
fwrite($f,"0");
fclose($f);
}
// Read the current value of our counter file
$f = fopen($counter_name,"r");
$counterVal = fread($f, filesize($counter_name));
fclose($f);
// Has visitor been counted in this session?
// If not, increase counter value by one
if(!isset($_SESSION['hasVisited'])){
$_SESSION['hasVisited']="yes";
$counterVal++;
$f = fopen($counter_name, "w");
fwrite($f, $counterVal);
fclose($f);
}
?>
Now, if I add a value in the txt file.. like 1040... and go to the website it starts to work... then after a week or so I check it .. its blank again.
Any ideas?
I am thinking that this may be happening because the website might get a TON of views during dinner time friday night.. and the simple script can't handle it so.. while its trying to write a added a number it just breaks and go to blank.. and never starts back up again.
The structure is this.
/counter/ folder has
counter.php and a hits.txt file
Every page of the website the very first thing is
<?php include ('counter/counter.php'); ?>
and in the footer of the website we have
<?php echo $counterVal; ?>
Your code looks perfect, but let's understand the situation. You have a file which can be accessed concurrently for many users, because page visit can be done by multiple users on same time. This does't seem right you have to lock the file manipulation for another user while someone is modifying it, right?. Please have a look
Visits counter without database with PHP
It is most likely because you have two concurrent scripts that tried to open the file at one and one of them fail. You have to use flock() when there are multiple instances of the script that could operate at the same time. Counter are some of the heaviest things if you going to use file reading and writing. I wrote this wrapper to easily implement file locking.
If you want to check out one of my counters that in operation try http://ozlu.org. That dynamic counter image was self-built. The fileReadAll will read the entire file in one shot. The file writer only has two modes, write or append. You can pass the fileWriter an array or a string and it will write it to the file. The function will not add any \n to format your text so you would have to add that. The default mode for the fileWriteAll is w if you do not set the third argument.
function fileWriteAll($file, $content, $mode = "w"){
$mode = $mode === "w" || $mode === "a"? $mode : "w";
$FILE = fopen($file, $mode);
while (!flock($FILE, LOCK_EX)) { usleep(1); }
if( is_array($content) ){
for ($i = 0; $i < count($content); $i++){
fwrite($FILE, $content[$i]);
}
} else {
fwrite($FILE, $content);
}
flock($FILE, LOCK_UN);
fclose($FILE);
}
function fileReadAll($file){
$FILE = fopen($file, 'r');
while (!flock($FILE, LOCK_SH)) { usleep(1); }
$content = fread($FILE, filesize($file));
flock($FILE, LOCK_UN);
fclose($FILE);
return $content;
}
Your modified code:
session_start();
$counterName = './views.txt';
if (!file_exists($counterName)) {
$file = fopen($counterName, 'w');
fwrite($file, '0');
fclose($file);
}
$file = fopen($counterName, 'r');
$value = fread($file, filesize($counterName));
fclose($file);
if (! isset($_SESSION['visited'])) {
$_SESSION['visited'] = 'yes';
$value++;
$file = fopen($counterName, 'w');
fwrite($file, $value);
fclose($file);
}
session_unset();
echo $value;

Editing a common temp file in php an preventing conflicts

I want to have a temp file that gets updated from time to time.
What I was thinking of doing is:
<!-- language: lang-php -->
// get the contents
$s = file_get_contents( ... );
// does it need updating?
if( needs_update() )
{
$s = 'some new content';
file_put_contents( ... );
}
The issue that I could see happening is that whatever condition causes 'needs_update()' to return true could cause more than one process to update the same file at, (almost), the same time.
In an ideal situation, I would have one single process updating the file and prevent all other processes from reading the file until I am done with it.
So as soon as 'needs_update()' return true is called I would prevent others processes from reading the file.
<!-- language: lang-php -->
// wait here if anybody is busy writing to the file.
wait_if_another_process_is_busy_with_the_file();
// get the contents
$s = file_get_contents( ... );
// does it need updating?
if( needs_update() )
{
// prevent read/write access to the file for a moment
prevent_read_write_to_file_and_wait();
// rebuild the new content
$s = 'some new content';
file_put_contents( ... );
}
That way, only one process could possibly update the file and the files would all be getting the latest values.
Any suggestions on how I could prevent such a conflict?
Thanks
FFMG
You are looking for the flock function. flock will work as long as everyone that acess the file is using it. Example from php manual:
$fp = fopen("/tmp/lock.txt", "r+");
if (flock($fp, LOCK_EX)) { // acquire an exclusive lock
ftruncate($fp, 0); // truncate file
fwrite($fp, "Write something here\n");
fflush($fp); // flush output before releasing the lock
flock($fp, LOCK_UN); // release the lock
} else {
echo "Couldn't get the lock!";
}
fclose($fp);
Manual: http://php.net/manual/en/function.flock.php

Load file lines into an array AFTER the file is open and locked (PHP)

Is there a quick way to load every line of a file into an array from a file once it has already been opened?
For example:
$handle = fopen("file", "r+");
flock($handle, LOCK_EX);
$array = load_lines($handle); <- need this
// compute on the array
fwrite($handle, $array);
flock($handle, LOCK_UN):
fclose($handle);
The reason I need this is because I currently use the file() function to grab the contents of a file and put them into an array. However, I need to incorporate file locking into my design and I'm hoping to not have to change it too much (it is current array-based). Is there an easy way to do this?
On php <5.3, or if you choose to with LOCK_NB, file locks in php are advisory. That is, you have to test the lock yourself .. they don't actually prevent you from updating the file. This will do:
$fh = fopen(__FILE__, 'r+');
if (flock($fh, LOCK_EX)) {
$array = file(__FILE__);
fwrite($fh, implode($array));
flock($fh, LOCK_UN);
flcose($fh);
}
else {
echo "Could not acquire the lock!"
}
I also tested this out in php 5.3. It seems that file() ignores locking.
Try this:
function load_lines($handle)
{
$array = array();
while(!feof($handle)
{
$array[] = fgets($handle);
}
return $array;
}

Lock files in PHP without using flock

When a user upload a file(users can upload multiple files)
exec('nohup php /main/apache2/work/upload/run.php &');
I am using nohup as the it needs to be executed in the back end.
In my original design run.php scans the directory using scandir everytime it's executed. Get an exclusive lock LOCK_EX on the file using flock and use LOCK_NB to skip the file if it has a lock and go the next one. If a file has a lock //Do logic. The problem is that the server is missing fcntl() library and since flock uses that library to execute the locking mechanism, flock won't work at the moment. It's going to take a month or two to get that installed(I have no control over that).
So my work around for that is have a temporary file lock.txt that acts a lock. If the filename exists in lock.txt skip the file and go to the next one.
$dir = "/main/apache2/work/upload/files/";
$files = scandir($dir);
$fileName = "lock.txt";
for($i=0; $i<count($files); $i++)
{
if(substr(strrchr($files[$i],'.csv'),-4) == '.csv')
{
if($file_handle = fopen("$fileName", "rb"))
{
while(!feof($file_handle))
{
$line = fgets($file_handle);
$line = rtrim($line);
if($line == "")
{
break;
}
else
{
if($files[$i] == $line)
{
echo "Reading from lock: ".$line."</br>";
$i++; //Go to next file
}
}
}
fclose($file_handle);
}
if($i >= count($files))
{
die("$i End of file");
}
if($file_handle = fopen("$fileName", "a+"))
{
if(is_writable($fileName))
{
$write = fputs($file_handle, "$files[$i]"."\n");
//Do logic
//Delete the file name - Stuck here
fclose($file_handle);
}
}
}
else
{
//Do nothing
}
}
How can I delete the filename from lock.txt?
More importantly, is there a better way to lock a file in php without using flock?
Having a shared lock database simply moves the locking problem to that file; it doesn't solve it.
A much better solution is to use one lock file per real file. If you want to lock access to myFile.csv then you check file_exists('myFile.csv.lock') and touch('myFile.csv.lock') if it doesn't exist. And unlink('myFile.csv.lock') when done.
Now, there is a possible race-condition between file_exists() and touch(), which can be mitigated by storing the PID in the file and checking if getmypid() is indeed the process holding the lock.

Categories