Skip to content

Commit 8c72ca0

Browse files
committed
Added JsonRpc serializer. Improved exceptions to contain additional data, error handling improved too.
1 parent aaa5cd8 commit 8c72ca0

11 files changed

+291
-18
lines changed

Action.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public function parseJsonRpcBody($rawBody)
5555

5656
return $parameters;
5757
} catch (InvalidArgumentException $e) {
58-
throw new ParseErrorException('', $e);
58+
throw new ParseErrorException('', [], $e);
5959
}
6060
}
6161

@@ -120,9 +120,11 @@ public function runWithParams($params)
120120
}
121121
throw new InvalidRequestException();
122122
}
123-
} catch (InvalidRequestException $e) {
123+
}
124+
catch (InvalidRequestException $e) {
124125
$batchResponse[] = new ErrorResponse($e, $request ?: null);
125-
} catch (JsonRpcException $e) {
126+
}
127+
catch (JsonRpcException $e) {
126128
// We do not return response to notifications
127129
if ($request && !is_null($request->id)) {
128130
$batchResponse[] = new ErrorResponse($e, $request ?: null);

ErrorHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ protected function renderException($exception)
3636
}
3737

3838
$response->setStatusCode(200);
39-
$response->data = new ErrorResponse(new InternalErrorException('Error while processing request', $exception));
39+
$response->data = new ErrorResponse(new InternalErrorException('Error while processing request', [], $exception));
4040
$response->send();
4141
}
4242

JsonRpcError.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace georgique\yii2\jsonrpc;
44

5+
use georgique\yii2\jsonrpc\exceptions\JsonRpcException;
56
use yii\base\UserException;
67
use yii\helpers\ArrayHelper;
78

@@ -26,10 +27,14 @@ public function __construct(\Exception $exception)
2627
{
2728
$this->code = $exception->getCode();
2829
$this->message = $this->getExceptionMessage($exception);
30+
2931
$this->data = [];
32+
if ($exception instanceof JsonRpcException && !empty($exception->getData())) {
33+
$this->data = $exception->getData();
34+
}
3035

3136
if (defined('YII_DEBUG') && YII_DEBUG) {
32-
$this->data = $this->convertExceptionToArray($exception);
37+
$this->data['exception'] = $this->convertExceptionToArray($exception);
3338
}
3439

3540
// We need to provide client with an original error message

JsonRpcRequest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,11 @@ public function execute()
187187
} catch (JsonRpcException $e) {
188188
throw $e;
189189
} catch (BadRequestHttpException $e) {
190-
throw new InvalidParamsException('Invalid params', $e);
190+
throw new InvalidParamsException('Invalid params', [], $e);
191191
} catch (InvalidRouteException $e) {
192-
throw new MethodNotFoundException('Method not found: ' . $route . '.', $e);
192+
throw new MethodNotFoundException('Method not found: ' . $route . '.', [], $e);
193193
} catch (\Exception $e) {
194-
throw new InternalErrorException('Internal error', $e);
194+
throw new InternalErrorException('Internal error', [], $e);
195195
}
196196
return $result;
197197
}

