218 lines
9.6 KiB
PHP
218 lines
9.6 KiB
PHP
<?php
|
|
namespace App\Helpers;
|
|
|
|
use Exception;
|
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
|
|
|
class ExcelMergeHelper
|
|
{
|
|
|
|
protected static $logger;
|
|
|
|
public static function initialize()
|
|
{
|
|
self::$logger = \Config\Services::mylogger();
|
|
}
|
|
public static function mergeExcelFiles(array $filesToMerge, string $outputPath)
|
|
{
|
|
try {
|
|
self::initialize();
|
|
if (empty($filesToMerge)) {
|
|
throw new Exception("No files provided for merging.");
|
|
}
|
|
|
|
// Validate files exist and are readable
|
|
foreach ($filesToMerge as $fileData) {
|
|
if (!file_exists($fileData['file_path'])) {
|
|
throw new Exception("File not found: " . $fileData['file_path']);
|
|
}
|
|
if (!is_readable($fileData['file_path'])) {
|
|
throw new Exception("File not readable: " . $fileData['file_path']);
|
|
}
|
|
}
|
|
|
|
self::$logger->logme('error',"Creating new spreadsheet...");
|
|
$mergedSpreadsheet = new Spreadsheet();
|
|
$firstSheet = true;
|
|
|
|
foreach ($filesToMerge as $fileIndex => $fileData) {
|
|
$filePath = $fileData['file_path'];
|
|
$sheetsToMerge = $fileData['sheets'];
|
|
self::$logger->logme('error',"Processing file $fileIndex: $filePath");
|
|
|
|
try {
|
|
self::$logger->logme('error',"Attempting to load file: $filePath");
|
|
$reader = IOFactory::createReaderForFile($filePath);
|
|
$reader->setReadDataOnly(true); // This can help with memory usage
|
|
self::$logger->logme('error',"Reader created successfully");
|
|
|
|
$currentSpreadsheet = $reader->load($filePath);
|
|
self::$logger->logme('error',"File loaded successfully");
|
|
|
|
$sheetCount = $currentSpreadsheet->getSheetCount();
|
|
self::$logger->logme('error',"File $filePath has $sheetCount sheets.");
|
|
|
|
if (empty($sheetsToMerge)) {
|
|
$sheetsToMerge = range(0, $sheetCount - 1);
|
|
}
|
|
|
|
foreach ($sheetsToMerge as $sourceSheetIndex) {
|
|
self::$logger->logme('error',"Processing sheet index: $sourceSheetIndex");
|
|
|
|
if ($sourceSheetIndex < 0 || $sourceSheetIndex >= $sheetCount) {
|
|
self::$logger->logme('error',"Invalid sheet index $sourceSheetIndex in file $filePath");
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
$sourceSheet = $currentSpreadsheet->getSheet($sourceSheetIndex);
|
|
self::$logger->logme('error',"Source sheet loaded");
|
|
|
|
$baseName = $sourceSheet->getTitle();
|
|
self::$logger->logme('error',"Processing sheet: $baseName");
|
|
|
|
$newSheetName = $baseName;
|
|
$counter = 1;
|
|
|
|
while ($mergedSpreadsheet->sheetNameExists($newSheetName)) {
|
|
$newSheetName = $baseName . '_' . $fileIndex . '_' . $counter;
|
|
$counter++;
|
|
}
|
|
self::$logger->logme('error',"New sheet name will be: $newSheetName");
|
|
|
|
if ($firstSheet) {
|
|
$targetSheet = $mergedSpreadsheet->getActiveSheet();
|
|
$targetSheet->setTitle($newSheetName);
|
|
$firstSheet = false;
|
|
self::$logger->logme('error',"Using first sheet");
|
|
} else {
|
|
$targetSheet = new Worksheet($mergedSpreadsheet, $newSheetName);
|
|
$mergedSpreadsheet->addSheet($targetSheet);
|
|
self::$logger->logme('error',"Created new sheet");
|
|
}
|
|
|
|
self::$logger->logme('error',"Starting content copy for sheet: $newSheetName");
|
|
self::copyWorksheetContent($sourceSheet, $targetSheet);
|
|
self::$logger->logme('error',"Content copied successfully for sheet: $newSheetName");
|
|
|
|
} catch (Exception $e) {
|
|
self::$logger->logme('error',"Error processing sheet $sourceSheetIndex: " . $e->getMessage());
|
|
self::$logger->logme('error',"Error trace: " . $e->getTraceAsString());
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Free up memory
|
|
$currentSpreadsheet->disconnectWorksheets();
|
|
unset($currentSpreadsheet);
|
|
self::$logger->logme('error',"Cleaned up spreadsheet resources");
|
|
|
|
} catch (Exception $e) {
|
|
self::$logger->logme('error',"Error processing file $filePath: " . $e->getMessage());
|
|
self::$logger->logme('error',"Error trace: " . $e->getTraceAsString());
|
|
continue;
|
|
}
|
|
|
|
self::$logger->logme('error',"Current sheet names in merged spreadsheet:");
|
|
foreach ($mergedSpreadsheet->getSheetNames() as $name) {
|
|
self::$logger->logme('error'," - $name");
|
|
}
|
|
}
|
|
|
|
if ($mergedSpreadsheet->getSheetCount() === 0) {
|
|
throw new Exception("No sheets were successfully merged.");
|
|
}
|
|
|
|
self::$logger->logme('error',"Setting active sheet to index 0");
|
|
$mergedSpreadsheet->setActiveSheetIndex(0);
|
|
|
|
self::$logger->logme('error',"Preparing to save merged file to: $outputPath");
|
|
|
|
// Ensure output directory exists
|
|
$outputDir = dirname($outputPath);
|
|
if (!is_dir($outputDir)) {
|
|
mkdir($outputDir, 0777, true);
|
|
}
|
|
|
|
if (!is_writable($outputDir)) {
|
|
throw new Exception("Output directory is not writable: $outputDir");
|
|
}
|
|
|
|
if (ob_get_length()) {
|
|
ob_end_clean();
|
|
}
|
|
|
|
self::$logger->logme('error',"Creating Excel writer");
|
|
$writer = new Xlsx($mergedSpreadsheet);
|
|
|
|
self::$logger->logme('error',"Saving file...");
|
|
$writer->save($outputPath);
|
|
|
|
// Free up memory
|
|
$mergedSpreadsheet->disconnectWorksheets();
|
|
unset($mergedSpreadsheet);
|
|
|
|
self::$logger->logme('error',"Merge successful. File saved at: $outputPath");
|
|
return $outputPath;
|
|
|
|
} catch (Exception $e) {
|
|
self::$logger->logme('error',"Excel Merge Error: " . $e->getMessage());
|
|
self::$logger->logme('error',"Error trace: " . $e->getTraceAsString());
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
private static function copyWorksheetContent($sourceSheet, $targetSheet)
|
|
{
|
|
try {
|
|
self::$logger->logme('error',"Starting copyWorksheetContent");
|
|
|
|
// Get the highest row and column indexes
|
|
$highestRow = $sourceSheet->getHighestRow();
|
|
$highestColumn = $sourceSheet->getHighestColumn();
|
|
self::$logger->logme('error',"Copying data from range A1:{$highestColumn}{$highestRow}");
|
|
|
|
// Copy cell values only first for better performance
|
|
foreach ($sourceSheet->getRowIterator() as $row) {
|
|
$cellIterator = $row->getCellIterator();
|
|
$cellIterator->setIterateOnlyExistingCells(true);
|
|
foreach ($cellIterator as $cell) {
|
|
$column = $cell->getColumn();
|
|
$rowIndex = $cell->getRow();
|
|
$targetSheet->setCellValue($column . $rowIndex, $cell->getValue());
|
|
}
|
|
}
|
|
self::$logger->logme('error',"Basic cell values copied");
|
|
|
|
// Copy column dimensions
|
|
foreach ($sourceSheet->getColumnDimensions() as $columnId => $sourceDim) {
|
|
$targetSheet->getColumnDimension($columnId)
|
|
->setWidth($sourceDim->getWidth())
|
|
->setVisible($sourceDim->getVisible())
|
|
->setAutoSize($sourceDim->getAutoSize());
|
|
}
|
|
self::$logger->logme('error',"Column dimensions copied");
|
|
|
|
// Copy row dimensions
|
|
foreach ($sourceSheet->getRowDimensions() as $rowId => $sourceDim) {
|
|
$targetSheet->getRowDimension($rowId)
|
|
->setRowHeight($sourceDim->getRowHeight())
|
|
->setVisible($sourceDim->getVisible());
|
|
}
|
|
self::$logger->logme('error',"Row dimensions copied");
|
|
|
|
// Copy basic worksheet properties
|
|
$targetSheet->setShowGridlines($sourceSheet->getShowGridlines());
|
|
$targetSheet->setShowRowColHeaders($sourceSheet->getShowRowColHeaders());
|
|
self::$logger->logme('error',"Worksheet properties copied");
|
|
|
|
} catch (Exception $e) {
|
|
self::$logger->logme('error',"Error in copyWorksheetContent: " . $e->getMessage());
|
|
self::$logger->logme('error',"Error trace: " . $e->getTraceAsString());
|
|
throw $e;
|
|
}
|
|
}
|
|
} |