HTTP verbs such as GET
and POST
let us send our intention along with the URL so we can instruct the server what to do with it. Web requests are more than just a series of addresses, and verbs contribute to the rich fabric of the journey. This chapter covers how to make and respond to HTTP requests using a selection of common HTTP verbs, including lots of examples.
I mentioned GET
and POST
because it’s very likely you’re already familiar with those. There are many verbs that can be used with HTTP—in fact, we can even invent our own—but we’ll get to that later in the chapter (see “Using Other HTTP Verbs”). First, let’s revisit GET
and POST
in some detail, looking at when to use each one and what the differences are between them.
URLs used with GET
can be bookmarked, they can be called as many times as needed, and the request should change the data it accesses. A great example of using a GET
request when filling in a web form is when using a search form, which should always use GET
. Searches can be repeated safely, and the URLs can be shared.
Consider the simple web form in Figure 2-1, which allows users to state which category of results they’d like and how many results to show. The code for displaying the form and the (placeholder) search results on the page could be something like this:
<html>
<head>
<title>
GET Form</title>
<link
rel=
"stylesheet"
href=
"http://yui.yahooapis.com/pure/0.6.0/pure-min.css"
>
</head>
<body>
<div
style=
"margin: 20px"
>
<h1>
A GET Form</h1>
<?php if(empty($_GET)): ?>
<form
name=
"search"
method=
"get"
class=
"pure-form pure-form-stacked"
>
Category:<select
name=
"category"
>
<option
value=
"entertainment"
>
Entertainment</option>
<option
value=
"sport"
>
Sport</option>
<option
value=
"technology"
>
Technology</option>
</select>
Rows per page:<select
name=
"rows"
>
<option
value=
"10"
>
10</option>
<option
value=
"20"
>
20</option>
<option
value=
"50"
>
50</option>
</select>
<input
type=
"submit"
value=
"Search"
class=
"pure-button pure-button-primary"
/>
</form>
<?php else: ?>
<p>
Wonderfully filtered search results</p>
<?php endif; ?>
</div>
</body>
</html>
You can see that PHP simply checks if it has been given some search criteria (or indeed any data in the $_GET
superglobal) and if not, it displays the empty form. If there was data, then it would process it (although probably in a more interesting way than this trivial example does). The data gets submitted on the URL when the form is filled in (GET
requests typically have no body data), resulting in a URL like this:
http://localhost/book/get-form-page.php?category=technology&rows=20
Having the data visible on the URL is a design choice. When this happens, a user can easily bookmark or share this URL with others, which is sometimes very useful, for example, to bookmark a particular set of search results, or a product page. In other use cases, such as submitting a form to update a user’s profile, we really don’t want users to be able to share or save the request that they made, so a POST
request would be more appropriate. As software developers, we need to choose whether to submit forms via GET
or POST
, and in general a good rule of thumb is that if the request is safe to repeat, then GET
is a good choice; otherwise use POST
. We’ll see more examples of the correct use of verbs in APIs as well as forms during this chapter.
The previous example showed how PHP responds to a GET
request, but how does it make one? Well, as discussed in Chapter 1, there are many ways to approach this. For a very quick solution, use PHP’s stream handling to create the complete request to send:
<?
php
$url
=
'http://localhost/book/get-form-page.php'
;
$data
=
[
"category"
=>
"technology"
,
"rows"
=>
20
];
$get_addr
=
$url
.
'?'
.
http_build_query
(
$data
);
$page
=
file_get_contents
(
$get_addr
);
echo
$page
;
In a Real World™ system, it is prudent to be cautious of the data coming in from external APIs; it is best to filter the contents of $page
before outputting it or using it anywhere else. As an alternative to using PHP’s stream features, you could use whatever functionality your existing frameworks or libraries offer, or make use of the cURL extension that is built in to PHP.
Using cURL, our code would instead look like this:
<?
php
$url
=
'http://localhost/book/get-form-page.php'
;
$data
=
[
"category"
=>
"technology"
,
"rows"
=>
20
];
$get_addr
=
$url
.
'?'
.
http_build_query
(
$data
);
$ch
=
curl_init
(
$get_addr
);
curl_setopt
(
$ch
,
CURLOPT_RETURNTRANSFER
,
1
);
$page
=
curl_exec
(
$ch
);
echo
$page
;
Either of these approaches works well when you want to fetch data into your PHP script from an external API or page. The examples here show web pages, but they apply when working with HTML, XML, JSON, or anything else.
In contrast to GET
requests, a POST
request is one that does cause change on the server that handles the request. These requests shouldn’t be repeated or bookmarked, which is why your browser warns you when it is resubmitting data. Let’s use a POST
form when the request changes data on the server side. Figure 2-2, for example, involves updating a bit of user profile information.
When a form is submitted via GET
, we can see the variables being sent on the URL. With POST
, however, the data goes into the body of the request, and the Content-Type
header denotes what kind of data can be found in the body. When we fill in the form in Figure 2-2, the request looks like this:
POST
/book/post-form-page.php
HTTP
/
1.1
Host
:
localhost
Content-Length
:
48
Accept
:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Content-Type
:
application/x-www-form-urlencoded
Accept-Encoding
:
gzip,deflate,sdch
Accept-Language
:
en-GB,en-US;q=0.8,en;q=0.6
Accept-Charset
:
ISO-8859-1,utf-8;q=0.7,*;q=0.3
email=lorna%40example.com&display_name=LornaJane
In this example, you can see the data in the body, with the Content-Type
and Content-Length
headers set appropriately so that the server can decode the response (more about content negotiation in Chapter 3). Next we’ll look at the server side of the conversation.
PHP knows how to handle form data, so it can parse this out and place the fields into $_POST
, so it will be ready for use in the script. Here is the code behind this page, showing the form without any incoming data; if data existed, it would be displayed:
<html>
<head>
<title>
POST Form</title>
<link
rel=
"stylesheet"
href=
"http://yui.yahooapis.com/pure/0.6.0/pure-min.css"
>
</head>
<body>
<div
style=
"margin: 20px"
>
<h1>
A POST Form</h1>
<?php if(empty($_POST)): ?>
<form
name=
"user"
method=
"post"
class=
"pure-form pure-form-stacked"
>
Email:<input
type=
"text"
length=
"60"
name=
"email"
/>
Display name:<input
type=
"text"
length=
"60"
name=
"display_name"
/>
<input
type=
"submit"
value=
"Go"
class=
"pure-button pure-button-primary"
/>
</form>
<?php else:
echo "New user email: " . filter_input(INPUT_POST,
"email", FILTER_VALIDATE_EMAIL);
endif; ?>
</div>
</body>
</html>
It is very common to build PHP forms and parse data in this way, but when handling HTTP requests, it is also important to consider how the requests can be made and responded to (spoiler: it looks a lot like our GET
request code).
To POST
data to this form using streams (as in “Making GET Requests”), the same basic approach can be used, but some context should be added to the stream, so it will know which methods, headers, and verbs to use:
<?
php
$url
=
'http://localhost/book/post-form-page.php'
;
$data
=
[
"email"
=>
"[email protected]"
,
"display_name"
=>
"LornaJane"
];
$options
=
[
"http"
=>
[
"method"
=>
"POST"
,
"header"
=>
"Content-Type: application/x-www-form-urlencoded"
,
"content"
=>
http_build_query
(
$data
)
]
];
$page
=
file_get_contents
(
$url
,
NULL
,
stream_context_create
(
$options
));
echo
$page
;
When POST
data is sent to the page created, the data sent appears in the output rather than in the form, so it shows “New user email: [email protected].” This code looks very similar to the previous streams example, but this example uses stream_context_create()
to add some additional information to the stream.
You can see that we added the body content as a simple string, formatted it as a URL using http_build_query()
, and indicated which content type the body is. This means that other data formats can very easily be sent by formatting the strings correctly and setting the headers.
Here is an example that makes the same POST
request again, but this time using Guzzle (these examples are for version 6 of Guzzle):
<?
php
require
"vendor/autoload.php"
;
$url
=
'http://localhost/book/post-form-page.php'
;
$data
=
[
"email"
=>
"[email protected]"
,
"display_name"
=>
"LornaJane"
];
$client
=
new
GuzzleHttpClient
();
$page
=
$client
->
post
(
$url
,
[
"form_params"
=>
$data
]);
echo
$page
->
getBody
();
This looks very similar to the previous example, but using the built-in form_params
option to Guzzle means that the Content-Type
will be specified for us (there is also a multipart
option if you need to send file uploads using Guzzle). When we make the request, we get a response object back rather than a string, but we can access the content using the getBody()
method.
In these simple examples, we can make our code make POST
requests to HTML forms because the forms have no security features. In reality, most forms will have some CSRF (Cross-Site Request Forgery) protection in them, so you’ll find that you usually can’t make requests like this against forms published on the wider Internet. I would always recommend that you include security features in your own forms—except when you’re trying out the previous examples, of course.
There are many specifications relating to HTTP, as well as protocols based upon it, and between them they define a wide selection of verbs that can be used with HTTP. Even better, there is always room to invent new HTTP verbs; so long as your client and server both know how to handle a new verb, it is valid to use it. However, be aware that not all elements of network infrastructure between these two points will necessarily know how to handle every verb. Some pieces of network infrastructure do not support PATCH
, for example, or the verbs used by the WebDAV protocol. When working with APIs, particularly RESTful ones, it is normal to make use of two additional verbs: PUT
and DELETE
. REST is covered in detail in Chapter 8, but for now it is useful to examine some examples of how to use these less common verbs in applications.
The simplest of these two is DELETE
, because it doesn’t have any body data associated with it. It is possible to see what kind of request was made to a PHP script acting as a server by inspecting the $_SERVER["REQUEST_METHOD"]
value, which indicates which verb was used in the request.
To make the request from PHP, it is necessary to set the verb and then make the request as normal. Here’s an example using the cURL extension:
<?
php
$url
=
'http://localhost/book/example-delete.php'
;
$ch
=
curl_init
(
$url
);
curl_setopt
(
$ch
,
CURLOPT_CUSTOMREQUEST
,
"DELETE"
);
curl_exec
(
$ch
);
This example simply issues a request to the $url
shown using a DELETE
verb.
Using PUT
is slightly more involved because, like POST
, it can be accompanied by data and the data can be in a variety of formats. In “Handling POST Requests”, I mentioned that for incoming form data, PHP reads form-encoded values for POST
and creates a $_POST
array for us. There is no equivalent $_PUT
superglobal, but we can still make use of the php://input stream to inspect the body data of the request to which the script is sending a response at that time.
When using PHP to respond to PUT
requests, the code runs along the lines of this example:
<?
php
if
(
$_SERVER
[
'REQUEST_METHOD'
]
==
"PUT"
)
{
$data
=
[];
$incoming
=
file_get_contents
(
"php://input"
);
parse_str
(
$incoming
,
$data
);
echo
"New user email: "
.
filter_var
(
$data
[
"email"
],
FILTER_VALIDATE_EMAIL
);
}
else
{
echo
"The request did not use a PUT method"
;
}
This example inspects the $_SERVER
superglobal to see which verb was used, and then responds accordingly. The data coming into this example is form style, meaning it uses file_get_contents()
to grab all the body data, then parse_str()
to decode it.
Be careful with parse_str()
—if the second argument is omitted, the variables will be extracted as local variables, rather than contained in an array.
In order to use PHP to make a request that the previous script can handle, it is necessary to create the contents of the body of the request and specify that it is a PUT
request. Below is an example using the Guzzle library:
<?
php
require
"vendor/autoload.php"
;
$url
=
"http://localhost/book/put-form-page.php"
;
$data
=
[
"email"
=>
"[email protected]"
,
"display_name"
=>
"LornaJane"
];
$client
=
new
GuzzleHttpClient
();
$result
=
$client
->
put
(
$url
,
[
"headers"
=>
[
"Content-Type"
=>
"application/x-www-form-urlencoded"
],
"body"
=>
http_build_query
(
$data
)
]);
echo
$result
->
getBody
();
The PUT
verb is specified in this example, and the correct header for the form-encoded data is set. We dictate the data to PUT
(manually building the form elements into a string) and then send the request. We will discuss more about other data formats in Chapter 5 and Chapter 6, which cover JSON and XML specifically, but the basic principles of preparing the data and setting the Content-Type
header accordingly still stand.
Armed with this knowledge of how to handle GET
, POST
, DELETE
, and PUT
verbs, we are able to work with many different kinds of API acting as both a client and as a server. When using other verbs, either those that already exist as part of the HTTP spec or those that are custom to your application, you can use the approaches described here for PUT
and DELETE
.