JsonRpcSerializer.php

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
<?php
2+
3+
namespace georgique\yii2\jsonrpc;
4+
5+
use georgique\yii2\jsonrpc\exceptions\InternalErrorException;
6+
use georgique\yii2\jsonrpc\exceptions\JsonRpcException;
7+
use georgique\yii2\jsonrpc\JsonRpcError;
8+
use Yii;
9+
use yii\base\Arrayable;
10+
use yii\base\Component;
11+
use yii\base\Exception;
12+
use yii\base\Model;
13+
use yii\data\DataProviderInterface;
14+
use yii\data\Pagination;
15+
use yii\helpers\ArrayHelper;
16+
use yii\web\Link;
17+
use yii\web\Request;
18+
use yii\web\Response;
19+
20+
/**
21+
* Serializer converts resource objects and collections into array representation.
22+
* Implementation based on yii\rest\Serializer.
23+
*/
24+
class JsonRpcSerializer extends Component
25+
{
26+
/**
27+
* @var string the name of the query parameter containing the information about which fields should be returned
28+
* for a [[Model]] object. If the parameter is not provided or empty, the default set of fields as defined
29+
* by [[Model::fields()]] will be returned.
30+
*/
31+
public $fieldsParam = 'fields';
32+
33+
/**
34+
* @var string the name of the query parameter containing the information about which fields should be returned
35+
* in addition to those listed in [[fieldsParam]] for a resource object.
36+
*/
37+
public $expandParam = 'expand';
38+
39+
/**
40+
* @var string the name of the envelope (e.g. `items`) for returning the resource objects in a collection.
41+
* This is used when serving a resource collection. When this is set and pagination is enabled, the serializer
42+
* will return a collection in the following format:
43+
*
44+
* ```php
45+
* [
46+
* 'items' => [...], // assuming collectionEnvelope is "items"
47+
* '_links' => { // pagination links as returned by Pagination::getLinks()
48+
* 'self' => '...',
49+
* 'next' => '...',
50+
* 'last' => '...',
51+
* },
52+
* '_meta' => { // meta information as returned by Pagination::toArray()
53+
* 'totalCount' => 100,
54+
* 'pageCount' => 5,
55+
* 'currentPage' => 1,
56+
* 'perPage' => 20,
57+
* },
58+
* ]
59+
* ```
60+
*
61+
* If this property is not set, the resource arrays will be directly returned without using envelope.
62+
* The pagination information as shown in `_links` and `_meta` can be accessed from the response HTTP headers.
63+
*/
64+
public $collectionEnvelope = 'items';
65+
66+
/**
67+
* @var string the name of the envelope (e.g. `_links`) for returning the links objects.
68+
* It takes effect only, if `collectionEnvelope` is set.
69+
* @since 2.0.4
70+
*/
71+
public $linksEnvelope = '_links';
72+
73+
/**
74+
* @var string the name of the envelope (e.g. `_meta`) for returning the pagination object.
75+
* It takes effect only, if `collectionEnvelope` is set.
76+
* @since 2.0.4
77+
*/
78+
public $metaEnvelope = '_meta';
79+
80+
/**
81+
* @var Request the current request. If not set, the `request` application component will be used.
82+
*/
83+
public $request;
84+
85+
/**
86+
* @var Response the response to be sent. If not set, the `response` application component will be used.
87+
*/
88+
public $response;
89+
90+
/**
91+
* @var bool whether to preserve array keys when serializing collection data.
92+
* Set this to `true` to allow serialization of a collection as a JSON object where array keys are
93+
* used to index the model objects. The default is to serialize all collections as array, regardless
94+
* of how the array is indexed.
95+
* @see serializeDataProvider()
96+
* @since 2.0.10
97+
*/
98+
public $preserveKeys = false;
99+
100+
101+
/**
102+
* {@inheritdoc}
103+
*/
104+
public function init()
105+
{
106+
if ($this->request === null) {
107+
$this->request = Yii::$app->getRequest();
108+
}
109+
if ($this->response === null) {
110+
$this->response = Yii::$app->getResponse();
111+
}
112+
}
113+
114+
/**
115+
* Serializes the given data into a format that can be easily turned into other formats.
116+
* This method mainly converts the objects of recognized types into array representation.
117+
* It will not do conversion for unknown object types or non-object data.
118+
* The default implementation will handle [[Model]] and [[DataProviderInterface]].
119+
* You may override this method to support more object types.
120+
* @param mixed $data the data to be serialized.
121+
* @return mixed the converted data.
122+
*/
123+
public function serialize($data)
124+
{
125+
if ($data instanceof Model && $data->hasErrors()) {
126+
return $this->serializeModelErrors($data);
127+
} elseif ($data instanceof Arrayable) {
128+
return $this->serializeModel($data);
129+
} elseif ($data instanceof DataProviderInterface) {
130+
return $this->serializeDataProvider($data);
131+
}
132+
133+
return $data;
134+
}
135+
136+
/**
137+
* @return array the names of the requested fields. The first element is an array
138+
* representing the list of default fields requested, while the second element is
139+
* an array of the extra fields requested in addition to the default fields.
140+
* @see Model::fields()
141+
* @see Model::extraFields()
142+
*/
143+
protected function getRequestedFields()
144+
{
145+
$fields = $this->request->get($this->fieldsParam);
146+
$expand = $this->request->get($this->expandParam);
147+
148+
return [
149+
is_string($fields) ? preg_split('/\s*,\s*/', $fields, -1, PREG_SPLIT_NO_EMPTY) : [],
150+
is_string($expand) ? preg_split('/\s*,\s*/', $expand, -1, PREG_SPLIT_NO_EMPTY) : [],
151+
];
152+
}
153+
154+
/**
155+
* Serializes a data provider.
156+
* @param DataProviderInterface $dataProvider
157+
* @return array the array representation of the data provider.
158+
*/
159+
protected function serializeDataProvider($dataProvider)
160+
{
161+
if ($this->preserveKeys) {
162+
$models = $dataProvider->getModels();
163+
} else {
164+
$models = array_values($dataProvider->getModels());
165+
}
166+
167+
$result = [
168+
$this->collectionEnvelope => $this->serializeModels($models),
169+
];
170+
171+
if (($pagination = $dataProvider->getPagination()) !== false) {
172+
return array_merge($result, $this->serializePagination($pagination));
173+
}
174+
175+
return $result;
176+
}
177+
178+
/**
179+
* Serializes a pagination into an array.
180+
* @param Pagination $pagination
181+
* @return array the array representation of the pagination
182+
* @see addPaginationHeaders()
183+
*/
184+
protected function serializePagination($pagination)
185+
{
186+
return [
187+
$this->linksEnvelope => Link::serialize($pagination->getLinks(true)),
188+
$this->metaEnvelope => [
189+
'totalCount' => $pagination->totalCount,
190+
'pageCount' => $pagination->getPageCount(),
191+
'currentPage' => $pagination->getPage() + 1,
192+
'perPage' => $pagination->getPageSize(),
193+
],
194+
];
195+
}
196+
197+
/**
198+
* Serializes a model object.
199+
* @param Arrayable $model
200+
* @return array the array representation of the model
201+
*/
202+
protected function serializeModel($model)
203+
{
204+
if ($this->request->getIsHead()) {
205+
return null;
206+
}
207+
208+
list($fields, $expand) = $this->getRequestedFields();
209+
return $model->toArray($fields, $expand);
210+
}
211+
212+
/**
213+
* Serializes the validation errors in a model.
214+
* @param Model $model
215+
* @throws JsonRpcException exception containing the array representation of the errors
216+
*/
217+
protected function serializeModelErrors($model)
218+
{
219+
$result = [
220+
'validation_errors' => []
221+
];
222+
foreach ($model->getFirstErrors() as $name => $message) {
223+
$result['validation_errors'][] = [
224+
'field' => $name,
225+
'message' => $message,
226+
];
227+
}
228+
229+
$exception = new Exception('Data validation failed', 0);
230+
throw new InternalErrorException('Internal error', $result, $exception);
231+
}
232+
233+
/**
234+
* Serializes a set of models.
235+
* @param array $models
236+
* @return array the array representation of the models
237+
*/
238+
protected function serializeModels(array $models)
239+
{
240+
list($fields, $expand) = $this->getRequestedFields();
241+
foreach ($models as $i => $model) {
242+
if ($model instanceof Arrayable) {
243+
$models[$i] = $model->toArray($fields, $expand);
244+
} elseif (is_array($model)) {
245+
$models[$i] = ArrayHelper::toArray($model);
246+
}
247+
}
248+
249+
return $models;
250+
}
251+
}

