14 KiB
ExcelRowHandler Service Guide
Overview
The ExcelRowHandler service is a modern, object-oriented replacement for the addRowInTable helper function. It provides a clean and maintainable way to add formatted rows to Excel sheets in the Register Creator system.
Location
- Service Class:
App\Services\RegisterCreator\ExcelRowHandler - Integration:
App\Services\RegisterCreator\DocumentProcessors\AbstractDocumentProcessor
Features
- ✅ Duplicate Detection: Automatically prevents duplicate report entries
- ✅ File Management: Handles PDF and XLSX file copying with smart overwrite logic
- ✅ Page Counting: Automatically counts PDF pages using pdftk
- ✅ Excel Formatting: Preserves styles, formulas, merged cells, and row heights
- ✅ Comprehensive Logging: Provides detailed logging with emoji indicators
- ✅ Statistics Tracking: Tracks file types and successful operations
- ✅ Type Safety: Full PHP type hints for better IDE support and error prevention
Architecture
Service Class: ExcelRowHandler
The main service class handles all row addition operations:
use App\Services\RegisterCreator\ExcelRowHandler;
$handler = new ExcelRowHandler();
$newRow = $handler->addRow(
$search, // Array of file paths to search
$selectDocument, // Document information array
$fullFolder, // Target folder path
$lineNumber, // Line identifier
$documentDate, // Document date
$rowNo, // Row sequence number
$sheet, // PhpSpreadsheet Worksheet object
$currentRow, // Current row position
$override // Whether to overwrite existing files
);
Integration with AbstractDocumentProcessor
The service is integrated into AbstractDocumentProcessor for easy use in all document processors:
protected function addRowToExcel(
array $search,
string $lineNumber,
string $documentDate,
int $rowNo
): int
Usage in Document Processors
Basic Usage Example
Here's how to use the service in a document processor:
class MyDocumentProcessor extends AbstractDocumentProcessor
{
public function process(
array $weldLogData,
array $document,
Worksheet $sheet,
int &$currentRow,
array $settings
): int {
// Initialize processor (this creates the ExcelRowHandler instance)
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
// Get line identifier
$lineIdentifier = $weldLogData[$this->registerColumnBased];
// Search for files
$search = $this->searchFiles("{$document['path']}/*{$lineIdentifier}*.pdf");
// Get document date
$placeholderReplacer = new \App\Services\RegisterCreator\PlaceholderReplacer();
$documentDate = $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased);
// Add row to Excel using the service
$newRow = $this->addRowToExcel(
$search,
$lineIdentifier,
$documentDate,
1 // Row number in sequence
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
return $currentRow;
}
}
Real Example: DrawingsProcessor
class DrawingsProcessor extends AbstractDocumentProcessor
{
public function process(
array $weldLogData,
array $document,
Worksheet $sheet,
int &$currentRow,
array $settings
): int {
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
$this->log("Processing Drawings");
$lineIdentifier = $weldLogData[$this->registerColumnBased];
$normalized = $this->normalizeSearchTerm($lineIdentifier);
$search = $this->searchFiles("{$document['path']}/*{$normalized}*.pdf");
$placeholderReplacer = new \App\Services\RegisterCreator\PlaceholderReplacer();
$documentDate = $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased);
// Use the new ExcelRowHandler service method
$newRow = $this->addRowToExcel(
$search,
$lineIdentifier,
$documentDate,
1
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
return $currentRow;
}
}
Method Parameters
addRow() Method
| Parameter | Type | Description |
|---|---|---|
$search |
array |
Array of file paths to search for |
$selectDocument |
array |
Document information containing title, path, type, order, etc. |
$fullFolder |
string |
Target folder path (e.g., "storage/documents/project/line/") |
$lineNumber |
string |
Line number or identifier |
$documentDate |
string |
Document date |
$rowNo |
int |
Row sequence number |
$sheet |
Worksheet |
PhpSpreadsheet Worksheet object (passed by reference) |
$currentRow |
int |
Current row position in Excel |
$override |
bool |
Whether to overwrite existing files (default: false) |
Returns: int - Next row position
addRowToExcel() Helper Method
| Parameter | Type | Description |
|---|---|---|
$search |
array |
Array of file paths to search for |
$lineNumber |
string |
Line number or identifier |
$documentDate |
string |
Document date |
$rowNo |
int |
Row sequence number |
Returns: int - Next row position
Document Information Structure
The $selectDocument array should contain:
[
'title1' => 'Main title',
'title2' => 'NDT Report Number',
'title3' => 'PDF Document Title',
'title4' => 'NDT Register Title',
'path' => 'storage/documents/...',
'type' => 'drawing|template|...',
'order' => 0, // Display order
'file_name' => 'Custom file name (optional)',
'incoming_control_description' => 'Description (optional)'
]
Excel Template Placeholders
The service replaces the following placeholders in the Excel template:
| Placeholder | Description |
|---|---|
{rowNo} |
Row sequence number |
{type_order_no} |
Document type order number (e.g., 1 for QA, 2 for WDB) |
{sub_number} |
Sub-number within document type (1, 2, 3...) |
{full_number} |
Combined number in format "type_order_no.sub_number" (e.g., "1.1", "1.2", "3.1") |
{rowTitle} |
Document title (processed) |
{documentReportNumber} |
Line number/identifier |
{documentDate} |
Formatted document date |
{constructor} |
Contractor name |
{pageCount} |
Number of pages in PDF |
{pageIndex} |
Page range (e.g., "1-3") |
{title1} |
Document title 1 |
{ndt_report_no} |
NDT report number (title2) |
{pdf_document_title} |
PDF document title (title3) |
{ndt_register_title} |
NDT register title (title4) |
Log Format
The service generates structured logs with emoji indicators:
📋 1. Document Title - LINE001 | 1 file(s)
-- filename.pdf
📁 File: copied
📋 2. Another Document - LINE002 | 2 file(s)
-- document1.pdf
-- document2.pdf
📁 File: updated
📊 XLSX: copied
❌ 3. Missing Document - LINE003 | NO FILES FOUND
Search paths:
-- expected_filename.pdf
🔄 DUPLICATE: Report Title - LINE001
Log Indicators
- 📋 = Document processed
- ✅ = Success
- ❌ = Error or not found
- 🔄 = Duplicate (skipped)
- 📁 = PDF file operation
- 📊 = XLSX file operation
- ❓ = Warning or missing optional file
File Copy Status
copied= New file copiedupdated= File content changed, updatedoverwritten= File forcefully overwritten (override=true)same content, skipped= File exists with same content, no action needed
File Management
PDF Files
All PDF files are automatically:
- Validated for existence
- Page counted using pdftk
- Copied to target location
- Named according to order and title
XLSX Files (Templates)
For documents with type="template":
- Searches for corresponding .xlsx file
- Copies alongside PDF if found
- Logs result (found/not found)
Smart Copy Logic
The service uses MD5 hash comparison to avoid unnecessary file operations:
- If file exists and content is identical → skip
- If file exists and content differs → update
- If override flag is true → always overwrite
Statistics
The service tracks statistics that can be accessed statically:
// Get file type statistics
$stats = ExcelRowHandler::getFileTypeStats();
// Returns: ['pdf' => 150, 'xlsx' => 25, ...]
// Get successful operations count
$count = ExcelRowHandler::getSuccessfulOperations();
// Returns: 150
// Reset statistics
ExcelRowHandler::resetStatistics();
Duplicate Prevention
The service automatically prevents duplicate entries based on:
- Document title (title2)
- Line number
If a duplicate is detected:
- Row is skipped
- Duplicate log is written
- Original row position is returned (no increment)
Error Handling
The service handles errors gracefully:
try {
$newRow = $this->addRowToExcel($search, $lineNumber, $documentDate, 1);
} catch (\Throwable $th) {
// Error is logged automatically
// Exception is re-thrown for upstream handling
}
Error Logging
All errors are logged to:
- Laravel log (with context)
- Project-specific log.txt file
- Console output
Cache Dependencies
The service relies on cached values:
| Cache Key | Description |
|---|---|
rc_contractor |
Contractor name |
rc_firstPage |
First page number |
rc_lastPage |
Last page number |
rc_template_row |
Template row number |
These are managed by RegisterCreatorService.
Migration from addRowInTable Helper
Old Way (Helper Function)
$newRow = addRowInTable(
$search,
$document,
$this->getFullFolder(),
$lineIdentifier,
$documentDate,
1,
$this->sheet,
$currentRow,
$this->settings['override'] ?? true
);
New Way (Service Method)
$newRow = $this->addRowToExcel(
$search,
$lineIdentifier,
$documentDate,
1
);
Benefits of Migration
- Cleaner Code: Less parameters, more intuitive
- Better Testability: Service can be mocked and tested
- Type Safety: Full type hints
- Object-Oriented: Follows SOLID principles
- Easier Maintenance: All logic in one place
- Better IDE Support: Autocomplete and hints
Best Practices
1. Always Initialize
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
This creates the ExcelRowHandler instance and sets up necessary properties.
2. Update Current Row
$newRow = $this->addRowToExcel(...);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
Always check if row was actually added before updating position.
3. Handle Search Results
$search = $this->searchFiles("{$document['path']}/*{$normalized}*.pdf");
if (empty($search)) {
$this->log("No files found for pattern", "warning");
return $currentRow;
}
Check if files exist before calling addRowToExcel.
4. Use Normalized Search Terms
$normalized = $this->normalizeSearchTerm($lineIdentifier);
$search = $this->searchFiles("{$document['path']}/*{$normalized}*.pdf");
Normalize search terms for better file matching.
5. Log Processing Steps
$this->log("Processing Drawings");
$newRow = $this->addRowToExcel(...);
$this->log("Drawings processed successfully");
Use the log() method for consistent logging.
Troubleshooting
Files Not Found
- Check file paths in search array
- Verify storage permissions
- Check if files exist in expected location
- Review log.txt for exact search paths
Duplicate Detection Issues
- Check title2 field consistency
- Verify line number format
- Reset statistics if testing:
ExcelRowHandler::resetStatistics()
Page Count Issues
- Ensure pdftk is installed:
which pdftk - Check LANG environment variable
- Verify PDF file is not corrupted
Excel Formatting Issues
- Verify template row is set correctly
- Check placeholder names match exactly
- Ensure template has proper merged cells
Performance Considerations
- File Operations: Uses Laravel Storage for efficiency
- MD5 Comparison: Only calculates when necessary
- Static Properties: Shared across instances for statistics
- Lazy Loading: PDFs only opened when needed
Testing
Example test structure:
use Tests\TestCase;
use App\Services\RegisterCreator\ExcelRowHandler;
class ExcelRowHandlerTest extends TestCase
{
public function test_adds_row_successfully()
{
$handler = new ExcelRowHandler();
// Setup test data
$search = ['storage/documents/test.pdf'];
$document = ['title2' => 'Test', 'order' => 0, ...];
// Execute
$newRow = $handler->addRow(
$search,
$document,
'storage/documents/test/',
'LINE001',
'2024-01-01',
1,
$this->mockSheet,
10,
false
);
// Assert
$this->assertEquals(11, $newRow);
}
}
Related Documentation
Version History
- v1.0 (2024-10): Initial release
- Migrated from addRowInTable helper function
- Added full type hints
- Improved error handling
- Added statistics tracking