Every request has a method that tells the server what the client wants to do.
| Method | Meaning | Changes data? | Has a body? |
|---|---|---|---|
GET | Read a resource | No | No |
POST | Create a resource (or submit data) | Yes | Yes |
PUT | Replace a resource completely | Yes | Yes |
PATCH | Change part of a resource | Yes | Yes |
DELETE | Remove a resource | Yes | Usually no |
HEAD | Like GET, headers only | No | No |
OPTIONS | Ask what is allowed (used by CORS) | No | No |
A REST-style API
A common way to organise an API around a “products” resource:
| Request | Meaning |
|---|---|
GET /products | List products |
GET /products/42 | Show product 42 |
POST /products | Create a product |
PUT /products/42 | Replace product 42 |
PATCH /products/42 | Change some fields of product 42 |
DELETE /products/42 | Delete product 42 |
Read the method in PHP
<?php
$method = $_SERVER["REQUEST_METHOD"]; // "GET", "POST", ...
Read the data for each method
- GET:
$_GET. - POST with a normal HTML form:
$_POST. - JSON in any method (POST, PUT, PATCH): the raw body.
- PUT and PATCH: PHP does not fill
$_POST. Read the body yourself.
<?php
function readBody(string $contentType, string $rawBody): array
{
if (str_starts_with($contentType, "application/json")) {
$data = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
return is_array($data) ? $data : [];
}
parse_str($rawBody, $data); // application/x-www-form-urlencoded
return $data;
}
print_r(readBody("application/json", '{"name":"Pen","price":12}'));
print_r(readBody("application/x-www-form-urlencoded", "name=Pen&price=12"));
Array
(
[name] => Pen
[price] => 12
)
Array
(
[name] => Pen
[price] => 12
)
In a real request: $raw = file_get_contents("php://input"); and $_SERVER["CONTENT_TYPE"] ?? "". (PHP 8.4 also adds request_parse_body() for PUT and PATCH forms.)
Route by method
<?php
function handle(string $method, string $path): array
{
$routes = [
"GET /products" => [200, "list products"],
"POST /products" => [201, "create a product"],
"GET /products/1" => [200, "show product 1"],
"DELETE /products/1" => [204, "delete product 1"],
];
$key = "$method $path";
if (isset($routes[$key])) {
return $routes[$key];
}
// Is the path known, but with another method?
foreach (array_keys($routes) as $known) {
if (str_ends_with($known, " $path")) {
return [405, "method not allowed"];
}
}
return [404, "not found"];
}
foreach ([["GET", "/products"], ["POST", "/products"], ["PUT", "/products"], ["GET", "/nothing"]] as [$m, $p]) {
[$status, $message] = handle($m, $p);
echo "$m $p -> $status $message\n";
}
GET /products -> 200 list products
POST /products -> 201 create a product
PUT /products -> 405 method not allowed
GET /nothing -> 404 not found
A 405 response should also include an Allow header that lists the allowed methods.
Safe and idempotent
- Safe methods (
GET,HEAD,OPTIONS) never change data. A crawler can call them freely. - Idempotent methods (
GET,PUT,DELETE) give the same result if you repeat them. Deleting product 42 twice leaves the same state.POSTis not idempotent: sending it twice creates two records.
Browsers only send GET and POST from forms
HTML forms support only GET and POST. For PUT and DELETE from a form, many frameworks use a hidden _method field and treat a POST as the other method. APIs called with JavaScript fetch() can use all methods.
Never change data with GET
A GET request can be triggered by a link, an image tag or a crawler. Using it to delete or buy things is a security and reliability bug (see CSRF).
Use the method as the verb
/products/42), and the method gives the action. That is much cleaner than /deleteProduct.php?id=42.