Passed
Push — master ( e3c27b...b957a6 )
by Adrien
17:10
created

AbstractExcel   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 286
Duplicated Lines 0 %

Test Coverage

Coverage 93.85%

Importance

Changes 0
Metric Value
wmc 24
eloc 126
c 0
b 0
f 0
dl 0
loc 286
ccs 61
cts 65
cp 0.9385
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A write() 0 19 5
A applyZebra() 0 11 2
A initialize() 0 12 1
A __construct() 0 9 1
A applyAutoFilter() 0 4 2
A getExtension() 0 3 1
A finalize() 0 11 1
B writeHeaders() 0 26 9
A writeTitle() 0 2 1
A writeFooter() 0 2 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Application\Service\Exporter;
6
7
use DateTimeImmutable;
8
use DateTimeInterface;
9
use Money\Currencies\ISOCurrencies;
10
use Money\Formatter\DecimalMoneyFormatter;
11
use Money\Money;
12
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
13
use PhpOffice\PhpSpreadsheet\Shared\Date;
14
use PhpOffice\PhpSpreadsheet\Spreadsheet;
15
use PhpOffice\PhpSpreadsheet\Style\Alignment;
16
use PhpOffice\PhpSpreadsheet\Style\Border;
17
use PhpOffice\PhpSpreadsheet\Style\Conditional;
18
use PhpOffice\PhpSpreadsheet\Style\Fill;
19
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
20
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
21
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
22
23
abstract class AbstractExcel extends AbstractExporter
24
{
25
    /**
26
     * Zebra striping of data rows.
27
     */
28
    protected bool $zebra = true;
29
30
    /**
31
     * Enable auto filter on the headers.
32
     */
33
    protected bool $autoFilter = true;
34
35
    /**
36
     * Column of current cell we are writing in.
37
     */
38
    protected int $column = 1;
39
40
    /**
41
     * Row of current cell we are writing in.
42
     */
43
    protected int $row = 1;
44
45
    /**
46
     * Index of first column containing data.
47
     */
48
    private int $firstDataColumn = 1;
49
50
    /**
51
     * Index of first row containing data.
52
     */
53
    private int $firstDataRow = 1;
54
55
    /**
56
     * Index of last column containing data.
57
     */
58
    protected int $lastDataColumn = 1;
59
60
    /**
61
     * Index of last row containing data.
62
     */
63
    protected int $lastDataRow = 1;
64
65
    private readonly Spreadsheet $workbook;
66
67
    protected Worksheet $sheet;
68
69
    protected DecimalMoneyFormatter $moneyFormatter;
70
71
    protected static array $dateFormat = [
72
        'numberFormat' => ['formatCode' => NumberFormat::FORMAT_DATE_XLSX14],
73
    ];
74
75
    protected static array $defaultFormat = [
76
        'font' => ['size' => 11],
77
        'alignment' => ['vertical' => Alignment::VERTICAL_CENTER],
78
    ];
79
80
    protected static array $titleFormat = [
81
        'font' => ['size' => 14],
82
    ];
83
84
    protected static array $headerFormat = [
85
        'font' => ['bold' => true, 'color' => ['argb' => 'FFEAEAEA']],
86
        'alignment' => ['wrapText' => true],
87
        'fill' => [
88
            'fillType' => Fill::FILL_SOLID,
89
            'startColor' => [
90
                'argb' => 'FF666666',
91
            ],
92
        ],
93
    ];
94
95
    protected static array $zebraFormat = [
96
        'fill' => [
97
            'fillType' => Fill::FILL_SOLID,
98
            'startColor' => [
99
                'argb' => 'FFE6E6E6',
100
            ],
101
            'endColor' => [
102
                'argb' => 'FFE6E6E6',
103
            ],
104
        ],
105
    ];
106
107
    protected static array $totalFormat = [
108
        'font' => ['bold' => true],
109
        'alignment' => ['wrapText' => true],
110
        'fill' => [
111
            'fillType' => Fill::FILL_SOLID,
112
            'startColor' => [
113
                'argb' => 'FFDDDDDD',
114
            ],
115
        ],
116
    ];
117
118
    protected static array $centerFormat = [
119
        'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
120
    ];
121
122
    protected static array $rightFormat = [
123
        'alignment' => ['horizontal' => Alignment::HORIZONTAL_RIGHT],
124
    ];
125
126
    protected static array $leftFormat = [
127
        'alignment' => ['horizontal' => Alignment::HORIZONTAL_LEFT],
128
    ];
129
130
    protected static array $wrapFormat = [
131
        'alignment' => ['wrapText' => true],
132
    ];
133
134
    protected static array $inactiveFormat = [
135
        'font' => ['color' => ['argb' => 'FFC0C0C0']],
136
    ];
137
138
    /**
139
     * Define border cells inside list of data (very light borders).
140
     */
141
    protected static array $bordersInside = [
142
        'borders' => [
143
            'inside' => [
144
                'borderStyle' => Border::BORDER_HAIR,
145
            ],
146
            'outline' => [
147
                'borderStyle' => Border::BORDER_MEDIUM,
148
            ],
149
        ],
150
    ];
151
152
    /**
153
     * Define border cells for total row (thick border).
154
     */
155
    protected static array $bordersTotal = [
156
        'borders' => [
157
            'outline' => [
158
                'borderStyle' => Border::BORDER_THICK,
159
            ],
160
        ],
161
    ];
162
163
    protected static array $bordersBottom = [
164
        'borders' => [
165
            'bottom' => [
166
                'borderStyle' => Border::BORDER_MEDIUM,
167
            ],
168
        ],
169
    ];
170
171
    protected static array $bordersBottomLight = [
172
        'borders' => [
173
            'bottom' => [
174
                'borderStyle' => Border::BORDER_HAIR,
175
            ],
176
        ],
177
    ];
178
179
    /**
180
     * Constructor.
181
     */
182 2
    public function __construct(string $hostname)
183
    {
184 2
        parent::__construct($hostname);
185 2
        $currencies = new ISOCurrencies();
186 2
        $this->moneyFormatter = new DecimalMoneyFormatter($currencies);
187 2
        $this->workbook = new Spreadsheet();
0 ignored issues
show
Bug introduced by
The property workbook is declared read-only in Application\Service\Exporter\AbstractExcel.
Loading history...
188 2
        $this->sheet = $this->workbook->getActiveSheet();
189
190 2
        $this->sheet->getDefaultRowDimension()->setRowHeight(20);
191
    }
192
193
    protected function writeTitle(): void
194
    {
195
    }
196
197
    protected function writeFooter(): void
198
    {
199
    }
200
201 2
    private function writeHeaders(): void
202
    {
203
        // Headers
204 2
        foreach ($this->getHeaders() as $header) {
205
            // Apply width
206 2
            if (isset($header['width'])) {
207 2
                $colDimension = $this->sheet->getColumnDimensionByColumn($this->column);
208 2
                if ($header['width'] === 'auto') {
209
                    $colDimension->setAutoSize(true);
210
                } else {
211 2
                    $colDimension->setWidth($header['width']);
212
                }
213
            }
214
            // Apply default format
215 2
            if (!isset($header['formats'])) {
216
                $header['formats'] = [self::$headerFormat];
217
            }
218
219 2
            if (isset($header['colspan']) && $header['colspan'] > 1) {
220 1
                $this->sheet->mergeCellsByColumnAndRow($this->column, $this->row, $this->column + (int) $header['colspan'] - 1, $this->row);
221
            }
222
223 2
            $this->write($header['label'], ...$header['formats']);
224
225 2
            if (isset($header['colspan']) && $header['colspan'] > 1) {
226 1
                $this->column += (int) $header['colspan'] - 1;
227
            }
228
        }
229
    }
230
231
    abstract protected function getHeaders(): array;
232
233
    /**
234
     * Write the value and style in the cell selected by `column` and `row` variables and move to next column.
235
     *
236
     * @param mixed $value
237
     * @param array[] ...$formats optional list of formats to be applied successively
238
     */
239 2
    protected function write($value, array ...$formats): void
240
    {
241 2
        $cell = $this->sheet->getCellByColumnAndRow($this->column++, $this->row);
242 2
        if ($formats) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $formats of type array<integer,array> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
243 2
            $style = $cell->getStyle();
244 2
            foreach ($formats as $format) {
245 2
                $style->applyFromArray($format);
246
            }
247
        }
248
249
        // Automatic conversion of date to Excel format
250 2
        if ($value instanceof DateTimeInterface) {
251
            $dateTime = new DateTimeImmutable($value->format('c'));
252
            $value = Date::PHPToExcel($dateTime);
253 2
        } elseif ($value instanceof Money) {
254 1
            $value = $this->moneyFormatter->format($value);
255
        }
256
257 2
        $cell->setValue($value);
258
    }
