diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index e9de6707aa..154c064a27 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -73,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/data/Pagination.php b/framework/data/Pagination.php index 8fa8b87b61..1c2b534518 100644 --- a/framework/data/Pagination.php +++ b/framework/data/Pagination.php @@ -86,6 +86,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 `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. * 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. */ diff --git a/framework/web/JsonParser.php b/framework/web/JsonParser.php new file mode 100644 index 0000000000..2b1b1b5406 --- /dev/null +++ b/framework/web/JsonParser.php @@ -0,0 +1,49 @@ + + * @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 a [[BadRequestHttpException]] if the body is invalid json + */ + public $throwException = true; + + + /** + * 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, $contentType) + { + try { + return Json::decode($rawBody, $this->asArray); + } catch (InvalidParamException $e) { + if ($this->throwException) { + 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 4f33b0c22d..f9563dac2e 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -128,6 +128,26 @@ class Request extends \yii\base\Request * @see getRestParams() */ public $restVar = '_method'; + /** + * @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 = []; private $_cookies; @@ -249,16 +269,33 @@ 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; - } elseif(strncmp($this->getContentType(), 'application/json', 16) === 0) { - $this->_restParams = Json::decode($this->getRawBody(), true); + unset($this->_restParams[$this->restVar]); + } 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(), $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); @@ -824,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. diff --git a/framework/web/RequestParserInterface.php b/framework/web/RequestParserInterface.php new file mode 100644 index 0000000000..b819b0b964 --- /dev/null +++ b/framework/web/RequestParserInterface.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +interface RequestParserInterface +{ + /** + * 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, $contentType); +}