Skip to content

Commit 0d2f1e2

Browse files
committed
Code structure refactoring
1 parent 8c492d9 commit 0d2f1e2

File tree

1 file changed

+86
-36
lines changed

1 file changed

+86
-36
lines changed

src/AbstractJsonEncoder.php

Lines changed: 86 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212
abstract class AbstractJsonEncoder implements \Iterator
1313
{
14-
/** @var \Generator[] Current value stack in encoding */
14+
/** @var \Iterator[] Current value stack in encoding */
1515
private $stack;
1616

1717
/** @var bool[] True for every object in the stack, false for an array */
@@ -54,13 +54,13 @@ public function __construct($value)
5454
$this->options = 0;
5555
$this->errors = [];
5656
$this->indent = ' ';
57-
$this->step = null;
5857
}
5958

6059
/**
6160
* Sets the JSON encoding options.
6261
* @param int $options The JSON encoding options that are used by json_encode
6362
* @return $this Returns self for call chaining
63+
* @throws \RuntimeException If changing encoding options during encoding operation
6464
*/
6565
public function setOptions($options)
6666
{
@@ -76,6 +76,7 @@ public function setOptions($options)
7676
* Sets the indent for the JSON output.
7777
* @param string|int $indent A string to use as indent or the number of spaces
7878
* @return $this Returns self for call chaining
79+
* @throws \RuntimeException If changing indent during encoding operation
7980
*/
8081
public function setIndent($indent)
8182
{
@@ -101,7 +102,7 @@ public function getErrors()
101102
*/
102103
private function initialize()
103104
{
104-
if (!isset($this->stack)) {
105+
if ($this->stack === null) {
105106
$this->rewind();
106107
}
107108
}
@@ -164,11 +165,11 @@ public function next()
164165

165166
if (!empty($this->stack)) {
166167
$this->step++;
167-
$generator = end($this->stack);
168+
$iterator = end($this->stack);
168169

169-
if ($generator->valid()) {
170-
$this->processStack($generator, end($this->stackType));
171-
$generator->next();
170+
if ($iterator->valid()) {
171+
$this->processStack($iterator, end($this->stackType));
172+
$iterator->next();
172173
} else {
173174
$this->popStack();
174175
}
@@ -178,22 +179,22 @@ public function next()
178179
}
179180

180181
/**
181-
* Handles the next value from the generator to be encoded as JSON.
182-
* @param \Generator $generator The generator used to generate the next value
183-
* @param bool $isObject True if the generator is being handled as an object, false if not
182+
* Handles the next value from the iterator to be encoded as JSON.
183+
* @param \Iterator $iterator The iterator used to generate the next value
184+
* @param bool $isObject True if the iterator is being handled as an object, false if not
184185
*/
185-
private function processStack(\Generator $generator, $isObject)
186+
private function processStack(\Iterator $iterator, $isObject)
186187
{
187188
if ($isObject) {
188-
if (!$this->processKey($generator->key())) {
189+
if (!$this->processKey($iterator->key())) {
189190
return;
190191
}
191192
} elseif (!$this->first) {
192193
$this->outputLine(',', JsonToken::T_COMMA);
193194
}
194195

195196
$this->first = false;
196-
$this->processValue($generator->current());
197+
$this->processValue($iterator->current());
197198
}
198199

199200
/**
@@ -228,13 +229,7 @@ private function processKey($key)
228229
*/
229230
private function processValue($value)
230231
{
231-
while ($this->isResolvable($value)) {
232-
if ($value instanceof \JsonSerializable) {
233-
$value = $value->jsonSerialize();
234-
} elseif ($value instanceof \Closure) {
235-
$value = $value();
236-
}
237-
}
232+
$value = $this->resolveValue($value);
238233

239234
if (is_array($value) || is_object($value)) {
240235
$this->pushStack($value);
@@ -244,13 +239,23 @@ private function processValue($value)
244239
}
245240

246241
/**
247-
* Tells if the given value should be resolved prior to encoding.
248-
* @param mixed $value The value to test
249-
* @return bool true if the value is resolvable, false if not
242+
* Resolves the actual value of any given value that is about to be processed.
243+
* @param mixed $value The value to resolve
244+
* @return mixed The resolved value
250245
*/
251-
private function isResolvable($value)
246+
protected function resolveValue($value)
252247
{
253-
return $value instanceof \JsonSerializable || $value instanceof \Closure;
248+
do {
249+
if ($value instanceof \JsonSerializable) {
250+
$value = $value->jsonSerialize();
251+
} elseif ($value instanceof \Closure) {
252+
$value = $value();
253+
} else {
254+
break;
255+
}
256+
} while (true);
257+
258+
return $value;
254259
}
255260

256261
/**
@@ -279,8 +284,8 @@ private function addError($message)
279284
*/
280285
private function pushStack($iterable)
281286
{
282-
$generator = $this->getIterator($iterable);
283-
$isObject = $this->isObject($iterable, $generator);
287+
$iterator = $this->getIterator($iterable);
288+
$isObject = $this->isObject($iterable, $iterator);
284289

285290
if ($isObject) {
286291
$this->outputLine('{', JsonToken::T_LEFT_BRACE);
@@ -289,7 +294,7 @@ private function pushStack($iterable)
289294
}
290295

291296
$this->first = true;
292-
$this->stack[] = $generator;
297+
$this->stack[] = $iterator;
293298
$this->stackType[] = $isObject;
294299
}
295300

@@ -308,18 +313,42 @@ private function getIterator($iterable)
308313
/**
309314
* Tells if the given iterable should be handled as a JSON object or not.
310315
* @param object|array $iterable The iterable value to test
311-
* @param \Generator $generator Generator created from the iterable value
316+
* @param \Iterator $iterator An Iterator created from the iterable value
312317
* @return bool True if the given iterable should be treated as object, false if not
313318
*/
314-
private function isObject($iterable, \Generator $generator)
319+
private function isObject($iterable, \Iterator $iterator)
315320
{
316321
if ($this->options & JSON_FORCE_OBJECT) {
317322
return true;
318-
} elseif (is_array($iterable)) {
319-
return $iterable !== [] && array_keys($iterable) !== range(0, count($iterable) - 1);
320323
}
321324

322-
return $generator->valid() && $generator->key() !== 0;
325+
if (is_array($iterable)) {
326+
return $this->isAssociative($iterable);
327+
}
328+
329+
return $iterator->valid() ? $iterator->key() !== 0 : !$iterable instanceof \Traversable;
330+
}
331+
332+
/**
333+
* Tells if the given array is an associative array.
334+
* @param array $array The array to test
335+
* @return bool True if the array is associative, false if not
336+
*/
337+
private function isAssociative(array $array)
338+
{
339+
if ($array === []) {
340+
return false;
341+
}
342+
343+
$expected = 0;
344+
345+
foreach ($array as $key => $_) {
346+
if ($key !== $expected++) {
347+
return true;
348+
}
349+
}
350+
351+
return false;
323352
}
324353

325354
/**
@@ -349,14 +378,35 @@ private function popStack()
349378
private function outputJson($value, $token)
350379
{
351380
$encoded = json_encode($value, $this->options);
381+
$error = json_last_error();
352382

353-
if (json_last_error() !== JSON_ERROR_NONE) {
354-
$this->addError(json_last_error_msg());
383+
if ($error !== JSON_ERROR_NONE) {
384+
$this->addError(sprintf('%s (%s)', json_last_error_msg(), $this->getJsonErrorName($error));
355385
}
356386

357387
$this->output($encoded, $token);
358388
}
359389

390+
/**
391+
* Returns the name of the JSON error constant.
392+
* @param int $error The error code to find
393+
* @return string The name for the error code
394+
*/
395+
private function getJsonErrorName($error)
396+
{
397+
$matches = array_keys(get_defined_constants(), $error, true);
398+
$prefix = 'JSON_ERROR_';
399+
$prefixLength = strlen($prefix);
400+
401+
foreach ($matches as $match) {
402+
if (strncmp($match, $prefix, $prefixLength) === 0) {
403+
return $match;
404+
}
405+
}
406+
407+
return 'UNKNOWN';
408+
}
409+
360410
/**
361411
* Passes the given token to the output stream and ensures the next token is preceded by a newline.
362412
* @param string $string The token to write to the output stream
@@ -383,7 +433,7 @@ private function output($string, $token)
383433
$this->write($indent, JsonToken::T_WHITESPACE);
384434
}
385435

386-
$this->line += 1;
436+
$this->line++;
387437
$this->column = strlen($indent) + 1;
388438
}
389439

0 commit comments

Comments
 (0)