I'm trying to use PHP Simple HTML Dom Parser to parse some information from SQL query results. But it seems, that there is some HUGE memory problem with it. I create an html table using the SQL query results and then export the html table to a csv file. I am really new to this so my code is not the most efficient one. When I my query results are small the csv file is created successfully. But when the query results are large, the exported csv file does not have any sql results and instead shows this :
Fatal error: Call to a member function find() on boolean in /opt/lampp/htdocs/test.php on line 101
This is my function that takes the sqlresult and creates an html table and then exports it into a csv file:
echo sql_to_html_table($sqlresult, $delim="\n" );
function sql_to_html_table($sqlresult, $delim="\n") {
// starting table
include_once('simple_html_dom.php');
$htmltable = "<table>" . $delim ;
$counter = 0 ;
// putting in lines
//while( $row = $sqlresult->mysqli_fetch_assoc() ){
while($row = mysqli_fetch_assoc($sqlresult)) {
if ( $counter===0 ) {
// table header
$htmltable .= "<tr>" . $delim;
foreach ($row as $key => $value ) {
$htmltable .= "<th>" . $key . "</th>" . $delim ;
}
$htmltable .= "</tr>" . $delim ;
$counter = 22;
}
// table body
$htmltable .= "<tr>" . $delim ;
foreach ($row as $key => $value ) {
$htmltable .= "<td>" . $value . "</td>" . $delim ;
}
$htmltable .= "</tr>" . $delim ;
}
// closing table
$htmltable .= "</table>" . $delim ;
// return
//return( $htmltable ) ;
$html = str_get_html($htmltable);
header('Content-type: application/ms-excel');
header('Content-Disposition: attachment; filename=sample.csv');
$fp = fopen("php://output", "w");
foreach($html->find('tr') as $element)
{
$td = array();
foreach( $element->find('th') as $row)
{
$td [] = $row->plaintext;
}
fputcsv($fp, $td);
$td = array();
foreach( $element->find('td') as $row)
{
$td [] = $row->plaintext;
}
fputcsv($fp, $td);
}
fclose($fp);
}
I have tried throwing an exception after $html = str_get_html($htmltable); like this:
if (!str_get_html($htmltable)) {
throw new exception('exception') ;
}
and when I try to run the code my browser gives me this error:
Fatal error: Uncaught exception 'Exception' with message 'exception' in /opt/lampp/htdocs/test.php:96 Stack trace: #0 /opt/lampp/htdocs/test.php(62): sql_to_html_table(Object(mysqli_result), '\n') #1 {main} thrown in /opt/lampp/htdocs/test.php on line 96
Looking at a copy of simple_html_dom.php from SourceForge, this sounds like expected behavior for a sufficiently big HTML string. I see that str_get_html() has a check that will cause it to return false if the size of the string is greater than MAX_FILE_SIZE. And MAX_FILE_SIZE is defined with:
define('MAX_FILE_SIZE', 600000);
So it looks like simple_html_dom won't handle any string bigger than about 600kb. Since that's a built-in limitation, I guess your options are to either try to change the limit and see what happens or use a different library.
Alternatively, you could just skip the HTML portion altogether. If you need to generate the HTML for other purposes, that's fine, but there's no reason you can't bypass this problem by just building the CSV directly from the database results rather than from the HTML.
Maybe this is a little easier to understand:
function sql_to_csv($sqlresult, $delim = "\n") {
// Loop each result into a csv row string
while($row = mysqli_fetch_assoc($sqlresult)) {
// Create/reset a var to hold the csv row content
$csvRow = '';
// Append each column value comma separated
// Be warned of column values containing commas
foreach ($row AS $columnValue) {
$csvRow .= $columnValue . ',';
}
// Remove the trailing comma from the final column
rtrim($csvRow, ',');
// Send your CSV row to the browser
echo $csvRow . $delim;
}
header('Content-type: text/csv');
header('Content-Disposition: attachment; filename=sample.csv');
}
There are various issues with this approach not limited to, large output buffers, columns with multi-commas ...etc I recognise these problems but wanted to give an early approach to the solution instead of a large block of text.
The easiest way to debug PHP code is to run it with de-bugg outputting, the following may help you if the above is not useful:
var_dump($variable);
exit;
This will enable you to see the contents of the variable at run time, and may give better indication to your exception, given the line-number in your exceptions.
Goodluck.
Related
I am trying to write a file to a database 500 lines at a time so I do not run low on memory by avoiding dealing with very large arrays. For some reason, I am not getting any errors, but I am seeing a very, very small fraction entered into my table.
$ln = intval(shell_exec("wc -l $text_filename_with_path"));
echo "FILENAME WITH PATH: " . $text_filename_with_path ."\n\n";
echo "ARRAY LENGTH: " . $ln . "\n\n";
//pointer is initialized at zero
$fp = fopen($text_filename_with_path, "r");
$offset = 0;
$c = 0;
while($offset < $ln){
$row_limit = 500;
//get a 500 row section of the file
$chunk = fgets($fp, $row_limit);
//prepare for `pg_copy_from` by exploding to array
$chunk = explode("\n", $chunk);
//each record from the file being read is just one element
//prepare for three column DB table by adding columns (one
//unique PK built from UNIX time concat with counter, the
//other from a non-unique batch ID)
array_walk($chunk,
function (&$item, $key) use ($datetime, $c) {
$item = time() . $c . $key . "\t" . $datetime . "\t" . $item;
}
);
//increase offset to in order to move pointer forward
$offset += $row_limit;
//set pointer ahead to new position
fseek($fp, $offset);
echo "CURRENT POINTER: " . ftell($fp) . "\n"; //prints out 500, 1000, 1500 as expected
//insert array directly into DB from array
pg_copy_from($con, "ops.log_cache_test", $chunk, "\t", "\\NULL");
//increment to keep PK column unique
$c++;
}
I am getting as I say a fraction of the contents of the file, and lots of the data looks a bit messed up, eg about have the entries are blank in the part of the array element that gets assigned by $item within my array_walk() callback. Further it seems that exploding on \n is not working properly as lines seem exploded at ununiform positions (ie, log records don't look symmetrical). Have I just made a total mess out of this
You are not using fgets properly (2nd parameter isn't the number of rows);
There are two ways I can think of at the moment to solve it:
1. A loop getting one line at a time, until you've reached your row limit.
code should look something like this (not tested, assuming the end of line char is "\n" and no "\r")
<?php
/**Your code and initialization here*/
while (!feof($file)){
$counter = 0;
$buffer = array();
while (($line = fgets($file)) !== false && $counter < $row_limit) {
$line = str_replace("\n", "", $line); // fgets gets the line with the newline char at the end of line.
$buffer[] = $line;
$counter++;
}
insertRows($rows);
}
function insertRows($rows){
/** your code here */
}?>
Assuming the file isn't too big- using file_get_contents();
code should look something like this (same assumptions)
<?php
/**Your code and initialization here*/
$data = file_get_contents($filename);
if ($data === FALSE )
echo "Could not get content for file $filename\n";
$data = explode("\n",$data);
for ($offset=0;$offset<count($data);$offset+=$row_limit){
insertRows(array_slice ($rows,$offset,$row_limit));
}
function insertRows($rows){
/** your code here */
}
I didn't test it, so I hope it's ok.
I'm trying to generate a CSV file on the fly, depending on what the user selects as report output. Retrieving the data and writing it to a file using CakeResponse is done, however I'm struggling to set the file extension to '.csv', the file get downloaded as a normal text file.
CakePHP documentation suggests I do this:
$this->response->type('csv');
..but even this is not working, I'm still getting a text file. Can anyone shed some light? Please note, I'm not looking for new methods to generate a CSV file, I just want to change the extension. Thank you.
This is how I download the file:
$this->response->body($this->constructFileBody($logs));
return $this->response;
This is the method 'constructFileBody', although I think its beyond the scope of this question:
public function constructFileBody($logs = array()){
$content = "";
for($i = 0; $i < count($logs); $i++){
$row = $logs[$i]['EventLog'];
$line = $row['description'] . "," . $row['user'] . "," . $row['affected_user'] . "," . $row['report_title'] . "," . $row['date_created'] . "\n";
$content = $content . $line;
}
return $content;
}
As i saw your code, I don't think you used the header anywhere, try this code:
//create a file
$filename = "export_".date("Y.m.d").".csv";
$csv_file = fopen('php://output', 'w');
header('Content-type: application/csv');
header('Content-Disposition: attachment; filename="'.$filename.'"');
$results = $this->ModelName->query($sql); // This is your sql query to pull that data you need exported
//or
$results = $this->ModelName->find('all', array());
// The column headings of your .csv file
$header_row = array("ID", "Received", "Status", "Content", "Name", "Email", "Source", "Created");//columns you want in csv file
fputcsv($csv_file,$header_row,',','"');
// Each iteration of this while loop will be a row in your .csv file where each field corresponds to the heading of the column
foreach($results as $result)
{
// Array indexes correspond to the field names in your db table(s)
$row = array(
$result['ModelName']['id'],
$result['ModelName']['received'],
$result['ModelName']['status'],
$result['ModelName']['content'],
$result['ModelName']['name'],
$result['ModelName']['email'],
$result['ModelName']['source'],
$result['ModelName']['created']
);
fputcsv($csv_file,$row,',','"');
}
fclose($csv_file);
Now look at your code and get the line of code mine which needs to be replaced.
I am trying to output the results of a PostgreSQL query to CSV format using PHP.
On the main page is a link that sends the SQL statements as a string to another function in another PHP class, which in turn takes the SQL and executes the query using pg_query() and return the result sets.
My problem is that when I open the CSV file, all the results of my query are there, but at the end of the file I see the HTML code from the page that sent the query.
I looked at several StackOverflow posts, but to no avail.
Here is my code:
Main class:
$o .= '<p>You can convert the result set to CSV format to be opened in Excel.</p>';
$link = array('op1' => 'PatternExport', 'op2' => 'outputToCSV', 'id' => $pattern_id, 'data' => $pattern_SQL);
$o .= 'Download Query Results as CSV File';
Receiving class:
function outputToCSV()
{
$ptid = $this->oQS->getValue('id');
$sql = $this->oQS->getValue('data');
$href = $this->oQS->buildEncryptedURL(array('op1'=>'PatternManager', 'op2'=>'listPatterns'),'/aatsc/index.php');
$result = pg_query($sql);
// filename for download
$filename = "query_results_" . date('Ymd') . "_" . $ptid . ".csv";
$output = fopen('php://temp/maxmemory:' . (12*1024*1024), 'rw+');
foreach(pg_fetch_assoc($result,0) AS $field=>$value)
{
$output .= '' . $field . ',';
}
$output = rtrim($output,',') . "\n";
for($i=0;$i<pg_num_rows($result);$i++)
{
foreach(pg_fetch_assoc($result,$i) AS $field=>$value)
$output .= '' . $value . ',';
$output = rtrim($output,',') . "\n";
}
header("Content-Type: application/vnd.ms-excel;");
header("Content-Disposition: attachment; filename=\"$filename\";");
header("Pragma: no-cache");
print($output);
//$href = $this->oQS->buildEncryptedURL(array('op1'=>'PatternManager', 'op2'=>'listPatterns'),'/aatsc/index.php');
//header("Location: $href");
}
Could you tell me whether I am using the right approach to export query results to CSV, and what in my code is causing the whole HTML code to be streamed?
Thanks
You're generating $output twice, if you remove the foreach loop & fopen line, it should work.
You merely just need to issue the SQL COPY statement as follows:
COPY (select * from tbl) to stdout with csv header
resulting in:
col1,col2,col3
2013-05-22 07:28:59.732,192.168.1.67,3
I have to run a pairing algorithm for a game and when the pairing is done, I display the pairing on HTML and create a csv file as well. Right now, once I am done with pairing, I create a multidimensional array to store the specific value and then pass it to the function in same php file to generate the csv file. However, doing this outputs the entire page code i.e. html and php code to the .csv file. Here is the code:
function performPairing()
{
....
$count=0;
$resultArray[][] = array();
while ($currrow = #mysql_fetch_row($result))
{
$playerone = $currrow;
$playertwo = #mysql_fetch_row($result);
$resultArray[$count][] = $playerone[1];
$resultArray[$count][] = $playerone[0];
$resultArray[$count][] = $playertwo[1];
$resultArray[$count][] = $playertwo[0];
$count++;
updateforeachrow($playerone, $playertwo);
}
generateDocument($resultArray, $count);
}
function generateDocument($resultArray, $count)
{
$output = fopen('php://temp/maxmemory'.(5*1024*1024), 'r+');
$columns = array('Player One Col1', 'Player One Col2', 'Player Two Col1', 'Player Two Col2');
fputcsv($output, $columns);
for ($index=0 ; $index <=$count; $index++)
{
fputcsv($output, $resultArray[$index]);
}
rewind($output);
$export = stream_get_contents($output);
fclose($output);
header('Content-type: application/octet-stream');
header('Content-Disposition: attachment; filename = "export.csv"');
echo $export;
}
However doing this outputs the entire html code to csv rather than specific rows. Can anyone please help me on this?
. make string
. output string to file
. send header with (is this allowed this way?)
use file_put_contents ( filename, str ), and than send it with headers.
Make code simpler :)
using php i exported report in excel.But got stuck in merging cells.
i want to merge the first 10 cells to dispaly the Name of the Company.
The variable which has the company name is in one cell, tried to merge the cells but i cudn't...
I used this function to export,
where $query variable holds the mysql query which is sent as a parameter,
and in $fieldname variable, array of fieldnames to display header.
everything is ok, n works properly.
one thing i cudn't do was merging cells....
function to_excel_export($query,$fieldName)
{
$filename = date('d-m-Y');
$headers = '';
$data = '';
$obj =& get_instance();
if ($query->num_rows() == 0)
{
echo '<p>The table appears to have no data.</p>';
}
else
{
for($i=0;$i<sizeof($fieldName);$i++)
{
$headers .= $fieldName[$i] . "\t";
}
foreach ($query->result() as $row)
{
$line = '';
foreach($row as $value)
{
if ((!isset($value)) OR ($value == ""))
{
$value = "\t";
}
else
{
$value = str_replace('"', '""', $value);
$value = '"' . $value . '"' . "\t";
}
$line .= $value;
}
$data .= trim($line)."\n";
}
$data = str_replace("\r","",$data);
header("Content-type: application/x-msdownload");
header("Content-Disposition: attachment; filename=$filename.xls");
$compName = 'C O M P A N Y - N A M E ';
echo $compName."\n\n";
echo $headers."\n".$data;
}
}
$compName = 'C O M P A N Y - N A M E ';
echo $compName."\n\n";
how to merge the cells to display the name which is in $compName variable.
You're not creating an Excel file, you're creating a CSV file (tab separated in this case), and that format does NOT support any kind of formatting (font, color, even merging cells isn't an option).... and you're not even using PHP's built-in fputcsv() function to do so :(
Simply giving a file an extension of .xls doesn't make it an Excel file. MS Excel is capable of reading CSV files, but some versions of Excel will actually warn you that the format isn't correct when you load it.
Create a proper Excel BIFF or OfficeOpenXML file using one of the many libraries available to do so (such as PHPExcel), and then you'll be able to set formatting such as cell background colours.