Skip to content

HTTP Methods Reference — PHP source

A searchable reference for every HTTP request method - GET, POST, PUT, PATCH, DELETE, and more. See at a glance which are safe, idempotent, and cacheable, then compare any two methods side by side.

This is the PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.

<?php
// Source file: php.php
// Language: PHP (8.1+)
//
// HTTP request-method reference — pure, deterministic data + lookups.
//
// CosmoDev polyglot showcase port of the `http-methods` tool, ported from
// src/lib/http-methods.ts. Functionally equivalent to the TypeScript original:
// identical inputs yield identical outputs (case-insensitive lookup, flag +
// free-text filtering, and a human-readable semantic comparison report).
//
// Property flags (safe / idempotent / cacheable / hasBody) follow RFC 9110
// and the MDN reference table.
//
// Display source — part of CosmoDev's polyglot tool pages.

declare(strict_types=1);

namespace CosmoDev\HttpMethods;

/**
 * One HTTP request method and its semantic properties.
 *
 * Readonly constructor promotion (PHP 8.1) gives immutable, typed records —
 * the table is structural and never mutated at runtime.
 */
final class MethodEntry
{
    public function __construct(
        public readonly string $method,
        /** Read-only semantics — no server state change. */
        public readonly bool   $safe,
        /** Repeating the call has the same effect as a single call. */
        public readonly bool   $idempotent,
        /** Responses may be stored by a cache (RFC 9110 / MDN). */
        public readonly bool   $cacheable,
        /** The method conventionally carries a request body. */
        public readonly bool   $hasBody,
        public readonly string $description,
        public readonly string $typicalUse,
    ) {}
}

/**
 * Narrowing options for {@see HttpMethods::filter()}.
 *
 * Boolean fields default to `null` so callers can distinguish "constrain to
 * false" from "no constraint" — a tri-state a bare `bool` cannot express.
 */
final class MethodFilter
{
    public function __construct(
        public readonly ?bool   $safe       = null,
        public readonly ?bool   $idempotent = null,
        public readonly ?bool   $cacheable  = null,
        /** Free text matched case-insensitively against method, description, typicalUse. */
        public readonly ?string $query      = null,
    ) {}
}

/**
 * Semantic diff between two methods.
 */
final class MethodComparison
{
    /**
     * @param bool[]   $sameSafety
     * @param string[] $differences One sentence per mismatched property.
     */
    public function __construct(
        public readonly bool   $sameSafety,
        public readonly bool   $sameIdempotence,
        public readonly array  $differences,
    ) {}
}

/**
 * Canonical table + read-only lookups for the nine HTTP request methods.
 *
 * Implemented as a static facade over a lazily-built, immutable list — the
 * canonical single source of truth that the helpers return references into.
 */
final class HttpMethods
{
    /** @return list<MethodEntry> */
    public static function all(): array
    {
        // Built once and cached in a static; the records themselves are readonly.
        static $methods = null;
        if ($methods === null) {
            $methods = [
                new MethodEntry('GET', safe: true, idempotent: true, cacheable: true, hasBody: false,
                    description: 'Retrieves a representation of the target resource; a read-only request.',
                    typicalUse: 'Fetching a web page, reading an API resource, loading an image.'),
                new MethodEntry('POST', safe: false, idempotent: false, cacheable: true, hasBody: true,
                    description: 'Submits data to be processed, typically creating a new resource or triggering an action.',
                    typicalUse: 'Submitting a form, creating a record, publishing a message.'),
                new MethodEntry('PUT', safe: false, idempotent: true, cacheable: false, hasBody: true,
                    description: 'Replaces the target resource entirely with the request body.',
                    typicalUse: 'Updating a full record at a known URL, uploading a file by its path.'),
                new MethodEntry('PATCH', safe: false, idempotent: false, cacheable: false, hasBody: true,
                    description: 'Applies a partial modification to the target resource.',
                    typicalUse: 'Updating one field of a record, toggling a flag.'),
                new MethodEntry('DELETE', safe: false, idempotent: true, cacheable: false, hasBody: false,
                    description: 'Removes the target resource.',
                    typicalUse: 'Deleting a record or file by its URL.'),
                new MethodEntry('HEAD', safe: true, idempotent: true, cacheable: true, hasBody: false,
                    description: 'Identical to GET but returns only the response headers, no body.',
                    typicalUse: 'Checking existence, size, or freshness before downloading.'),
                new MethodEntry('OPTIONS', safe: true, idempotent: true, cacheable: false, hasBody: false,
                    description: 'Describes the communication options for the target resource.',
                    typicalUse: 'CORS preflight requests, discovering allowed methods.'),
                new MethodEntry('CONNECT', safe: false, idempotent: false, cacheable: false, hasBody: false,
                    description: 'Establishes a tunnel to the server (used with TLS/HTTPS proxies).',
                    typicalUse: 'Proxying encrypted connections through an intermediary.'),
                new MethodEntry('TRACE', safe: true, idempotent: true, cacheable: false, hasBody: false,
                    description: 'Performs a message loop-back test along the path to the target (debugging only).',
                    typicalUse: 'Diagnosing request transformations by intermediaries.'),
            ];
        }
        return $methods;
    }

