ngodingdulu
Web & DatabaseTutorialPemula

PHP Modern: Versi 8, Composer, dan Autoload

Dari include satu per satu ke autoload, dari fungsi tanpa tipe ke class yang ketat. Kamu akan membuat program kasir kecil dengan fitur PHP 8 dan Composer.

Oleh Afa Hernandes15 menit baca

Daftar isi

Kode PHP native era lama biasanya terlihat seperti ini: belasan baris include 'fungsi.php'; di atas setiap halaman, fungsi tanpa tipe yang menerima apa saja, library yang diunduh dalam bentuk ZIP, dan password database di koneksi.php. Semuanya masih bisa jalan, tapi makin besar aplikasinya, makin sulit dirawat. Lupa satu include, muncul error. Salah kirim teks ke fungsi yang mengharapkan angka, hasilnya ngawur tanpa peringatan.

PHP 8 dan Composer menyelesaikan masalah-masalah itu. Composer mengunduh library dan, yang sama pentingnya, memuat class-mu secara otomatis lewat autoload. PHP 8 menambahkan tipe data yang ketat, enum, match, class readonly, dan beberapa fitur lain yang membuat kode lebih pendek sekaligus lebih aman.

Di tutorial ini kamu akan membuat program kasir kecil untuk kedai Kopi Senja. Programnya berjalan di terminal, dan strukturnya sama dengan proyek PHP modern pada umumnya, termasuk proyek Laravel.

Sebelum mulai

Langkah 1: Cek PHP dan pasang Composer

Buka terminal dan periksa versi PHP:

Terminal
php -v
Output
PHP 8.5.11 (cli) (built: Sep 22 2026 13:50:46) (ZTS Visual C++ 2022 x64)

Kalau muncul pesan bahwa php tidak dikenali, berarti folder PHP belum ada di PATH. Di XAMPP, foldernya biasanya C:\xampp\php. Tambahkan folder itu ke PATH lewat Edit the system environment variables, lalu buka terminal baru.

Untuk memasang Composer di Windows, unduh Composer-Setup.exe dari getcomposer.org dan jalankan. Installer akan menanyakan lokasi php.exe, lalu menambahkan Composer ke PATH. Setelah selesai, tutup terminal, buka lagi, lalu cek:

Terminal
composer --version
Output
Composer version 2.10.3 2026-08-27 13:34:23

Kalau kamu memakai Laragon, Composer biasanya sudah tersedia.

Langkah 2: Buat proyek dan composer.json

Buat folder proyek dengan struktur seperti ini:

Struktur folder
kasir-kopi/
├── bin/
│ └── kasir.php
├── src/
└── composer.json

Folder src berisi class, dan bin berisi program yang dijalankan. Isi composer.json:

kasir-kopi/composer.json
{
"name": "kopisenja/kasir",
"description": "Kasir sederhana kedai Kopi Senja",
"type": "project",
"license": "proprietary",
"require": {
"php": ">=8.3"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}

Dua bagian yang penting:

  • "require" mencatat apa saja yang dibutuhkan proyek. Baris "php": ">=8.3" membuat Composer menolak dipasang di PHP yang lebih lama.
  • "autoload" dengan "psr-4" adalah aturan pemetaan: class dengan namespace App\ ada di folder src/. Jadi class App\Produk pasti ada di src/Produk.php. PSR-4 adalah standar yang dipakai hampir semua proyek PHP modern, termasuk Laravel dan Symfony.

Perintah composer init juga bisa membuat file ini lewat tanya-jawab, tapi menulisnya sendiri membuatmu paham isinya. Lalu jalankan:

Terminal
composer dump-autoload
Output
Generating autoload files
Generated autoload files

Composer membuat folder vendor/ berisi autoload.php. Mulai sekarang, setiap program cukup memuat satu file itu, dan class apa pun di src/ akan dimuat otomatis saat pertama kali dipakai. Tidak ada lagi deretan include.

Kamu hanya perlu menjalankan composer dump-autoload lagi kalau mengubah bagian autoload di composer.json. Menambah file class baru di src/ tidak perlu.

Langkah 3: Tipe data dan strict_types

Sebelum membuat class, lihat dulu perubahan paling penting di PHP modern. Fungsi gaya lama menerima apa saja:

coba/lama.php
<?php
function tambah($a, $b) { return $a + $b; }
echo tambah("5", 3), "\n";
Output
8

Teks "5" diam-diam diubah menjadi angka. Kelihatannya membantu, tapi di aplikasi sungguhan ini menyembunyikan bug: data dari form yang seharusnya divalidasi malah lolos begitu saja. Versi modernnya:

coba/baru.php
<?php
declare(strict_types=1);
function tambah(int $a, int $b): int { return $a + $b; }
echo tambah("5", 3), "\n";
Output
Fatal error: Uncaught TypeError: tambah(): Argument #1 ($a) must be of type int, string given
  • int $a artinya parameter $a harus berupa bilangan bulat. Tipe lain yang sering dipakai: string, float, bool, array, dan nama class.
  • ): int artinya fungsi ini mengembalikan bilangan bulat.
  • declare(strict_types=1); di baris kedua membuat PHP menolak tipe yang tidak cocok, bukan mengubahnya diam-diam. Baris ini berlaku per file, jadi taruh di setiap file PHP-mu.

Error di sini bagus: kesalahannya ketahuan saat kamu mengetes, bukan saat pelanggan melihat total yang salah. Perbaikannya adalah mengubah data dari luar secara sadar, misalnya (int) $_POST['jumlah'] setelah divalidasi.

Langkah 4: Class readonly dan named argument

Buat class pertama di src/Produk.php:

kasir-kopi/src/Produk.php
<?php
declare(strict_types=1);
namespace App;
final readonly class Produk
{
public function __construct(
public string $nama,
public int $harga,
public Kategori $kategori,
public int $diskon = 0,
) {}
public function hargaAkhir(): int
{
return intdiv($this->harga * (100 - $this->diskon), 100);
}
}

Banyak hal baru dalam beberapa baris ini:

  • namespace App; harus cocok dengan aturan PSR-4 tadi. File ini ada di src/, jadi namespace-nya App, dan nama class-nya Produk, sama dengan nama file Produk.php.
  • Constructor promotion. Menulis public string $nama langsung di parameter constructor sekaligus membuat properti $nama dan mengisinya. Di PHP lama, ini butuh tiga bagian terpisah: deklarasi properti, parameter, lalu $this->nama = $nama;.
  • readonly artinya setelah objek dibuat, propertinya tidak bisa diubah. Harga produk di keranjang tidak bisa berubah diam-diam di tengah perhitungan.
  • final artinya class ini tidak bisa diturunkan. Ini kebiasaan yang baik: buka pewarisan hanya kalau memang dibutuhkan.
  • intdiv() membagi bilangan bulat dan membuang sisanya. Harga rupiah disimpan sebagai bilangan bulat, bukan desimal, supaya tidak ada masalah pembulatan.

Kalau ada kode yang mencoba mengubah harga:

$teh = new Produk('Teh', 8000, Kategori::Minuman);
$teh->harga = 1000;
Output
Fatal error: Uncaught Error: Cannot modify readonly property App\Produk::$harga

Saat membuat objek, kamu juga bisa memakai named argument: menyebut nama parameternya.

$croissant = new Produk(
nama: 'Croissant',
harga: 25000,
kategori: Kategori::Makanan,
diskon: 20,
);

Ini jauh lebih jelas daripada new Produk('Croissant', 25000, Kategori::Makanan, 20), karena kamu tidak perlu menebak angka 20 itu artinya apa.

Langkah 5: enum dan match

Kategori produk hanya boleh “minuman” atau “makanan”. Dulu biasanya ditulis sebagai string biasa, sehingga salah ketik seperti 'minumam' lolos tanpa error. Sekarang ada enum:

kasir-kopi/src/Kategori.php
<?php
declare(strict_types=1);
namespace App;
enum Kategori: string
{
case Minuman = 'minuman';
case Makanan = 'makanan';
public function label(): string
{
return match ($this) {
Kategori::Minuman => 'Minuman',
Kategori::Makanan => 'Makanan',
};
}
}
  • enum Kategori: string membuat tipe baru dengan pilihan nilai yang tetap. Setiap case punya nilai string, yang cocok untuk disimpan di kolom database.
  • match adalah pengganti switch yang lebih ketat. Ia mengembalikan nilai, membandingkan dengan ===, tidak butuh break, dan melempar error kalau ada nilai yang tidak ditangani.

Nilai dari database atau form bisa diubah menjadi enum dengan from() atau tryFrom():

coba/enum.php
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use App\Kategori;
var_dump(Kategori::from('minuman'));
var_dump(Kategori::tryFrom('snack'));
Kategori::from('snack');
Output
enum(App\Kategori::Minuman)
NULL
Fatal error: Uncaught ValueError: "snack" is not a valid backing value for enum App\Kategori

tryFrom() mengembalikan null kalau nilainya tidak dikenal, cocok untuk input pengguna yang perlu dicek dulu. from() langsung melempar error, cocok untuk data yang seharusnya selalu benar.

Langkah 6: Keranjang dan format rupiah

Dua class lagi. Pertama, format rupiah:

kasir-kopi/src/Rupiah.php
<?php
declare(strict_types=1);
namespace App;
final class Rupiah
{
public static function format(int $nilai): string
{
return 'Rp' . number_format($nilai, 0, ',', '.');
}
}

Lalu keranjang belanja:

kasir-kopi/src/Keranjang.php
<?php
declare(strict_types=1);
namespace App;
final class Keranjang
{
/** @var array<int, array{produk: Produk, jumlah: int}> */
private array $items = [];
public function tambah(Produk $produk, int $jumlah = 1): void
{
if ($jumlah < 1) {
throw new \InvalidArgumentException('Jumlah minimal 1.');
}
$this->items[] = ['produk' => $produk, 'jumlah' => $jumlah];
}
/** @return array<int, array{produk: Produk, jumlah: int}> */
public function items(): array
{
return $this->items;
}
public function total(): int
{
return array_sum(array_map(
fn (array $item): int => $item['produk']->hargaAkhir() * $item['jumlah'],
$this->items,
));
}
}
  • private array $items hanya bisa diubah dari dalam class. Kode di luar harus lewat tambah(), yang memeriksa jumlahnya dulu. Ini cara sederhana menjaga data tetap valid.
  • Komentar /** @var ... */ tidak dibaca PHP, tapi dibaca editor dan alat analisis kode. Isinya menjelaskan bentuk array: setiap item berisi produk dan jumlah.
  • fn (...) => ... adalah arrow function, fungsi pendek satu ekspresi. array_map menjalankannya untuk setiap item, lalu array_sum menjumlahkan hasilnya.
  • \InvalidArgumentException diawali \ karena class ini ada di namespace global, bukan di App.

Langkah 7: Paket dari Packagist dan file .env

Nama toko sebaiknya tidak ditulis di kode, sama seperti password database. Tempatnya di file .env. Untuk membacanya, pakai paket vlucas/phpdotenv dari Packagist, gudang paket PHP yang dipakai Composer:

Terminal
composer require vlucas/phpdotenv
Output
Using version ^5.8 for vlucas/phpdotenv
Package operations: 6 installs, 0 updates, 0 removals
- Installing vlucas/phpdotenv (v5.8.0): Extracting archive
Generating autoload files