259
260 2
    protected function initialize(string $path): void
261
    {
262 2
        $this->workbook->getDefaultStyle()->applyFromArray(self::$defaultFormat);
263 2
        $this->row = 1;
264 2
        $this->column = 1;
265 2
        $this->writeTitle();
266 2
        $this->column = 1;
267 2
        $this->writeHeaders();
268 2
        ++$this->row;
269 2
        $this->column = 1;
270 2
        $this->firstDataRow = $this->row;
271 2
        $this->firstDataColumn = $this->column;
272
    }
273
274 2
    protected function finalize(string $path): void
275
    {
276 2
        $this->applyZebra();
277 2
        $this->applyAutoFilter();
278
279 2
        $this->column = 1;
280 2
        $this->row = $this->lastDataRow + 1;
281 2
        $this->writeFooter();
282
283 2
        $writer = new Xlsx($this->workbook);
284 2
        $writer->save($path);
285
    }
286
287 2
    protected function getExtension(): string
288
    {
289 2
        return 'xlsx';
290
    }
291
292 2
    private function applyZebra(): void
293
    {
294 2
        if (!$this->zebra) {
295 1
            return;
296
        }
297
298 1
        $zebraRange = Coordinate::stringFromColumnIndex($this->firstDataColumn) . $this->firstDataRow . ':' . Coordinate::stringFromColumnIndex($this->lastDataColumn) . $this->lastDataRow;
299 1
        $zebraCondition = new Conditional();
300 1
        $zebraCondition->setConditionType(Conditional::CONDITION_EXPRESSION)->setOperatorType(Conditional::OPERATOR_EQUAL)->addCondition('MOD(ROW(),2)=0');
301 1
        $zebraCondition->getStyle()->applyFromArray(self::$zebraFormat);
302 1
        $this->sheet->getStyle($zebraRange)->setConditionalStyles([$zebraCondition]);
303
    }
304
305 2
    private function applyAutoFilter(): void
306
    {
307 2
        if ($this->autoFilter) {
308 1
            $this->sheet->setAutoFilterByColumnAndRow($this->firstDataColumn, $this->firstDataRow - 1, $this->lastDataColumn, $this->lastDataRow);
309
        }
310
    }
311
}
312