PHPExcel deleting rows from large worksheet - php

I am trying to delete rows based on conditions such as Cell B = 59/61. However, PHPExcel loads the rows very slowly. For a worksheet with 18000 rows/3MB, it takes around 4 hours 30 minutes to load. How can I improve the speed of the data worksheet to load and delete?
set_include_path(get_include_path() . PATH_SEPARATOR . 'Classes/');
/** PHPExcel_IOFactory */
include 'PHPExcel/IOFactory.php';
//Defining File Type
$fileType = "Excel2007";
//Retrieving File
$tmpfname = "bigfile.xlsx";
//Loading file into PHPExcel
$objPHPExcel = PHPExcel_IOFactory::load($tmpfname);
$worksheet = $objPHPExcel->getSheet(0); //Worksheet of file defined as first
$lastRow = $worksheet->getHighestRow();
//Determine which rows to be remove
$DeletedRows = [];
$DeletedRowCount = 0;
for ($row = 2; $row <= $lastRow; $row++) {
//Checker
$CellA = $worksheet->getCell('A' . $row)->getValue();
$CellB = $worksheet->getCell('B' . $row)->getValue();
$CellE = $worksheet->getCell('E' . $row)->getValue();
//To check condition
if ($CellB != 8 && $CellB != 9 && $CellB != 18 && $CellB != 19) {
$DeletedRows[] = $row;
continue;
}
//To check if condition
else if ($CellE == 59 || $CellE == 61){
$DeletedRows[] = $row;
continue;
}
}
//Removing the rows
//Deleting this way as when one row deleted, one row less.
foreach ($DeletedRows as $key => $value) {
$row = $value - $DeletedRowCount;
$objPHPExcel->getActiveSheet()->removeRow($row, 1);
$DeletedRowCount++;
}
//Write file into original file
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, $fileType);
$objWriter->save($tmpfname);

Well one way to improve performance is to work upwards from the bottom of the spreadsheet rather than downwards.
When you delete a row, PHPExcel has to check the cells in every row below it and adjust references in those cells if necessary. This is more efficient, with fewer checks/updates necessary when you work from the bottom of the worksheet upwards.
It's also a lot more efficient if you can delete more than one row at a time. If you need to delete rows 15, 16 and 17; then
$objPHPExcel->getActiveSheet()->removeRow(15, 3);
is 3x faster than
$objPHPExcel->getActiveSheet()->removeRow(15, 1);
$objPHPExcel->getActiveSheet()->removeRow(16, 1);
$objPHPExcel->getActiveSheet()->removeRow(17, 1);
So spending a bit of time evaluating your $DeletedRows array looking for ranges could be beneficial
Your code would also be a lot faster if, instead of building an array of row numbers to delete in the first loop and deleting them in the second, you deleted them in the first loop... 1 loop will inevitably be faster than 2 loops
Lastly, there is one major flaw with your approach. If your first loop tells you that you need to delete rows 1, 5; you delete row 1 and then 5.... except that row 5 is actually row 4 since you deleted row 1, and you've actually just deleted the row that was row 6 when you did your original loop to assess which rows to delete.... working from bottom to top would prevent this problem as well as being more efficient

Related

PHP How to handle/parse csv files that have missing columns

