From 29a5cd20c47eecb0c9a6ca58d5b74111d1834c48 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 30 Sep 2014 00:15:28 -0400 Subject: [PATCH] more guide. --- docs/guide/README.md | 8 +- docs/guide/runtime-requests.md | 139 +++++++++++++++ docs/guide/runtime-responses.md | 258 +++++++++++++++++++++++++++ docs/internals/translation-status.md | 4 +- framework/web/HeaderCollection.php | 3 +- 5 files changed, 404 insertions(+), 8 deletions(-) create mode 100644 docs/guide/runtime-requests.md create mode 100644 docs/guide/runtime-responses.md diff --git a/docs/guide/README.md b/docs/guide/README.md index f51d308933..79c6b8cc15 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -49,9 +49,9 @@ Handling Requests * [Overview](runtime-overview.md) * [Bootstrapping](runtime-bootstrapping.md) -* **TBD** [Routing](runtime-routing.md) -* **TBD** [Requests](runtime-requests.md) -* **TBD** [Responses](runtime-responses.md) +* [Routing](runtime-routing.md) +* [Requests](runtime-requests.md) +* [Responses](runtime-responses.md) * **TBD** [Sessions and Cookies](runtime-sessions-cookies.md) * [URL Parsing and Generation](runtime-url-handling.md) * [Handling Errors](runtime-handling-errors.md) @@ -152,7 +152,7 @@ Testing ------- * [Overview](test-overview.md) -* [Testing environment setup](test-endvironment-setup.md) +* [Testing environment setup](test-environment-setup.md) * [Unit Tests](test-unit.md) * [Functional Tests](test-functional.md) * [Acceptance Tests](test-acceptance.md) diff --git a/docs/guide/runtime-requests.md b/docs/guide/runtime-requests.md new file mode 100644 index 0000000000..fb74091efe --- /dev/null +++ b/docs/guide/runtime-requests.md @@ -0,0 +1,139 @@ +Requests +======== + +Requests made to an application are represented in terms of [[yii\web\Request]] objects which provide information +such as request parameters, HTTP headers, cookies, etc. For a given request, you can get access to the corresponding +request object via the `request` [application component](structure-application-components.md). In this section, +we will describe how you can make use of this component in your applications. + + +## Request Parameters + +To get request parameters, you can call [[yii\web\Request::get()|get()]] and [[yii\web\Request::post()|post()]] methods +of the `request` component. They return the values of `$_GET` and `$_POST`, respectively. For example, + +```php +$request = Yii::$app->request; + +$get = $request->get(); +// equivalent to: $get = $_GET; + +$id = $request->get('id'); +// equivalent to: $id = isset($_GET['id']) ? $_GET['id'] : null; + +$id = $request->get('id', 1); +// equivalent to: $id = isset($_GET['id']) ? $_GET['id'] : 1; + +$post = $request->post(); +// equivalent to: $post = $_POST; + +$name = $request->post('name'); +// equivalent to: $name = isset($_POST['name']) ? $_POST['name'] : null; + +$name = $request->post('name', ''); +// equivalent to: $name = isset($_POST['name']) ? $_POST['name'] : ''; +``` + +> Info: Instead of directly accessing `$_GET` and `$_POST` to retrieve the request parameters, it is recommended + that you get them via the `request` component like shown above. This will make writing tests easier because + you can create a mock request component with faked request data. + +When implementing [RESTful APIs](rest-quick-start.md), you often need to retrieve parameters that are submitted +via PUT, PATCH or other [request methods](#request-methods). You can get these parameters by calling +the [[yii\web\Request::getBodyParam()]] methods. For example, + +```php +$request = Yii::$app->request; + +// returns all parameters +$params = $request->bodyParams; + +// returns the parameter "id" +$param = $request->getBodyParam('id'); +``` + +> Info: Unlike `GET` parameters, parameters submitted via `POST`, `PUT`, `PATCH` etc. are sent in the request body. + The `request` component will parse these parameters when you access them through the methods described above. + You can customize the way how these parameters are parsed by configuring the [[yii\web\Request::parsers]] property. + + +## Request Methods + +You can get the HTTP method used by the current request via the expression `Yii::$app->request->method`. +A whole set of boolean properties are also provided for you to check if the current method is of certain type. +For example, + +```php +$request = Yii::$app->request; + +if ($request->isAjax) { // the request is an AJAX request } +if ($request->isGet) { // the request method is GET } +if ($request->isPost) { // the request method is POST } +if ($request->isPut) { // the request method is PUT } +``` + +## Request URLs + +The `request` component provides many ways of inspecting the currently requested URL. + +Assuming the URL being requested is `http://example.com/admin/index.php/product?id=100`, you can get various +parts of this URL as summarized in the following: + +* [[yii\web\Request::url|url]]: returns `/admin/index.php/product?id=100`, which is the URL without the host info part. +* [[yii\web\Request::absoluteUrl|absoluteUrl]]: returns `http://example.com/admin/index.php/product?id=100`, + which is the whole URL including the host info part. +* [[yii\web\Request::hostInfo|hostInfo]]: returns `http://example.com`, which is the host info part of the URL. +* [[yii\web\Request::pathInfo|pathInfo]]: returns `/product`, which is the part after the entry script and + before the question mark (query string). +* [[yii\web\Request::queryString|queryString]]: returns `id=100`, which is the part after the question mark. +* [[yii\web\Request::baseUrl|baseUrl]]: returns `/admin`, which is the part after the host info and before + the entry script name. +* [[yii\web\Request::scriptUrl|scriptUrl]]: returns `/admin/index.php`, which is the URL without path info and query string. +* [[yii\web\Request::serverName|serverName]]: returns `example.com`, which is the host name in the URL. +* [[yii\web\Request::serverPort|serverPort]]: returns 80, which is the port used by the Web server. + + +## HTTP Headers + +You can get the HTTP header information through the [[yii\web\HeaderCollection|header collection]] returned +by the [[yii\web\Request::headers]] property. For example, + +```php +// $headers is an object of yii\web\HeaderCollection +$headers = Yii::$app->request->headers; + +// returns the Accept header value +$accept = $headers->get('Accept'); + +if ($headers->has('User-Agent')) { // there is User-Agent header } +``` + +The `request` component also provides support for quickly accessing some commonly used headers, including + +* [[yii\web\Request::userAgent|userAgent]]: returns the value of the `User-Agent` header. +* [[yii\web\Request::contentType|contentType]]: returns the value of the `Content-Type` header which indicates + the MIME type of the data in the request body. +* [[yii\web\Request::acceptableContentTypes|acceptableContentTypes]]: returns the content MIME types acceptable by users. + The returned types ordered by the quality score. Types with the highest scores will be returned first. +* [[yii\web\Request::acceptableLanguages|acceptableLanguages]]: returns the languages acceptable by users. + The returned languages are ordered by their preference level. The first element represents the most preferred language. + +If your application supports multiple languages and you want to display pages in the language that is the most preferred +by the end user, you may use the language negotiation method [[yii\web\Request::getPreferredLanguage()]]. +This method takes a list of languages supported by your application, compares them with [[yii\web\Request::acceptableLanguages|acceptableLanguages]], +and returns the most appropriate language. + +> Tip: You may also use the [[yii\filters\ContentNegotiator|ContentNegotiator]] filter to dynamically determine + what content type and language should be used in the response. The filter implements the content negotiation + on top the properties and methods described above. + + +## Client Information + +You can get the host name and IP address of the client machine through [[yii\web\Request::userHost|userHost]] +and [[yii\web\Request::userIP|userIP]], respectively. For example, + +```php +$userHost = Yii::$app->request->userHost; +$userIP = Yii::$app->request->userIP; +``` diff --git a/docs/guide/runtime-responses.md b/docs/guide/runtime-responses.md new file mode 100644 index 0000000000..e4b1896fa9 --- /dev/null +++ b/docs/guide/runtime-responses.md @@ -0,0 +1,258 @@ +Responses +========= + +When an application finishes handling a [request](runtime-requests.md), it generates a [[yii\web\Response|response]] object +and sends it to the end user. The response object contains information such as the HTTP status code, HTTP headers and body. +The ultimate goal of Web application development is essentially to build such response objects upon various requests. + +In most cases you should mainly deal with the `response` [application component](structure-application-components.md). +However, Yii also allows you to create your own response objects and send them to end users. + +In this section, we will describe how to compose and send responses to end users. + + +## Status Code + +One of the first things you would do when building a response is to state whether the request is successfully handled. +This is done by setting the [[yii\web\Response::statusCode]] property which can take one of the valid +[HTTP status codes](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html). For example, to indicate the request +is successfully handled, you may set the status code to be 200, like the following: + +```php +Yii::$app->response->statusCode = 200; +``` + +However, in most cases you do not need to explicitly set the status code. This is because the default value +of [[yii\web\Response::statusCode]] is 200. And if you want to indicate the request is unsuccessful, you may +throw an appropriate HTTP exception like the following: + +```php +throw new \yii\web\NotFoundHttpException; +``` + +When the [error handler](runtime-handling-errors.md) catches an exception, it will extract the status code +from the exception and assign it to the response. For the [[yii\web\NotFoundHttpException]] above, it is +associated with the HTTP status 404. The following HTTP exceptions are predefined in Yii: + +* [[yi\web\BadRequestHttpException]]: status code 400. +* [[yi\web\ConflictHttpException]]: status code 409. +* [[yi\web\ForbiddenHttpException]]: status code 403. +* [[yi\web\GoneHttpException]]: status code 410. +* [[yi\web\MethodNotAllowedHttpException]]: status code 405. +* [[yi\web\NotAcceptableHttpException]]: status code 406. +* [[yi\web\NotFoundHttpException]]: status code 404. +* [[yi\web\ServerErrorHttpException]]: status code 500. +* [[yi\web\TooManyRequestsHttpException]]: status code 429. +* [[yi\web\UnauthorizedHttpException]]: status code 401. +* [[yi\web\UnsupportedMediaTypeHttpException]]: status code 415. + +If the exception that you want to throw is not among the above list, you may create one by extending +from [[yii\web\HttpException]], or directly throw it with a status code, for example, + +```php +throw new \yii\web\HttpException(402); +``` + + +## HTTP Headers + +You can send HTTP headers by manipulating the [[yii\web\Response::headers|header collection]] in the `response` component. +For example, + +```php +$headers = Yii::$app->response->headers; + +// add a Pragma header. Existing Pragma headers will NOT be overwritten. +$headers->add('Pragma', 'no-cache'); + +// set a Pragma header. Any existing Pragma headers will be discarded. +$headers->add('Pragma', 'no-cache'); + +// remove Pragma header(s) and return the removed Pragma header values in array +$values = $headers->remove('Pragma'); +``` + +> Info: Header names are case insensitive. And the newly registered headers are not sent to the user until + the [[yii\web\Response::send()]] method is called. + + +## Response Body + +Most responses should have a body which gives the content that you want to show to end users. + +If you already have a formatted body string, you may assign it to the [[yii\web\Response::content]] property +of the response. For example, + +```php +Yii::$app->request->content = 'hello world!'; +``` + +If you data needs to be formatted before sending to end users, you should set both of the +[[yii\web\Response::format|format]] and [[yii\web\Response::data|data]] properties. The [[yii\web\Response::format|format]] +property specifies in which format should the [[yii\web\Response::data|data]] be formatted as. For example, + +```php +$response = Yii::$app->request; +$response->format = \yii\web\Response::FORMAT_JSON; +$response->data = ['message' => 'hello world']; +``` + +Yii supports the following formats out of box, each implemented by a [[yii\web\ResponseFormatterInterface|formatter]] class. +You can customize these formatters or add new ones by configuring the [[yii\web\Response::formatters]] property. + +* [[yii\web\Response::FORMAT_HTML|HTML]]: implemented by [[yii\web\HtmlResponseFormatter]]. +* [[yii\web\Response::FORMAT_XML|XML]]: implemented by [[yii\web\XmlResponseFormatter]]. +* [[yii\web\Response::FORMAT_JSON|JSON]]: implemented by [[yii\web\JsonResponseFormatter]]. +* [[yii\web\Response::FORMAT_JSONP|JSONP]]: implemented by [[yii\web\JsonResponseFormatter]]. + +While response body can be set explicitly as shown above, in most cases you may set it implicitly by the return value +of [action](structure-controllers.md) methods. A common use case is like the following: + +```php +public function actionIndex() +{ + return $this->render('index'); +} +``` + +The `index` action above returns the rendering result of the `index` view. The return value will be taken +by the `response` component, formatted and then sent to end users. + +Because by default, the response format is as [[yii\web\Response::FORMAT_HTML|HTML]], you should only return a string +in an action method. If you want to use a different response format, you should set it first before returning the data. +For example, + +```php +public function actionInfo() +{ + \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; + return [ + 'message' => 'hello world', + 'code' => 100, + ]; +} +``` + +As aforementioned, besides using the default `response` application component, you can also create your own +response objects and send them to end users. You can do so by returning such an object in an action method, like the following, + +```php +public function actionInfo() +{ + return \Yii::createObject([ + 'class' => 'yii\web\Response', + 'format' => \yii\web\Response::FORMAT_JSON, + 'data' => [ + 'message' => 'hello world', + 'code' => 100, + ], + ]); +} +``` + +> Note: If you are creating your own response objects, you will not be able to take advantage of the configurations + that you set for the `response` component in the application configuration. You can, however, use + [dependency injection](concept-di-container.md) to apply common configuration to your new response objects. + + +## Browser Redirection + +Browser redirection relies on sending a `Location` HTTP header. Because this feature is commonly used, Yii provides +some special supports for it. + +You can redirect the user browser to a URL by calling the [[yii\web\Response::redirect()]] method. The method +sets the appropriate `Location` header with the given URL and returns the response object itself. In an action method, +you can call its shortcut version [[yii\web\Controller::redirect()]]. For example, + +```php +public function actionOld() +{ + return $this->redirect('http://example.com/new', 301); +} +``` + +In the above code, the action method returns the result of the `redirect()` method. As explained before, the response +object returned by an action method will be used as the response sending to end users. + +In places other than an action method, you should call [[yii\web\Response::redirect()]] directly followed by +a call to the [[yii\web\Response::send()]] method to ensure no extra content will be appended to the response. + +```php +\Yii::$app->response->redirect('http://example.com/new', 301)->send(); +``` + +> Info: By default, the [[yii\web\Response::redirect()]] method sets the response status code to be 302 which instructs + the browser that the resource being requested is *temporarily* located in a different URI. You can pass in a status + code 301 to tell the browser that the resource has been *permanently* relocated. + +When the current request is an AJAX request, sending a `Location` header will not automatically cause the browser +redirection. To solve this problem, the [[yii\web\Response::redirect()]] method sets an `X-Redirect` header with +the redirection URL as its value. On the client side you may write JavaScript code to read this header value and +redirect the browser accordingly. + +> Info: Yii comes with a `yii.js` JavaScript file which provides a set of commonly used JavaScript utilities, + including browser redirection based on the `X-Redirect` header. Therefore, if you are using this JavaScript file + (by registering the [[yii\web\YiiAsset]] asset bundle), you do not need to write anything to support AJAX redirection. + + +## Sending Files + +Like browser redirection, file sending is another feature that relies on specific HTTP headers. Yii provides +a set of methods to support various file sending needs. They all have built-in support for HTTP range header. + +* [[yii\web\Response::sendFile()]]: sends an existing file to client. +* [[yii\web\Response::sendContentAsFile()]]: sends a text string as a file to client. +* [[yii\web\Response::sendStreamAsFile()]]: sends an existing file stream as a file to client. + +These methods have the same method signature with the response object as the return value. If the file +to be sent is very big, you should consider using [[yii\web\Response::sendStreamAsFile()]] because it is more +memory efficient. The following example shows how to send a file in a controller action: + +```php +public function actionDownload() +{ + return \Yii::$app->response->sendFile('path/to/file.txt'); +} +``` + +If you are calling the file sending method in places other than an action method, you should also call +the [[yii\web\Response::send()]] method afterwards to ensure no extra content will be appended to the response. + +```php +\Yii::$app->response->sendFile('path/to/file.txt')->send(); +``` + +Some Web servers have a special file sending support called *X-Sendfile*. The idea is to redirect the +request for a file to the Web server which will directly serve the file. As a result, the Web application +can terminate earlier while the Web server is sending the file. To use this feature, you may call +the [[yii\web\Response::xSendFile()]]. The following list summarizes how to enable the `X-Sendfile` feature +for some popular Web servers: + +- Apache: [X-Sendfile](http://tn123.org/mod_xsendfile) +- Lighttpd v1.4: [X-LIGHTTPD-send-file](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file) +- Lighttpd v1.5: [X-Sendfile](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file) +- Nginx: [X-Accel-Redirect](http://wiki.nginx.org/XSendfile) +- Cherokee: [X-Sendfile and X-Accel-Redirect](http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile) + + +## Sending Response + +The content in a response is not sent to the user until the [[yii\web\Response::send()]] method is called. +By default, this method will be called automatically at the end of [[yii\base\Application::run()]]. You can, however, +explicitly call this method to force sending out the response immediately. + +The [[yii\web\Response::send()]] method takes the following steps to send out a response: + +1. Trigger the [[yii\web\Response::EVENT_BEFORE_SEND]] event. +2. Call [[yii\web\Response::prepare()]] to format [[yii\web\Response::data|response data]] into + [[yii\web\Response::content|response content]]. +3. Trigger the [[yii\web\Response::EVENT_AFTER_PREPARE]] event. +4. Call [[yii\web\Response::sendHeaders()]] to send out the registered HTTP headers. +5. Call [[yii\web\Response::sendContent()]] to send out the response body content. +6. Trigger the [[yii\web\Response::EVENT_AFTER_SEND]] event. + +After the [[yii\web\Response::send()]] method is called once, any further call to this method will be ignored. +This means once the response is sent out, you will not be able to append more content to it. + +As you can see, the [[yii\web\Response::send()]] method triggers several useful events. By responding to +these events, it is possible to adjust or decorate the response. diff --git a/docs/internals/translation-status.md b/docs/internals/translation-status.md index ecad04daf6..d057ed84f5 100644 --- a/docs/internals/translation-status.md +++ b/docs/internals/translation-status.md @@ -28,8 +28,8 @@ structure-extensions.md | Yes runtime-overview.md | Yes runtime-bootstrapping.md | Yes runtime-routing.md | Yes -runtime-requests.md | -runtime-responses.md | +runtime-requests.md | Yes +runtime-responses.md | Yes runtime-sessions-cookies.md | runtime-url-handling.md | runtime-handling-errors.md | diff --git a/framework/web/HeaderCollection.php b/framework/web/HeaderCollection.php index 4642fd792d..39a22b3dfb 100644 --- a/framework/web/HeaderCollection.php +++ b/framework/web/HeaderCollection.php @@ -142,7 +142,7 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces /** * Removes a header. * @param string $name the name of the header to be removed. - * @return string the value of the removed header. Null is returned if the header does not exist. + * @return array the value of the removed header. Null is returned if the header does not exist. */ public function remove($name) { @@ -150,7 +150,6 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces if (isset($this->_headers[$name])) { $value = $this->_headers[$name]; unset($this->_headers[$name]); - return $value; } else { return null;