HTTP Methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS

Know all seven common HTTP methods, what each is conventionally used for, and understand the safety and idempotency properties that distinguish them — then use $_SERVER['REQUEST_METHOD'] in PHP to branch behavior correctly based on the incoming method.

The HTTP method is the verb of a request — it tells the server what kind of operation the client intends, independent of the path or body. Choosing the right method (and having your PHP code respect its guarantees) is central to building predictable, cacheable, and safe-to-retry web APIs. This lesson covers all seven methods you'll encounter in everyday PHP web development, plus the two properties — safety and idempotency — that determine how clients, browsers, and proxies are allowed to treat each one.

The seven methods and what they're for

Each method carries a convention that clients, servers, browsers, and intermediate caches/proxies all agree to honor, even though the HTTP protocol itself doesn't literally enforce most of it — respecting these conventions is what makes the web (and REST-style APIs in particular) predictable to build against.

• GET — retrieve a resource; no side effects expected; the default method browsers use for links and address-bar navigation • POST — submit data to create a resource or trigger a non-idempotent action (e.g. 'place an order', 'send an email') • PUT — replace a resource entirely at a known URL (send the full representation; creates it if absent, in many APIs) • PATCH — apply a partial modification to a resource (send only the fields that change) • DELETE — remove a resource at a known URL • HEAD — like GET, but the server returns only headers, no body; used to check existence/metadata (size, Last-Modified) cheaply • OPTIONS — ask the server what methods/headers are allowed on a resource, without performing any action; central to CORS preflight requests

Safety and idempotency

A method is 'safe' if it is not expected to change server state at all — the client is only asking to read something, and can do so any number of times (or not at all) with no side effects. GET, HEAD, and OPTIONS are safe. This is why browsers freely prefetch links, why search engine crawlers follow GET links but never POST forms, and why a GET request should never be the thing that, say, deletes a user's account.

A method is 'idempotent' if making the same request multiple times has the same effect on server state as making it once (the response itself can still differ, e.g. a changing timestamp). GET, HEAD, OPTIONS, PUT, and DELETE are idempotent: calling DELETE /orders/42 five times in a row leaves the same end state as calling it once (deleted) — even though the 2nd through 5th calls might return 404 since the resource is already gone. POST and PATCH are, by convention, NOT idempotent: POSTing 'place an order' three times typically creates three orders, and PATCH applying a relative change (like 'increment count by 1') three times triples the effect. This matters enormously for retry logic — an HTTP client (or a flaky network layer) can safely auto-retry a failed idempotent request, but auto-retrying a POST risks a duplicate side effect.

• Safe (read-only, no state change expected): GET, HEAD, OPTIONS • Idempotent (repeating = same end state as once): GET, HEAD, OPTIONS, PUT, DELETE • Not idempotent by convention: POST, PATCH • All safe methods are idempotent, but not all idempotent methods are safe (PUT/DELETE change state but are still idempotent)

Method properties reference

'Idempotent' describes the effect on server state, not the HTTP response body — repeated DELETEs can validly return different status codes (204 the first time, 404 afterward) while still being idempotent. PUT expects the full resource representation; sending only changed fields to PUT is a common mistake — that's what PATCH is for.

Method   | Typical use                    | Safe | Idempotent | Has body?
---------|---------------------------------|------|------------|----------
GET      | Retrieve a resource              | Yes  | Yes        | No (conventionally)
HEAD     | Retrieve headers only            | Yes  | Yes        | No
OPTIONS  | Discover allowed methods/CORS    | Yes  | Yes        | No
POST     | Create / trigger a non-idem action| No   | No         | Yes
PUT      | Replace a resource entirely      | No   | Yes        | Yes
PATCH    | Partially modify a resource      | No   | No         | Yes
DELETE   | Remove a resource                | No   | Yes        | Usually no

Code Examples

$_SERVER['REQUEST_METHOD'] is how PHP exposes the incoming request's method. A simple if/elseif ladder is enough for a tiny script; larger apps use a router that maps method+path pairs to handlers, but under the hood it's this same check. Expected result: Fetching data (safe, read-only)

This mirrors a typical single-resource REST endpoint: GET reads, PUT replaces wholesale, PATCH updates only the submitted fields, DELETE removes and returns 204 with no body. Any other method gets a 405 with an Allow header listing what IS supported, per the HTTP spec's expectation.

header('Allow: GET, PUT, PATCH, DELETE');

OPTIONS answers purely with capability metadata and an empty body — this is exactly what a browser sends automatically as a CORS 'preflight' before certain cross-origin requests. HEAD must produce the same headers a GET would (including an accurate Content-Length) but must never emit a body — getting this wrong breaks HTTP clients that use HEAD to check a resource's size before deciding whether to GET it.

Common mistake: Forgetting to exit after the HEAD branch and accidentally letting execution fall through into GET's echo would incorrectly send a body on a HEAD request, violating the method's contract.

<?php
$method = $_SERVER['REQUEST_METHOD'];

if ($method === 'GET') {
    echo "Fetching data (safe, read-only)\n";
} elseif ($method === 'POST') {
    echo "Creating a new resource\n";
} else {
    http_response_code(405);
    header('Allow: GET, POST');
    echo "Method not allowed\n";
}
<?php
// A REST-style single-resource endpoint dispatching on method
$method = $_SERVER['REQUEST_METHOD'];
header('Content-Type: application/json');

