From 2e3eeea361b2a6b31ceffaf1d61abf2ee764e3af Mon Sep 17 00:00:00 2001 From: cranetm Date: Wed, 18 Jan 2017 00:47:25 +0200 Subject: [PATCH] All JsonRpc2 Exception was made translatable. Added Yii error logging for response errors --- JsonRpc2/Controller.php | 16 +++-- JsonRpc2/Validator.php | 5 +- JsonRpc2/Validator/ValidateInArray.php | 19 ++++- JsonRpc2/Validator/ValidateMaxSize.php | 7 +- JsonRpc2/Validator/ValidateMinSize.php | 6 +- JsonRpc2/Validator/ValidateNotNull.php | 4 +- JsonRpc2/Validator/ValidateNull.php | 8 ++- JsonRpc2/Validator/ValidateVar.php | 29 ++++++-- JsonRpc2/Validator/Value.php | 18 ++++- README.md | 97 +++++++++++++++++++++----- 10 files changed, 166 insertions(+), 43 deletions(-) diff --git a/JsonRpc2/Controller.php b/JsonRpc2/Controller.php index 29aca27..81146ee 100644 --- a/JsonRpc2/Controller.php +++ b/JsonRpc2/Controller.php @@ -49,7 +49,7 @@ public function runAction($id, $params = []) $resultData = null; if (empty($requests)) { $isBatch = false; - $resultData = [$this->formatResponse(null, new Exception("Invalid Request", Exception::INVALID_REQUEST))]; + $resultData = [$this->formatResponse(null, new Exception(Yii::t('yii', 'Invalid Request'), Exception::INVALID_REQUEST))]; } else { foreach ($requests as $request) { if($response = $this->getActionResponse($request)) @@ -88,7 +88,7 @@ private function getActionResponse($requestObject) $error = $e; } } catch (\Exception $e) { - $error = new Exception("Internal error", Exception::INTERNAL_ERROR); + $error = new Exception(Yii::t('yii', 'Internal error'), Exception::INTERNAL_ERROR); } if (!isset($this->requestObject->id) && (empty($error) || !in_array($error->getCode(), [Exception::PARSE_ERROR, Exception::INVALID_REQUEST]))) @@ -112,7 +112,7 @@ public function createAction($id) { $action = parent::createAction($id); if (empty($action)) - throw new Exception("Method not found", Exception::METHOD_NOT_FOUND); + throw new Exception(Yii::t('yii', 'Method not found').' '.$id, Exception::METHOD_NOT_FOUND); $this->prepareActionParams($action); @@ -216,13 +216,13 @@ private function initRequest($id) private function parseAndValidateRequestObject($requestObject) { if (null === $requestObject) - throw new Exception("Parse error", Exception::PARSE_ERROR); + throw new Exception(Yii::t('yii', 'Parse error'), Exception::PARSE_ERROR); if (!is_object($requestObject) || !isset($requestObject->jsonrpc) || $requestObject->jsonrpc !== '2.0' || empty($requestObject->method) || "string" != gettype($requestObject->method) ) - throw new Exception("Invalid Request", Exception::INVALID_REQUEST); + throw new Exception(Yii::t('yii', 'Invalid Request'), Exception::INVALID_REQUEST); $this->requestObject = $requestObject; if (!isset($this->requestObject->params)) @@ -372,10 +372,12 @@ public function formatResponse($result = null, Exception $error = null, $id = nu 'id' => $id, ]; - if (!empty($error)) + if (!empty($error)) { + \Yii::error($error, 'jsonrpc'); $resultArray['error'] = $error->toArray(); - else + } else { $resultArray['result'] = $result; + } return $resultArray; } diff --git a/JsonRpc2/Validator.php b/JsonRpc2/Validator.php index 51a1a20..36bc9ed 100644 --- a/JsonRpc2/Validator.php +++ b/JsonRpc2/Validator.php @@ -41,10 +41,7 @@ protected function validate() protected function throwError($message) { - if ($this->value->parent instanceof JsonRpc2\Dto) - throw new Exception("In class ".get_class($this->value->parent). " " . $message, Exception::INVALID_PARAMS, $this->getErrorData()); - else - throw new Exception($message, Exception::INVALID_PARAMS, $this->getErrorData()); + throw new Exception($message, Exception::INVALID_PARAMS, $this->getErrorData()); } protected function getErrorData() diff --git a/JsonRpc2/Validator/ValidateInArray.php b/JsonRpc2/Validator/ValidateInArray.php index 4ba4d38..e8b6a8f 100644 --- a/JsonRpc2/Validator/ValidateInArray.php +++ b/JsonRpc2/Validator/ValidateInArray.php @@ -18,10 +18,25 @@ protected function validate() if (!empty($matches) && in_array($type, ["integer", "double", "float", "string"])) { eval("\$restrictions = {$matches[1]};"); if (!is_array($restrictions)) - throw new Exception(get_class($this).": Invalid syntax in {$this->value->name} tag @inArray{$matches[2]}", Exception::INTERNAL_ERROR); + throw new Exception( + \Yii::t('yii', "{className}: Invalid syntax in {valueName} tag @inArray{restrictions}", + [ + 'className' => get_class($this->value->parent), + 'valueName' => $this->value->getFullName(), + 'restrictions' => $matches[2], + ] + ), + Exception::INTERNAL_ERROR); if (!empty($restrictions) && !in_array($this->value->data, $restrictions)) - $this->throwError(sprintf("For property '{$this->value->name}' value '{$this->value->data}' is not allowed. Allowed values is '%s'", implode("','", $restrictions))); + $this->throwError( + \Yii::t('yii', "Value '{valueData}' is not allowed for {valueName}. Allowed values is '{restrictions}'", + [ + 'valueName' => $this->value->getFullName(), + 'valueData' => $this->value->data, + 'restrictions' => implode("','", $restrictions), + ] + )); } } diff --git a/JsonRpc2/Validator/ValidateMaxSize.php b/JsonRpc2/Validator/ValidateMaxSize.php index 4386855..8426fe7 100644 --- a/JsonRpc2/Validator/ValidateMaxSize.php +++ b/JsonRpc2/Validator/ValidateMaxSize.php @@ -19,7 +19,12 @@ protected function validate() || $type === "string" && mb_strlen($this->value->data) > $maxSize || in_array($type, ["integer", "double", "float"]) && $this->value->data > $maxSize ) { - $this->throwError("For property '{$this->value->name}' allowed max size is {$maxSize}"); + $this->throwError( + \Yii::t('yii', 'For {valueName} allowed max size is {maxSize}', + ['valueName' => $this->value->getFullName(), 'maxSize' => $maxSize] + ) + ); + } } } \ No newline at end of file diff --git a/JsonRpc2/Validator/ValidateMinSize.php b/JsonRpc2/Validator/ValidateMinSize.php index 5bf24c7..035b8c3 100644 --- a/JsonRpc2/Validator/ValidateMinSize.php +++ b/JsonRpc2/Validator/ValidateMinSize.php @@ -20,7 +20,11 @@ protected function validate() || $type === "string" && mb_strlen($this->value->data) < $minSize || in_array($type, ["integer", "double", "float"]) && $this->value->data < $minSize ) { - $this->throwError("For property '{$this->value->name}' allowed min size is {$minSize}"); + $this->throwError( + \Yii::t('yii', 'For {valueName} allowed min size is {minSize}', + ['valueName' => $this->value->getFullName(), 'minSize' => $minSize] + ) + ); } } } \ No newline at end of file diff --git a/JsonRpc2/Validator/ValidateNotNull.php b/JsonRpc2/Validator/ValidateNotNull.php index 59811b9..b6750f2 100644 --- a/JsonRpc2/Validator/ValidateNotNull.php +++ b/JsonRpc2/Validator/ValidateNotNull.php @@ -14,6 +14,8 @@ class ValidateNotNull extends JsonRpc2\Validator protected function validate() { if (null === $this->value->data) - $this->throwError("Property '{$this->value->name}' is required and cannot be Null."); + $this->throwError( + \Yii::t('yii', "{valueName} is required and cannot be Null.", ['valueName' => $this->value->getFullName()]) + ); } } \ No newline at end of file diff --git a/JsonRpc2/Validator/ValidateNull.php b/JsonRpc2/Validator/ValidateNull.php index 6c24414..71cbc99 100644 --- a/JsonRpc2/Validator/ValidateNull.php +++ b/JsonRpc2/Validator/ValidateNull.php @@ -13,6 +13,12 @@ class ValidateNull extends JsonRpc2\Validator */ protected function validate() { - throw new Exception("@Null for '{$this->value->name}' is deprecated. All values can be NULL by default. Please use @NotNull for required properties.", Exception::INVALID_PARAMS); + throw new Exception( + \Yii::t('yii', + '@Null for {valueName} is deprecated. All values can be NULL by default. Please use @NotNull for required properties.', + ['valueName' => $this->value->getFullName()] + ), + Exception::INVALID_PARAMS + ); } } \ No newline at end of file diff --git a/JsonRpc2/Validator/ValidateVar.php b/JsonRpc2/Validator/ValidateVar.php index 181d32d..9b058e1 100644 --- a/JsonRpc2/Validator/ValidateVar.php +++ b/JsonRpc2/Validator/ValidateVar.php @@ -4,7 +4,6 @@ use JsonRpc2; use JsonRpc2\Exception; -use JsonRpc2\Helper; class ValidateVar extends JsonRpc2\Validator { @@ -33,15 +32,25 @@ private function bringValueToType($parent, $type, $value) $typeParts = explode("[]", $type); $singleType = current($typeParts); if (count($typeParts) > 2) - throw new Exception(sprintf("In %s type '$type' is invalid", get_class($parent)), Exception::INTERNAL_ERROR); + throw new Exception( + \Yii::t('yii', 'In {className} type \'{type}\' is invalid', + ['className' => get_class($parent), 'type' => $type] + ), + Exception::INTERNAL_ERROR + ); //for array type if (count($typeParts) === 2) { if (!is_array($value)) { if ($parent instanceof \JsonRpc2\Dto) - throw new Exception(sprintf("In %s value has type %s, but array expected", get_class($parent), gettype($value)), Exception::INTERNAL_ERROR); + throw new Exception( + \Yii::t('yii', 'In {className} value has type \'{type}\', but array expected', + ['className' => get_class($parent), 'type' => gettype($value)] + ), + Exception::INTERNAL_ERROR + ); else - throw new Exception("Value has type %s, but array expected", gettype($value), Exception::INTERNAL_ERROR); + throw new Exception(\Yii::t('yii', 'Value has type \'{type}\', but array expected', ['type' => gettype($value)]), Exception::INTERNAL_ERROR); } foreach ($value as $key=>$childValue) { @@ -56,7 +65,12 @@ private function bringValueToType($parent, $type, $value) } if (class_exists($type)) { if (!is_subclass_of($type, '\\JsonRpc2\\Dto')) - throw new Exception(sprintf("In %s class '%s' MUST be instance of '\\JsonRpc2\\Dto'", get_class($parent), $type), Exception::INTERNAL_ERROR); + throw new Exception( + \Yii::t('yii', 'In {className} class \'{type}\' MUST be instance of \'\\JsonRpc2\\Dto\'', + ['className' => get_class($parent), 'type' => $type] + ), + Exception::INTERNAL_ERROR + ); return new $type($value); } else { if (is_array($value) || $value instanceof \stdClass) { @@ -71,7 +85,10 @@ private function bringValueToType($parent, $type, $value) case "double": return (float)$value; case "array": - throw new Exception("Parameter type 'array' is deprecated. Use square brackets with simply types or DTO based classes instead.", Exception::INTERNAL_ERROR); + throw new Exception( + \Yii::t('yii', 'Parameter type \'array\' is deprecated. Use square brackets with simply types or DTO based classes instead.'), + Exception::INTERNAL_ERROR + ); case "bool": return (bool)$value; } diff --git a/JsonRpc2/Validator/Value.php b/JsonRpc2/Validator/Value.php index c2689b8..a120611 100644 --- a/JsonRpc2/Validator/Value.php +++ b/JsonRpc2/Validator/Value.php @@ -2,6 +2,8 @@ namespace JsonRpc2\Validator; +use JsonRpc2\Dto; + class Value { /** @@ -32,4 +34,18 @@ public function getType() { return gettype($this->data); } -} \ No newline at end of file + + /** + * Return value's name with DTO's class prefix if exists + * @return string + */ + public function getFullName() + { + if ($this->parent instanceof Dto) + return get_class($this->parent) . '::$' . $this->name; + elseif ('result' === $this->name) + return 'Result'; + + return '$' . $this->name; + } +} diff --git a/README.md b/README.md index 500568c..b63d205 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,10 @@ - [Example 4](#example-4) - [Response data validation](#response-data-validation) - [Example 5](#example-5) - - [Null values and @null tags](#null-values-and-null-tags) - - [Value restrictions and @inArray tag](#value-restrictions-and-inarray-tag) + - [Validators](#validators) + - [Null values and @notNull tag](#null-values-and-not-null-tag) + - [Value restrictions and @inArray tag](#value-restrictions-and-inarray-tag) + - [Limit value with @minSize & @maxSize tags](#minsize-maxsize-tags) - [CORS Support](#cors-support) ## Validation features: @@ -21,8 +23,10 @@ 2. Validation for params types
2.1 Using DTOs as structured type
2.2 Using square brackets for array types like string[], int[], bool[] or for DTO: ClassName[]
-3. @null tag to allowing null values (by default all data brings to specific type) -4. @inArray tag to restrict values like @inArray["red","brown","yellow"]. Works only with string and int datatypes. +3. Validators: + 3.1 @notNull tag to deny null values (make it required) + 3.2 @inArray tag to restrict values like @inArray["red","brown","yellow"]. Works only with string and int datatypes. + 3.3 @minSize & @maxSize to limit length for strings or values for numbers. ## Using @@ -288,7 +292,7 @@ class User extends Dto public $type = 'user'; /** @var string */ - public $rights; + public $rights=""; } ~~~ @@ -325,21 +329,45 @@ Every element of array from response will be converted to User DTO: ~~~ > Even if some values is missing in response array, data brings to User type with all variables described in DTO -## Null values and @null tags -By default null types are not allowed and all null values are converted to specific types: -+ string - "" -+ int/float - 0 -+ bool - false -+ DTO - empty object (if default value exists, it will use) -+ array - [] +## Validators +There are set of validators which can change value or check it for some rules (or both). +To use it just write it name in phpdoc in the new line after needed variable or property. +For example: +~~~php + /** + * @var int + * @inArray[1,2] + */ + public $example=0; +~~~ +Here we have _inArray_ validator for _$example_ property. +Script tries to find class with name _JsonRpc2\Validator\ValidateInArray_ and run _validate()_ method(see JsonRpc2\Validator folder). +If validation error occurs, response object will have data property with explanation +~~~javascript +{ + "cause":"rights", // failed property + "type":"inArray", // validator name + "value":0, // passed value + "restriction":'"1","2"' // active restrictions +} +~~~ -But in many cases you need a null value and you need to add tag @null in the next line after the description of the type of data. +### Null values and @notNull tags +DTO's values can be NULL by default if is not initialized(same as in php). +If you need default empty value but not NULL, define it and if you pass NULL defined default value will use(like empty string in example): +~~~php + /** + * @var string + */ + public $rights=""; +~~~ +But in many cases you need required value which must be passed in DTO. In this case clear default value(if exists) and use tag @notNull . -Let's update User's rights variable to be nullable +Let's update User's rights variable to be not NULL and without default value(required) ~~~php /** * @var string - * @null + * @notNull */ public $rights; ~~~ @@ -348,11 +376,11 @@ Let's update User's rights variable to be nullable {"jsonrpc": "2.0","id": 1,"method": "get-users","params": []} //response -{"jsonrpc":"2.0","id":1,"result":[{"id":1,"name":"Marco Polo","type":"admin","rights":null},{"id":234,"name":"John Doe","type":"user","rights":"settings"}]} +{"jsonrpc":"2.0","id":1,"error":{"code":-32603,"message":"JsonRpc2\\Dto\\User::$rights is required and cannot be Null.","data":{"cause":"rights","value":null,"type":"notNull","restriction":""}}} ~~~ -As we can see, rights variable for Marco Polo is null now. +As we can see, rights variable now required. -## Value restrictions and @inArray tag +### Value restrictions and @inArray tag There are many cases where the value may be limited to several variants and should be validated for their presence.
How it works?
Let's make restrictions for variable User's rights and try to make request. @@ -369,7 +397,7 @@ Let's make restrictions for variable User's rights and try to make request. {"jsonrpc": "2.0","id": 1,"method": "get-users","params": []} //response -{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"string value '' is not allowed. Allowed values is 'dashboard','settings'","data":null},"result":[]} +{"jsonrpc":"2.0","id":1,"error":{"code":-32603,"message":"Value '' is not allowed for JsonRpc2\\Dto\\User::$rights property. Allowed values is 'dashboard','settings'","data":{"cause":"rights","value":"","type":"inArray","restriction":"\"dashboard\",\"settings\""}}} ~~~ Ups... there is error occurs for Marco Polo and about null value in rights which converts to string and became empty string "".
But there are restrictions with no empty strings (["dashboard","settings"]) so we have an error.
@@ -390,6 +418,37 @@ And response will be {"jsonrpc":"2.0","id":1,"result":[{"id":1,"name":"Marco Polo","type":"admin","rights":"dashboard"},{"id":234,"name":"John Doe","type":"user","rights":"settings"}]} ~~~ +### Limit value with @minSize & @maxSize tags +This tags limit length for strings or values for numbers. +~~~php + //can be used together + /** + * @var string + * @minSize 15 + * @maxSize 50 + */ + public $name; + + //...or separatly + /** + * @var string + * @minSize 15 + */ + public $name; + + //for numbers + /** + * @var int + * @minSize 100 + * @minSize 500 + */ + public $money; +~~~ +If values is out of this ranges, you will have an error +~~~javascript +{"jsonrpc":"2.0","id":1,"error":{"code":-32603,"message":"For JsonRpc2\Dto\User::$money allowed min size is 100","data":{"cause":"money","value":37,"type":"minSize","restriction":"100"}}} +~~~ + ## CORS Support Extention supports CORS requests from 1.2.5 release. You may use CORS filter by attaching it as a behavior to a controller, just follow instructions [here](http://www.yiiframework.com/doc-2.0/yii-filters-cors.html)