PHP Overwriting at a specific location in a file - php

I am trying to write a text at a specific position in a file that already has some content. After writing I find the file truncated to the size of the text plus fseek position and the first characters with value 0. Is this the normal behaviour or am I missing something? I want to mention that I'm trying to avoid loading the file into memory and writing it back.
$file = fopen("text.txt","w");
fseek($file,3);
fwrite($file,"Hello");
fclose($file);

You need to open the file in c mode, else it's truncated on fopen:
$file = fopen("text.txt","c");
See http://php.net/manual/de/function.fopen.php for a documentation of all file open modes and what exactly they do. Also see the http://www.php.net/manual/en/function.fseek.php manual

Yes this is normal behaviour :
fopen($file, "w"):
place the file pointer at the beginning of the file and truncate the file to zero length.
fseek():
In general, it is allowed to seek past the end-of-file; if data is then written, reads in any unwritten region between the end-of-file and the sought position will yield bytes with value 0. [..]
If you have opened the file in append (a or a+) mode, any data you write to the file will always be appended, regardless of the file position, and the result of calling fseek() will be undefined.
You probably want to open the file in a non truncating write mode (e.g. "c" but not "a").

Related

PHP fopen with c/c+ mode

As far as I am understanding c/c+ mode, I am expecting behaviour like 'a/a+ mode, so the file shouldn't be truncated, and any fwrite() result should be prepended to existing file.
In fact the file seems to be truncated anyway as the file always contains only tle last fwrite() content anyway.
Is it a possible bug in my PHP version (7.0), or I am misunderstanding something?
<?php
$fp = fopen($fpath,'c+');
fwrite($fp, date("H:i:s")." test\n");
fclose($fp);
What makes you think that this should behave different? According to the documentation, using c the pointer is "positioned on the beginning of the file". Starting to write from that specific position, you would always override whatever is already present in that file
Maybe a small addition to what has been said:
<?php
/* file test.txt contains string(4): 'test' */
$fp = fopen('test.txt','c+');
fclose($fp);
file test.txt is NOT truncated to zero length(as w or w+ would do on fopen() ),
it still contains string(4): test
Now see what happens when we write one character to the file using c+
$fp = fopen('test.txt','c+');
fwrite($fp, 'b');
fclose($fp);
file test.txt now contains: best, the file pointer was positioned at the beginning of the file and only overwrites the first character in the original - test has become best

binary safe write on file with php to create a DBF file