switch ($method) {
    case 'GET':
        http_response_code(200);
        echo json_encode(['id' => 42, 'name' => 'Wireless Mouse']);
        break;
    case 'PUT':
        $data = json_decode(file_get_contents('php://input'), true);
        // Replace the ENTIRE resource with $data
        http_response_code(200);
        echo json_encode(['id' => 42, 'name' => $data['name'] ?? '']);
        break;
    case 'PATCH':
        $data = json_decode(file_get_contents('php://input'), true);
        // Apply only the fields present in $data
        http_response_code(200);
        echo json_encode(['id' => 42, 'updated_fields' => array_keys($data)]);
        break;
    case 'DELETE':
        http_response_code(204); // no body on success
        break;
    default:
        http_response_code(405);
        header('Allow: GET, PUT, PATCH, DELETE');
        echo json_encode(['error' => 'Method not allowed']);
}
<?php
// Handling HEAD and OPTIONS correctly, including CORS preflight
$method = $_SERVER['REQUEST_METHOD'];

if ($method === 'OPTIONS') {
    // Preflight: answer with allowed methods/headers, no body, no side effects
    http_response_code(204);
    header('Allow: GET, POST, OPTIONS');
    header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type, Authorization');
    exit;
}

if ($method === 'HEAD') {
    // Same headers a GET would send, but PHP must emit no body
    http_response_code(200);
    header('Content-Type: application/json');
    header('Content-Length: ' . strlen(json_encode(['id' => 42])));
    exit; // critical: no echo for HEAD
}

if ($method === 'GET') {
    http_response_code(200);
    header('Content-Type: application/json');
    echo json_encode(['id' => 42, 'name' => 'Wireless Mouse']);
}

Important Rules

Use GET only for reads; never make a GET request the trigger for a state-changing side effect.

PUT replaces a resource wholesale; PATCH updates only the fields provided — don't use PUT for partial updates.

Idempotent methods (GET, HEAD, OPTIONS, PUT, DELETE) are safe to auto-retry on network failure; POST and PATCH are not, without extra safeguards like idempotency keys.

Always respond to unsupported methods with 405 Method Not Allowed and an Allow header listing what IS supported.

HEAD responses must match what the equivalent GET's headers would be, but must never include a body.

Where this shows up

REST API frameworks (Symfony routing, Laravel's Route::get/post/put/patch/delete, Slim) are built entirely around dispatching on method + path. Browsers automatically send an OPTIONS preflight before certain cross-origin fetch() calls, and getting the OPTIONS response wrong (missing CORS headers) is one of the most common causes of 'my API works in Postman but fails from the browser' bug reports.

<?php // A link that deletes an account on click // <a href="/delete-account?id=42">Delete my account</a> if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['id'])) { deleteAccount($_GET['id']); }

<?php // <form method="POST" action="/delete-account">... if ($_SERVER['REQUEST_METHOD'] === 'POST') { deleteAccount($_POST['id']); }

Triggering a destructive action from a GET breaks the safety contract: search engine crawlers, browser link-prefetching, and 'open in new tab' can all fire GET requests without user intent, causing accidental deletions. State-changing actions belong behind POST/PUT/PATCH/DELETE.

Which of these methods is idempotent but NOT considered safe? A. GET B. HEAD C. PUT D. POST

C. PUT — PUT changes server state (so it isn't 'safe'), but repeating an identical PUT leaves the resource in the same end state as doing it once, so it is idempotent. GET and HEAD are both safe and idempotent; POST is neither.

What is the key functional difference between PUT and PATCH? A. PUT is safe, PATCH is not B. PUT replaces the entire resource; PATCH applies a partial modification C. PUT is used only for images, PATCH only for text D. There is no difference; they are aliases

B. PUT replaces the entire resource; PATCH applies a partial modification — PUT expects the complete new representation of a resource and replaces it wholesale; PATCH is meant to carry only the fields that should change, leaving the rest untouched.

Why is it unsafe to auto-retry a failed POST request the way you might auto-retry a failed GET? A. POST requests are always encrypted, which slows retries B. POST is not idempotent by convention, so repeating it can create duplicate side effects (e.g. duplicate orders) C. Browsers block all POST retries automatically D. GET requests cannot be retried either, so the comparison is moot

B. POST is not idempotent by convention, so repeating it can create duplicate side effects (e.g. duplicate orders) — GET is both safe and idempotent, so retrying it is harmless. POST is neither, so blindly retrying a POST after an ambiguous failure (e.g. a timeout where the server may have already processed it) risks performing the action twice.

Write a PHP script for a /tasks/{id} style single-resource endpoint that supports GET (return the task as JSON), DELETE (return 204 with no body), and responds to any other method with 405 plus an Allow header listing GET and DELETE.

<?php $method = $_SERVER['REQUEST_METHOD']; header('Content-Type: application/json'); switch ($method) { case 'GET': http_response_code(200); echo json_encode(['id' => 1, 'title' => 'Buy milk']); break; case 'DELETE': http_response_code(204); break; default: http_response_code(405); header('Allow: GET, DELETE'); echo json_encode(['error' => 'Method not allowed']); }

Remember DELETE conventionally has no response body on success — use http_response_code(204) and no echo.