Skip to content

Commit aab8555

Browse files
authored
Merge pull request #4 from Riimu/next
Implement changes for v2.3.0
2 parents 9920b50 + ab58fd6 commit aab8555

9 files changed

+395
-36
lines changed

CHANGES.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog #
22

3+
## v2.3.0 (2017-07-15) ##
4+
5+
* Added `string.utf8` option which causes the string encoder to escape all
6+
valid multibyte UTF-8 characters using the PHP7 unicode code point syntax.
7+
* Added `string.binary` option which causes the string encoder to encode all
8+
non UTF-8 strings using a `base64_encode()`.
9+
* Added `integer.type` option that accepts values `binary`, `octal`, `decimal`
10+
or `hexadecimal` which can be used to change the output syntax of integers.
11+
* Added `hex.capitalize` option that causes all hexadecimal character in
12+
output to appear in upper case
13+
* Added `float.export` option that forces float encoder to use `var_export`
14+
for encoding floating point numbers
15+
* Float encoder now delegates integer encoding to the integer encoder (to
16+
allow different integer types).
17+
318
## v2.2.0 (2017-07-08) ##
419

520
* Increase the minimum PHP version requirement to 5.6

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ apply to following calls.
147147
When set to `false`, generation of all extra whitespace is disabled and all
148148
other settings that affect whitespace are ignored.
149149

150+
* **hex.capitalize** : &lt;boolean&gt; (false)<br>
151+
When set to `true` all hexadecimal characters in the output are written
152+
using upper case instead of lower case.
153+
150154
* **null.capitalize** : &lt;boolean&gt; (false)<br>
151155
When set to `true`, all `null` values are written in upper case instead of
152156
lower case.
@@ -155,25 +159,45 @@ apply to following calls.
155159
When set to `true`, all `true` and `false` values are written in upper case
156160
instead of lower case.
157161

162+
* **integer.type** : &lt;"binary"|"octal"|"decimal"|"hexadecimal"&gt; ("decimal")<br>
163+
Change the output syntax of integers. For example, using the type `"hexadecimal"`
164+
would output the number `15` as `0xf`.
165+
158166
* **float.integers** : &lt;boolean|"all"&gt; (false)<br>
159167
When set to `true`, any float that represents an integer and has a value
160168
that is accurately represented by the floating point number will be encoded
161169
as an integer instead of a float. (e.g. the value `2.0` will be encoded as
162170
`2`). To include the values that are not accurately represented, you may set
163171
option to `"all"`.
164172

173+
* **float.export** : &lt;boolean&gt; (false)<br>
174+
When set to `true` floats are encoded using `var_export()`, which causes a
175+
slightly different output on non integer floating point numbers compared to
176+
the standard implemented method. In some cases, this may produce more
177+
accurate numbers but with less cleaner representation.
178+
165179
* **float.precision** : &lt;integer|false&gt; (17)<br>
166180
The maximum precision of encoded floating point values, which usually also
167181
means the maximum number of digits in encoded floats. If the value is set to
168182
`false`, the PHP ini setting `serialize_precision` will be used instead.
169183
Note that due to the way floating point values work, a value greater than 17
170184
does not provide any additional precision.
171185

186+
* **string.binary** : &lt;boolean&gt; (false)<br>
187+
When set to `true` any string that is not valid UTF-8 will be encoded in
188+
base 64 and wrapped with `base64_decode()` call.
189+
172190
* **string.escape** : &lt;boolean&gt; (true)<br>
173191
When set to `true`, all strings containing bytes outside the 32-126 ASCII
174192
range will be written with double quotes and the characters outside the
175193
range will be escaped.
176194

195+
* **string.utf8** : &lt;boolean&gt; (false)<br>
196+
When both this option and `string.escape` are set to `true`, all valid
197+
multibyte UTF-8 characters in strings are encoded using the PHP7 unicode
198+
code point syntax. Note that this syntax does not work in PHP versions
199+
earlier than 7.0.
200+
177201
* **array.short** : &lt;boolean&gt; (true)<br>
178202
When set to `true`, arrays are enclosed using square brackets `[]` instead
179203
using of the long array notation `array()`.