I have many csv files generated by a third party, for which I have no say or control.
So each day I must import these csv data to mysql.
Some tables have correct matching number of columns to header.
Others do not.
Even when I did a prepared statement, it still did not import.
I tried to create a repair csv function, to add extra columns to each row, if their count of columns was less than the count of header columns.
As part of this project I am using the composer package league csv.
https://csv.thephpleague.com/
But here is my function code:
public function repaircsv(string $filepath) {
// make sure incoming file exists
if (!file_exists($filepath)) {
// return nothing
return;
}
// setup variables
$tempfile = pathinfo($filepath,PATHINFO_DIRNAME).'temp.csv';
$counter = 0;
$colcount = 0;
$myline = '';
// check if temp file exists if it does delete it
if (file_exists($tempfile)) {
// delete the temp file
unlink($tempfile);
}
// C:\Users\admin\vendor\league\csv
require('C:\Users\admin\vendor\league\csv\autoload.php');
// step one get header column count
$csv = Reader::createFromPath($filepath);
// set the header offset
$csv->setHeaderOffset(0);
//returns the CSV header record
$header = $csv->getHeader();
// get the header column count
$header_count = count($header);
// check if greater than zero and not null
if ($header_count < 1 || empty($header_count)) {
// return nothing
return $header_count;
}
// loop thru csv file
// now read file line by line skipping line 1
$file = fopen($filepath, 'r');
$temp = fopen($tempfile, 'w');
// loop thru each line
while (($line = fgetcsv($file)) !== FALSE) {
// if first row just straight append
if ($counter = 0) {
// append line to temp file
fputcsv($temp, $line);
}
// if all other rows compare column count to header column count
if ($counter > 0) {
// get column count for normal rows
$colcount = count($line);
// compare to header column count
$coldif = $header_count - $colcount;
// loop til difference is zero
while ($colcount != $header_count) {
// add to line extra comma
$line .= ',';
// get new column count
$colcount = count($line);
}
// append to temp file
fputcsv($temp, $line);
// show each line
$myline .= 'Line: ['.$line.']<br/><br/>';
}
// increment counter
$counter++;
}
// check file size of temp file
$fs = filesize($tempfile);
// if below 200 ignore and do not copy
if ($fs > 200) {
// copy temp to original filename
copy($tempfile,$filepath);
}
return $myline;
}
The logic is to copy the original csv file to a new temp csv file and add extra commas to rows of data that have missing columns.
Thank you for any help.
Edit: So the various csv's contain private data, so I can not share them.
But let us for example say i download multiple csvs for different data daily.
Each csv has a header row, and data.
If the number of columns in each row isn't 100% the same number of columns as in the header, it errors out.
If there are any special characters, it errors out.
There are 1000's of rows of data.
The code above is my first attempt to try to fix rows that have missing columns.
Here is an example
FirstName, LastName, Email
Steve,Jobs
,Johnson,sj#johns.com
Just a very small example.
I have no control of how the csvs are created, I do control the download process and import process.
Which then i use the csv data to update mysql tables.
I have tried the load data infile but that errors out too.
So I need to fix the csv files after they are downloaded.
Any ideas?
Do not mix array and string, instead of
$line .= ',';
do
$Line[]= '';
Also fix:
$myline .= 'Line: ['.implode(',', $line).']<br/><br/>';
Suggestion, you can replace your while loop with:
$line = array_pad($line, $header_count, ''); // append missing items
$line = array_slice($line, 0, $header_count); // remove eventual excess items

How to process CSV with 100k+ lines in PHP?

I have a CSV file with more than 100.000 lines, each line has 3 values separated by semicolon. Total filesize is approx. 5MB.
CSV file is in this format:
stock_id;product_id;amount
==========================
1;1234;0
1;1235;1
1;1236;0
...
2;1234;3
2;1235;2
2;1236;13
...
3;1234;0
3;1235;2
3;1236;0
...
We have 10 stocks which are indexed 1-10 in CSV. In database we have them saved as 22-31.
CSV is sorted by stock_id, product_id but I think it doesn't matter.
What I have
<?php
session_start();
require_once ('db.php');
echo '<meta charset="iso-8859-2">';
// convert table: `CSV stock id => DB stock id`
$stocks = array(
1 => 22,
2 => 23,
3 => 24,
4 => 25,
5 => 26,
6 => 27,
7 => 28,
8 => 29,
9 => 30,
10 => 31
);
$sql = $mysqli->query("SELECT product_id FROM table WHERE fielddef_id = 1");
while ($row = $sql->fetch_assoc()) {
$products[$row['product_id']] = 1;
}
$csv = file('export.csv');
// go thru CSV file and prepare SQL UPDATE query
foreach ($csv as $row) {
$data = explode(';', $row);
// $data[0] - stock_id
// $data[1] - product_id
// $data[2] - amount
if (isset($products[$data[1]])) {
// in CSV are products which aren't in database
// there is echo which should show me queries
echo " UPDATE t
SET value = " . (int)$data[2] . "
WHERE fielddef_id = " . (int)$stocks[$data[0]] . " AND
product_id = '" . $data[1] . "' -- product_id isn't just numeric
LIMIT 1<br>";
}
}
Problem is that writing down 100k lines by echo is soooo slow, takes long minutes. I'm not sure what MySQL will do, if it will be faster, or take ± the same time. I have no testing machine here, so I'm worry about testing in on prod server.
My idea was to load CSV file into more variables (better array) like below, but I don't know why.
$csv[0] = lines 0 - 10.000;
$csv[1] = lines 10.001 - 20.000;
$csv[2] = lines 20.001 - 30.000;
$csv[3] = lines 30.001 - 40.000;
etc.
I found eg. Efficiently counting the number of lines of a text file. (200mb+), but I'm not sure how it can help me.
When I replace foreach for print_r, I get dump in < 1 sec. The task is to make the foreach loop with database update faster.
Any ideas how to updates so many records in database?
Thanks.
Something like this (please note this is 100% untested and off top of my head may need some tweaking to actually work :) )
//define array may (probably better ways of doing this
$stocks = array(
1 => 22,
2 => 23,
3 => 24,
4 => 25,
5 => 26,
6 => 27,
7 => 28,
8 => 29,
9 => 30,
10 => 31
);
$handle = fopen("file.csv", "r")); //open file
while (($data = fgetcsv($handle, 1000, ";")) !== FALSE) {
//loop through csv
$updatesql = "UPDATE t SET `value` = ".$data[2]." WHERE fielddef_id = ".$stocks[$data[0]]." AND product_id = ".$data[1];
echo "$updatesql<br>";//for debug only comment out on live
}
There is no need to do your initial select since you're only ever setting your product data to 1 anyway in your code and it looks from your description that your product id's are always correct its just your fielddef column which has the map.
Also just for live don't forget to put your actual mysqli execute command in on your $updatesql;
To give you a comparison to actual usage code (I can benchmark against!)
This is some code I use for an importer of an uploaded file (its not perfect but it does its job)
if (isset($_POST['action']) && $_POST['action']=="beginimport") {
echo "<h4>Starting Import</h4><br />";
// Ignore user abort and expand time limit
//ignore_user_abort(true);
set_time_limit(60);
if (($handle = fopen($_FILES['clientimport']['tmp_name'], "r")) !== FALSE) {
$row = 0;
//defaults
$sitetype = 3;
$sitestatus = 1;
$startdate = "2013-01-01 00:00:00";
$enddate = "2013-12-31 23:59:59";
$createdby = 1;
//loop and insert
while (($data = fgetcsv($handle, 10000, ",")) !== FALSE) { // loop through each line of CSV. Returns array of that line each time so we can hard reference it if we want.
if ($row>0) {
if (strlen($data[1])>0) {
$clientshortcode = mysqli_real_escape_string($db->mysqli,trim(stripslashes($data[0])));
$sitename = mysqli_real_escape_string($db->mysqli,trim(stripslashes($data[0]))." ".trim(stripslashes($data[1])));
$address = mysqli_real_escape_string($db->mysqli,trim(stripslashes($data[1])).",".trim(stripslashes($data[2])).",".trim(stripslashes($data[3])));
$postcode = mysqli_real_escape_string($db->mysqli,trim(stripslashes($data[4])));
//look up client ID
$client = $db->queryUniqueObject("SELECT ID FROM tblclients WHERE ShortCode='$clientshortcode'",ENABLE_DEBUG);
if ($client->ID>0 && is_numeric($client->ID)) {
//got client ID so now check if site already exists we can trust the site name here since we only care about double matching against already imported sites.
$sitecount = $db->countOf("tblsites","SiteName='$sitename'");
if ($sitecount>0) {
//site exists
echo "<strong style=\"color:orange;\">SITE $sitename ALREADY EXISTS SKIPPING</strong><br />";
} else {
//site doesn't exist so do import
$db->execute("INSERT INTO tblsites (SiteName,SiteAddress,SitePostcode,SiteType,SiteStatus,CreatedBy,StartDate,EndDate,CompanyID) VALUES
('$sitename','$address','$postcode',$sitetype,$sitestatus,$createdby,'$startdate','$enddate',".$client->ID.")",ENABLE_DEBUG);
echo "IMPORTED - ".$data[0]." - ".$data[1]."<br />";
}
} else {
echo "<strong style=\"color:red;\">CLIENT $clientshortcode NOT FOUND PLEASE ENTER AND RE-IMPORT</strong><br />";
}
fcflush();
set_time_limit(60); // reset timer on loop
}
} else {
$row++;
}
}
echo "<br />COMPLETED<br />";
}
fclose($handle);
unlink($_FILES['clientimport']['tmp_name']);
echo "All Imports finished do not reload this page";
}
That imported 150k rows in about 10 seconds
Due to answers and comments for the question, I have the solution. The base for that is from #Dave, I've only updated it to pass better to question.
<?php
require_once 'include.php';
// stock convert table (key is ID in CSV, value ID in database)
$stocks = array(
1 => 22,
2 => 23,
3 => 24,
4 => 25,
5 => 26,
6 => 27,
7 => 28,
8 => 29,
9 => 30,
10 => 31
);
// product IDs in CSV (value) and Database (product_id) are different. We need to take both IDs from database and create an array of e-shop products
$sql = mysql_query("SELECT product_id, value FROM cms_module_products_fieldvals WHERE fielddef_id = 1") or die(mysql_error());
while ($row = mysql_fetch_assoc($sql)) {
$products[$row['value']] = $row['product_id'];
}
$handle = fopen('import.csv', 'r');
$i = 1;
while (($data = fgetcsv($handle, 1000, ';')) !== FALSE) {
$p_id = (int)$products[$data[1]];
if ($p_id > 0) {
// if product exists in database, continue. Without this condition it works but we do many invalid queries to database (... WHERE product_id = 0 updates nothing, but take a time)
if ($i % 300 === 0) {
// optional, we'll see what it do with the real traffic
sleep(1);
}
$updatesql = "UPDATE table SET value = " . (int)$data[2] . " WHERE fielddef_id = " . $stocks[$data[0]] . " AND product_id = " . (int)$p_id . " LIMIT 1";
echo "$updatesql<br>";//for debug only comment out on live
$i++;
}
}
// cca 1.5sec to import 100.000k+ records
fclose($handle);
Like I said in the comment, use SPLFileObject to iterate over the CSV file. Use Prepared statements to reduce performance overhead of calling the UPDATE in each loop. Also, merge your two queries together, there isn't any reason to pull all of the product rows first and check them against the CSV. You can use a JOIN to ensure that only those stocks in the second table that are related to the product in the first and that is the current CSV row will get updated:
/* First the CSV is pulled in */
$export_csv = new SplFileObject('export.csv');
$export_csv->setFlags(SplFileObject::READ_CSV | SplFileObject::DROP_NEW_LINE | SplFileObject::READ_AHEAD);
$export_csv->setCsvControl(';');
/* Next you prepare your statement object */
$stmt = $mysqli->prepare("
UPDATE stocks, products
SET value = ?
WHERE
stocks.fielddef_id = ? AND
product_id = ? AND
products.fielddef_id = 1
LIMIT 1
");
$stmt->bind_param('iis', $amount, $fielddef_id, $product_id);
/* Now you can loop through the CSV and set the fields to match the integers bound to the prepared statement and execute the update on each loop. */
foreach ($export_csv as $csv_row) {
list($stock_id, $product_id, $amount) = $csv_row;
$fielddef_id = $stock_id + 21;
if(!empty($stock_id)) {
$stmt->execute();
}
}
$stmt->close();
Make the query bigger, i.e. use the loop to compile a larger query. You may need to split it up into chunks (e.g. process 100 at a time), but certainly don't do one query at a time (applies for any kind, insert, update, even select if possible). This should greatly increase the performance.
It's generally recommended that you don't query in a loop.
Updating every record every time will be too expensive (mostly due to seeks, but also from writing).
You should TRUNCATE the table first and then insert all the records again (assuming you won't have external foreign keys linking to this table).
To make it even faster, you should lock the table before the insert and unlock it afterwards. This will prevent the indexing from happening at every insert.

Optimizing setCellValueExplicit() in PHPExcel

I am dealing with 700 rows of data in my excel.
And I add on a column this entry:
foreach($data as $k => $v){
$users ->getCell('A'.$k)->setValue($v['Username']);
$users->setCellValueExplicit('B'.$k,
'=INDEX(\'Feed\'!H2:H'.$lastRow.',MATCH(A'.$k.',\'Feed\'!G2:G'.$lastRow.',0))',
PHPExcel_Cell_DataType::TYPE_FORMULA);
}
$users stands for a spreadsheet.
I see that writing 700 cells with the above setCellValueExplicit() takes more than 2 minutes to get processed. If I omit that line it takes 4 seconds for the same machine to process it.
2 minutes can be ok, but what if I have 2000 cells. Is there any way that can be speed optimized?
ps: =VLOOKUP is the same slow as the above function.
Update
The whole idea of the script:
read a CSV file (13 columns and at least 100 rows), write it into a spreadsheet, create a new spreadsheet ($users), read two columns, sort them based to one column and write it to the $users spreadsheet.
Read the columns:
$data = array();
for ($i = 1; $i <= $lastRow; $i++) {
$user = $Feed ->getCell('G'.$i)->getValue();
$number = $Feed ->getCell('H'.$i)->getValue();
$row = array('User' => $user, 'Number' => $number);
array_push($data, $row);
}
Sort the data
function cmpb($a,$b){
//get which string is less or 0 if both are the same
if($a['Number']>$b['Number']){
$cmpb = -1;
}elseif($a['Number']<$b['Number']){
$cmpb = 1;
}else{
$cmpb = 0;
}
//if the strings are the same, check name
if($cmpb == 0){
//compare the name
$cmpb = strcasecmp($a['User'], $b['User']);
}
return $cmpb;
}
usort($data, 'cmpb');
Write data
foreach($data as $k => $v){
$users ->getCell('A'.$k)->setValue($v['Username']);
$users ->getCell("B{$k}")->setValueExplicit("=INDEX('Feed'!H2:H{$lastRow},MATCH(A{$k},'Feed'!G2:G{$lastRow},0))",
PHPExcel_Cell_DataType::TYPE_FORMULA);
}
and also unset the data for memory:
unset($data);
So if comment the line with setValueExplicit everything becomes smoother.
Looking at PHPExcel's source code, this is PHPExcel_Worksheet::setCellValueExplicit function:
public function setCellValueExplicitByColumnAndRow($pColumn = 0, $pRow = 1, $pValue = null, $pDataType = PHPExcel_Cell_DataType::TYPE_STRING)
{
return $this->getCell(PHPExcel_Cell::stringFromColumnIndex($pColumn) . $pRow)->setValueExplicit($pValue, $pDataType);
}
For the data type you're using, PHPExcel_Cell_DataType::TYPE_FORMULA, the PHPExcel_Cell::setValueExplicit function just executes:
case PHPExcel_Cell_DataType::TYPE_FORMULA:
$this->_value = (string)$pValue;
break;
I can't find a logical explanation for the old up on the execution of that particular instruction. Try to replace it for the following and let me know if there was any improvement:
$users ->getCell("B{$k}")->setValueExplicit("=INDEX('Feed'!H2:H{$lastRow},MATCH(A{$k},'Feed'!G2:G{$lastRow},0))", PHPExcel_Cell_DataType::TYPE_FORMULA);
As a last resource my advice would be to time track the execution of the instruction to find the bottleneck.

How to remove rows below a certain row number phpexcel

I would like to remove the bottom records. When I reach row 6 anything below that I would like to remove in phpexcel.
for($tot=2; $tot<$highestRowEMDist; $tot++){
$chkven = $objPHPExcel->getActiveSheet()->getCell('C'.$tot)->getValue();
if ($chkven!="Test" ) {
$objPHPExcel->getActiveSheet()->removeRow(7,0);
}
}
All I want is if I reach row 6 anything below that needs to be deleted
I have found the solution
for($row=2; $row < $highestRowEMDist; $row++){
$value = $objPHPExcel->getActiveSheet()->getCell('C'.$row)->getValue();
if ($value != "Quest") {
$objPHPExcel->getActiveSheet()->removeRow($row, $row);
}
}

How to find out how many rows and columns to read from an Excel file with PHPExcel?

With the following code, I am able to read the cells out of an Excel file with PHPExcel.
I currently manually define how many rows and columns to read.
Is there a way that PHPExcel can tell me how many rows and columns I have to read to get all the data out of the worksheet, e.g. even if some rows and columns are left blank?
$file_name = htmlentities($_POST['file_name']);
$sheet_name = htmlentities($_POST['sheet_name']);
$number_of_columns = htmlentities($_POST['number_of_columns']);
$number_of_rows = htmlentities($_POST['number_of_rows']);
$objReader = PHPExcel_IOFactory::createReaderForFile("data/" . $file_name);
$objReader->setLoadSheetsOnly(array($sheet_name));
$objReader->setReadDataOnly(true);
$objPHPExcel = $objReader->load("data/" . $file_name);
echo '<table border="1">';
for ($row = 1; $row < $number_of_rows; $row++) {
echo '<tr>';
for ($column = 0; $column < $number_of_columns; $column++) {
$value = $objPHPExcel->setActiveSheetIndex(0)->getCellByColumnAndRow($column, $row)->getValue();
echo '<td>';
echo $value . ' ';
echo '</td>';
}
echo '</tr>';
}
echo '</table>';
Solution:
Thanks, Mark, here's the full solution with those functions:
$file_name = htmlentities($_POST['file_name']);
$sheet_name = htmlentities($_POST['sheet_name']);
$number_of_columns = htmlentities($_POST['number_of_columns']);
$number_of_rows = htmlentities($_POST['number_of_rows']);
$objReader = PHPExcel_IOFactory::createReaderForFile("data/" . $file_name);
$objReader->setLoadSheetsOnly(array($sheet_name));
$objReader->setReadDataOnly(true);
$objPHPExcel = $objReader->load("data/" . $file_name);
$highestColumm = $objPHPExcel->setActiveSheetIndex(0)->getHighestColumn();
$highestRow = $objPHPExcel->setActiveSheetIndex(0)->getHighestRow();
echo 'getHighestColumn() = [' . $highestColumm . ']<br/>';
echo 'getHighestRow() = [' . $highestRow . ']<br/>';
echo '<table border="1">';
foreach ($objPHPExcel->setActiveSheetIndex(0)->getRowIterator() as $row) {
$cellIterator = $row->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(false);
echo '<tr>';
foreach ($cellIterator as $cell) {
if (!is_null($cell)) {
$value = $cell->getCalculatedValue();
echo '<td>';
echo $value . ' ';
echo '</td>';
}
}
echo '</tr>';
}
echo '</table>';
$objPHPExcel->setActiveSheetIndex(0)->getHighestColumn();
and
$objPHPExcel->setActiveSheetIndex(0)->getHighestRow();
or
$objPHPExcel->setActiveSheetIndex(0)->calculateWorksheetDimension();
which returns a range as a string like A1:AC2048
although trailing blank rows and columns are included in these.
EDIT
or you can use the iterators to loop through the existing rows and columns to get each cell within the worksheets used range. See /Tests/28iterator.php in the production distribution for an example. The iterators can be set to ignore blanks.
From the 1.7.6 and below PHPExcel versions it is possible to get worksheet information without reading whole file:
$objReader = PHPExcel_IOFactory::createReader("Excel2007");
$worksheetData = $objReader->listWorksheetInfo($uploadedfile);
$totalRows = $worksheetData[0]['totalRows'];
$totalColumns = $worksheetData[0]['totalColumns'];
You can do it much less cell reads than itterating all the rows(columns).
In my case, the first column is SKU of item and it's mandatory.
If you expect file with many many rows, in my case it can be 100 000 rows or more, i'm reading the value of the first column at every 10 000 row.
If cell A10000 is not empty, read A20000 and so on.
In this way, for a file with 100 000 rows I need max 10 reads of a single cell to decide in which segment of 10 000 rows the file ends.
For example, let say it's between 30 000 and 40 000 row.
Now get the average from above value - 35 000. One read of cell A35000 will further reduce the scope to 5000 rows. Next average (and the single cell read) will further reduce the scope to 2500 and so on.
Approximately you will need around 13-14 single cell reads, if you know in which 10 000 segment is the end of the file. If you expect file with 100 000 rows add maximum 10 cell reads to determine the exact segment of 10 000 rows. This means maximum of around 25 cell reads for file with 100 000 rows.
Edit: if you expect empty rows - read little more cells, for example, if you expect no more than 1 consequent empty row, read 2 consequent cells every time, for example A10000 and A10001, one of them should be non-empty, or you are beyond the end of the file. If you expect no more than 2 consequent empty rows, read 3 cells every time, for example A10000, A10001 and A10002, and so on.
I don't think you can do that, you would have to loop through starting from say 1000 and go backwards until you hit the first non-blank cell and that would be your last row or column.
You can write a macro to do this in excel which may help but I don't know if you can execute it with PHPExcel.
Following #nikolay's thinking from answer above, I decided to make the first cell of every row mandatory. That way, I just look at each cell of each row first to find out how many rows actually have data, depending on the first row.
$uploadedfile = \PHPExcel_IOFactory::load(Yii::getAlias('uploads').'/'.$file_location);
$uploadeddata = $uploadedfile->getActiveSheet()->toArray(null, true, true, true);
//we need to first know how many rows actually have data
//my first two rows have column labels, so i start with the third row.
$row_count = 3;
// read through the data and see how many rows actually have data
//the idea is that for every row, the first cell should be mandatory...
//if we find one that is not, we stop there...
do
{
$row_count++;
} while($uploadeddata[$row_count]['A'] == "null");
//get the actual number of rows with data, removing the column labels
$actual_rows = $row_count-3;
$spreadsheet = new Spreadsheet();
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('xls');
$reader->setReadDataOnly(true);
$spreadsheet = $reader->load('file location here');
$worksheet = $spreadsheet->getActiveSheet();
foreach ($worksheet->getRowIterator() AS $row) {
$cellIterator = $row->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(FALSE); // This loops through all cells,
$cells = [];
foreach ($cellIterator as $cell) {
$cells[] = $cell->getValue();
}
$rows[] = $cells;
}
//blade html
<table class="table table-hover">
<thead>
<tr>
#foreach($rows[0] as $heading)
<th>{{$heading}}</th>
#endforeach
</tr>
</thead>
<tbody>
#foreach($rows as $key=>$val)
#if(!($key == 0))
<tr>
#foreach($val as $value)
<td>{{$value}}</td>
#endforeach
</tr>
#endif
#endforeach
</tbody>
</table>
```

Categories