1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace OneSheet; |
4
|
|
|
|
5
|
|
|
use OneSheet\Xml\RowXml; |
6
|
|
|
use OneSheet\Style\Style; |
7
|
|
|
use OneSheet\Xml\SheetXml; |
8
|
|
|
use OneSheet\Size\SizeCalculator; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Class Sheet |
12
|
|
|
* |
13
|
|
|
* @package OneSheet |
14
|
|
|
*/ |
15
|
|
|
class Sheet |
16
|
|
|
{ |
17
|
|
|
/** |
18
|
|
|
* @var CellBuilder |
19
|
|
|
*/ |
20
|
|
|
private $cellBuilder; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* @var SizeCalculator |
24
|
|
|
*/ |
25
|
|
|
private $sizeCalculator; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var bool |
29
|
|
|
*/ |
30
|
|
|
private $useCellAutosizing = false; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var int |
34
|
|
|
*/ |
35
|
|
|
private $freezePaneCellId; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Track next row index. |
39
|
|
|
* |
40
|
|
|
* @var int |
41
|
|
|
*/ |
42
|
|
|
private $rowIndex = 1; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Holds width/column count of the widest row. |
46
|
|
|
* |
47
|
|
|
* @var int |
48
|
|
|
*/ |
49
|
|
|
private $maxColumnCount; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Holds widths of the widest cells for column sizing. |
53
|
|
|
* |
54
|
|
|
* @var array |
55
|
|
|
*/ |
56
|
|
|
private $columnWidths = array(); |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Holds minimum allowed column width. |
60
|
|
|
* |
61
|
|
|
* @var float|int |
62
|
|
|
*/ |
63
|
|
|
private $minColumnWidth = 0; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Holds maximum allowed column width. 254.86 appears |
67
|
|
|
* to be the default maximum width. |
68
|
|
|
* |
69
|
|
|
* @var float|int |
70
|
|
|
*/ |
71
|
|
|
private $maxColumnWidth = 254.86; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Sheet constructor. |
75
|
|
|
* |
76
|
|
|
* @param CellBuilder $cellBuilder |
77
|
|
|
* @param SizeCalculator $sizeCalculator |
78
|
|
|
*/ |
79
|
15 |
|
public function __construct(CellBuilder $cellBuilder, SizeCalculator $sizeCalculator) |
80
|
|
|
{ |
81
|
15 |
|
$this->cellBuilder = $cellBuilder; |
82
|
15 |
|
$this->sizeCalculator = $sizeCalculator; |
83
|
15 |
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Enable cell autosizing (~30-100% performance hit!). |
87
|
|
|
*/ |
88
|
5 |
|
public function enableCellAutosizing() |
89
|
|
|
{ |
90
|
5 |
|
$this->useCellAutosizing = true; |
91
|
5 |
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Disable cell autosizing (default). |
95
|
|
|
*/ |
96
|
2 |
|
public function disableCellAutosizing() |
97
|
|
|
{ |
98
|
2 |
|
$this->useCellAutosizing = false; |
99
|
2 |
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* @param int $cellId |
103
|
|
|
*/ |
104
|
2 |
|
public function setFreezePaneCellId($cellId) |
105
|
|
|
{ |
106
|
2 |
|
$this->freezePaneCellId = $cellId; |
107
|
2 |
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Set custom column widths with 0 representing the first column. |
111
|
|
|
* |
112
|
|
|
* @param array $columnWidths |
113
|
|
|
* @throws \InvalidArgumentException |
114
|
|
|
*/ |
115
|
4 |
|
public function setFixedColumnWidths(array $columnWidths) |
116
|
|
|
{ |
117
|
4 |
|
if ($columnWidths !== array_filter($columnWidths, 'is_numeric') |
118
|
4 |
|
|| array_keys($columnWidths) !== array_filter(array_keys($columnWidths), 'is_int') |
119
|
4 |
|
) { |
120
|
2 |
|
throw new \InvalidArgumentException('Array must contain integer keys and numeric values only!'); |
121
|
|
|
} |
122
|
|
|
|
123
|
2 |
|
$this->columnWidths = $columnWidths + $this->columnWidths; |
124
|
2 |
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Set lower and/or upper limits for column widths. |
128
|
|
|
* |
129
|
|
|
* @param float|null $minWidth |
130
|
|
|
* @param float|null $maxWidth |
131
|
|
|
*/ |
132
|
1 |
|
public function setColumnWidthLimits($minWidth = null, $maxWidth = null) |
133
|
|
|
{ |
134
|
1 |
|
$this->minColumnWidth = is_numeric($minWidth) && $minWidth >= 0 ? $minWidth : 0; |
|
|
|
|
135
|
1 |
|
$this->maxColumnWidth = is_numeric($maxWidth) && $maxWidth < 255.86 ? $maxWidth : 255.86; |
|
|
|
|
136
|
1 |
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Return array containing all column widths, limited to min or max |
140
|
|
|
* column width, if one or both of them are set. |
141
|
|
|
* |
142
|
|
|
* @return array |
143
|
|
|
*/ |
144
|
5 |
|
public function getColumnWidths() |
145
|
|
|
{ |
146
|
5 |
|
foreach ($this->columnWidths as $column => $width) { |
147
|
4 |
|
if ($this->maxColumnWidth && $width > $this->maxColumnWidth) { |
148
|
|
|
$this->columnWidths[$column] = $this->maxColumnWidth; |
149
|
4 |
|
} elseif ($this->minColumnWidth && $width < $this->minColumnWidth) { |
150
|
|
|
$this->columnWidths[$column] = $this->minColumnWidth; |
151
|
|
|
} |
152
|
5 |
|
} |
153
|
|
|
|
154
|
5 |
|
return $this->columnWidths; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* Add single row and style to sheet. |
159
|
|
|
* |
160
|
|
|
* @param array $row |
161
|
|
|
* @param Style $style |
162
|
|
|
* |
163
|
|
|
* @return string |
164
|
|
|
*/ |
165
|
5 |
|
public function addRow(array $row, Style $style) |
166
|
|
|
{ |
167
|
5 |
|
$columnCount = count($row); |
168
|
5 |
|
$this->updateMaxColumnCount($columnCount); |
169
|
|
|
|
170
|
5 |
|
$this->sizeCalculator->setFont($style->getFont()); |
171
|
5 |
|
$cellXml = $this->getCellXml($row, $style); |
172
|
|
|
|
173
|
5 |
|
return $this->getRowXml($style, $columnCount, $cellXml); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Track column count for dimensions xml (e.g. A1:K123). |
178
|
|
|
* |
179
|
|
|
* @param int $columnCount |
180
|
|
|
*/ |
181
|
5 |
|
private function updateMaxColumnCount($columnCount) |
182
|
|
|
{ |
183
|
5 |
|
if ($this->maxColumnCount < $columnCount) { |
184
|
5 |
|
$this->maxColumnCount = $columnCount; |
185
|
5 |
|
} |
186
|
5 |
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Build and return xml string for single row. |
190
|
|
|
* |
191
|
|
|
* @param Style $style |
192
|
|
|
* @param int $columnCount |
193
|
|
|
* @param string $cellXml |
194
|
|
|
* @return string |
195
|
|
|
*/ |
196
|
5 |
|
private function getRowXml(Style $style, $columnCount, $cellXml) |
197
|
|
|
{ |
198
|
5 |
|
if ($style->getFont()->getSize() < 14) { |
199
|
5 |
|
return sprintf(RowXml::DEFAULT_XML, $this->rowIndex++, $columnCount, $cellXml); |
200
|
|
|
} |
201
|
|
|
|
202
|
1 |
|
return sprintf(RowXml::HEIGHT_XML, $this->rowIndex++, $columnCount, |
203
|
1 |
|
$this->sizeCalculator->getRowHeight(), $cellXml); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Build and return xml string for single cell and update cell widths. |
208
|
|
|
* |
209
|
|
|
* @param array $row |
210
|
|
|
* @param Style $style |
211
|
|
|
* @return string |
212
|
|
|
*/ |
213
|
5 |
|
private function getCellXml(array $row, Style $style) |
214
|
|
|
{ |
215
|
5 |
|
$cellXml = ''; |
216
|
5 |
|
foreach (array_values($row) as $cellIndex => $cellValue) { |
217
|
5 |
|
if (0 < strlen($cellValue)) { |
218
|
5 |
|
$this->updateColumnWidths($cellValue, $cellIndex, $style); |
219
|
5 |
|
$cellXml .= $this->cellBuilder->build( |
220
|
5 |
|
$this->rowIndex, $cellIndex, $cellValue, $style->getId() |
221
|
5 |
|
); |
222
|
5 |
|
} |
223
|
5 |
|
} |
224
|
|
|
|
225
|
5 |
|
return $cellXml; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* Track cell width for column width sizing if its enabled. |
230
|
|
|
* |
231
|
|
|
* @param mixed $value |
232
|
|
|
* @param int $cellIndex |
233
|
|
|
* @param Style $style |
234
|
|
|
*/ |
235
|
5 |
|
private function updateColumnWidths($value, $cellIndex, Style $style) |
236
|
|
|
{ |
237
|
5 |
|
if ($this->useCellAutosizing) { |
238
|
3 |
|
$cellWidth = $this->sizeCalculator->getCellWidth($value, $style->getFont()); |
239
|
3 |
|
if (!isset($this->columnWidths[$cellIndex]) |
240
|
3 |
|
|| $this->columnWidths[$cellIndex] < $cellWidth |
241
|
3 |
|
) { |
242
|
3 |
|
$this->columnWidths[$cellIndex] = $cellWidth; |
243
|
3 |
|
} |
244
|
3 |
|
} |
245
|
5 |
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* Return xml string for dimension. |
249
|
|
|
* |
250
|
|
|
* @return string |
251
|
|
|
*/ |
252
|
2 |
|
public function getDimensionXml() |
253
|
|
|
{ |
254
|
2 |
|
return sprintf(SheetXml::DIMENSION_XML, |
255
|
2 |
|
$this->cellBuilder->getCellId($this->maxColumnCount - 1, $this->rowIndex - 1) |
256
|
2 |
|
); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Return sheetViews xml containing the freeze pane. |
261
|
|
|
* <sheetViews> Currently leads to random excel crashes :-/ |
262
|
|
|
* |
263
|
|
|
* @return string |
264
|
|
|
*/ |
265
|
3 |
|
public function getSheetViewsXml() |
266
|
|
|
{ |
267
|
3 |
|
if (1 !== preg_match('~^[A-Z]+(\d+)$~', $this->freezePaneCellId, $m)) { |
268
|
3 |
|
return ''; |
269
|
|
|
} |
270
|
|
|
|
271
|
1 |
|
return sprintf(SheetXml::SHEETVIEWS_XML, array_pop($m) - 1, $this->freezePaneCellId); |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.