Composer mengunduh paketnya beserta paket lain yang ia butuhkan ke folder vendor/, menambahkan barisnya ke composer.json, dan memperbarui autoload. ^5.8 artinya proyek ini menerima versi 5.8 ke atas, tapi tidak sampai versi 6, karena versi besar baru bisa membawa perubahan yang merusak.

Buat file .env di folder proyek:

kasir-kopi/.env
NAMA_TOKO="Kopi Senja"

Lalu class terakhir untuk mencetak struk:

kasir-kopi/src/Struk.php
<?php
declare(strict_types=1);
namespace App;
final class Struk
{
public function __construct(private string $namaToko) {}
public function cetak(Keranjang $keranjang): string
{
$baris = ["== {$this->namaToko} =="];
foreach ($keranjang->items() as ['produk' => $produk, 'jumlah' => $jumlah]) {
$baris[] = sprintf(
'%dx %-12s %10s',
$jumlah,
$produk->nama,
Rupiah::format($produk->hargaAkhir() * $jumlah),
);
}
$baris[] = sprintf('%-15s %10s', 'Total', Rupiah::format($keranjang->total()));
return implode("\n", $baris) . "\n";
}
}

foreach (... as ['produk' => $produk, 'jumlah' => $jumlah]) langsung membongkar setiap item menjadi dua variabel. sprintf dengan %-12s dan %10s meratakan kolom supaya struknya rapi.

Langkah 8: Program kasir

Sekarang program utamanya:

kasir-kopi/bin/kasir.php
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use App\Kategori;
use App\Keranjang;
use App\Produk;
use App\Struk;
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
$dotenv->required('NAMA_TOKO')->notEmpty();
$kopiSusu = new Produk('Kopi susu', 18000, Kategori::Minuman);
$croissant = new Produk(
nama: 'Croissant',
harga: 25000,
kategori: Kategori::Makanan,
diskon: 20,
);
$keranjang = new Keranjang();
$keranjang->tambah($kopiSusu, 2);
$keranjang->tambah($croissant);
$struk = new Struk($_ENV['NAMA_TOKO']);
echo $struk->cetak($keranjang);
  • require __DIR__ . '/../vendor/autoload.php'; adalah satu-satunya require yang dibutuhkan. __DIR__ adalah folder file ini, jadi path-nya tetap benar dari mana pun program dijalankan.
  • use App\Produk; membuat kamu cukup menulis Produk, bukan App\Produk lengkap.
  • required('NAMA_TOKO')->notEmpty() membuat program langsung berhenti dengan pesan jelas kalau .env belum diisi, daripada jalan dengan nama toko kosong.

Jalankan:

Terminal
php bin/kasir.php
Output
== Kopi Senja ==
2x Kopi susu Rp36.000
1x Croissant Rp20.000
Total Rp56.000

Croissant Rp25.000 dengan diskon 20% menjadi Rp20.000, dan totalnya Rp36.000 + Rp20.000 = Rp56.000. Tidak ada satu pun include untuk file class.

Langkah 9: Simpan ke Git dengan benar

Buat .gitignore di folder proyek:

kasir-kopi/.gitignore
/vendor/
.env
  • vendor/ tidak ikut di-commit. Isinya bisa dibuat ulang kapan saja dengan composer install.
  • .env tidak ikut di-commit, karena nanti isinya password dan API key. Sebagai gantinya, buat .env.example berisi nama variabel tanpa nilai rahasia, supaya orang lain tahu apa yang perlu diisi.
  • composer.lock justru wajib di-commit. File ini mencatat versi persis setiap paket yang terpasang. Saat temanmu atau server menjalankan composer install, versi yang dipasang sama persis dengan punyamu.

Terakhir, periksa apakah ada paket yang punya celah keamanan yang sudah diketahui:

Terminal
composer audit
Output
No security vulnerability advisories found.

Jalankan perintah ini secara berkala, karena celah baru bisa ditemukan di paket yang kemarin masih aman.

