Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
},
"require": {
"php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
"laminas/laminas-json": "^3.1",
"laminas/laminas-servicemanager": "^4.5",
"laminas/laminas-stdlib": "^3.19"
},
Expand All @@ -59,6 +58,9 @@
],
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"static-analysis": "psalm --shepherd --stats",
"static-analysis:b": "psalm --update-baseline",
"static-analysis:clear": "psalm --clear-cache",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml"
},
Expand Down
66 changes: 2 additions & 64 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 58 additions & 8 deletions src/Adapter/Json.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@
namespace Laminas\Serializer\Adapter;

use InvalidArgumentException;
use Laminas\Json\Json as LaminasJson;
use JsonException;
use Laminas\Serializer\Exception;

use function in_array;
use function is_array;
use function is_int;
use function is_object;
use function json_decode;
use function json_encode;

use const JSON_THROW_ON_ERROR;

final class Json extends AbstractAdapter
{
/** @var JsonOptions|null */
Expand Down Expand Up @@ -51,14 +60,14 @@ public function serialize(mixed $value): string
$cycleCheck = $options->getCycleCheck();
$opts = [
'enableJsonExprFinder' => $options->getEnableJsonExprFinder(),
'objectDecodeType' => $options->getObjectDecodeType(),
'objectDecodeType' => $options->isAssocArray(),
];

try {
return LaminasJson::encode($value, $cycleCheck, $opts);
return $this->encode($value, $cycleCheck, $opts);
} catch (InvalidArgumentException $e) {
throw new Exception\InvalidArgumentException('Serialization failed: ' . $e->getMessage(), 0, $e);
} catch (\Exception $e) {
} catch (JsonException $e) {
throw new Exception\RuntimeException('Serialization failed: ' . $e->getMessage(), 0, $e);
}
}
Expand All @@ -72,13 +81,54 @@ public function serialize(mixed $value): string
public function unserialize(string $serialized): mixed
{
try {
$ret = LaminasJson::decode($serialized, $this->getOptions()->getObjectDecodeType());
} catch (InvalidArgumentException $e) {
throw new Exception\InvalidArgumentException('Unserialization failed: ' . $e->getMessage(), 0, $e);
} catch (\Exception $e) {
$ret = $this->decode($serialized, $this->getOptions()->isAssocArray());
} catch (JsonException $e) {
throw new Exception\RuntimeException('Unserialization failed: ' . $e->getMessage(), 0, $e);
}

return $ret;
}

/**
* @param mixed[] $options
*/
private function encode(mixed $value, bool $cycleCheck, array $options): string
{
if ($cycleCheck) {
$seen = [];
$detectCycles = function (mixed &$val) use (&$seen, &$detectCycles): void {
if (is_array($val) || is_object($val)) {
if (in_array($val, $seen, true)) {
throw new InvalidArgumentException("Cycle detected in value to be JSON encoded");
}
$seen[] = $val;
foreach ($val as &$item) {
$detectCycles($item);
}
}
};
$detectCycles($value);
}

$jsonOptions = isset($options['json_encode_options']) && is_int($options['json_encode_options'])
? $options['json_encode_options']
: 0;

$encoded = json_encode($value, $jsonOptions | JSON_THROW_ON_ERROR);
if ($encoded === false) {
throw new JsonException('Syntax error');
}

return $encoded;
}

private function decode(string $value, bool $assoc): mixed
{
return json_decode(
$value,
$assoc,
512, // depth
JSON_THROW_ON_ERROR
);
}
}
28 changes: 5 additions & 23 deletions src/Adapter/JsonOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,14 @@

namespace Laminas\Serializer\Adapter;

use Laminas\Json\Json as LaminasJson;
use Laminas\Serializer\Exception;

final class JsonOptions extends AdapterOptions
{
protected bool $cycleCheck = false;

protected bool $enableJsonExprFinder = false;

/** @var LaminasJson::TYPE_* */
protected int $objectDecodeType = LaminasJson::TYPE_ARRAY;
private bool $assocArray = true;

public function setCycleCheck(bool $flag): void
{
Expand All @@ -37,30 +34,15 @@ public function getEnableJsonExprFinder(): bool
}

/**
* @param LaminasJson::TYPE_* $type
* @throws Exception\InvalidArgumentException
*/
public function setObjectDecodeType(int $type): void
public function setAssocArray(bool $assocArray): void
{
/**
* @psalm-suppress DocblockTypeContradiction Due to the way how the options for plugins work, i.e. using
* {@see AbstractOptions::setFromArray()}, having an additional check
* here can provide more detailed errors.
*/
if ($type !== LaminasJson::TYPE_ARRAY && $type !== LaminasJson::TYPE_OBJECT) {
throw new Exception\InvalidArgumentException(
'Unknown decode type: ' . $type
);
}

$this->objectDecodeType = $type;
$this->assocArray = $assocArray;
}

/**
* @return LaminasJson::TYPE_*
*/
public function getObjectDecodeType(): int
public function isAssocArray(): bool
{
return $this->objectDecodeType;
return $this->assocArray;
}
}
9 changes: 4 additions & 5 deletions test/Adapter/JsonTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace LaminasTest\Serializer\Adapter;

use Laminas\Json\Json as LaminasJson;
use Laminas\Serializer\Adapter\Json;
use Laminas\Serializer\Adapter\JsonOptions;
use Laminas\Serializer\Exception\RuntimeException;
Expand All @@ -28,13 +27,13 @@ public function testAdapterAcceptsOptions(): void
$options = new JsonOptions([
'cycle_check' => true,
'enable_json_expr_finder' => true,
'object_decode_type' => 1,
'assoc_array' => true,
]);
$adapter->setOptions($options);

self::assertEquals(true, $adapter->getOptions()->getCycleCheck());
self::assertEquals(true, $adapter->getOptions()->getEnableJsonExprFinder());
self::assertEquals(1, $adapter->getOptions()->getObjectDecodeType());
self::assertTrue($adapter->getOptions()->isAssocArray());
}

public function testSerializeString(): void
Expand Down Expand Up @@ -134,7 +133,7 @@ public function testUnserializeAsObject(): void
$expected = new stdClass();
$expected->test = 'test';

$this->adapter->getOptions()->setObjectDecodeType(LaminasJson::TYPE_OBJECT);
$this->adapter->getOptions()->setAssocArray(false);

$data = $this->adapter->unserialize($value);
self::assertEquals($expected, $data);
Expand All @@ -144,7 +143,7 @@ public function testUnserialzeInvalid(): void
{
$value = 'not a serialized string';
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Unserialization failed: Decoding failed: Syntax error');
$this->expectExceptionMessage('Unserialization failed: Syntax error');
$this->adapter->unserialize($value);
}
}