exceptions/InternalErrorException.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
class InternalErrorException extends JsonRpcException
1010
{
1111
const CODE = -32603;
12-
public function __construct($message = "", \Exception $previous = null)
12+
public function __construct($message = "", $data = [], \Exception $previous = null)
1313
{
14-
parent::__construct($message, static::CODE, $previous);
14+
parent::__construct($message, static::CODE, $data, $previous);
1515
}
1616

1717
/**

exceptions/InvalidParamsException.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ class InvalidParamsException extends JsonRpcException
1010
{
1111
const CODE = -32602;
1212

13-
public function __construct($message = "", \Exception $previous = null)
13+
public function __construct($message = "", $data = [], \Exception $previous = null)
1414
{
15-
parent::__construct($message, static::CODE, $previous);
15+
parent::__construct($message, static::CODE, $data, $previous);
1616
}
1717

1818
/**

exceptions/InvalidRequestException.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ class InvalidRequestException extends JsonRpcException
1010
{
1111
const CODE = -32600;
1212

13-
public function __construct($message = "", \Exception $previous = null)
13+
public function __construct($message = "", $data = [], \Exception $previous = null)
1414
{
15-
parent::__construct($message, static::CODE, $previous);
15+
parent::__construct($message, static::CODE, $data, $previous);
1616
}
1717

1818
/**

exceptions/JsonRpcException.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace georgique\yii2\jsonrpc\exceptions;
44

5+
use Throwable;
56
use yii\base\Exception;
67

78
/**
@@ -10,6 +11,16 @@
1011
*/
1112
class JsonRpcException extends Exception
1213
{
14+
15+
protected $data;
16+
17+
public function __construct($message = "", $code = 0, $data = [], \Exception $previous = null)
18+
{
19+
parent::__construct($message, $code, $previous);
20+
21+
$this->data = $data;
22+
}
23+
1324
/**
1425
* @inheritdoc
1526
*/
@@ -21,4 +32,8 @@ public function getName()
2132
return 'JSON-RPC Error';
2233
}
2334
}
35+
36+
public function getData() {
37+
return $this->data;
38+
}
2439
}

0 commit comments

Comments
 (0)