I'm writing a PHP app that has a 'control panel' that writes a prefs file with certain variables. On every POST, if the file doesn't exist, it is created. If it does exist, it is unlinked and a new file is touched with the same filename and new variables. This file is then included on another page with displays content based on the variables inside it.
$file = "phpsettings.php";
if (!file_exists($file)) {
touch($file);
$handle = fopen ($file, 'r+');
$str = "<?php \$pref1 = \"$mypref\"; ?>";
} else {
unlink($file);
touch($file);
$handle = fopen ($file, 'r+');
$str = "<?php \$pref1 = \"$mypref\"; ?>";
}
fwrite ($handle, $str);
fclose ($handle);
Is this a safe way of writing preferences, provided this file will be overwritten many times per day? What is a good way of both alerting the user of this control panel if the file wasn't saved correctly, and in that case, what would be a good contingency plan to avoid breaking the page this prefs file is included on short of defining a default set of variables to fill if !(file_exists)?
If you store your settings in an array, you can serialize() them and write to a text file, rather than writing raw php to a php file and including it.
If you're not sanitising your input for those preferences, and say $mypref1 represents someone's name, there's nothing stopping them from filling this out in the form field:
\"; echo \"PWNED
and your resulting PHP will become
<?php \$pref1 = \"$mypref\"; echo \"PWNED\"; ?>
So firstly, storing your preferences in an array and using serialize() is much safer:
$prefs = array('mypref1' => 'somethingorother');
$handle = fopen ($file, 'w');
fwrite($handle, serialize($prefs));
fclose($h);
// example code demonstrating unserialization
$prefs2 = unserialize(file_get_contents($file));
var_dump($prefs == $prefs2); // should output "(bool) true"
In your question, you also mention that if the file does exist, it is unlinked. You can simply truncate it to zero length by passing "w" as the second argument to fopen - you don't need to manually delete it. This should set the mtime anyway, negating the need for the call to touch().
If the values being written to the file are preferences, surely each preference could have a default, unless there are hundreds? array_merge will allow you to overwrite on a per-key basis, so if you do something like this:
// array of defaults
$prefs = array(
'mypref1' => 'pants',
'mypref2' => 'socks',
);
if (file_exists($file)) {
// if this fails, an E_NOTICE is raised. are you checking your server error
// logs regularly?
if ($userprefs = unserialize(file_get_contents($file))) {
$prefs = array_merge($prefs, $userprefs);
}
}
If the issue is that there are heaps, and you don't want to have to initialise them all, you could have a get_preference method which just wraps an isset call to the prefs array.
function get_preference($name, &$prefs) {
if (isset($pref[$name]))
return $pref[$name];
return null;
}
var_dump(get_preference('mypref1', $prefs));
Beyond all of the questions this raises though, the reality is that with your app, in the unlikely event that something does go wrong with the fopen, it should be regarded as a serious failure anyway, and the handful of users you're likely to have making use of this feature are going to be contacting you pretty darn quick if something goes wrong.
It is always better to store your users state in a session and only persist that state when needed.
Why not just use the truncation capabilities of fopen()? I believe instead of "r+", you'll need to pass "w+"... Then if the file exists, it will be truncated, if it doesn't you'll just create a new file. So the code becomes:
$file = "phpsettings.php";
$handle = fopen( $file, 'w+' );
$str = "<?php \$pref1 = \"$mypref\"; ?>";
fwrite ($handle, $str);
fclose ($handle);
Related
I'm trying to make a download counter in a website for a video game in PHP, but for some reason, instead of incrementing the contents of the downloadcount.txt file by 1, it takes the number, increments it, and appends it to the end of the file. How could I just make it replace the file contents instead of appending it?
Here's the source:
<?php
ob_start();
$newURL = 'versions/v1.0.0aplha/Dungeon1UP.zip';
//header('Location: '.$newURL);
//increment download counter
$file = fopen("downloadcount.txt", "w+") or die("Unable to open file!");
$content = fread($file,filesize("downloadcount.txt"));
echo $content;
$output = (int) $content + 1;
//$output = 'test';
fwrite($file, $output);
fclose($file);
ob_end_flush();
?>
The number in the file is supposed to increase by one every time, but instead, it gives me numbers like this: 101110121011101310111012101110149.2233720368548E+189.2233720368548E+189.2233720368548E+18
As correctly pointed out in one of the comments, for your specific case you can use fseek ( $file, 0 ) right before writing, such as:
fseek ( $file, 0 );
fwrite($file, $output);
Or even simpler you can rewind($file) before writing, this will ensure that the next write happens at byte 0 - ie the start of the file.
The reason why the file gets appended it is because you're opening the file in append and truncate mode, that is "w+". You have to open it in readwrite mode in case you do not want to reset the contents, just "r+" on your fopen, such as:
fopen("downloadcount.txt", "r+")
Just make sure the file exists before writing!
Please see fopen modes here:
https://www.php.net/manual/en/function.fopen.php
And working code here:
https://bpaste.net/show/iasj
It will be much simpler to use file_get_contents/file_put_contents:
// update with more precise path to file:
$content = file_get_contents(__DIR__ . "/downloadcount.txt");
echo $content;
$output = (int) $content + 1;
// by default `file_put_contents` overwrites file content
file_put_contents(__DIR__ . "/downloadcount.txt", $output);
That appending should just be a typecasting problem, but I would not encourage you to handle counts the file way. In order to count the number of downloads for a file, it's better to make a database update of a row using transactions to handle concurrency properly, as doing it the file way could compromise accuracy.
You can get the content, check if the file has data. If not initialise to 0 and then just replace the content.
$fileContent = file_get_contents("downloadcount.txt");
$content = (!empty($fileContent) ? $fileContent : 0);
$content++;
file_put_contents('downloadcount.txt', $content);
Check $str or directly content inside the file
I extracted some data from a log file and put it in an array (lets call it $line_content). I copied the 1st 15 lines from the array into another array ($line_content15). I want to delete/remove these 15 lines from the log file. How should I do it? I tried to use str_replace, like in this code snippet:
file_put_contents($filename, str_replace($line_content15 . "\r\n", "",
file_get_contents($filename)));
Any input would be helpful. Thank you!
As #user3783243 commented, I needed to implode the array and use
array_splice($imploded_content,$initial_line_number,$last_line_number);.
In this case,
$initial_line_number=0 and $last_line_number=15.
Update:
I also need to change permission for PHP to access and modify the file.
Given that log files often get very large then trying to do this in memory is not a good solution. Also, given that log files are usually part of one's audit trail, they should not be modified. But assuming there is a valid reason.....
function delete_lines($fname, $startline, $endline)
{
$tmp=tmpfile();
$in=fopen($fname, 'r+');
if (!flock($in, LOCK_EX, $wouldblock) || $wouldblock) {
trigger_error("Unable to lock file");
return false;
}
for ($x=0; $x<$startline; $x++) {
fputs($tmp, fgets($in));
}
for ($x=0; $x<($endline-$startline); $x++) {
fgets($in);
}
while (!feof($in)) {
fputs($tmp, fgets($in));
}
fseek($tmp,0);
fseek($in, 0);
$newsize=0;
while (!feof($tmp)) {
$newsize+=fputs($in, fgets($tmp));
}
ftruncate($in,$newsize);
fclose($in);
fclose($tmp);
return true;
}
You may want to add additional error handling in the above. This can be implemented with a single open file - but it can become messy quickly.
This question already has answers here:
Need to write at beginning of file with PHP
(10 answers)
Closed 9 years ago.
Hi I want to append a row at the beginning of the file using php.
Lets say for example the file is containing the following contnet:
Hello Stack Overflow, you are really helping me a lot.
And now i Want to add a row on top of the repvious one like this:
www.stackoverflow.com
Hello Stack Overflow, you are really helping me a lot.
This is the code that I am having at the moment in a script.
$fp = fopen($file, 'a+') or die("can't open file");
$theOldData = fread($fp, filesize($file));
fclose($fp);
$fp = fopen($file, 'w+') or die("can't open file");
$toBeWriteToFile = $insertNewRow.$theOldData;
fwrite($fp, $toBeWriteToFile);
fclose($fp);
I want some optimal solution for it, as I am using it in a php script. Here are some solutions i found on here:
Need to write at beginning of file with PHP
which says the following to append at the beginning:
<?php
$file_data = "Stuff you want to add\n";
$file_data .= file_get_contents('database.txt');
file_put_contents('database.txt', $file_data);
?>
And other one here:
Using php, how to insert text without overwriting to the beginning of a text file
says the following:
$old_content = file_get_contents($file);
fwrite($file, $new_content."\n".$old_content);
So my final question is, which is the best method to use (I mean optimal) among all the above methods. Is there any better possibly than above?
Looking for your thoughts on this!!!.
function file_prepend ($string, $filename) {
$fileContent = file_get_contents ($filename);
file_put_contents ($filename, $string . "\n" . $fileContent);
}
usage :
file_prepend("couldn't connect to the database", 'database.logs');
My personal preference when writing to a file is to use file_put_contents
From the manual:
This function is identical to calling fopen(), fwrite() and fclose()
successively to write data to a file.
Because the function automatically handles those three functions for me I do not have to remember to close the resource after I'm done with it.
There is no really efficient way to write before the first line in a file. Both solutions mentioned in your questions create a new file from copying everything from the old one then write new data (and there is no much difference between the two methods).
If you are really after efficiency, ie avoiding the whole copy of the existing file, and you need to have the last inserted line being the first in the file, it all depends how you plan on using the file after it is created.
three files
Per you comment, you could create three files header, content and footer and output each of them in sequence ; that would avoid the copy even if header is created after content.
work reverse in one file
This method puts the file in memory (array).
Since you know you create the content before the header, always write lines in reverse order, footer, content, then header:
function write_reverse($lines, $file) { // $lines is an array
for($i=count($lines)-1 ; $i>=0 ; $i--) fwrite($file, $lines[$i]);
}
then you call write_reverse() first with footer, then content and finally header. Each time you want to add something at the beginning of the file, just write at the end...
Then to read the file for output
$lines = array();
while (($line = fgets($file)) !== false) $lines[] = $line;
// then print from last one
for ($i=count($lines)-1 ; $i>=0 ; $i--) echo $lines[$i];
Then there is another consideration: could you avoid using files at all - eg via PHP APC
You mean prepending. I suggest you read the line and replace it with next line without losing data.
<?php
$dataToBeAdded = "www.stackoverflow.com";
$file = "database.txt";
$handle = fopen($file, "r+");
$final_length = filesize($file) + strlen($dataToBeAdded );
$existingData = fread($handle, strlen($dataToBeAdded ));
rewind($handle);
$i = 1;
while (ftell($handle) < $final_length)
{
fwrite($handle, $dataToBeAdded );
$dataToBeAdded = $existingData ;
$existingData = fread($handle, strlen($dataToBeAdded ));
fseek($handle, $i * strlen($dataToBeAdded ));
$i++;
}
?>
I am working with large text files in php (1GB+), I am using
file_get_contents("file.txt", NULL, NULL, 100000000,100);
To get data from the middle of the file, but if i wanted to change the data in the file to something that is of different change than the origional data, I would have to re-write the entire file.
How can I change data within the file (variable length) without overwriting data if the data is larger than the original? I keep an index of the different data blocks within the file and their byte location. It seems that the only alternative is to dedicate x amount of bytes to each piece of data and then rewrite that block if i wanted to change it... the problem with this is that it would take up a lot more space than needed in just null bytes, and it would take longer to write... and that still would not solve how to "remove" data, as the file could never shrink in size... I really need some help here...
If I used prefixed blocks for each piece of data in the file, like 1 mb, then I wanted to enter data that was only 100kb, that entry would take 10x actual needed space, and the entry could never be changed to something more than 1mb of data, as it would overwrite more than 1st dedicated block... removing it would not be possible... hope this makes any sense... I am not looking for alternatives, I am looking to write and change data in the middle of files, hehe...
UPDATE: Yes, I would like to replace the old data, but if the new data extends more than the old data I would want the rest of the data to be pushed further into the file...
consider this: 0000000HELLODATA00000000
the zeros represent empty space, nothing... now I would like to replace HELLO with SOMETHING, now since something is larger than hello, simply writing in the starting point of hello would extend byond hello and start overwriting data... therefore i would like DATA to be pushed futher into the file, to make room for SOMETHING without overwriting DATA... hehe
To Overwrite Data :
$fp = fopen("file.txt", "rw+");
fseek($fp, 100000000); // move to the position
fwrite($fp, $string, 100); // Overwrite the data in this position
fclose($fp);
To Inject Data
This is a tricky because you have to rewrite the file. It can be optimized with partial modificationfrom point of injection rather than the whole file
$string = "###INJECT THIS DATA ##### \n";
injectData("file.txt", $string, 100000000);
Function Used
function injectData($file, $data, $position) {
$fpFile = fopen($file, "rw+");
$fpTemp = fopen('php://temp', "rw+");
$len = stream_copy_to_stream($fpFile, $fpTemp); // make a copy
fseek($fpFile, $position); // move to the position
fseek($fpTemp, $position); // move to the position
fwrite($fpFile, $data); // Add the data
stream_copy_to_stream($fpTemp, $fpFile); // #Jack
fclose($fpFile); // close file
fclose($fpTemp); // close tmp
}
A variant on Baba's answer, not sure if it would be more efficient when working with larger files:
function injectData($file, $data, $position) {
$fpFile = fopen($file, "rw+");
$fpTemp = fopen('php://temp', "rw+");
stream_copy_to_stream($fpFile, $fpTemp, $position);
fwrite($fpTemp, $data);
stream_copy_to_stream($fpFile, $fpTemp, -1, $position);
rewind($fpFile);
rewind($fpTemp);
stream_copy_to_stream($fpTemp, $fpFile);
fclose($fpFile);
fclose($fpTemp);
}
injectData('testFile.txt', 'JKL', 3);
Variant of my earlier method that eliminates one of the stream_copy_to_stream() calls, so should be a shade faster:
function injectData3($file, $data, $position) {
$fpFile = fopen($file, "rw+");
$fpTemp = fopen('php://temp', "rw+");
stream_copy_to_stream($fpFile, $fpTemp, -1, $position);
fseek($fpFile, $position);
fwrite($fpFile, $data);
rewind($fpTemp);
stream_copy_to_stream($fpTemp, $fpFile);
fclose($fpFile);
fclose($fpTemp);
}
Another variant of the injectData() function:
function injectData($file, $data, $position)
{
$temp = fopen('php://temp', "rw+");
$fd = fopen($file, 'r+b');
fseek($fd, $position);
stream_copy_to_stream($fd, $temp); // copy end
fseek($fd, $position); // seek back
fwrite($fd, $data); // write data
rewind($temp);
stream_copy_to_stream($temp, $fd); // stich end on again
fclose($temp);
fclose($fd);
}
It copies the end of file (from $position onwards) into a temporary file, seeks back to write the data and stitches everything back up.
I have the following PHP code that checks which choice from a radio button was selected and then write to a file of the same name.
For example, from a Radio button group called "instrument", where the 4 choices are
Wind
Strings
Percussion
Vocal
If the user selects "Wind", then it would create and write to a file called "wind_instrument.txt". If "strings" is selected it would create the file "string_instrument.txt" and so on.
Here is my PHP code:
if ($_POST['instrument'] == "wind")
{
$lines = file('wind_instrument.txt');
$fopen = fopen("wind_instrument.txt", "w+");
}
elseif ($_POST['instrument'] == "strings")
{
$lines = file('strings_instrument.txt');
$fopen = fopen("strings_instrument.txt", "w+");
}
elseif ($_POST['instrument'] == "percussion")
{
$lines = file('percussion_instrument.txt');
$fopen = fopen("percussion_instrument.txt", "w+");
}
elseif ($_POST['instrument'] == "vocal")
{
$lines = file('vocal_instrument.txt');
$fopen = fopen("vocal_instrument.txt", "w+");
}
Now, if one of the conditions is met, would then go on to the next step in my code, being:
fwrite($fopen, ("Instrument: ")."");
fwrite($fopen, $_POST["instrument"]."\n");
fwrite($fopen, ("<br>")."\n");
The problem I have with this, is that it is not creating a file, and I do have permissions set.
Any help will be greatly appreciated, thank you.
You actually could do some refactoring in order to make it easier to maintain, nevertheless that wasn't your problem but I shall try to help you out.
<?php
$instruments = array('wind', 'strings', 'percussion', 'vocal');
if (in_array($_POST['instrument'], $instruments))
{
$instrument = $_POST['instrument'];
$file_handle = fopen($instrument.'_instrument.txt', 'a+');
$line = 'Instrument: '.$instrument."\n";
fwrite($file_handle, $line);
}
?>
The important thing to know is how I open the file. I use the mode a+. The documentation says
Open for reading and writing; place the file pointer at the end of the file. If the file does not exist, attempt to create it.
Hope that helps.
If you've verified that you have permissions to open and write to a file, then there should be no problem with doing it based on a conditional. I suggest checking the contents of $_POST and making sure that instrument is present and meets one of your conditions. Alternately, you could add an else clause that will write the submission to an error file if no valid instrument was received. If that works, it would confirm that the problem is with the POSTed variable, not with fopen/fwrite.
If the options you provided in the bulleted list are the literal values of your radio buttons, then your problem is that they're capitalized and the values you test in the if statement aren't. Either capitalize them consistently, or use strtolower() to convert everything to a consistent case before comparing.