examples/export_difference.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
require __DIR__ . '/../vendor/autoload.php';
4+
5+
$precision = 17;
6+
$normalEncoder = new \Riimu\Kit\PHPEncoder\PHPEncoder(['float.precision' => $precision]);
7+
$exportEncoder = new \Riimu\Kit\PHPEncoder\PHPEncoder(['float.export' => true]);
8+
9+
ini_set('serialize_precision', $precision);
10+
11+
$count = 0;
12+
$mindiff = 1;
13+
$maxdiff = 0;
14+
$total = 0;
15+
16+
for ($i = 0; $i <= 2 ** 53; $i++) {
17+
$normal = $normalEncoder->encode($i / 2 ** 53); // $i / 2 ** 53
18+
$export = $exportEncoder->encode($i / 2 ** 53); // $i / 2 ** 53
19+
20+
$normalResult = eval("return $normal;");
21+
$exportResult = eval("return $export;");
22+
23+
if ($normalResult !== $exportResult) {
24+
$diff = abs($normalResult - $exportResult);
25+
26+
$count++;
27+
$mindiff = min($diff, $mindiff);
28+
$maxdiff = max($diff, $maxdiff);
29+
$total += $diff;
30+
$avgdiff = $total / $count;
31+
$pct = round($count / $i * 100, 2);
32+
33+
echo "Different ($count / $i, $pct%): $normal, $export, Diff: $mindiff / $avgdiff / $maxdiff\n";
34+
}
35+
}

src/Encoder/FloatEncoder.php

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class FloatEncoder implements Encoder
1717
private static $defaultOptions = [
1818
'float.integers' => false,
1919
'float.precision' => 17,
20+
'float.export' => false,
2021
];
2122

2223
public function getDefaultOptions()
@@ -37,30 +38,27 @@ public function encode($value, $depth, array $options, callable $encode)
3738
return $value < 0 ? '-INF' : 'INF';
3839
}
3940

40-
return $this->encodeNumber($value, $options);
41+
return $this->encodeNumber($value, $options, $encode);
4142
}
4243

4344
/**
4445
* Encodes the number as a PHP number representation.
4546
* @param float $float The number to encode
4647
* @param array $options The float encoding options
48+
* @param callable $encode Callback used to encode values
4749
* @return string The PHP code representation for the number
4850
*/
49-
private function encodeNumber($float, array $options)
51+
private function encodeNumber($float, array $options, callable $encode)
5052
{
5153
if ($this->isInteger($float, $options['float.integers'])) {
52-
return number_format($float, 0, '.', '');
54+
return $this->encodeInteger($float, $encode);
5355
} elseif ($float === 0.0) {
5456
return '0.0';
57+
} elseif ($options['float.export']) {
58+
return var_export((float) $float, true);
5559
}
5660

57-
$precision = $options['float.precision'];
58-
59-
if ($precision === false) {
60-
$precision = ini_get('serialize_precision');
61-
}
62-
63-
return $this->encodeFloat($float, $precision);
61+
return $this->encodeFloat($float, $this->determinePrecision($options));
6462
}
6563

