-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPhpGen.php
513 lines (459 loc) · 12.5 KB
/
PhpGen.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
<?php
namespace Aza\Components\PhpGen;
use ReflectionFunction;
use SplFileObject;
use Traversable;
/**
* PHP code generation
*
* @uses reflection
* @uses spl
*
* @project Anizoptera CMF
* @package system.phpgen
* @author Amal Samally <amal.samally at gmail.com>
* @license MIT
*/
class PhpGen
{
/**
* Length of one tab in spaces
*/
public $tabLength = 4;
/**
* Use spaces instead of tabs for indentation
*/
public $useSpaces = false;
/**
* Mix tabs with spaces in the end of indentation
*/
public $mixSpaces = true;
/**
* Mix tabs with spaces in the end of indentation
*/
public $spacesAfterKey = true;
/**
* Output string variables as one line.
* Converts \n and \t symbols to escaped characters.
*
* By default only other control and non-visible
* chars are escaped.
*/
public $oneLineStrings = false;
/**
* Escape all characters in strings.
*/
public $binaryStrings = false;
/**
* Automatic recognition of binary strings
*/
public $binaryAutoCheck = true;
/**
* Output array keys for serial arrays.
*/
public $outputSerialKeys = false;
/**
* Use php 5.4 short array syntax
*/
public $shortArraySyntax = false;
/**
* If array value breaks the list (multiline),
* alignment before and after calculated separately
*/
public $alignMultilineBreaks = true;
/**
* Approximate maximum line length
*/
public $maxLineLength = 60;
/**
* Array of different custom type handlers.
*
* @var array[]
*/
protected $customHandlers = array();
/**
* Returns singleton instance of the class
*
* @return self
*/
public static function instance()
{
static $instance;
return $instance ?: $instance = new self;
}
/**
* Constructor
*/
public function __construct()
{
$this->shortArraySyntax = version_compare(PHP_VERSION, '5.4', '>=');
}
/**
* Adds custom handler for the specified object type
*
* @param string|object $type <p>
* Object type for instanceof check
* </p>
* @param callable $handler <p>
* Callback "string fun($object)"
* </p>
*/
public function addCustomHandler($type, $handler)
{
$this->customHandlers[] = array($type, $handler);
}
/**
* Generation of php code for various data without formatting.
*
* WARNING!
* Don't use for self referencing arrays and objects!
*
* @see getCode
*
* @param mixed $data Data
* @param bool $noTail Don't add trailing ";"
*
* @return string
*/
public function getCodeNoFormat($data, $noTail = false)
{
return $this->getCode($data, 0, true, $noTail);
}
/**
* Generation of php code for various data without trailing semicolon (;).
*
* WARNING!
* Don't use for self referencing arrays and objects!
*
* @see getCode
*
* @param mixed $data Data
* @param int $indent Indent size in tabs for array php code
* @param bool $noFormat No formatting and indention
*
* @return string
*/
public function getCodeNoTail($data, $indent = 0, $noFormat = false)
{
return $this->getCode($data, $indent, $noFormat, true);
}
/**
* Generation of php code for various data.
*
* WARNING!
* Don't use for self referencing arrays and objects!
*
* @param mixed $data Data
* @param int $indent Indent size in tabs for array php code
* @param bool $noFormat No formatting and indention
* @param bool $noTail Don't add trailing semicolon (;)
*
* @return string
*/
public function getCode($data, $indent = 0, $noFormat = false, $noTail = false)
{
$tail = $noTail ? '' : ';';
// Null
if (!isset($data)) {
// var_export returns uppercased, so use own variant
return 'null' . $tail;
}
// Bool / Int / Float
else if (is_bool($data) || is_int($data) || is_float($data)) {
return var_export($data, true) . $tail;
}
// Array
else if (($traversable = ($data instanceof Traversable)) || is_array($data)) {
if ($traversable) {
$data = iterator_to_array($data, true);
}
return $this->getArray($data, $indent, $noFormat) . $tail;
}
// Object
else if (is_object($data)) {
return $this->getObject($data, $indent, $noFormat) . $tail;
}
// String
return $this->getString((string)$data) . $tail;
}
/**
* Returns php code for string
*
* @link http://php.net/language.types.string#language.types.string.syntax.double
*
* @param string $string <p>
* String data
* </p>
*
* @return string
*/
protected function getString($string)
{
// Binary strings are completely encoded in contrast to ordinary
// So we try to distinguish between a binary string and a usual
if (!($binary = $this->binaryStrings) && $this->binaryAutoCheck) {
// Simple check based on PCRE error for malformed UTF-8 data (not work for valid ASCII)
$binary = false === preg_match('~~u', $string)
// Match for most non printable chars somewhat taking multibyte chars into account
// Variant from sebastianbergmann/exporter by Sebastian Bergmann <sebastian@phpunit.de>
// Works good for binary strings that consist of valid ASCII chars
|| preg_match('~[^\x09-\x0d\x20-\xff]~S', $string);
}
// Build regexp
if ($binary) {
$regexp = '~.~Ss';
} else {
$regexp = $this->oneLineStrings
// All control chars
? '\x00-\x1F\x7F'
// Ctrl chars without \n & \t
: '\x00-\x08\x0B-\x1F\x7F';
// Additional chars: \x22 ("), \x24 ($), \x5C (\\)
$regexp = '~['.$regexp.'\x22\x24\x5C]~S';
}
// Format string
$string = preg_replace_callback(
$regexp,
function($char) use ($binary) {
// linefeed (LF or 0x0A (10) in ASCII)
if ("\n" === ($char = $char[0])) {
return '\n';
}
// carriage return (CR or 0x0D (13) in ASCII)
else if ("\r" === $char) {
return '\r';
}
// horizontal tab (HT or 0x09 (9) in ASCII)
else if ("\t" === $char) {
return '\t';
}
// vertical tab (VT or 0x0B (11) in ASCII) (since PHP 5.2.5)
else if ("\v" === $char) {
return '\v';
}
// escape (ESC or 0x1B (27) in ASCII) (since PHP 5.4.0)
// works only for PHP 5.4.0 or above, so disable
// else if ("\e" === $char) {
// return '\e';
// }
// form feed (FF or 0x0C (12) in ASCII) (since PHP 5.2.5)
else if ("\f" === $char) {
return '\f';
}
// chars that must be escaped in a double-quoted string
else if (!$binary && ('\\' === $char || '$' === $char || '"' === $char)) {
return "\\$char";
}
// all other chars
return sprintf('\x%02X', ord($char));
},
(string)$string
);
return '"' . $string . '"';
}
/**
* Returns php code for object
*
* @see IPhpGenerable
* @see CustomCode
*
* @param object|IPhpGenerable $object <p>
* Object data
* </p>
*
* @return string
*/
protected function getObject($object)
{
// User custom code
if ($object instanceof IPhpGenerable) {
return $object->generateCode();
}
// Closures special (partial) support
// WARNING: many closures on one line are not supported
// WARNING: closures with "use" are not supported
else if ($object instanceof \Closure) {
$ref = new ReflectionFunction($object);
// Open file and seek to the first line of the closure
$file = new SplFileObject($ref->getFileName());
$file->seek($ref->getStartLine()-1);
// Retrieve all of the lines that contain code for the closure
$endLine = $ref->getEndLine();
$code = '';
while ($file->key() < $endLine) {
$code .= $file->current();
$file->next();
}
// Only keep the code defining that closure
$begin = stripos($code, 'function');
$end = strrpos($code, '}');
$code = substr($code, $begin, $end - $begin + 1);
return $code;
}
// Different custom handlers
else if ($handlers = $this->customHandlers) {
foreach ($handlers as $h) {
list($type, $handler) = $h;
if ($object instanceof $type) {
return $handler($object);
}
}
}
// TODO: Add __set_state function support?
// Default - serialization
return "unserialize({$this->getCodeNoTail(serialize($object))})";
}
/**
* Returns php code for array
*
* @param array|Traversable $array <p>
* Array data
* </p>
* @param int $indent [optional] <p>
* Indent size in tabs for array php code
* </p>
* @param bool $noFormat [optional] <p>
* No formatting and indention
* </p>
*
* @return string
*/
protected function getArray($array, $indent = 0, $noFormat = false)
{
$shortSyntax = $this->shortArraySyntax;
$resultCode = $shortSyntax ? '[' : 'array(';
if ($array) {
$newLine = "\n";
$tabLength = $this->tabLength;
$mixSpaces = $this->mixSpaces;
$useSpaces = $this->useSpaces;
$tab = $useSpaces ? str_repeat(' ', $tabLength) : "\t";
$spacePostfix = $useSpaces || $this->spacesAfterKey;
$alignBreaks = $this->alignMultilineBreaks;
// The overall indent
$indentString = $noFormat ? '' : str_repeat($tab, $indent);
// First calculations and code build for keys/values
$keyLength = $keyLengthBlock = $i = 0;
$maxKeyLenBlocks = array($keyLengthBlock => 0);
$maxKeyLength = &$maxKeyLenBlocks[$keyLengthBlock];
$arrayIsSimple = !$this->outputSerialKeys;
$multiline = false;
$arrayParts = array();
foreach ($array as $key => $val) {
if ($arrayIsSimple && $key !== $i++) {
$arrayIsSimple = false;
}
// Build code for keys and values
$key = $this->getCode($key, 0, true, true);
$val = $this->getCode($val, $indent+1, $noFormat, true);
// We don't need this information if formatting is disabled
if (!$noFormat) {
// We need to save maximum key code length for alignment
$keyLength = mb_strlen($key, 'UTF-8');
$keyLength > $maxKeyLength
&& $maxKeyLength = $keyLength;
// Multiline value breaks the list, so
// alignment before and after calculated separately
if ($multiline = $alignBreaks
? false !== strpos($val, $newLine)
: false
) {
$maxKeyLenBlocks[++$keyLengthBlock] = 0;
$maxKeyLength = &$maxKeyLenBlocks[$keyLengthBlock];
}
}
$arrayParts[] = array(
$key,
$val,
$keyLength,
$multiline
);
}
unset($array, $maxKeyLength);
// Disable formatting if array have only one serial value
// (not multiline and not too long)
if ($arrayIsSimple && !$noFormat && count($arrayParts) === 1
&& !$arrayParts[0][3]
&& $this->maxLineLength >= mb_strlen($arrayParts[0][1], 'UTF-8')
+ ($indent*$tabLength)
) {
$noFormat = true;
}
// Build code
$keyLengthBlock = 0;
$maxKeyLength = $maxKeyLenBlocks[$keyLengthBlock];
foreach ($arrayParts as &$data) {
list(
$key,
$val,
$keyLength,
$multiline
) = $data;
if (!$noFormat) {
// Full align length in chars (after key, before value)
$alignLength = $maxKeyLength - $keyLength + 1;
// Simple spaces alignment
if ($spacePostfix) {
$key .= str_repeat(' ', $alignLength);
}
// Tabs with mix of spaces alignment (for shortness)
else if ($mixSpaces) {
if ($curTabTail = $keyLength % $tabLength) {
$curTabTail = $tabLength-$curTabTail;
}
$alignTail = $alignLength % $tabLength;
if ($curTabTail <= $alignTail
|| $alignLength / $tabLength >= 1
) {
if ($curTabTail) {
$key .= $tab;
$alignLength -= $curTabTail;
}
if ($alignLength) {
if ($align = floor($alignLength / $tabLength)) {
$key .= str_repeat($tab, $align);
}
$alignTail = $alignLength % $tabLength;
} else {
$alignTail = 0;
}
}
if ($alignTail) {
$key .= str_repeat(' ', $alignTail);
}
}
// Only tabs alignment
else {
if ($curTabTail = $keyLength % $tabLength) {
$key .= $tab;
$alignLength -= $tabLength-$curTabTail;
}
$key .= str_repeat(
$tab,
ceil($alignLength / $tabLength)
);
}
// Different align before and after multiline value
if ($multiline) {
$maxKeyLength = $maxKeyLenBlocks[++$keyLengthBlock];
}
}
$data = ($arrayIsSimple
? ''
: $key . '=>' . ($noFormat ? '' : ' ')
) . $val . ',';
}
// Join code parts
if ($noFormat) {
$resultCode .= join('', $arrayParts);
$resultCode = substr($resultCode, 0, -1);
} else {
$resultCode .= "{$newLine}{$indentString}{$tab}"
. join("{$newLine}{$indentString}{$tab}", $arrayParts)
. "{$newLine}{$indentString}";
}
}
$resultCode .= $shortSyntax ? ']' : ')';
return $resultCode;
}
}