Kodeloka
  1. Kodeloka
  2. Kelas PHP
  3. Materi

2 · OOP & Struktur Kode · Modul 11 dari 25

Ekspresi Reguler (PCRE2 preg_*), Attributes (#[...]), dan Reflection API

Mencocokkan dan mengekstrak pola teks menggunakan fungsi PCRE2 (preg_match, preg_match_all, preg_replace_callback), serta merancang metadata deklaratif menggunakan PHP Attributes (#[...]) yang dibaca saat runtime melalui Reflection API.

1. Ekspresi Reguler di PHP dengan Ekstensi PCRE2 (preg_*)

Di PHP, ekspresi reguler menggunakan mesin **PCRE2 (Perl Compatible Regular Expressions)** di mana pola selalu diapit oleh karakter pembatas (delimiter, biasanya garis miring /pola/flags atau tanda pagar #pola#flags jika pola mengandung banyak garis miring URL):

<?php

declare(strict_types=1);

// 1. preg_match(): Mengembalikan 1 jika cocok, 0 jika tidak cocok, atau false jika terjadi galat
//    Gunakan Named Subpatterns (?<nama>...) dan flag 'u' (UTF-8 mode)!
$polaKodeKelas = '/^(?<bidang>[a-z]+)-(?<nomor>\d{3})$/u';

if (preg_match($polaKodeKelas, 'backend-104', $tangkapan) === 1) {
    echo "Bidang: {$tangkapan['bidang']}, Nomor: {$tangkapan['nomor']}\n";
    // Bidang: backend, Nomor: 104
}

// 2. preg_replace_callback(): Mengubah teks yang cocok menggunakan fungsi callback yang aman!
$teksMarkdown = 'Baca modul [[01-cara-web-bekerja]] dan [[02-struktur-dokumen-html]].';

$teksHtml = preg_replace_callback(
    '/\[\[(?<slug>[a-z0-9-]+)\]\]/u',
    static fn(array $m): string => sprintf(
        '<a href="/materi/%s">%s</a>',
        htmlspecialchars($m['slug'], ENT_QUOTES, 'UTF-8'),
        htmlspecialchars($m['slug'], ENT_QUOTES, 'UTF-8'),
    ),
    $teksMarkdown,
);

echo $teksHtml;
⚠️ Jebakan Keamanan Klasik pada Regex di PHP: ^ dan $ vs \A dan \z!

Jika Anda menggunakan flag multiline (m), atau bahkan tanpa flag m, jangkar akhir $ di PCRE secara bawaan masih mengizinkan satu karakter baris baru (\n) di paling ujung string! Untuk validasi keamanan ketat yang memastikan tidak ada baris baru yang menyelundup di akhir string, gunakan \A (awal mutlak string) dan \z (akhir mutlak string), atau tambahkan flag D (PCRE_DOLLAR_ENDONLY)!

2. Mengenal PHP Attributes (#[...] — Sejak PHP 8.0 hingga 8.5)

Di pelajaran sebelumnya, kita telah memakai atribut bawaan PHP seperti #[\Override] dan #[\NoDiscard]; atribut bawaan lain seperti #[\Deprecated] serta atribut PHPUnit seperti #[Test] dan #[DataProvider] akan ditemui pada pelajaran berikutnya.

Apa sebenarnya Attributes (#[...]) itu? Sebelum PHP 8.0, pengembang terpaksa menulis konfigurasi metadata di dalam komentar teks /** @Route("/kelas") */ (docblock annotations) lalu mengurainya dengan regex yang lambat dan rawan salah ketik. Sejak PHP 8.0, Attributes menyediakan metadata terstruktur kelas satu di tingkat sintaks bahasa PHP yang dapat ditempelkan pada class, metode, fungsi, properti, parameter, maupun konstanta!

3. Membuat Custom Attribute Sendiri dan Membacanya dengan Reflection API

Mari kita lihat bagaimana framework modern (seperti Symfony atau Laravel) mendaftarkan rute URL atau izin akses secara otomatis menggunakan Custom Attribute dan ReflectionClass / ReflectionMethod:

<?php

declare(strict_types=1);

namespace Kodeloka\Routing;

use Attribute;
use ReflectionClass;
use ReflectionMethod;

// 1. Mendefinisikan class Attribute kita sendiri:
//    Batasi agar atribut #[Rute] hanya boleh dipasang pada METODE (TARGET_METHOD)
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
readonly class Rute
{
    public function __construct(
        public string $jalur,
        public string $metodeHttp = 'GET',
    ) {}
}

// 2. Memasang atribut #[Rute] pada metode di dalam Controller:
final class KelasController
{
    #[Rute(jalur: '/kelas', metodeHttp: 'GET')]
    public function daftarKelas(): string
    {
        return 'Menampilkan daftar semua kelas Kodeloka';
    }

    #[Rute(jalur: '/kelas/daftar', metodeHttp: 'POST')]
    public function prosesPendaftaran(): string
    {
        return 'Memproses pendaftaran peserta baru';
    }
}

// 3. Membaca metadata Attribute saat runtime menggunakan Reflection API!
function ekstrakDaftarRute(string $namaClassController): array
{
    $refleksiKelas = new ReflectionClass($namaClassController);
    $petaRute = [];

    foreach ($refleksiKelas->getMethods(ReflectionMethod::IS_PUBLIC) as $metode) {
        // Mengambil seluruh atribut #[Rute] yang menempel pada metode ini
        $atributRute = $metode->getAttributes(Rute::class);

        foreach ($atributRute as $attr) {
            /** @var Rute $instansRute */
            $instansRute = $attr->newInstance(); // Menginstansiasi objek Rute!

            $petaRute[] = [
                'http'   => $instansRute->metodeHttp,
                'jalur'  => $instansRute->jalur,
                'target' => $namaClassController . '::' . $metode->getName(),
            ];
        }
    }

    return $petaRute;
}

print_r(ekstrakDaftarRute(KelasController::class));

(Catatan: Sejak PHP 8.5, parameter di dalam atribut #[...] bahkan sudah dapat menerima static closures dan first-class callables secara langsung!)

Latihan Praktik: Validator Objek Otomatis Berbasis Atribut #[WajibDiisi] dan #[PanjangTeks]

Buatlah berkas validator-atribut.php dan jalankan dengan php validator-atribut.php:

  1. Buat atribut #[Attribute(Attribute::TARGET_PROPERTY)] readonly class PanjangTeks dengan properti public int $min dan public int $maks.
  2. Buat DTO class InputKursus yang propertinya dipasangi #[PanjangTeks(min: 5, maks: 50)] public string $judul.
  3. Buat fungsi validasiObjek(object $dto): array yang menggunakan new ReflectionClass($dto) untuk memeriksa semua properti yang memiliki atribut PanjangTeks dan mengembalikan daftar pesan kesalahan jika panjangnya tidak sesuai!