6664
/**
@@ -80,6 +78,39 @@ private function isInteger($float, $allowIntegers)
8078
return $allowIntegers === 'all';
8179
}
8280

81+
/**
82+
* Encodes the given float as an integer.
83+
* @param float $float The number to encode
84+
* @param callable $encode Callback used to encode values
85+
* @return string The PHP code representation for the number
86+
*/
87+
private function encodeInteger($float, callable $encode)
88+
{
89+
$minimum = defined('PHP_INT_MIN') ? PHP_INT_MIN : ~PHP_INT_MAX;
90+
91+
if ($float >= $minimum && $float <= PHP_INT_MAX) {
92+
return $encode((int) $float);
93+
}
94+
95+
return number_format($float, 0, '.', '');
96+
}
97+
98+
/**
99+
* Determines the float precision based on the options.
100+
* @param array $options The float encoding options
101+
* @return int The precision used to encode floats
102+
*/
103+
private function determinePrecision($options)
104+
{
105+
$precision = $options['float.precision'];
106+
107+
if ($precision === false) {
108+
$precision = defined('HHVM_VERSION') ? 17 : ini_get('serialize_precision');
109+
}
110+
111+
return max(1, (int) $precision);
112+
}
113+
83114
/**
84115
* Encodes the number using a floating point representation.
85116
* @param float $float The number to encode
@@ -88,10 +119,9 @@ private function isInteger($float, $allowIntegers)
88119
*/
89120
private function encodeFloat($float, $precision)
90121
{
91-
$precision = max(1, (int) $precision);
92122
$log = (int) floor(log(abs($float), 10));
93123

94-
if (abs($float) < self::FLOAT_MAX && $log > -5 && abs($log) < $precision) {
124+
if ($log > -5 && abs($float) < self::FLOAT_MAX && abs($log) < $precision) {
95125
return $this->formatFloat($float, $precision - $log - 1);
96126
}
97127

src/Encoder/IntegerEncoder.php

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,38 @@
1010
*/
1111
class IntegerEncoder implements Encoder
1212
{
13+
/** @var array Default values for options in the encoder */
14+
private static $defaultOptions = [
15+
'integer.type' => 'decimal',
16+
];
17+
18+
/** @var \Closure[] Encoders for different types of integers */
19+
private $encoders;
20+
21+
/**
22+
* IntegerEncoder constructor.
23+
*/
24+
public function __construct()
25+
{
26+
$this->encoders = [
27+
'binary' => function ($value) {
28+
return $this->encodeBinary($value);
29+
},
30+
'octal' => function ($value) {
31+
return $this->encodeOctal($value);
32+
},
33+
'decimal' => function ($value, $options) {
34+
return $this->encodeDecimal($value, $options);
35+
},
36+
'hexadecimal' => function ($value, $options) {
37+
return $this->encodeHexadecimal($value, $options);
38+
},
39+
];
40+
}
41+
1342
public function getDefaultOptions()
1443
{
15-
return [];
44+
return self::$defaultOptions;
1645
}
1746

1847
public function supports($value)
@@ -22,12 +51,76 @@ public function supports($value)
2251

2352
public function encode($value, $depth, array $options, callable $encode)
2453
{
25-
$string = (string) $value;
54+
if (!isset($this->encoders[$options['integer.type']])) {
55+
throw new \InvalidArgumentException('Invalid integer encoding type');
56+
}
57+
58+
$callback = $this->encoders[$options['integer.type']];
59+
60+
return $callback((int) $value, $options);
61+
}
62+
63+
/**
64+
* Encodes an integer into binary representation.
65+
* @param int $integer The integer to encode
66+
* @return string The PHP code representation for the integer
67+
*/
68+
public function encodeBinary($integer)
69+
{
70+
return sprintf('%s0b%b', $this->sign($integer), abs($integer));
71+
}
72+
73+
/**
74+
* Encodes an integer into octal representation.
75+
* @param int $integer The integer to encode
76+
* @return string The PHP code representation for the integer
77+
*/
78+
public function encodeOctal($integer)
79+
{
80+
return sprintf('%s0%o', $this->sign($integer), abs($integer));
81+
}
82+
83+
/**
84+
* Encodes an integer into decimal representation.
85+
* @param int $integer The integer to encode
86+
* @param array $options The integer encoding options
87+
* @return string The PHP code representation for the integer
88+
*/
89+
public function encodeDecimal($integer, $options)
90+
{
91+
if ($integer === 1 << (PHP_INT_SIZE * 8 - 1)) {
92+
return sprintf('(int)%s%d', $options['whitespace'] ? ' ' : '', $integer);
93+
}
94+
95+
return var_export($integer, true);
96+
}
2697

27-
if ($value === 1 << (PHP_INT_SIZE * 8 - 1)) {
28-
$string = sprintf('(int)%s%s', $options['whitespace'] ? ' ' : '', $string);
98+
/**
99+
* Encodes an integer into hexadecimal representation.
100+
* @param int $integer The integer to encode
101+
* @param array $options The integer encoding options
102+
* @return string The PHP code representation for the integer
103+
*/
104+
public function encodeHexadecimal($integer, $options)
105+
{
106+
if ($options['hex.capitalize']) {
107+
return sprintf('%s0x%X', $this->sign($integer), abs($integer));
108+
}
109+
110+
return sprintf('%s0x%x', $this->sign($integer), abs($integer));
111+
}
112+
113+
/**
114+
* Returns the negative sign for negative numbers.
115+
* @param int $integer The number to test for negativity
116+
* @return string The minus sign for negative numbers and empty string for positive numbers
117+
*/
118+
private function sign($integer)
119+
{
120+
if ($integer < 0) {
121+
return '-';
29122
}
30123

31-
return $string;
124+
return '';
32125
}
33126
}

src/Encoder/ObjectEncoder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ private function encodeObject($object, array $options, callable $encode)
6666
* @param array $options List of encoder options
6767
* @param callable $encode Callback used to encode values
6868
* @return string The object encoded as string
69+
* @throws \RuntimeException If the object format is invalid
6970
*/
7071
private function encodeObjectArray($object, array $options, callable $encode)
7172
{

0 commit comments

Comments
 (0)