1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Main file |
4
|
|
|
* |
5
|
|
|
* All functions for XML writing |
6
|
|
|
* |
7
|
|
|
* @category Controller |
8
|
|
|
* @package PurePhpXmlWriter |
9
|
|
|
* @author Jan Drda <[email protected]> |
10
|
|
|
* @copyright Jan Drda |
11
|
|
|
* @license https://opensource.org/licenses/MIT MIT |
12
|
|
|
* |
13
|
|
|
*/ |
14
|
|
|
|
15
|
|
|
namespace PurePhpXmlWriter; |
16
|
|
|
|
17
|
|
|
|
18
|
|
|
class PurePhpXmlWriter |
19
|
|
|
{ |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* XML file pointer |
23
|
|
|
* |
24
|
|
|
* @var resource |
25
|
|
|
*/ |
26
|
|
|
private $_filePointer; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* XML file name |
30
|
|
|
* |
31
|
|
|
* @var string |
32
|
|
|
*/ |
33
|
|
|
private $_fileName; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* File encoding |
37
|
|
|
* |
38
|
|
|
* @var string |
39
|
|
|
*/ |
40
|
|
|
private $_encoding; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Prefix of file name (used only with temporary files) |
44
|
|
|
* |
45
|
|
|
* @var string |
46
|
|
|
*/ |
47
|
|
|
private $_fileNamePrefix; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Write mode for file |
51
|
|
|
* |
52
|
|
|
* @var string |
53
|
|
|
* @see https://secure.php.net/manual/en/function.fopen.php |
54
|
|
|
*/ |
55
|
|
|
private $_writeMode; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Indicator if EOL should be used |
59
|
|
|
* |
60
|
|
|
* @var bool |
61
|
|
|
*/ |
62
|
|
|
private $_isCompressed; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* XML file header |
66
|
|
|
* |
67
|
|
|
* @var string |
68
|
|
|
*/ |
69
|
|
|
private $_fileHeader; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Indicates how deep child element is |
73
|
|
|
* |
74
|
|
|
* @var int |
75
|
|
|
*/ |
76
|
|
|
private $_elementDeep = 0; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Setter for file name |
80
|
|
|
* |
81
|
|
|
* @param $filename |
82
|
|
|
*/ |
83
|
|
|
public function setFileName($filename){ |
84
|
|
|
$this->_fileName = $filename; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Setter for encoding |
89
|
|
|
* |
90
|
|
|
* @param $encoding |
91
|
|
|
*/ |
92
|
|
|
public function setEncoding($encoding){ |
93
|
|
|
$this->_encoding = $encoding; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Setter for file name prefix (for generated names only) |
98
|
|
|
* |
99
|
|
|
* @param $fileNamePrefix |
100
|
|
|
*/ |
101
|
|
|
public function setFileNamePrefix($fileNamePrefix){ |
102
|
|
|
$this->_fileNamePrefix = $fileNamePrefix; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Setter for write mode |
107
|
|
|
* |
108
|
|
|
* @param $writeMode |
109
|
|
|
* @see https://secure.php.net/manual/en/function.fopen.php |
110
|
|
|
*/ |
111
|
|
|
public function setWriteMode($writeMode){ |
112
|
|
|
$this->_writeMode = $writeMode; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Setter for compressed format (EOL used or not) |
117
|
|
|
* |
118
|
|
|
* @param $compressed bool |
119
|
|
|
*/ |
120
|
|
|
public function setIsCompressed($isCompressed){ |
121
|
|
|
$this->_isCompressed = $isCompressed; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Set file header |
127
|
|
|
* |
128
|
|
|
* @param string $fileHeader |
129
|
|
|
*/ |
130
|
|
|
public function setHeader($fileHeader = null){ |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Set standard XML header if not defined |
134
|
|
|
*/ |
135
|
|
|
if($fileHeader === null){ |
136
|
|
|
$this->_fileHeader = '<?xml version="1.0" encoding="' . $this->_encoding .'"?>'; |
137
|
|
|
} |
138
|
|
|
else{ |
139
|
|
|
$this->_fileHeader = $fileHeader; |
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Getter for file name |
145
|
|
|
* |
146
|
|
|
* @return string |
147
|
|
|
*/ |
148
|
|
|
public function getFileName(){ |
149
|
|
|
return $this->_fileName; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Constructor |
154
|
|
|
* |
155
|
|
|
* @param string $fileName |
156
|
|
|
* @param bool $autoOpenFile Indicates if file should be opened immediately |
157
|
|
|
* @param string $encoding |
158
|
|
|
* @param string $fileNamePrefix |
159
|
|
|
* @param string $writeMode |
160
|
|
|
* @param bool $compressed Indicates if EOL should be used or not |
161
|
|
|
*/ |
162
|
|
|
public function __construct($fileName = null, $autoOpenFile = false, $encoding = 'utf-8', $fileNamePrefix = 'ppxw', |
163
|
|
|
$writeMode = 'w', $isCompressed = false) |
164
|
|
|
{ |
165
|
|
|
/** |
166
|
|
|
* Setters |
167
|
|
|
*/ |
168
|
|
|
$this->setEncoding($encoding); |
169
|
|
|
$this->setFileNamePrefix($fileNamePrefix); |
170
|
|
|
$this->setWriteMode($writeMode); |
171
|
|
|
$this->setIsCompressed($isCompressed); |
172
|
|
|
$this->setHeader(); |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Generate filename if needed |
176
|
|
|
*/ |
177
|
|
|
if($fileName === null){ |
178
|
|
|
$this->setFileName(tempnam(sys_get_temp_dir(), $this->_fileNamePrefix)); |
179
|
|
|
} |
180
|
|
|
else{ |
181
|
|
|
$this->setFileName($fileName); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Autoopen file if needed |
186
|
|
|
*/ |
187
|
|
|
if($autoOpenFile === true) { |
188
|
|
|
$this->openFile(); |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Destructor |
194
|
|
|
* |
195
|
|
|
* Explicitly close the file to make a sure buffer has been written |
196
|
|
|
*/ |
197
|
|
|
public function __destruct() |
198
|
|
|
{ |
199
|
|
|
$this->closeFile(); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Open XML file |
204
|
|
|
*/ |
205
|
|
|
public function openFile($autoAddHeader = true){ |
206
|
|
|
try { |
207
|
|
|
$this->_filePointer = fopen($this->_fileName, $this->_writeMode); |
|
|
|
|
208
|
|
|
} |
209
|
|
|
catch (\Exception $e){ |
210
|
|
|
die('File cannot be opened: ' . $e->getMessage()); |
|
|
|
|
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
if($autoAddHeader === true){ |
214
|
|
|
$this->writeHeader(); |
215
|
|
|
} |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Close XML file if is opened |
220
|
|
|
*/ |
221
|
|
|
public function closeFile(){ |
222
|
|
|
if(is_resource($this->_filePointer) === true){ |
223
|
|
|
fclose($this->_filePointer); |
224
|
|
|
} |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Write the EOL if file is not compressed |
229
|
|
|
*/ |
230
|
|
|
private function _writeEol(){ |
231
|
|
|
if($this->_isCompressed === false){ |
232
|
|
|
fwrite($this->_filePointer, PHP_EOL); |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* Write the Tabs if file is not compressed |
238
|
|
|
*/ |
239
|
|
|
private function _writeTabs(){ |
240
|
|
|
if($this->_isCompressed === false){ |
241
|
|
|
for($a = 0; $a < $this->_elementDeep; $a++) { |
242
|
|
|
fwrite($this->_filePointer, "\t"); |
243
|
|
|
} |
244
|
|
|
} |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* Write string to file |
249
|
|
|
* |
250
|
|
|
* @param string $string |
251
|
|
|
* @param bool $useTabs Indicates if use tabs |
252
|
|
|
* @param bool $useEol Indicates if use EOL |
253
|
|
|
*/ |
254
|
|
|
private function _writeString($string = '', $useTabs = true, $useEol = true){ |
255
|
|
|
if($useTabs === true){ |
256
|
|
|
$this->_writeTabs(); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
fwrite($this->_filePointer, $string); |
260
|
|
|
|
261
|
|
|
if($useEol === true){ |
262
|
|
|
$this->_writeEol(); |
263
|
|
|
} |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* Determines if an array is associative. |
268
|
|
|
* @param array $array |
269
|
|
|
* @return bool |
270
|
|
|
*/ |
271
|
|
|
function isAssoc(array $array) |
|
|
|
|
272
|
|
|
{ |
273
|
|
|
$keys = array_keys($array); |
274
|
|
|
|
275
|
|
|
return array_keys($keys) !== $keys; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Concatenates the attribute - value pairs into a string |
280
|
|
|
* |
281
|
|
|
* @param array $attr |
282
|
|
|
*/ |
283
|
|
|
private function _getAttributes ($attr = null) |
284
|
|
|
{ |
285
|
|
|
$str = ''; |
286
|
|
|
|
287
|
|
|
if(!is_null($attr) && is_array($attr)) { |
288
|
|
|
// check if is an associative array |
289
|
|
|
if($this->isAssoc($attr)) { |
290
|
|
|
foreach ($attr as $name => $value) { |
291
|
|
|
$str .= ' ' . $name . '="' . str_replace('"', "'", $value) . '"'; |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
return $str; |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Write file header |
301
|
|
|
*/ |
302
|
|
|
public function writeHeader(){ |
303
|
|
|
$this->_writeString($this->_fileHeader, false, true); |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
|
|
* Open XML element |
308
|
|
|
* |
309
|
|
|
* @param $tag |
310
|
|
|
* @param bool $expectedChildren Indicates if children are expected |
311
|
|
|
* @param array $attr associative array with attribute - value pairs |
312
|
|
|
*/ |
313
|
|
|
public function openXMLElement($tag, $expectedChildren = true, $attr = null) |
314
|
|
|
{ |
315
|
|
|
$this->_writeString('<' . $tag . $this->_getAttributes($attr) . '>', true, $expectedChildren); |
316
|
|
|
$this->_elementDeep++; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* Close XML element |
321
|
|
|
* |
322
|
|
|
* @param $tag |
323
|
|
|
* @param bool $expectedChildren Indicates if children are expected |
324
|
|
|
*/ |
325
|
|
|
public function closeXMLElement($tag, $expectedChildren = true) |
326
|
|
|
{ |
327
|
|
|
$this->_elementDeep--; |
328
|
|
|
$this->_writeString('</' . $tag . '>', $expectedChildren,true); |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
/** |
332
|
|
|
* Write blank XML element |
333
|
|
|
* |
334
|
|
|
* @param $tag |
335
|
|
|
* @param array $attr associative array with attribute - value pairs |
336
|
|
|
*/ |
337
|
|
|
public function blankXMLElement($tag, $attr = null) |
338
|
|
|
{ |
339
|
|
|
$this->_writeString('<' . $tag . $this->_getAttributes($attr) . '/>', true,true); |
340
|
|
|
|
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* Save element with value |
345
|
|
|
* |
346
|
|
|
* @param $tag |
347
|
|
|
* @param $value |
348
|
|
|
* @param int $decimals Only using with number |
349
|
|
|
* @param bool $useCdata |
350
|
|
|
* @param bool $forceCdata |
351
|
|
|
*/ |
352
|
|
|
public function saveElementWithValue($tag, $value, $decimals = 0, $useCdata = true, $forceCdata = false){ |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Empty element |
356
|
|
|
*/ |
357
|
|
|
if (is_numeric($value) === false && strlen(trim($value)) < 1) { |
358
|
|
|
$this->blankXMLElement($tag); |
359
|
|
|
} |
360
|
|
|
else { |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* Non-empty element |
364
|
|
|
*/ |
365
|
|
|
if ($useCdata === true && (is_numeric($value) === false || $forceCdata === true)) { |
366
|
|
|
$completeValue = '<![CDATA[' . $value . ']]>'; |
367
|
|
|
} else { |
368
|
|
|
if (is_numeric($value) === true) { |
369
|
|
|
$completeValue = round($value, $decimals); |
370
|
|
|
} else { |
371
|
|
|
$completeValue = $value; |
372
|
|
|
} |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
$this->openXMLElement($tag, false); |
376
|
|
|
$this->_writeString($completeValue, false, false); |
377
|
|
|
$this->closeXMLElement($tag, false); |
378
|
|
|
} |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
} |
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.