Kodeloka
  1. Kodeloka
  2. Kelas PHP
  3. Materi

3 · Web & Keamanan Dasar · Modul 12 dari 25

Menangani Permintaan HTTP, Validasi filter_input(), dan Ekstensi Uri (PHP 8.5)

Membedah siklus permintaan HTTP di PHP melalui variabel superglobal ($_SERVER, $_GET, $_POST), membaca body JSON dari php://input, memvalidasi masukan pengguna dengan filter_input() dan filter_var(), serta mengurai URL secara presisi menggunakan ekstensi Uri bawaan PHP 8.5.

1. Bagaimana Permintaan HTTP Masuk ke Skrip PHP Anda?

Ketika peramban mengirimkan permintaan HTTP ke server web (Nginx) dan diteruskan ke PHP-FPM, PHP menyiapkan lingkungan khusus untuk permintaan tersebut dan mengisi sejumlah array Superglobals tentang permintaan yang baru saja tiba:

SuperglobalApa Isinya?Catatan Keamanan Penting
$_SERVERMetode HTTP (REQUEST_METHOD), jalur URI (REQUEST_URI), alamat IP (REMOTE_ADDR), dan header HTTP (HTTP_*).Semua kunci yang diawali HTTP_* (seperti HTTP_HOST, HTTP_USER_AGENT, HTTP_REFERER, HTTP_X_FORWARDED_FOR) dikirim oleh klien dan bisa dipalsukan oleh penyerang!
$_GETParameter query string dari URL (?halaman=2&kategori=php).Semua nilai di dalam $_GET bertipe string (atau array jika ditulis ?tag[]=a&tag[]=b)!
$_POSTData tubuh permintaan dari formulir application/x-www-form-urlencoded atau multipart/form-data.Tidak terisi jika klien mengirim body berformat application/json.
$_COOKIENilai cookie yang dikirimkan oleh peramban melalui header Cookie.Dapat dimodifikasi oleh pengguna di peramban; selalu validasi!
$_FILESMetadata berkas yang diunggah lewat multipart/form-data.Wajib divalidasi dengan finfo dan move_uploaded_file().
⚠️ Jebakan Keamanan: Nilai di dalam $_GET atau $_POST Bisa Berupa array!

Banyak pengembang mengira $_GET['pencarian'] selalu berupa string lalu langsung menulis trim($_GET['pencarian']). Apa yang terjadi jika pengguna jahil memanggil URL /cari?pencarian[]=halo (dengan tanda kurung siku [])? PHP akan mengubah $_GET['pencarian'] menjadi sebuah array, sehingga trim($_GET['pencarian']) langsung melempar TypeError: trim(): Argument #1 ($string) must be of type string, array given! Solusi: Selalu periksa is_string($nilai) atau gunakan filter_input()!

<?php

declare(strict_types=1);

// AMAN: Memastikan nilai parameter benar-benar bertipe string sebelum di-trim
function ambilQueryString(string $kunci, string $bawaan = ''): string
{
    $nilai = $_GET[$kunci] ?? null;
    return is_string($nilai) ? trim($nilai) : $bawaan;
}

2. Validasi Ketat dengan filter_input() dan filter_var()

Ekstensi bawaan filter menyediakan validator standar yang aman terhadap serangan tipe data:

<?php

declare(strict_types=1);

// 1. Memvalidasi bilangan bulat dengan rentang minimum dan maksimum yang tegas
$idModul = filter_input(
    INPUT_GET,
    'id',
    FILTER_VALIDATE_INT,
    [
        'options' => [
            'min_range' => 1,
            'max_range' => 10_000,
        ],
    ],
);

// filter_input mengembalikan:
// - null  jika parameter 'id' tidak ada sama sekali di $_GET
// - false jika 'id' ada, tetapi bukan angka bulat antara 1..10000 (mis. "abc" atau "-5" atau array!)
if (!is_int($idModul)) {
    http_response_code(400);
    exit('Parameter ID modul harus berupa angka bulat positif.');
}

// 2. Memvalidasi alamat surel dan alamat IP
$surel = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL);
$ipKlien = filter_var($_SERVER['REMOTE_ADDR'] ?? '', FILTER_VALIDATE_IP);

3. Membaca Permintaan JSON (php://input) dan Mengenal request_parse_body() (PHP 8.4)

  1. Untuk Permintaan application/json: Gunakan file_get_contents('php://input') dan json_decode(..., associative: true, flags: JSON_THROW_ON_ERROR): ```php <?php

declare(strict_types=1);

function ambilPayloadJson(): array { $tipeKonten = $_SERVER['CONTENT_TYPE'] ?? ''; if (!str_starts_with(strtolower($tipeKonten), 'application/json')) { throw new InvalidArgumentException('Header Content-Type harus application/json.'); }

$bodyMentah = (string) file_get_contents('php://input'); $data = json_decode($bodyMentah, associative: true, depth: 32, flags: JSON_THROW_ON_ERROR);

if (!is_array($data)) { throw new InvalidArgumentException('Payload JSON harus berupa objek atau array.'); }

return $data; } ```

  1. Untuk Permintaan PUT / PATCH Multipart (request_parse_body() — Sejak PHP 8.4): Secara historis, PHP hanya mengurai multipart/form-data secara otomatis untuk metode POST. Sejak PHP 8.4, tersedia fungsi bawaan [$post, $files] = request_parse_body(); untuk mengurai body multipart/form-data (dan application/x-www-form-urlencoded) pada metode HTTP selain POST, misalnya PUT atau PATCH. Body hanya dapat dibaca sekali: jika php://input sudah dibaca lebih dulu, request_parse_body() mengembalikan data kosong.

4. Mengurai dan Memodifikasi URL dengan Ekstensi Uri Baru (Sejak PHP 8.5)

Fungsi lama parse_url() di PHP memiliki perilaku yang tidak konsisten terhadap standar RFC modern. Sejak PHP 8.5, ekstensi bawaan Uri menyediakan dua kelas imutabel standar:

  • Uri\Rfc3986\Uri: Untuk URI standar RFC 3986.
  • Uri\WhatWg\Url: Untuk URL yang mematuhi spesifikasi peramban WHATWG URL.
<?php

declare(strict_types=1);

use Uri\Rfc3986\Uri;

// Mengurai dan membangun URL pengalihan (redirect) yang aman di PHP 8.5:
$uriTujuan = new Uri('https://kodeloka.asadinedu.com/kelas/php');

// Objek Uri bersifat immutable; metode withQuery() menghasilkan instans Uri baru:
$uriDenganPesan = $uriTujuan->withQuery('status=berhasil&modul=10');

echo $uriDenganPesan->getHost();   // "kodeloka.asadinedu.com"
echo $uriDenganPesan->toString();  // "https://kodeloka.asadinedu.com/kelas/php?status=berhasil&modul=10"

Latihan Praktik: Endpoint Pemroses Pendaftaran Kelas (Form & JSON)

Buatlah berkas proses-request.php:

  1. Periksa $_SERVER['REQUEST_METHOD'] === 'POST' (jika bukan POST, kembalikan status 405 Method Not Allowed beserta header Allow: POST).
  2. Dukung penerimaan data baik dari formulir biasa ($_POST) maupun dari body JSON (php://input) berdasarkan pemeriksaan $_SERVER['CONTENT_TYPE'].
  3. Validasi nama (string 2–80 karakter), email (FILTER_VALIDATE_EMAIL), dan id_kelas (FILTER_VALIDATE_INT minimal 1).