templatePath = $templatePath; $fullPath = storage_path('documents/' . $templatePath); if (!file_exists($fullPath)) { throw new Exception("Excel template not found: {$fullPath}"); } Log::debug('Loading Excel template', ['path' => $fullPath]); $this->spreadsheet = IOFactory::load($fullPath); $this->sheet = $this->spreadsheet->getActiveSheet(); Log::debug('Excel template loaded', [ 'type' => get_class($this->spreadsheet), 'sheet_name' => $this->sheet->getTitle() ]); return $this; } /** * Get spreadsheet instance */ public function getSpreadsheet(): Spreadsheet { return $this->spreadsheet; } /** * Get active sheet */ public function getSheet(): Worksheet { return $this->sheet; } /** * Replace placeholders in Excel sheet */ public function replacePlaceholders(array $replacements): self { Log::debug('Replacing placeholders in Excel', [ 'count' => count($replacements) ]); foreach ($this->sheet->getRowIterator() as $row) { foreach ($row->getCellIterator() as $cell) { $cellValue = $cell->getValue(); // Handle RichText objects if ($cellValue instanceof \PhpOffice\PhpSpreadsheet\RichText\RichText) { $cellValue = $cellValue->getPlainText(); } // Convert to string for processing $cellValueStr = (string)$cellValue; if (!empty($cellValueStr)) { $originalValue = $cellValueStr; foreach ($replacements as $placeholder => $replacement) { if (strpos($cellValueStr, $placeholder) !== false) { $cellValueStr = str_replace($placeholder, $replacement, $cellValueStr); } } // Only update if value changed if ($cellValueStr !== $originalValue) { $cell->setValue($cellValueStr); Log::debug('Placeholder replaced', [ 'cell' => $cell->getCoordinate(), 'original' => $originalValue, 'new' => $cellValueStr ]); } } } } return $this; } /** * Save Excel file */ public function save(string $outputPath, bool $override = true): string { $fullPath = storage_path('documents/' . $outputPath); // Create directory if not exists $directory = dirname($fullPath); if (!file_exists($directory)) { mkdir($directory, 0777, true); Log::debug('Created directory', ['path' => $directory]); } // Check override setting if (!$override && file_exists($fullPath)) { $fullPath = $this->generateUniqueFilename($fullPath); $outputPath = str_replace(storage_path('documents/'), '', $fullPath); Log::debug('File exists and override is false, using unique name', [ 'path' => $fullPath ]); } // Check write permissions if (!is_writable($directory)) { throw new Exception("Directory is not writable: {$directory}"); } Log::debug('Saving Excel file', [ 'path' => $fullPath, 'memory_usage' => round(memory_get_usage(true) / 1024 / 1024, 2) . ' MB' ]); $writer = IOFactory::createWriter($this->spreadsheet, 'Xlsx'); $writer->save($fullPath); Log::debug('Excel file saved successfully', [ 'size' => filesize($fullPath) . ' bytes' ]); return $outputPath; } /** * Remove template row from sheet */ public function removeTemplateRow(int $templateRow): self { $this->sheet->removeRow($templateRow); Log::debug('Template row removed', ['row' => $templateRow]); return $this; } /** * Generate unique filename if file exists */ private function generateUniqueFilename(string $filePath): string { $counter = 1; $pathInfo = pathinfo($filePath); do { $newFileName = $pathInfo['dirname'] . '/' . $pathInfo['filename'] . '_' . $counter . '.' . $pathInfo['extension']; $counter++; } while (file_exists($newFileName)); return $newFileName; } /** * Cleanup resources */ public function cleanup(): void { if (isset($this->spreadsheet)) { $this->spreadsheet->disconnectWorksheets(); unset($this->spreadsheet); gc_collect_cycles(); Log::debug('Excel resources cleaned up'); } } /** * Destructor */ public function __destruct() { $this->cleanup(); } }