Adjust format of generated CSV-File - php

I am generating a very small CSV-File with only 4 columns and it works fine. When I press the designated button it downloads and I can open it and all the wanted data is present.
However the layout is very weird and it doesn't look good. Here is how it looks:
https://i.stack.imgur.com/klXcw.png
In the upper row are the column names and in the row below is the data.
Here is my code that generates the file:
public function exportData(Bewerbungen $bewerbung) {
$query = Portal::query()->where('email', '=', $bewerbung->bewerber_email)->get();
$headers = [
"Content-type" => "text/csv",
"Content-Disposition" => "attachment; filename=bewerberdaten.csv",
"Pragma" => "no-cache",
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
"Expires" => "0",
];
$columns = ['id', 'email', 'vorname', 'nachname', 'telefon'];
$callback = function () use ($query, $columns) {
$file = fopen('php://output', 'w');
fputcsv($file, $columns);
foreach ($query as $res) {
fputcsv($file, [$res->id, $res->email, $res->vorname, $res->nachname, $res->telefon]);
}
fclose($file);
};
return Response::stream($callback, 200, $headers);
}
I tried to play around with content encoding, but it is my first time working with csv and file exporting so I have no clue if it is the right direction.
Edit: Better screenshot
https://i.stack.imgur.com/8HvhA.png

The solution was to install this:
https://docs.laravel-excel.com/3.1/getting-started/installation.html
And then changing the variable 'excel_compatibility' => false to true
It is inside the config/excel.php file

Related

Set Column and Row Width in Excel using PHP [duplicate]