I need to split a big DBF file using php functions, this means that i have for example 1000 records, i have to create 2 files with 500 records each.
I do not have any dbase extension available nor i can install it so i have to work with basic php functions. Using basic fread function i'm able to correctly read and parse the file, but when i try to write a new dbf i have some problems.
As i have understood, the DBF file is structured in a 2 line file: the first line contains file info, header info and it's in binary. The second line contains the data and it's plain text. So i thought to simply write a new binary file replicating the first line and manually adding the first records in the first file, the other records in the other file.
That's the code i use to parse the file and it works nicely
$fdbf = fopen($_FILES['userfile']['tmp_name'],'r');
$fields = array();
$buf = fread($fdbf,32);
$header=unpack( "VRecordCount/vFirstRecord/vRecordLength", substr($buf,4,8));
$goon = true;
$unpackString='';
while ($goon && !feof($fdbf)) { // read fields:
$buf = fread($fdbf,32);
if (substr($buf,0,1)==chr(13)) {$goon=false;} // end of field list
else {
$field=unpack( "a11fieldname/A1fieldtype/Voffset/Cfieldlen/Cfielddec", substr($buf,0,18));
$unpackString.="A$field[fieldlen]$field[fieldname]/";
array_push($fields, $field);
}
}
fseek($fdbf, 0);
$first_line = fread($fdbf, $header['FirstRecord']+1);
fseek($fdbf, $header['FirstRecord']+1); // move back to the start of the first record (after the field definitions)
first_line is the variable the contains the header data, but when i try to write it in a new file something wrong happens and the row isn't written exactly as it was read. That's the code i use for writing:
$handle_log = fopen($new_filename, "wb");
fwrite($handle_log, $first_line, strlen($first_line) );
fwrite($handle_log, $string );
fclose($handle_log);
I've tried to add the b value to fopen mode parameter as suggested to open it in a binary way, i've also taken a suggestion to add exactly the length of the string to avoid the stripes of some characters but unsuccessfully since all the files written are not correctly in DBF format. What can i do to achieve my goal?
As i have understood, the DBF file is structured in a 2 line file: the
first line contains file info, header info and it's in binary. The
second line contains the data and it's plain text.
Well, it's a bit more complicated than that.
See here for a full description of the dbf file format.
So it would be best if you could use a library to read and write the dbf files.
If you really need to do this yourself, here are the most important parts:
Dbf is a binary file format, so you have to read and write it as binary. For example the number of records is stored in a 32 bit integer, which can contain zero bytes.
You can't use string functions on that binary data. For example strlen() will scan the data up to the first null byte, which is present in that 32 bit integer, and will return the wrong value.
If you split the file (the records), you'll have to adjust the record count in the header.
When splitting the records keep in mind that each record is preceded by an extra byte, a space 0x20 if the record is not deleted, an asterisk 0x2A if the record is deleted. (for example, if you have 4 fields of 10 bytes, the length of each record will be 41) - that value is also available in the header: bytes 10-11 - 16-bit number - Number of bytes in the record. (Least significant byte first)
The file could end with the end-of-file marker 0x1A, so you'll have to check for that as well.

Is it possible to load a text file but only part of it with PHP? [duplicate]

Considering i have a 100GB txt file containing millions of lines of text. How could i read this text file by block of lines using PHP?
i can't use file_get_contents(); because the file is too large. fgets() also read the text line by line which will likely takes longer time to finish reading the whole file.
If i'll be using fread($fp,5030) wherein '5030' is some length value for which it has to read. Would there be a case where it won't read the whole line(such as stop at the middle of the line) because it has reached the max length?
i can't use file_get_contents(); because the file is too large. fgets() also read the text line by line which will likely takes longer time to finish reading the whole file.
Don't see, why you shouldn't be able to use fgets()
$blocksize = 50; // in "number of lines"
while (!feof($fh)) {
$lines = array();
$count = 0;
while (!feof($fh) && (++$count <= $blocksize)) {
$lines[] = fgets($fh);
}
doSomethingWithLines($lines);
}
Reading 100GB will take time anyway.
The fread approach sounds like a reasonable solution. You can detect whether you've reached the end of a line by checking whether the final character in the string is a newline character ('\n'). If it isn't, then you can either read some more characters and append them to your existing string, or you can trim characters from your string back to the last newline, and then use fseek to adjust your position in the file.
Side point: Are you aware that reading a 100GB file will take a very long time?
i think that you have to use fread($fp, somesize), and check manually if you have founded the end of the line, otherwise read another chunk.
Hope this helps.
I would recommend implementing the reading of a single line within a function, hiding the implementation details of that specific step from the rest of your code - the processing function must not care how the line was retrieved. You can then implement your first version using fgets() and then try other methods if you notice that it is too slow. It could very well be that the initial implementation is too slow, but the point is: you won't know until you've benchmarked.
I know this is an old question, but I think there is value for a new answer for anyone that finds this question eventually.
I agree that reading 100GB takes time, that I why I also agree that we need to find the most effective option to read it so it can be as little as possible instead of just thinking "who cares how much it is if is already a lot", so, lets find out our lowest time possible.
Another solution:
Cache a chunk of raw data
Use fread to read a cache of that data
Read line by line
Read line by line from the cache until end of cache or end of data found
Read next chunk and repeat
Grab the un processed last part of the chunk (the one you were looking for the line delimiter) and move it at the front, then reads a chunk of the size you had defined minus the size of the unprocessed data and put it just after that un processed chunk, then, there you go, you have a new complete chunk.
Repeat the read by line and this process until the file is read completely.
You should use a cache chunk bigger than any expected size of line.
The bigger the cache size the faster you read, but the more memory you use.

