From 7f8850bc7013d45476784e24cec42f5321024961 Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Sun, 19 Jan 2014 14:36:10 -0800 Subject: [PATCH 01/10] Adds customizable parsing functionality to the web\Request class (Issue #2043) --- framework/web/JsonParser.php | 46 ++++++++++++++++++++++++ framework/web/Request.php | 15 ++++++-- framework/web/RequestParserInterface.php | 23 ++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 framework/web/JsonParser.php create mode 100644 framework/web/RequestParserInterface.php diff --git a/framework/web/JsonParser.php b/framework/web/JsonParser.php new file mode 100644 index 0000000000..91cd999def --- /dev/null +++ b/framework/web/JsonParser.php @@ -0,0 +1,46 @@ + + * @since 2.0 + */ +class JsonParser implements RequestParserInterface +{ + /** + * @var boolean whether to return objects in terms of associative arrays. + */ + public $asArray = true; + + /** + * @var boolean whether to throw an exception if the body is invalid json + */ + public $throwException = false; + + /** + * @param string $rawBody the raw HTTP request body + * @return array parameters parsed from the request body + */ + public function parse($rawBody) + { + try { + return Json::encode($rawBody, $this->asArray); + } catch (InvalidParamException $e) { + if ($this->throwException) { + throw $e; + } + return null; + } + } +} diff --git a/framework/web/Request.php b/framework/web/Request.php index 4f33b0c22d..4c5aa4dad2 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -128,6 +128,13 @@ class Request extends \yii\base\Request * @see getRestParams() */ public $restVar = '_method'; + /** + * @var array the parsers for converting the raw request body into [[restParams]] + * The array keys are the request content-types, and the array values are the + * corresponding configurations for creating the parser objects. + * @see getRestParams() + */ + public $parsers = []; private $_cookies; @@ -257,8 +264,12 @@ class Request extends \yii\base\Request if ($this->_restParams === null) { if (isset($_POST[$this->restVar])) { $this->_restParams = $_POST; - } elseif(strncmp($this->getContentType(), 'application/json', 16) === 0) { - $this->_restParams = Json::decode($this->getRawBody(), true); + } else if (isset($this->parsers[$this->getContentType()])) { + $parser = Yii::createObject($this->parsers[$this->getContentType()]); + if (! $parser instanceof RequestParserInterface) { + throw new InvalidConfigException("The '{$this->contentType}' request parser is invalid. It must implement the RequestParserInterface."); + } + $this->_restParams = $parser->parse($this->getRawBody()); } else { $this->_restParams = []; mb_parse_str($this->getRawBody(), $this->_restParams); diff --git a/framework/web/RequestParserInterface.php b/framework/web/RequestParserInterface.php new file mode 100644 index 0000000000..dfeed558f5 --- /dev/null +++ b/framework/web/RequestParserInterface.php @@ -0,0 +1,23 @@ + + * @since 2.0 + */ +interface RequestParserInterface +{ + /** + * @param string $rawBody the raw HTTP request body + * @return array parameters parsed from the request body + */ + public function parse($rawBody); +} From 00785d38c0adf730034a0be6eeba67c166634bfa Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Sun, 19 Jan 2014 14:45:23 -0800 Subject: [PATCH 02/10] Added support for custom web\Request parsers (#2043) --- framework/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 0df701826d..3204d070f1 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.0 beta under development ---------------------------- +- Enh #2043: Added support for custom web\Request parsers (danschmidt5189) - Bug #1326: The `visible` setting for `DetailView` doesn't work as expected (qiangxue) - Bug #1446: Logging while logs are processed causes infinite loop (qiangxue) - Bug #1497: Localized view files are not correctly returned (mintao) From 1026b9da5727d1652d4a2e4be25ea15a0ed0e1ee Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Sun, 19 Jan 2014 14:48:35 -0800 Subject: [PATCH 03/10] Tabs vs. spaces --- framework/web/JsonParser.php | 44 ++++++++++++++++++------------------ framework/web/Request.php | 22 +++++++++--------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/framework/web/JsonParser.php b/framework/web/JsonParser.php index 91cd999def..9102316a69 100644 --- a/framework/web/JsonParser.php +++ b/framework/web/JsonParser.php @@ -18,29 +18,29 @@ use yii\helpers\Json; */ class JsonParser implements RequestParserInterface { - /** - * @var boolean whether to return objects in terms of associative arrays. - */ - public $asArray = true; + /** + * @var boolean whether to return objects in terms of associative arrays. + */ + public $asArray = true; - /** - * @var boolean whether to throw an exception if the body is invalid json - */ - public $throwException = false; + /** + * @var boolean whether to throw an exception if the body is invalid json + */ + public $throwException = false; - /** - * @param string $rawBody the raw HTTP request body - * @return array parameters parsed from the request body - */ + /** + * @param string $rawBody the raw HTTP request body + * @return array parameters parsed from the request body + */ public function parse($rawBody) - { - try { - return Json::encode($rawBody, $this->asArray); - } catch (InvalidParamException $e) { - if ($this->throwException) { - throw $e; - } - return null; - } - } + { + try { + return Json::encode($rawBody, $this->asArray); + } catch (InvalidParamException $e) { + if ($this->throwException) { + throw $e; + } + return null; + } + } } diff --git a/framework/web/Request.php b/framework/web/Request.php index 4c5aa4dad2..198d86e15a 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -128,13 +128,13 @@ class Request extends \yii\base\Request * @see getRestParams() */ public $restVar = '_method'; - /** - * @var array the parsers for converting the raw request body into [[restParams]] - * The array keys are the request content-types, and the array values are the - * corresponding configurations for creating the parser objects. - * @see getRestParams() - */ - public $parsers = []; + /** + * @var array the parsers for converting the raw request body into [[restParams]] + * The array keys are the request content-types, and the array values are the + * corresponding configurations for creating the parser objects. + * @see getRestParams() + */ + public $parsers = []; private $_cookies; @@ -266,10 +266,10 @@ class Request extends \yii\base\Request $this->_restParams = $_POST; } else if (isset($this->parsers[$this->getContentType()])) { $parser = Yii::createObject($this->parsers[$this->getContentType()]); - if (! $parser instanceof RequestParserInterface) { - throw new InvalidConfigException("The '{$this->contentType}' request parser is invalid. It must implement the RequestParserInterface."); - } - $this->_restParams = $parser->parse($this->getRawBody()); + if (! $parser instanceof RequestParserInterface) { + throw new InvalidConfigException("The '{$this->contentType}' request parser is invalid. It must implement the RequestParserInterface."); + } + $this->_restParams = $parser->parse($this->getRawBody()); } else { $this->_restParams = []; mb_parse_str($this->getRawBody(), $this->_restParams); From 7c98ef591cb8c93180cb057a202d1c2090aef40f Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Sun, 19 Jan 2014 14:51:16 -0800 Subject: [PATCH 04/10] Fixed type in JsonParser --- framework/web/JsonParser.php | 2 +- framework/web/RequestParserInterface.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/framework/web/JsonParser.php b/framework/web/JsonParser.php index 9102316a69..8453ed0ff8 100644 --- a/framework/web/JsonParser.php +++ b/framework/web/JsonParser.php @@ -35,7 +35,7 @@ class JsonParser implements RequestParserInterface public function parse($rawBody) { try { - return Json::encode($rawBody, $this->asArray); + return Json::decode($rawBody, $this->asArray); } catch (InvalidParamException $e) { if ($this->throwException) { throw $e; diff --git a/framework/web/RequestParserInterface.php b/framework/web/RequestParserInterface.php index dfeed558f5..42e6ca412e 100644 --- a/framework/web/RequestParserInterface.php +++ b/framework/web/RequestParserInterface.php @@ -15,9 +15,9 @@ namespace yii\web; */ interface RequestParserInterface { - /** - * @param string $rawBody the raw HTTP request body - * @return array parameters parsed from the request body - */ + /** + * @param string $rawBody the raw HTTP request body + * @return array parameters parsed from the request body + */ public function parse($rawBody); } From 813a008da2c31f0bb2afbff9a9de502e0b7f1a5f Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 20 Jan 2014 12:51:43 +0100 Subject: [PATCH 05/10] improved request body parser docs and configuration added a fallback for handling all request types fixes #2066, related to #2043 --- framework/CHANGELOG.md | 2 +- framework/web/JsonParser.php | 17 ++++++---- framework/web/Request.php | 41 +++++++++++++++++++----- framework/web/RequestParserInterface.php | 8 +++-- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 40676d8a5f..154c064a27 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,7 +4,6 @@ Yii Framework 2 Change Log 2.0.0 beta under development ---------------------------- -- Enh #2043: Added support for custom web\Request parsers (danschmidt5189) - Bug #1326: The `visible` setting for `DetailView` doesn't work as expected (qiangxue) - Bug #1446: Logging while logs are processed causes infinite loop (qiangxue) - Bug #1497: Localized view files are not correctly returned (mintao) @@ -74,6 +73,7 @@ Yii Framework 2 Change Log - Enh #1973: `yii message/extract` is now able to generate `.po` files (SergeiKutanov, samdark) - Enh #1984: ActionFilter will now mark event as handled when action run is aborted (cebe) - Enh #2003: Added `filter` property to `ExistValidator` and `UniqueValidator` to support adding additional filtering conditions (qiangxue) +- Enh #2043: Added support for custom request body parsers (danschmidt5189, cebe) - Enh #2051: Do not save null data into database when using RBAC (qiangxue) - Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) diff --git a/framework/web/JsonParser.php b/framework/web/JsonParser.php index 8453ed0ff8..2b1b1b5406 100644 --- a/framework/web/JsonParser.php +++ b/framework/web/JsonParser.php @@ -11,7 +11,7 @@ use yii\base\InvalidParamException; use yii\helpers\Json; /** - * Parses a raw HTTP request using Json::encode() + * Parses a raw HTTP request using [[yii\helpers\Json::decode()]] * * @author Dan Schmidt * @since 2.0 @@ -22,23 +22,26 @@ class JsonParser implements RequestParserInterface * @var boolean whether to return objects in terms of associative arrays. */ public $asArray = true; - /** - * @var boolean whether to throw an exception if the body is invalid json + * @var boolean whether to throw a [[BadRequestHttpException]] if the body is invalid json */ - public $throwException = false; + public $throwException = true; + /** - * @param string $rawBody the raw HTTP request body + * Parses a HTTP request body. + * @param string $rawBody the raw HTTP request body. + * @param string $contentType the content type specified for the request body. * @return array parameters parsed from the request body + * @throws BadRequestHttpException if the body contains invalid json and [[throwException]] is `true`. */ - public function parse($rawBody) + public function parse($rawBody, $contentType) { try { return Json::decode($rawBody, $this->asArray); } catch (InvalidParamException $e) { if ($this->throwException) { - throw $e; + throw new BadRequestHttpException('Invalid JSON data in request body: ' . $e->getMessage(), 0, $e); } return null; } diff --git a/framework/web/Request.php b/framework/web/Request.php index 198d86e15a..f203361fb3 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -129,9 +129,22 @@ class Request extends \yii\base\Request */ public $restVar = '_method'; /** - * @var array the parsers for converting the raw request body into [[restParams]] - * The array keys are the request content-types, and the array values are the - * corresponding configurations for creating the parser objects. + * @var array the parsers for converting the raw HTTP request body into [[restParams]]. + * The array keys are the request `Content-Types`, and the array values are the + * corresponding configurations for [[Yii::createObject|creating the parser objects]]. + * A parser must implement the [[RequestParserInterface]]. + * + * To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example: + * + * ``` + * [ + * 'application/json' => 'yii\web\JsonParser', + * ] + * ``` + * + * To register a parser for parsing all request types you can use `'*'` as the array key. + * This one will be used as a fallback in case no other types match. + * * @see getRestParams() */ public $parsers = []; @@ -256,20 +269,32 @@ class Request extends \yii\base\Request /** * Returns the request parameters for the RESTful request. + * + * Request parameters are determined using the parsers configured in [[parsers]] property. + * If no parsers are configured for the current [[contentType]] it uses the PHP function [[mb_parse_str()]] + * to parse the [[rawBody|request body]]. * @return array the RESTful request parameters + * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]]. * @see getMethod() */ public function getRestParams() { if ($this->_restParams === null) { + $contentType = $this->getContentType(); if (isset($_POST[$this->restVar])) { $this->_restParams = $_POST; - } else if (isset($this->parsers[$this->getContentType()])) { - $parser = Yii::createObject($this->parsers[$this->getContentType()]); - if (! $parser instanceof RequestParserInterface) { - throw new InvalidConfigException("The '{$this->contentType}' request parser is invalid. It must implement the RequestParserInterface."); + } elseif (isset($this->parsers[$contentType])) { + $parser = Yii::createObject($this->parsers[$contentType]); + if (!($parser instanceof RequestParserInterface)) { + throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface."); } - $this->_restParams = $parser->parse($this->getRawBody()); + $this->_restParams = $parser->parse($this->getRawBody(), $contentType); + } elseif (isset($this->parsers['*'])) { + $parser = Yii::createObject($this->parsers['*']); + if (!($parser instanceof RequestParserInterface)) { + throw new InvalidConfigException("The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface."); + } + $this->_restParams = $parser->parse($this->getRawBody(), $contentType); } else { $this->_restParams = []; mb_parse_str($this->getRawBody(), $this->_restParams); diff --git a/framework/web/RequestParserInterface.php b/framework/web/RequestParserInterface.php index 42e6ca412e..b819b0b964 100644 --- a/framework/web/RequestParserInterface.php +++ b/framework/web/RequestParserInterface.php @@ -8,7 +8,7 @@ namespace yii\web; /** - * Interface for objects that parse the raw request body into a parameters array + * Interface for classes that parse the raw request body into a parameters array. * * @author Dan Schmidt * @since 2.0 @@ -16,8 +16,10 @@ namespace yii\web; interface RequestParserInterface { /** - * @param string $rawBody the raw HTTP request body + * Parses a HTTP request body. + * @param string $rawBody the raw HTTP request body. + * @param string $contentType the content type specified for the request body. * @return array parameters parsed from the request body */ - public function parse($rawBody); + public function parse($rawBody, $contentType); } From 36c296378ba04936f0f9b493a88eabc169df287a Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 20 Jan 2014 13:31:32 +0100 Subject: [PATCH 06/10] remove _method param from rest params --- framework/web/Request.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/web/Request.php b/framework/web/Request.php index f203361fb3..f9563dac2e 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -283,6 +283,7 @@ class Request extends \yii\base\Request $contentType = $this->getContentType(); if (isset($_POST[$this->restVar])) { $this->_restParams = $_POST; + unset($this->_restParams[$this->restVar]); } elseif (isset($this->parsers[$contentType])) { $parser = Yii::createObject($this->parsers[$contentType]); if (!($parser instanceof RequestParserInterface)) { @@ -860,7 +861,7 @@ class Request extends \yii\base\Request } /** - * Returns request content-type + * Returns request content-type * The Content-Type header field indicates the MIME type of the data * contained in [[getRawBody()]] or, in the case of the HEAD method, the * media type that would have been sent had the request been a GET. From 26514419cc8e67782b0131cb0acde4b5a1cf467b Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 20 Jan 2014 16:39:25 +0400 Subject: [PATCH 07/10] Added note about hash to Pagination::$params phpdoc --- framework/data/Pagination.php | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/data/Pagination.php b/framework/data/Pagination.php index 8fa8b87b61..ece266ccae 100644 --- a/framework/data/Pagination.php +++ b/framework/data/Pagination.php @@ -85,6 +85,7 @@ class Pagination extends Object /** * @var array parameters (name => value) that should be used to obtain the current page number * and to create new pagination URLs. If not set, all parameters from $_GET will be used instead. + * In order to add hash to all links use `'#' => 'hashtag'`. * * The array element indexed by [[pageVar]] is considered to be the current page number. * If the element does not exist, the current page number is considered 0. From 86933cd745988826d7e95bfd1329f642540b9f8b Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 20 Jan 2014 17:15:40 +0400 Subject: [PATCH 08/10] Fixed hash doc for pagination --- framework/data/Pagination.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/data/Pagination.php b/framework/data/Pagination.php index ece266ccae..d8908b2c0e 100644 --- a/framework/data/Pagination.php +++ b/framework/data/Pagination.php @@ -85,7 +85,8 @@ class Pagination extends Object /** * @var array parameters (name => value) that should be used to obtain the current page number * and to create new pagination URLs. If not set, all parameters from $_GET will be used instead. - * In order to add hash to all links use `'#' => 'hashtag'`. + * + * In order to add hash to all links use array_merge($_GET, ['#' => 'my-hash']). * * The array element indexed by [[pageVar]] is considered to be the current page number. * If the element does not exist, the current page number is considered 0. From ed14eaaae50815ef2c0370000eabe4d607e585b0 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 20 Jan 2014 14:33:04 +0100 Subject: [PATCH 09/10] added docs about # link to sort and paginiation fixes #2072 --- framework/data/Pagination.php | 2 +- framework/data/Sort.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/data/Pagination.php b/framework/data/Pagination.php index d8908b2c0e..1c2b534518 100644 --- a/framework/data/Pagination.php +++ b/framework/data/Pagination.php @@ -86,7 +86,7 @@ class Pagination extends Object * @var array parameters (name => value) that should be used to obtain the current page number * and to create new pagination URLs. If not set, all parameters from $_GET will be used instead. * - * In order to add hash to all links use array_merge($_GET, ['#' => 'my-hash']). + * In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`. * * The array element indexed by [[pageVar]] is considered to be the current page number. * If the element does not exist, the current page number is considered 0. diff --git a/framework/data/Sort.php b/framework/data/Sort.php index 8e86e00be2..ab49f49202 100644 --- a/framework/data/Sort.php +++ b/framework/data/Sort.php @@ -168,6 +168,8 @@ class Sort extends Object * @var array parameters (name => value) that should be used to obtain the current sort directions * and to create new sort URLs. If not set, $_GET will be used instead. * + * In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`. + * * The array element indexed by [[sortVar]] is considered to be the current sort directions. * If the element does not exist, the [[defaults|default order]] will be used. * From 65179b100a80b3df773165bde038175c78ea9539 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 20 Jan 2014 14:51:04 +0100 Subject: [PATCH 10/10] code style in fixtures --- framework/test/ActiveFixture.php | 1 + framework/test/DbFixture.php | 1 + framework/test/Fixture.php | 1 + framework/test/FixtureTrait.php | 1 + framework/test/InitDbFixture.php | 2 +- 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/test/ActiveFixture.php b/framework/test/ActiveFixture.php index 2ca053b957..30c1f1a248 100644 --- a/framework/test/ActiveFixture.php +++ b/framework/test/ActiveFixture.php @@ -47,6 +47,7 @@ class ActiveFixture extends BaseActiveFixture */ private $_table; + /** * @inheritdoc */ diff --git a/framework/test/DbFixture.php b/framework/test/DbFixture.php index 9651cf39c2..2b5e6cc492 100644 --- a/framework/test/DbFixture.php +++ b/framework/test/DbFixture.php @@ -28,6 +28,7 @@ abstract class DbFixture extends Fixture */ public $db = 'db'; + /** * @inheritdoc */ diff --git a/framework/test/Fixture.php b/framework/test/Fixture.php index 8ec0bbd1a0..bf2905b33e 100644 --- a/framework/test/Fixture.php +++ b/framework/test/Fixture.php @@ -35,6 +35,7 @@ class Fixture extends Component */ public $depends = []; + /** * Loads the fixture. * This method is called before performing every test method. diff --git a/framework/test/FixtureTrait.php b/framework/test/FixtureTrait.php index 78bc71dd08..fc8fafa888 100644 --- a/framework/test/FixtureTrait.php +++ b/framework/test/FixtureTrait.php @@ -38,6 +38,7 @@ trait FixtureTrait */ private $_fixtureAliases; + /** * Returns the value of an object property. * diff --git a/framework/test/InitDbFixture.php b/framework/test/InitDbFixture.php index f851d4f7ff..9e46427473 100644 --- a/framework/test/InitDbFixture.php +++ b/framework/test/InitDbFixture.php @@ -34,7 +34,7 @@ class InitDbFixture extends DbFixture public $initScript = '@app/tests/fixtures/initdb.php'; /** * @var array list of database schemas that the test tables may reside in. Defaults to - * [''], meaning using the default schema (an empty string refers to the + * `['']`, meaning using the default schema (an empty string refers to the * default schema). This property is mainly used when turning on and off integrity checks * so that fixture data can be populated into the database without causing problem. */