I need to generate an Excel file with extension .xlsx.
Here is my simple code:
$file = "test.xlsx";
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment; filename='.$file);
$content = "Col1\tCol2\tCol3\t\n";
$content .= "test1\ttest1\ttest3\t\n";
$content .= "testtest1\ttesttest2\ttesttest3\t\n";
echo $content;
But I get this error when I open the generated file:
Excel cannot open the file 'test.xlsx' because the file format or file extension is not valid.
Any ideas?
SimpleXLSXGen
$books = [
['ISBN', 'title', 'author', 'publisher', 'ctry' ],
[618260307, 'The Hobbit', 'J. R. R. Tolkien', 'Houghton Mifflin', 'USA'],
[908606664, 'Slinky Malinki', 'Lynley Dodd', 'Mallinson Rendel', 'NZ']
];
$xlsx = Shuchkin\SimpleXLSXGen::fromArray( $books );
$xlsx->saveAs('books.xlsx');
// $xlsx->downloadAs('books.xlsx');
After trying a few options, I found that PHP_XLSX_Writer suited my needs.
PHP_XLSX_Writer
-
...designed to be lightweight, minimal memory usage, generates an
Excel-compatible workbook in XLSX format, with basic features supported:
- supports PHP 5.2.1+
- takes 'UTF-8' characters (or encoded input)
- multiple worksheets
- supports currency/date/numeric cell formatting, simple formulas
- supports basic cell styling
- supports writing huge 100K+ row spreadsheets
(Adapted from the library's GitHub repository)
Here's an working example demonstrating a few features on 3 worksheets (tabs):
First create a file called xlsxwriter.class.php containing the code found here.
Create another PHP file (in the same folder) containing:
require('xlsxwriter.class.php');
$fname='my_1st_php_excel_workbook.xlsx';
$header1 = [ 'create_date' => 'date',
'quantity' => 'string',
'product_id' => 'string',
'amount' => 'money',
'description' => 'string' ];
$data1 = [ ['2021-04-20', 1, 27, '44.00', 'twig'],
['2021-04-21', 1, '=C1', '-44.00', 'refund'] ];
$data2 = [ ['2','7','ᑌᑎIᑕᗝᗪᗴ ☋†Ϝ-➑'],
['4','8','😁'] ];
$styles2 = array( ['font-size'=>6],['font-size'=>8],['font-size'=>10],['font-size'=>16] );
$writer = new XLSXWriter();
$writer->setAuthor('Your Name Here');
$writer->writeSheet($data1,'MySheet1', $header1); // with headers
$writer->writeSheet($data2,'MySheet2'); // no headers
$writer->writeSheetRow('MySheet2', $rowdata = array(300,234,456,789), $styles2 );
$writer->writeToFile($fname); // creates XLSX file (in current folder)
echo "Wrote $fname (".filesize($fname)." bytes)<br>";
// ...or instead of creating the XLSX you can just trigger a
// download by replacing the last 2 lines with:
// header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
// header('Content-Disposition: attachment;filename="'.$fname.'"');
// header('Cache-Control: max-age=0');
// $writer->writeToStdOut();
More Information:
GitHub repository
author's website
lots more examples
As others have mentioned, PhpSpreadsheet provides a nice library for this. Assuming you have it installed via composer and the vendor/autoload.php has been included in your project, you can use the function below to generate an xlsx from an array of arrays. I've included extensive comments here to help teach beginners about how PhpSpreadsheet works and what the code is doing:
function writeXLSX($filename, $rows, $keys = [], $formats = []) {
// instantiate the class
$doc = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$sheet = $doc->getActiveSheet();
// $keys are for the header row. If they are supplied we start writing at row 2
if ($keys) {
$offset = 2;
} else {
$offset = 1;
}
// write the rows
$i = 0;
foreach($rows as $row) {
$doc->getActiveSheet()->fromArray($row, null, 'A' . ($i++ + $offset));
}
// write the header row from the $keys
if ($keys) {
$doc->setActiveSheetIndex(0);
$doc->getActiveSheet()->fromArray($keys, null, 'A1');
}
// get last row and column for formatting
$last_column = $doc->getActiveSheet()->getHighestColumn();
$last_row = $doc->getActiveSheet()->getHighestRow();
// autosize all columns to content width
for ($i = 'A'; $i <= $last_column; $i++) {
$doc->getActiveSheet()->getColumnDimension($i)->setAutoSize(TRUE);
}
// if $keys, freeze the header row and make it bold
if ($keys) {
$doc->getActiveSheet()->freezePane('A2');
$doc->getActiveSheet()->getStyle('A1:' . $last_column . '1')->getFont()->setBold(true);
}
// format all columns as text
$doc->getActiveSheet()->getStyle('A2:' . $last_column . $last_row)->getNumberFormat()->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_TEXT);
if ($formats) {
// if there are user supplied formats, set each column format accordingly
// $formats should be an array with column letter as key and one of the PhpOffice constants as value
// https://phpoffice.github.io/PhpSpreadsheet/1.2.1/PhpOffice/PhpSpreadsheet/Style/NumberFormat.html
// EXAMPLE:
// ['C' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_00, 'D' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_00]
foreach ($formats as $col => $format) {
$doc->getActiveSheet()->getStyle($col . $offset . ':' . $col . $last_row)->getNumberFormat()->setFormatCode($format);
}
}
// write and save the file
$writer = new PhpOffice\PhpSpreadsheet\Writer\Xlsx($doc);
$writer->save($filename);
}
EXAMPLE USAGE:
$rows = [
['sku' => 'A123', 'price' => '99'],
['sku' => 'B456', 'price' => '5.35'],
['sku' => 'C789', 'price' => '17.7']
];
$keys = array_keys(current($rows));
$formats = ['B' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_00];
writeXLSX('pricelist.xlsx', $rows, $keys, $formats);
You can still use previous versions of PHPSpreadsheet if you are using old versions of PHP.
You have to include the version number of PHPSpreadsheet while installing it in your project directory.
This worked for me:
composer require phpoffice/phpspreadsheet:1.8.0
May be the file is not compatible with the version of Excel that is used.
Try with change your file extension 'xlsx' to 'xls and check it's working or not.
if it's working then this file extension(.xlsx) is not compatible with the version of Excel that, you used.
The content is not a valid xlsx content. Try sending your content as a comma separated value content and use xls to open that
As a suggestion, If u can change your content, May be you could try this?
$file = "test.csv";
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment; filename='.$file);
$content = "Col1,Col2,Col3\n";
$content .= "test1,test1,test3\n";
$content .= "testtest,ttesttest2,testtest3\n";
echo $content;
See my code, here I have made simple xlsx file
<?php
include 'PHPExcel/PHPExcel.php';
include 'PHPExcel/PHPExcel/Writer/Excel2007.php';
$objPHPExcel = new PHPExcel();
$objPHPExcel->setActiveSheetIndex(0);
$objPHPExcel->getActiveSheet()->SetCellValue('A1', 'Hello');
$objPHPExcel->getActiveSheet()->SetCellValue('B1', 'Trudeau');
$objPHPExcel->getActiveSheet()->SetCellValue('C1', 'Fernandes');
$objWriter = new PHPExcel_Writer_Excel2007($objPHPExcel);
$objWriter->save(str_replace('.php', '.xlsx', __FILE__));
echo " Click here for gerate xlsx file <a href='test.xlsx'>clicking me</a>";
?>
and for me, it's working fine.