Bonus: operator pipe di PHP 8.5

PHP 8.5 menambahkan operator |> untuk merangkai fungsi dari kiri ke kanan. Misalnya untuk membuat slug dari nama produk:

coba/pipe.php
<?php
declare(strict_types=1);
$slug = ' Kopi Susu Gula Aren '
|> trim(...)
|> strtolower(...)
|> (fn (string $s): string => str_replace(' ', '-', $s));
echo $slug, "\n";
Output
kopi-susu-gula-aren

Tanpa |>, kodenya menjadi str_replace(' ', '-', strtolower(trim($nama))), yang dibaca dari dalam ke luar. trim(...) dengan tiga titik mengubah fungsi menjadi nilai yang bisa dioper. Fitur ini hanya jalan di PHP 8.5 ke atas, jadi pakai hanya kalau server production-mu juga sudah 8.5.

Kode lengkap

Struktur akhir proyek:

Struktur folder
kasir-kopi/
├── bin/
│ └── kasir.php
├── src/
│ ├── Kategori.php
│ ├── Keranjang.php
│ ├── Produk.php
│ ├── Rupiah.php
│ └── Struk.php
├── vendor/ (dibuat Composer, tidak di-commit)
├── .env (tidak di-commit)
├── .env.example
├── .gitignore
├── composer.json
└── composer.lock

Isi setiap file ada di langkah 2 sampai 9. Untuk menjalankan proyek ini di komputer lain setelah di-clone dari Git:

Terminal
composer install
copy .env.example .env
php bin/kasir.php

Di macOS dan Linux, ganti copy dengan cp. Jangan lupa mengisi nilai di .env sebelum menjalankan program.

Error yang sering muncul

Fatal error: Uncaught Error: Class “App\Keranjang” not found

Penyebabnya salah satu dari ini:

  • Lupa require __DIR__ . '/../vendor/autoload.php';, atau belum pernah menjalankan composer dump-autoload sehingga folder vendor/ belum ada.
  • Namespace di file tidak cocok dengan aturan PSR-4. File di src/ harus ber-namespace App.
  • Nama file tidak sama persis dengan nama class. Hati-hati di sini: Windows tidak membedakan huruf besar dan kecil, jadi src/keranjang.php tetap jalan di laptopmu. Server Linux membedakannya, dan class yang sama tiba-tiba tidak ditemukan setelah deploy. Biasakan nama file sama persis dengan nama class: Keranjang.php.

Fatal error: Uncaught TypeError: App\Produk::__construct(): Argument #2 ($harga) must be of type int, string given

Kamu mengirim teks ke parameter yang meminta angka, misalnya new Produk('Teh', '8000', ...). Data dari form selalu berupa teks. Validasi dan ubah dulu, misalnya dengan filter_var($nilai, FILTER_VALIDATE_INT), yang mengembalikan false kalau isinya bukan bilangan bulat.

Fatal error: Uncaught Error: Cannot modify readonly property App\Produk::$harga

Properti readonly tidak bisa diubah setelah objek dibuat. Kalau memang butuh nilai berbeda, buat objek baru dengan nilai itu.

Fatal error: Uncaught Dotenv\Exception\InvalidPathException: Unable to read any of the environment file(s)

File .env tidak ada di folder yang ditunjuk createImmutable(). Pastikan .env ada di folder kasir-kopi/, bukan di bin/. Di server production yang memakai environment variable sungguhan, pakai $dotenv->safeLoad(), yang tidak protes kalau file .env tidak ada.

‘composer’ is not recognized as an internal or external command

Composer belum masuk PATH, atau terminal dibuka sebelum instalasi selesai. Tutup semua jendela terminal dan buka lagi. Kalau masih gagal, jalankan ulang Composer-Setup.exe.

Langkah berikutnya: rilis dan amankan

