Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Laminas\Json dependency removal #68

Closed
wants to merge 7 commits into from
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"php": "^7.4 || ~8.0.0 || ~8.1.0",
"ext-json": "*",
"laminas/laminas-eventmanager": "^3.4",
"laminas/laminas-json": "^3.3",
"laminas/laminas-stdlib": "^3.6"
},
"require-dev": {
Expand Down
26 changes: 0 additions & 26 deletions docs/book/helpers/json.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,3 @@ determine how to handle the content.
```php
<?= $this->json($this->data) ?>
```

> WARNING: **Deprecated**
>
> ### Enabling encoding using Laminas\Json\Expr
>
> **This feature of the Json view helper has been deprecated in version 2.16 and will be removed in version 3.0.**
>
> The JSON helper accepts an array of options that will be passed to `Laminas\Json\Json::encode()` and
> used internally to encode data.
> `Laminas\Json\Json::encode` allows the encoding of native JSON expressions using `Laminas\Json\Expr`
> objects. This option is disabled by default. To enable this option, pass a boolean `true` to the
> `enableJsonExprFinder` key of the options array:
>
> ```php
> <?= $this->json($this->data, ['enableJsonExprFinder' => true]) ?>
> ``
>
> The JSON helper accepts an array of options that will be passed to `Laminas\Json\Json::encode()` and
> used internally to encode data.
> `Laminas\Json\Json::encode` allows the encoding of native JSON expressions using `Laminas\Json\Expr`
> objects. This option is disabled by default. To enable this option, pass a boolean `true` to the
> `enableJsonExprFinder` key of the options array:
>
> ```php
> <?= $this->json($this->data, ['enableJsonExprFinder' => true]) ?>
> ```
28 changes: 15 additions & 13 deletions src/Helper/Json.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,30 @@
namespace Laminas\View\Helper;

use Laminas\Http\Response;
use Laminas\Json\Json as JsonFormatter;

use function trigger_error;
use function json_encode;

use const E_USER_DEPRECATED;
use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;

/**
* Helper for simplifying JSON responses
*/
class Json extends AbstractHelper
{
/** @var Response */
/** @var Response|null */
protected $response;

/**
* Encode data as JSON and set response header
*
* @param mixed $data
* @param array $jsonOptions Options to pass to JsonFormatter::encode()
* @return string|void
* @return string
*/
public function __invoke($data, array $jsonOptions = [])
{
if (isset($jsonOptions['enableJsonExprFinder']) && $jsonOptions['enableJsonExprFinder'] === true) {
trigger_error(
'Json Expression functionality is deprecated and will be removed in laminas-view 3.0',
E_USER_DEPRECATED
);
}

$data = JsonFormatter::encode($data, null, $jsonOptions);
$data = json_encode($data, $this->optionsToFlags($jsonOptions));

if ($this->response instanceof Response) {
$headers = $this->response->getHeaders();
Expand All @@ -45,6 +38,15 @@ public function __invoke($data, array $jsonOptions = [])
return $data;
}

private function optionsToFlags(array $options = []): int
{
$prettyPrint = $options['prettyPrint'] ?? false;
$flags = JSON_THROW_ON_ERROR;
$flags |= $prettyPrint ? 0 : JSON_PRETTY_PRINT;

return $flags;
}

/**
* Set the response object
*
Expand Down
26 changes: 20 additions & 6 deletions src/Model/JsonModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@

namespace Laminas\View\Model;

use Laminas\Json\Json;
use JsonException;
use Laminas\Stdlib\ArrayUtils;
use Laminas\View\Exception\DomainException;
use Traversable;

use function json_encode;

use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;

class JsonModel extends ViewModel
{
/**
Expand Down Expand Up @@ -56,13 +62,21 @@ public function serialize()
$variables = ArrayUtils::iteratorToArray($variables);
}

$options = [
'prettyPrint' => $this->getOption('prettyPrint'),
];
$options = (bool) $this->getOption('prettyPrint', false) ? JSON_PRETTY_PRINT : 0;

if (null !== $this->jsonpCallback) {
return $this->jsonpCallback . '(' . Json::encode($variables, false, $options) . ');';
return $this->jsonpCallback . '(' . $this->jsonEncode($variables, $options) . ');';
}
return $this->jsonEncode($variables, $options);
}

/** @param mixed $data */
private function jsonEncode($data, int $options): string
{
try {
return json_encode($data, $options | JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new DomainException('Failed to encode Json', $e->getCode(), $e);
}
return Json::encode($variables, false, $options);
}
}
33 changes: 23 additions & 10 deletions src/Renderer/JsonRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
namespace Laminas\View\Renderer;

use ArrayAccess;
use JsonException;
use JsonSerializable;
use Laminas\Json\Json;
use Laminas\Stdlib\ArrayUtils;
use Laminas\View\Exception;
use Laminas\View\Model\JsonModel;
Expand All @@ -18,8 +18,11 @@
use function array_replace_recursive;
use function get_object_vars;
use function is_object;
use function json_encode;
use function sprintf;

use const JSON_THROW_ON_ERROR;

/**
* JSON renderer
*/
Expand Down Expand Up @@ -65,6 +68,8 @@ public function getEngine()
public function setResolver(Resolver $resolver)
{
$this->resolver = $resolver;

return $this;
}

/**
Expand Down Expand Up @@ -131,28 +136,27 @@ public function render($nameOrModel, $values = null)
if ($nameOrModel instanceof JsonModel) {
$children = $this->recurseModel($nameOrModel, false);
$this->injectChildren($nameOrModel, $children);
$values = $nameOrModel->serialize();
$output = $nameOrModel->serialize();
} else {
$values = $this->recurseModel($nameOrModel);
$values = Json::encode($values);
$output = $this->recurseModel($nameOrModel);
$output = $this->jsonEncode($output);
}

if ($this->hasJsonpCallback()) {
$values = $this->jsonpCallback . '(' . $values . ');';
$output = $this->jsonpCallback . '(' . $output . ');';
}
return $values;
return $output;
}

// use case 2: $nameOrModel is populated, $values is not
// Serialize $nameOrModel
if (null === $values) {
if (! is_object($nameOrModel) || $nameOrModel instanceof JsonSerializable) {
$return = Json::encode($nameOrModel);
$return = $this->jsonEncode($nameOrModel);
} elseif ($nameOrModel instanceof Traversable) {
$nameOrModel = ArrayUtils::iteratorToArray($nameOrModel);
$return = Json::encode($nameOrModel);
$return = $this->jsonEncode(ArrayUtils::iteratorToArray($nameOrModel));
} else {
$return = Json::encode(get_object_vars($nameOrModel));
$return = $this->jsonEncode(get_object_vars($nameOrModel));
}

if ($this->hasJsonpCallback()) {
Expand Down Expand Up @@ -237,4 +241,13 @@ protected function injectChildren(Model $model, array $children): void
$model->setVariable($child, $value);
}
}

private function jsonEncode($data): string
{
try {
return json_encode($data, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new Exception\DomainException('Json encoding failed', $e->getCode(), $e);
}
}
}
27 changes: 11 additions & 16 deletions test/Helper/JsonTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@

use Laminas\Http\Header\HeaderInterface;
use Laminas\Http\Response;
use Laminas\Json\Json as JsonFormatter;
use Laminas\View\Helper\Json as JsonHelper;
use PHPUnit\Framework\TestCase;

use function json_encode;

use const JSON_THROW_ON_ERROR;

/**
* Test class for Laminas\View\Helper\Json
*
Expand All @@ -34,7 +37,7 @@ protected function setUp(): void
$this->helper->setResponse($this->response);
}

public function verifyJsonHeader(): void
private function verifyJsonHeader(): void
{
$headers = $this->response->getHeaders();
$this->assertTrue($headers->has('Content-Type'));
Expand All @@ -51,20 +54,12 @@ public function testJsonHelperSetsResponseHeader(): void

public function testJsonHelperReturnsJsonEncodedString(): void
{
$data = $this->helper->__invoke('foobar');
$this->assertIsString($data);
$this->assertEquals('foobar', JsonFormatter::decode($data));
}
$input = [
'dory' => 'blue',
'nemo' => 'orange',
];
$expect = json_encode($input, JSON_THROW_ON_ERROR);

public function testThatADeprecationErrorIsTriggeredWhenExpressionFinderOptionIsUsed(): void
{
$this->expectDeprecation();
$this->helper->__invoke(['foo'], ['enableJsonExprFinder' => true]);
}

public function testThatADeprecationErrorIsNotTriggeredWhenExpressionFinderOptionIsNotUsed(): void
{
$this->expectNotToPerformAssertions();
$this->helper->__invoke(['foo'], ['enableJsonExprFinder' => 'anything other than true']);
self::assertJsonStringEqualsJsonString($expect, ($this->helper)($input));
}
}
33 changes: 27 additions & 6 deletions test/Model/JsonModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@

namespace LaminasTest\View\Model;

use Laminas\Json\Json;
use Laminas\View\Exception\DomainException;
use Laminas\View\Model\JsonModel;
use Laminas\View\Variables;
use PHPUnit\Framework\TestCase;

use function json_encode;
use function sprintf;

use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;

class JsonModelTest extends TestCase
{
public function testAllowsEmptyConstructor(): void
Expand All @@ -23,28 +29,43 @@ public function testCanSerializeVariablesToJson(): void
$array = ['foo' => 'bar'];
$model = new JsonModel($array);
$this->assertEquals($array, $model->getVariables());
$this->assertEquals(Json::encode($array), $model->serialize());
$this->assertJsonStringEqualsJsonString(json_encode($array, JSON_THROW_ON_ERROR), $model->serialize());
}

public function testCanSerializeWithJsonpCallback(): void
{
$array = ['foo' => 'bar'];
$model = new JsonModel($array);
$model->setJsonpCallback('callback');
$this->assertEquals('callback(' . Json::encode($array) . ');', $model->serialize());
$expect = sprintf(
'callback(%s);',
json_encode($array, JSON_THROW_ON_ERROR)
);
$this->assertEquals($expect, $model->serialize());
}

public function testPrettyPrint(): void
{
$array = [
$array = [
'simple' => 'simple test string',
'stringwithjsonchars' => '\"[1,2]',
'complex' => [
'foo' => 'bar',
'far' => 'boo',
],
];
$model = new JsonModel($array, ['prettyPrint' => true]);
$this->assertEquals(Json::encode($array, false, ['prettyPrint' => true]), $model->serialize());
$model = new JsonModel($array, ['prettyPrint' => true]);
$expect = json_encode($array, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT);
$this->assertEquals($expect, $model->serialize());
}

public function testThatAnExceptionIsThrownIfItIsNotPossibleToEncodeThePayload(): void
{
$malformedUtf8 = [
'string' => "\x92",
];
$this->expectException(DomainException::class);
$this->expectExceptionMessage('Failed to encode Json');
(new JsonModel($malformedUtf8))->serialize();
}
}