I want to write to a text file at the inputed caret position with PHP

I want to wite to a text file at the inputed text position using PHP. I tried using fseek to point the wite position to the inputed number, but it saves as an empty file instead.
<?php
$testData = "testdata";
$testPosition = 3;
$fileReference = fopen("test.txt", "w");
fseek($fileReference, $testPosition);
fwrite($fileReference, $testData);
fclose($fileReference);
?>
How would I get the script to wite to the text file at the specified position correctly?
The w flag only allows for writing to a file. Try replacing the w flag with a r+ flag. This will allow for read/write to a file without truncating the file. the read /write is needed to allows you to go search for your caret and write to where it is. for more information on the fopen function please see php.net: fopen()
The w flag has this functionality:
'w' Open for writing only; place the file pointer at the beginning of the file and truncate the file to zero length. If the file does not exist, attempt to create it.
This could be why you're getting an empty file.
However, on my local server, this works fine. This makes me think your permissions may not be correct. For example, you might not have write permissions. Try and ensure that you have the correct permissions on this file.

Insert line on the fly in a file with PHP

I want to make a .php file downloadable by my users.
Every file is different from an user to another:
at the line #20 I define a variable equal to the user ID.
To do so I tried this: Copy the original file. Read it until line 19 (fgets) then fputs a PHP line, and then offer the file to download.
Problem is, the line is not inserted after line 19 but at the end of the .php file. Here is the code:
if (is_writable($filename)) {
if (!$handle = fopen($filename, 'a+')) {
echo "Cannot open file ($filename)";
exit;
}
for ($i = 1; $i <= 19; $i++) {
$offset = fgets($handle);
}
if (fwrite($handle, $somecontent) === FALSE) {
exit;
}
fclose($handle);
}
What would you do ?
append mode +a in fopen() places the handle's pointer at the end of the file. Your fgets() loop will fail as there's nothing left to read at the end of the file. You're basically doing 19 no-ops. Your fwrite will then output your new value at the end of the file, as expected.
To do your insert, you'd need to rewind() the handle to the beginning, then do your fgets() loop.
However, if you're just wanting people to get this modified file, why bother doing the "open file, scan through, write change, serve up file"? This'd leave a multitude of near-duplicates on your system. A better method would be to split your file into two parts, and then you could do a simple:
readfile('first_part.txt');
echo "The value you want to insert";
readfile('last_part.txt');
which saves you having to save the 'new' file each time. This would also allow arbitrary length inserts. Your fwrite method could potentially trash later parts of the file. e.g. You scan to offset "10" and write out 4 bytes, which replaces the original 4 bytes at that location in the original file. At some point, maybe it turns into 5 bytes of output, and now you've trashed a byte in the original and maybe have a corrupted file.
The a+ mode means:
'a+' 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.
You probably want r+
'r+' Open for reading and writing; place the file pointer at the beginning of the file.
Put your desired code in one string variable. Where you will have %s at point where you want to customize your code. After that just respond with php MIME type.
eg;
$phpCode = "if (foo == blah) { lala lala + 4; %s = 5; }", $user_specific_variable;
header('Content-type: text/php');
echo $phpCode;
Voila.
NB: Maybe mime type is not correct, I am talking out of my ass here.
I think instead of opening the file in "a+" mode, you should open the file in "r+" mode, because "a" always appends to the file. But I think the write will anyways overwrite your current data. So, the idea is that you'll need to buffer the file, from the point where you intend to write to the EOF. Then add your line followed by what you had buffered.
Another approach might be to keep some pattern in your PHP file, like ######. You can then:
1. copy the original PHP script
2. read the complete PHP script into a single variable, say $fileContent, using file_get_contents()
3. use str_replace() function to replace ###### in $fileContent with desired User ID
4. open the copied PHP script in "a" mode and rewrite $fileContent to it.

Categories