Program di tutorial ini berjalan di terminal, tapi polanya sama untuk aplikasi web. Bedanya hanya di folder public/ berisi index.php yang menjadi satu-satunya file yang bisa diakses dari browser, sementara src/, vendor/, dan .env ada di luar folder itu. Dengan begitu, file .env berisi password tidak bisa dibuka lewat URL. Aturan ini juga dipakai Laravel.

Untuk deploy, server menjalankan composer install --no-dev lalu mengisi environment variable lewat panel hosting, bukan dengan mengunggah file .env dari laptopmu. Alasannya dijelaskan di Environment Variable dan Secrets. Simpan kodenya di Git dan GitHub, dan jalankan composer audit sebelum setiap rilis. Kalau aplikasimu memakai database, tetap pakai prepared statement seperti di artikel SQL injection. Fitur PHP modern tidak otomatis membuat query aman.

Pertanyaan yang sering diajukan

Apakah kode PHP lama tetap jalan di PHP 8?

Sebagian besar ya, tapi tidak semua. Fungsi mysql_* sudah dihapus sejak PHP 7, dan PHP 8 lebih ketat terhadap beberapa hal yang dulu hanya menghasilkan peringatan. Uji aplikasimu di PHP 8 di laptop sebelum mengganti versi PHP di server.

Harus pakai class untuk semuanya?

Tidak. Fungsi biasa dengan tipe data dan strict_types sudah merupakan peningkatan besar. Class berguna saat data dan aturan yang menjaganya perlu disatukan, seperti Keranjang yang menolak jumlah nol. Autoload Composer juga bisa memuat file berisi fungsi lewat bagian "files" di composer.json.

Apa bedanya composer install dan composer update?

composer install memasang versi persis yang tercatat di composer.lock. Pakai ini di server dan saat baru clone proyek. composer update mencari versi terbaru yang diizinkan composer.json, lalu menulis ulang composer.lock. Pakai ini dengan sengaja, lalu tes aplikasinya sebelum commit.

Perlu Composer kalau tidak memakai paket apa pun?

Tetap berguna untuk autoload. Begitu proyek punya lebih dari beberapa class, autoload PSR-4 menghapus seluruh daftar include dan membuat struktur proyekmu mudah dipahami orang lain.

Kesimpulan

PHP modern berarti tiga kebiasaan: tipe data yang ketat dengan strict_types, struktur class yang dimuat otomatis lewat Composer dan PSR-4, dan konfigurasi yang dipisah dari kode lewat .env. Ditambah readonly, enum, match, dan named argument, kodemu jadi lebih pendek sekaligus lebih sulit dipakai dengan cara yang salah.

Dengan ini Level 1 jalur Upgrade dari Stack Lama selesai. Level berikutnya membahas Laravel untuk developer PHP native, yang dibangun di atas semua yang baru kamu pelajari.

Bagikan:WhatsAppTelegramXFacebook
Ilustrasi dua kartu bersebelahan: kartu kiri berisi daftar alat lama seperti XAMPP, mysql_query, jQuery, dan FTP, kartu kanan berisi padanan modernnya seperti PHP 8, prepared statement, fetch, dan git push
WebKonsep

Apa yang Berubah di Dunia Web Sejak Era XAMPP

Kalau terakhir ngoding web dengan XAMPP, PHP native, jQuery, dan FTP, ini peta yang berubah, yang tetap sama, dan urutan mengejarnya tanpa membuang pengalamanmu.

6 November 202610 mnt

Membuat Dropdown Pilih Bulan & Tahun di PHP
WebSource Code

Membuat Dropdown Pilih Bulan & Tahun di PHP

Pada tutorial bahasa pemrograman kali ini, saya akan membagikan sebuah tutorial ngoding dulu tentang bagaimana caranya membuat jframe rata tengah / center…

31 Desember 20212 mnt

Enter bukaEsc tutupHalaman pencarian