Create and save csv to storage in Laravel

Found a handful of questions on here about this with no answer, so hopefully, someone can point me in the right direction...
I'm trying to create and save a csv file to storage, then update the DB in Laravel. I can create the file successfully, and I can update the DB successfully... but I'm stuck on putting them both together. In my controller, I have this for creating the file (taken from here):
public function updatePaymentConfirm(Request $request) {
$users = User::all();
$fileName = 'test.csv';
$headers = array(
"Content-type" => "text/csv",
"Content-Disposition" => "attachment; filename=$fileName",
"Pragma" => "no-cache",
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
"Expires" => "0"
);
$columns = array('First Name', 'Email');
$callback = function() use($users, $columns) {
$file = fopen('php://output', 'w');
fputcsv($file, $columns);
foreach ($users as $user) {
$row['First Name'] = $user->first_name;
$row['Email'] = $user->email;
fputcsv($file, array($row['First Name'], $row['Email']));
}
fclose($file);
};
// return response()->stream($callback, 200, $headers);
}
When the function completes, the last line (that's commented out) prompts the user to download the newly created file (which is not the functionality I'm looking for). I tried adding this to my controller in its place for saving to storage and also updating the database:
$fileModel = new UserDocument;
if($callback) {
$filePath = $callback->storeAs('uploads', $fileName, 'public');
$fileModel->name = $fileName;
$fileModel->file_path = '/storage/' . $filePath;
$fileModel->save();
return back()
->with('success','File has been uploaded.')
->with('file', $fileName);
}
It saves a row to the db, albeit incorrectly, but it doesn't save the file to storage. I've reworked the $filePath line a million times, but I keep getting this error Call to a member function storeAs() on resource or something similar. I'm relatively new to working with Laravel, so I'm not sure what I should be looking for. Thoughts?
Removed everything and started over... got it! And for anyone else running into the same issue: just calling for a file that doesn't exist creates the file (unless the file exists - then it updates it), so you don't have to create a temp file or use $file = fopen('php://output', 'w'); to create the file. It'll automatically "save" the newly generated file in the file path you specified when you fclose() out of the file.
The only thing I'll note is that the file path has to exist (the file doesn't, but the file path does). In my instance, the file path already exists, but if yours doesn't or if you're not sure if it does, check to see if it exists, and then make the directory.
public function updatePaymentConfirm(Request $request) {
$user = Auth::user();
$path = storage_path('app/public/docs/user_docs/'.$user->id);
$fileName = $user->ein.'.csv';
$file = fopen($path.$fileName, 'w');
$columns = array('First Name', 'Email Address');
fputcsv($file, $columns);
$data = [
'First Name' => $user->first_name,
'Email Address' => $user->email,
];
fputcsv($file, $data);
fclose($file);
$symlink = 'public/docs/user_docs/'.$user->id.'/';
$fileModel = new UserDocument;
$fileModel->name = 'csv';
$fileModel->file_path = $symlink.$fileName;
$fileModel->save();
return redirect()->route('completed');
}
** UPDATE **
Everything worked perfectly locally, and when I pushed this to production, I received this error 🙄:
fopen(https://..../12-3456789.csv): failed to open stream: HTTP wrapper does not support writeable connections.
I'm saving to an s3 bucket, and I had to rework the entire process. You can't create and/or write to a file in the directory. I had to create a temp file first. Here's where I landed:
$user = Auth::user();
$s3 = Storage::disk('s3');
$storage = Storage::disk('s3')->url('/');
$path = 'public/docs/user_docs/'.$user->id.'/';
$csvFile = tmpfile();
$csvPath = stream_get_meta_data($csvFile)['uri'];
$fd = fopen($csvPath, 'w');
$columns = array('First Name', 'Email Address');
$data = array(
'First Name' => $user->first_name,
'Email Address' => $user->email,
);
fputcsv($fd, $columns);
fputcsv($fd, $data);
fclose($fd);
$s3->putFileAs('', $csvPath, $path.$user->ein.'.csv');
Today I have fixed it with this snipe:
// output up to 5MB is kept in memory, if it becomes bigger it will
// automatically be written to a temporary file
$csv = fopen('php://temp/maxmemory:'. (5*1024*1024), 'r+');
fputcsv($csv, array('blah','blah'));
rewind($csv);
$output = stream_get_contents($csv);
// Put the content directly in file into the disk
Storage::disk('myDisk')->put("report.csv", $output);
This code is easy and functional, use Laravel Storage Class
https://laravel.com/docs/9.x/filesystem#main-content
use Illuminate\Support\Facades\Storage;
// data array
$results = [
['id' => 0, 'name' => 'David', 'parent' => 1],
['id' => 1, 'name' => 'Ron', 'parent' => 0],
['id' => 2, 'name' => 'Mark', 'parent' => 1]
];
// create a variable to store data
$pages = "id,name,parent\n"; // use " not ' or \n not working
// use foreach to data
foreach ($results as $where) {
$pages .= "{$where['id']},{$where['name']},{$where['parent']}\n";
}
// use Fecades Laravel Storage
Storage::disk('local')->put('file.csv', $pages);

Browser doesn't prompt to download file - Laravel 6

[SOLVED] Had to split the download() method in 2, so it goes as generateCSV() and then getDownload().
After the the generation of the file, added this on sweetalert confirm button, which route points to getDownload().
preConfirm: () => {
window.location.href = "/customers/resale/filterToCSV/download";
}
After the user selects a few checkboxes for filtering a database table, the server writes to a CSV file, but it doesn't prompt the browser to download it.
route:
Route::get('/customers/resale/filterToCSV', 'Resale_customerController#getFilteredQueryResults');
blade view:
axios.get('/customers/resale/filterToCSV', {
params: {
dataFromClient: arrJson,
}
})
.then(function (response) {
Swal.fire({
icon: 'success',
title: '...',
text: '...',
})
console.log("Response (Filtered data to CSV): " + response.data);
});
controller:
public function getFilteredQueryResults(Request $request)
{
$arr = json_decode($request->dataFromClient, true);
$selection = $this->queryBuilderFromCheckboxSelection($arr);
$jsn = $selection->toJson();
$this->download($jsn);
}
which calls the download() method:
public function download($jsn)
{
$filePath = public_path().'\\file.csv';
$headers = array(
"Content-type" => "text/csv",
"Content-Disposition" => "attachment; filename=file.csv",
"Pragma" => "no-cache",
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
"Expires" => "0"
);
$jsonDecoded = json_decode($jsn, true);
$csvFileName = 'file.csv';
$fp = fopen($csvFileName, 'w');
foreach ($jsonDecoded as $row) {
fputcsv($fp, $row);
}
fclose($fp);
echo response()->download($filePath, $csvFileName, $headers);
return response()->download($filePath, $csvFileName, $headers);//->deleteFileAfterSend(true);
}
Any idea what am I missing? Thank you!

Laravel download query results as CSV [duplicate]

This question already has answers here:
How to create and download a csv file from php script?
(8 answers)
Closed 3 years ago.
I am trying to download a query as CSV and face currently two issues:
A file is created in the public folder. It contains the query data. That is really bad, because it should not exist in the public folder.
A file is also downloaded, but the downloaded file is empty.
Here is the function:
public function get_chatmessages(Request $data) {
try {
if ($data->chat_to_user) {
$result = DB::connection('mysql_live')->table('user_chatmessages')
->where(function($query) use($data) {
$query->where('from_user', $data->chat_from_user)->where('to_user', $data->chat_to_user);
})->orWhere(function($query) use($data) {
$query->where('to_user', $data->chat_from_user)->where('from_user', $data->chat_to_user);
})->orderBy('date_added', 'asc')->get();
} else {
$result = DB::connection('mysql_live')->table('user_chatmessages')
->where('from_user', $data->chat_from_user)
->orWhere('to_user', $data->chat_from_user)
->orderBy('date_added', 'asc')
->get();
}
//\Log::info($data);
//\Log::info($result);
$headers = array(
"Content-type" => "text/csv",
"Content-Disposition" => "attachment; filename=file.csv",
"Pragma" => "no-cache",
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
"Expires" => "0"
);
$columns = array('from_user', 'to_user', 'message', 'date_added');
$callback = function() use ($result, $columns) {
$file = fopen('output_chat.csv', 'w');
fputcsv($file, $columns);
foreach($result as $res) {
fputcsv($file, array($res->from_user, $res->to_user, $res->message, $res->date_added));
}
fclose($file);
};
//return response()->download('output_chat.csv', 'DL_output_chat.csv', $headers);
//return response()->make($callback, 200, $headers);
return response()->stream($callback, 200, $headers);
} catch (\Exception $e) {
return redirect('home')->with('error', $e->getMessage());
}
return redirect('home')->with('error', 'Etwas ist schief gelaufen');
}
I made little changes in your snippet regarding php://output
$headers = [
"Content-type" => "text/csv",
"Content-Disposition" => "attachment; filename=output_chat.csv", // <- name of file
"Pragma" => "no-cache",
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
"Expires" => "0",
];
$columns = ['from_user', 'to_user', 'message', 'date_added'];
$callback = function () use ($result, $columns) {
$file = fopen('php://output', 'w'); //<-here. name of file is written in headers
fputcsv($file, $columns);
foreach ($result as $res) {
fputcsv($file, [$res->from_user, $res->to_user, $res->message, $res->date_added]);
}
fclose($file);
};
For Laravel, there are many packages which provide us such implementations.
And for you to create download csv file with your database records you can use Laravel Excel package.
This package has many useful features. try to use it.
You can decide where you save files - you can do this using Laravel's Storage facade.
private function pathToPrivateStorage()
{
return '/private/CSVs';
}
Storage::put($this->pathToPrivateStorage, $YOURCSVFILE);
You can read more about storage here.
Try $file = fopen('php://output', 'w'); in place of $file = fopen('output_chat.csv', 'w');

How can I export .csv file using with Japanese character code in PHP?

I'm trying to create a csv file using laravel and php. The database used to create the csv contains Japanese characters which I want to appear exactly the same in the file.
Below is the code I've tried so far, but the japanese characters still appear as symbols.
$headers = array(
"Content-Encoding" => "sjis-win",
"Content-type" => "text/csv; charset=sjis-win",
"Content-Disposition" => "attachment; filename=User-List.csv",
"Pragma" => "no-cache",
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
"Expires" => "0"
);
$users= $this->users->orderBy('created_at', 'desc')->get();
$columns = array('氏名', '氏名(ローマ字)');
$callback = function() use ($users, $columns)
{
$file = fopen('php://output', 'w');
fputcsv($file, $columns);
foreach($users as $user) {
fputcsv($file, array($user->name, $user->name_alphabet));
}
fclose($file);
};
What am I missing? What needs to be changed to make the characters appear as Japanese automatically in the csv.
It working!
// You add $bom in when fputs file.
$headerColumns = [
'name',
'birthday',
'address',
];
$fileCSV = fopen($fileName, 'w');
fputs($fileCSV, chr(0xEF) . chr(0xBB) . chr(0xBF));
fputcsv($fileCSV, $headerColumns);
foreach ($data as $myField ){
fputcsv($fileCSV, $myField);
}
fclose($fileCSV);
// Good luck!
As the data your retrieving from the database is encoded in UTF-8, you will need to re-encode that data to match the encoding of your CSV file (SJIS-win).
You can use php's mb_convert_encoding() function to achieve this.
mb_convert_encoding($dataVariable, "SJIS-win", "UTF-8");
In your case you would use it as follows:
foreach($users as $user) {
fputcsv($file, array(
mb_convert_encoding($user->name, "SJIS-win", "UTF-8"),
mb_convert_encoding($user->name_alphabet, "SJIS-win", "UTF-8")
));
}
You may also need to re-encode the strings in your $columns = array('氏名', '氏名(ローマ字)') array too.

Categories