    /**
     * Case-insensitive single-method lookup.
     *
     * @return MethodEntry|null The entry, or null when unknown (the PHP
     *                          analogue of TS's `MethodEntry | null`).
     */
    public static function getMethod(string $name): ?MethodEntry
    {
        $n = strtoupper(trim($name));
        foreach (self::all() as $entry) {
            if ($entry->method === $n) {
                return $entry;
            }
        }
        return null;
    }

    /**
     * Filter the method set by boolean flags and an optional text query.
     *
     * A method must satisfy every present flag AND, when a query is given,
     * match it in at least one of {method, description, typicalUse}.
     *
     * @return list<MethodEntry>
     */
    public static function filter(?MethodFilter $opts = null): array
    {
        $opts ??= new MethodFilter();
        $q = strtolower(trim($opts->query ?? ''));

        return array_values(array_filter(self::all(), function (MethodEntry $m) use ($opts, $q): bool {
            // Each present flag is an AND constraint; null means skip.
            if ($opts->safe !== null && $m->safe !== $opts->safe) {
                return false;
            }
            if ($opts->idempotent !== null && $m->idempotent !== $opts->idempotent) {
                return false;
            }
            if ($opts->cacheable !== null && $m->cacheable !== $opts->cacheable) {
                return false;
            }
            if ($q !== '') {
                return str_contains(strtolower($m->method), $q)
                    || str_contains(strtolower($m->description), $q)
                    || str_contains(strtolower($m->typicalUse), $q);
            }
            return true;
        }));
    }

    /**
     * Compare two methods, surfacing where their semantics agree and differ.
     *
     * `differences` lists every mismatched property (safe, idempotent,
     * cacheable, hasBody) as a human-readable sentence, reproducing the
     * canonical phrasing verbatim so output stays identical across ports.
     */
    public static function compare(MethodEntry $a, MethodEntry $b): MethodComparison
    {
        $differences = [];

        if ($a->safe !== $b->safe) {
            $differences[] = sprintf(
                '%s is %s, %s is %s.',
                $a->method, self::safeWord($a->safe),
                $b->method, self::safeWord($b->safe),
            );
        }
        if ($a->idempotent !== $b->idempotent) {
            $differences[] = sprintf(
                '%s is %s, %s is %s.',
                $a->method, self::idempotentWord($a->idempotent),
                $b->method, self::idempotentWord($b->idempotent),
            );
        }
        if ($a->cacheable !== $b->cacheable) {
            $differences[] = sprintf(
                '%s is %s, %s is %s.',
                $a->method, self::cacheableWord($a->cacheable),
                $b->method, self::cacheableWord($b->cacheable),
            );
        }
        if ($a->hasBody !== $b->hasBody) {
            $differences[] = sprintf(
                '%s %s, %s %s.',
                $a->method, self::bodyWord($a->hasBody),
                $b->method, self::bodyWord($b->hasBody),
            );
        }

        return new MethodComparison(
            sameSafety: $a->safe === $b->safe,
            sameIdempotence: $a->idempotent === $b->idempotent,
            differences: $differences,
        );
    }

    // Phrasing helpers localize the canonical difference-sentence wording so
    // the output stays in lock-step across every polyglot port.

    private static function safeWord(bool $v): string
    {
        return $v ? 'safe' : 'not safe';
    }

    private static function idempotentWord(bool $v): string
    {
        return $v ? 'idempotent' : 'not idempotent';
    }

    private static function cacheableWord(bool $v): string
    {
        return $v ? 'cacheable' : 'not cacheable';
    }

    private static function bodyWord(bool $v): string
    {
        return $v ? 'takes a body' : 'does not take a body';
    }
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →