Save multiple arrays to CSV from PHP - php

There are two arrays that I'd like to save as CSV file from PHP. The problem is that this code works only for the first Call. E.g. in the below-given example I can save only array1, but array2 is not saved. If I swap places of array1 and array2, then array2 will be saved instead of array1. What's actually wrong in my code and how could I solve this problem?
header("Content-type: text/csv");
header("Pragma: no-cache");
$headers = array('xxx','yyy','zzz');
saveCSV($array1,"file1.csv",$headers);
header("Content-type: text/csv");
header("Pragma: no-cache");
$headers = array('aaa','bbb','ccc');
saveCSV($array2,"file2.csv",$headers);
function saveCSV($data,$fileName,$headers) {
$outstream = fopen($fileName, "a");
file_put_contents($fileName, "");
if ($headers != 0)
fputcsv($outstream,$headers);
function __outputCSV(&$vals, $key, $filehandler) {
fputcsv($filehandler, $vals);
}
array_walk($data, "__outputCSV", $outstream);
fclose($outstream);
}

Your setting the headers, twice.
Trying building up your csv data output, then once completed set the headers once.
Also you have a function declaration within another, which i'm sure is invalid.
move __outputCSV somwehere else, or maybe you intended it to be a closure?

As far as I know, you can only set headers once then return content. The code above sets some headers, replies with CSV, then sets headers again, and replies with a different CSV again. PHP/browser won't handle separate return.
Instead of pushing multiple miles from the server, perhaps you can just have the browser window.open() /path-to-get-csv-script.php?op=file1 and then op=file2. The browser can then open up both files if that's what you want....

Related

PHP FPDF Label outputting gibberish/garbage data if not enough labels are printed, but with enough it works just fine

I have a label generator that works fine if you have enough labels to print, in my case it's roughly 3 pages worth of 5160's, or ~70 individual entries. If you have less than that number you end up looking at this instead of downloading your PDF:
If you have enough entries it will however work properly and look like this:
Noteworthy is that when I changed from real data to dummy data so I could take a screen cap of it the number of pages needed to be ~6, so it seems to fail when there is not enough raw text, rather than individual entries.
I haven't got a clue how to debug this. This is my code that I am working with:
$pdf = new PDF_Label('5160');
$pdf->AddPage();
foreach ($entries as $entry) {
$name = 'Demo thing';
$date = Carbon::create($order->{'Delivery Date'})->format('M d, Y');
$text = sprintf("%s\n%s, %s, %s\n%s\n%s", 'Business', 'Employee', '4A', 'Floor 7' ?? '---', $date, $name);
$pdf->Add_Label($text);
}
$pdf->Output();
Has anyone encountered this while working with FPDF_Label before? Any help would be appreciated, thanks!
EDIT: More info about the error checking that FPDF does:
protected function _checkoutput()
{
if(PHP_SAPI!='cli')
{
if(headers_sent($file,$line))
$this->Error("Some data has already been output, can't send PDF file (output started at $file:$line)");
}
if(ob_get_length())
{
var_dump('test');
// The output buffer is not empty
if(preg_match('/^(\xEF\xBB\xBF)?\s*$/',ob_get_contents()))
{
// It contains only a UTF-8 BOM and/or whitespace, let's clean it
ob_clean();
}
else
$this->Error("Some data has already been output, can't send PDF file");
}
}
//below is within the main output function
$this->_checkoutput();
if(PHP_SAPI!='cli')
{
// We send to a browser
header('Content-Type: application/pdf');
header('Content-Disposition: inline; '.$this->_httpencode('filename',$name,$isUTF8));
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
}
echo $this->buffer;
break;
When I see my error it goes through this bit of code and does not output any of those errors you see, so we can say that there is no headers already sent or output in the buffer.
Try writing the following
ob_clean(); before $pdf = new PDF_Label('5160');
and
ob_flush();
exit;
after
$pdf->Output();
I found a solution for this, though it doesn't address the underlying problem. I was comparing headers in the network tab for the working and not working scenarios and noticed that 'Content-Type: application/pdf' was dropped for the shorter entry set (plus some other changes). Since this did not happen outside my Laravel site I tried adding 'exit;' the line after '$pdf->Output();' and it now works regardless of how many entries.
With the longer entry sheet I noticed it adds 'Transfer-Encoding: chunked' as well as keeping the 'Content-Type: application/pdf'. Perhaps there is a bug in the Laravel header handling related to that? I'm really not sure.

php write to file without prompting for download

The following code works fine for writing to a file, but I don't want it prompts the user to download whenever it executes. I would like to prevent this... Any help would be appreciatied. I've also tried it with fwrite with the same results.
$file_name = 'orders'.date('Ymd').'csv';
header('Content-Type: application/excel');
header('Content-Disposition: attachment; filename="'.$file_name.'"');
$data = array(
'aaa,bbb,ccc,dddd',
'123,456,789',
'"aaa","bbb"'
);
$fp = fopen('php://output', 'w');
foreach ( $data as $line )
{
$val = explode(",", $line);
fputcsv($fp, $val);
}
fclose($fp);
There is not possibility to do that (or even if there is some trick, you never should use it!).
Only way to avoid prompt is to change browser settings which of course can be done by user, not by you.
It'll not be safe for user if anybody would be able to save files on his computer without any prompt.
Remove the lines
header('Content-Type: application/excel');
header('Content-Disposition: attachment; filename="'.$file_name.'"');
and it will just write the file.
As it is, it does not appear to be outputting any content anyhow.
You are mixing up how HTTP (headers in particular) works and local files.
The way a HTTP response is interpreted by the browser can be suggested by using headers (content-type, content-disposition).
The way a file is interpreted by your operating system and applications has nothing to do with HTTP and HTTP headers.
Your script basically does two things:
tells the browser (through HTTP headers) to interpret the response as an application/excel attachment (triggers download and passes responsibility to the operating system);
writes some stuff to a file.
You need to skip step (1).
Also, if you don't care about performance in special cases, file_put_contents should be enoguh.

PHP efficiently write and output csv files using fputcsv

When writing .csv files i use fputcsv like this:
- open a temporary file $f = tmpfile();
- write content to file using fputcsv($f,$csv_row);
- send appropriate headers for attachment
- read file like this:
# move pointer back to beginning
rewind($f);
while(!feof($f))
echo fgets($f);
# fclose deletes temp file !
fclose($f);
Another aproach would be:
- open file $f = fopen('php://output', 'w');
- send appropriate headers for attachment
- write content to file using fputcsv($f,$csv_row);
- close $f stream
My question is: What would be the best approach to output the data faster and taking into account server resources ?
First method would use more writes and consume more resources but would output very fast.
Second method uses less writes and would output slower i think.
Eagerly waiting for your opinions on this.
Thanks.
fpassthru() will do what you're doing at a lower level. Use it like this:
# move pointer back to beginning
rewind($f);
while(fpassthru($f) !== false);
# fclose deletes temp file !
fclose($f);
Even though it may be a csv file, there is no need to restrict yourself to csv functions, unless you are generating the file at the time of output.
You could probably see a performance gain if you stream the CSV to output instead of to a file.
Why do you need to write the csv content to a tmp file/php's output stream ?
You just need to echo the csv content directly, there should not be any file operations.
send appropriate headers for attachment
echo the csv content.
header("Content-type: application/csv");
header("Content-Disposition: attachment; filename=file.csv");
header("Pragma: no-cache");
header("Expires: 0");
foreach ($csv_rows as $csv_row) {
echo $csv_row;
}
exit;

PHP writing CSV, outputting the entire webpage for some reason

function outputCSV($data) {
$outstream = fopen("php://output", 'w');
function __outputCSV(&$vals, $key, $filehandler){
fputcsv($filehandler, $vals, ',', '"');
}
array_walk($data, '__outputCSV', $outstream);
fclose($outstream);
}
function someFunctionInTheBigPHPFile() {
header("Content-type: text/csv");
header("Content-Disposition: attachment; filename=file.csv");
header("Pragma: no-cache");
header("Expires: 0");
$mydata = array(
array('data11', 'data12', 'data13'),
array('data21', 'data22', 'data23'),
array('data31', 'data32', 'data23'));
outputCSV($mydata);
exit;
}
The output CSV does contain the data array. The problem is, this array is displayed along with the rest of the webpage, that is everything before this function is called and everything that comes after it, despite these two functions being the only ones that deal with any fopen and writing to files.
How can I stop the rest of the webpage from interfering? I only want the data array in the CSV..
EDIT: I managed to chop off everything succeeding my array by adding exit;, but I still have the problem of the entire website being displayed before the array.
Stop execution after outputting the CSV data. You can do this with die() or exit().
At the beginning of the PHP file, check straight away if you want to print your CSV (this is probably passed through $_POST or $_GET). If so, run it straight through the function and end that function with an exit;.
This prevents anything from happening before or after the CSV is created. For some reason all code on the page is included in the new file, even if the file stream was opened and closed at times independant of when the page's content was computed.
And this effectively leaves you with only what you wanted, not the rubbish before or after it.
Maybe i misunderstood you, but someFunctionInTheBigPHPFile() prints out the file to the screen. So, why are you using this i you dont want to a screen output ?

PHP fopen contains unwanted content

Of course after hours of pondering this problem, the first comment on my question lead me to solve it immediately.
The problem was that, although I was including this code within its own function at the top of the page, I was calling it only if a certain flag was set in the $_POST array. I wasn't checking for the flag until the end of the PHP file. I moved that check before the function, and it worked.
The original question is below:
I'm trying to use the fopen() function in PHP to output a CSV file, and although it contains the data I want, it also contains the entire HTML structure of the page, as well as inline stylesheets, before the content that I actually want to output.
I'm using this code (from here) pretty much unchanged. I'm very unfamiliar with PHP streaming and output, so I started from what I hope was a firm foundation:
$fileName = 'somefile.csv';
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Description: File Transfer');
header("Content-type: text/csv");
header("Content-Disposition: attachment; filename={$fileName}");
header("Expires: 0");
header("Pragma: public");
$fh = #fopen( 'php://output', 'w' );
global $wpdb;
$query = "SELECT * FROM `{$wpdb->prefix}my_table`";
$results = $wpdb->get_results( $query, ARRAY_A );
$headerDisplayed = false;
foreach ( $results as $data ) {
// Add a header row if it hasn't been added yet
if ( !$headerDisplayed ) {
// Use the keys from $data as the titles
fputcsv($fh, array_keys($data));
$headerDisplayed = true;
}
// Put the data into the stream
fputcsv($fh, $data);
}
// Close the file
fclose($fh);
// Make sure nothing else is sent, our file is done
exit;
My assumption is that this example was intended to be included in its own external PHP file, but due to the constraints I'm dealing with, I'm trying to include it inline instead. I've mucked about with output buffering a bit with no positive results, but the PHP documentation on these is quite sparse, so there's probably something I'm missing.
Problem seems to be that, at the same time you try to output from the same PHP file, the CSV file AND some html content. You've got to separate them, to have 2 different URLs.
I guess your PHP code is surrounded by the html code (and css inline) you're talking about.
What you've got to do is:
have a PHP script that only outputs the CSV content (which only contains the code you showed us, with the opening php tag of course)
have another PHP script which produces html code, and provides a link to the previous script (for example).
You are on the right track with the 'include it inline' reason to why you're getting everything else before the data.
This script will need to be it's own separate file called directly, instead of including it inline in another script. I understand you have other database connections and such that have to be set up first. You'll have to extract those out of your standard pages and include those on this page.
Of course after hours of pondering this problem, the first comment on my question lead me to solve it immediately.
The problem was that, although I was including this code within its own function at the top of the page, I was calling it only if a certain flag was set in the $_POST array. I wasn't checking for the flag until the end of the PHP file. I moved that check before the function, and it worked.

Categories