Laravel bukan sekadar kumpulan fungsi — framework ini dibangun penuh dengan object-oriented programming (OOP). Controller adalah class, model adalah class, middleware adalah class, bahkan aplikasi itu sendiri adalah class Application. Ketika kita menulis Route::get(...) atau User::findOrFail(...), tanda :: dan -> yang kita pakai adalah sintaks interaksi antar object. Chapter ini menyegarkan konsep OOP PHP sekaligus menunjukkan wujud nyatanya di dalam Laravel.
Class, Property, dan Method #
Class adalah cetak biru; object adalah wujud nyata yang dibuat dari cetak biru tersebut dengan new. Di dalam class, data disimpan pada property dan perilaku ditulis sebagai method. Kata kunci $this merujuk pada object yang sedang memakai method tersebut:
<?php
class Tugas
{
public string $judul;
protected string $status = 'berjalan';
private int $id;
public function __construct(int $id, string $judul)
{
$this->id = $id;
$this->judul = $judul;
}
public function selesaikan(): void
{
$this->status = 'selesai';
}
public function ringkas(): string
{
return sprintf('#%d %s (%s)', $this->id, $this->judul, $this->status);
}
}
$tugas = new Tugas(1, 'Migrasi Database');
echo $tugas->ringkas() . "\n";
$tugas->selesaikan();
echo $tugas->ringkas() . "\n";Output yang Dihasilkan:
#1 Migrasi Database (berjalan)
#1 Migrasi Database (selesai)Method __construct() adalah constructor — method khusus yang otomatis dipanggil saat new Tugas(...) dijalankan. Tiga level penglihatan (visibility) mengatur siapa yang boleh mengakses property: public terbuka dari mana saja, protected hanya dari dalam class beserta turunannya, dan private hanya dari class itu sendiri. Pola ini menjelaskan kenapa kita menulis $tugas->judul tetapi tidak boleh menulis $tugas->id dari luar class.
Constructor Property Promotion dan static #
Menulis property lalu menugaskannya satu per satu di constructor merupakan pekerjaan berulang. PHP 8 menyediakan constructor property promotion: property dideklarasikan langsung pada parameter constructor. Bentuk ringkas ini dipakai di mana-mana dalam kode Laravel modern. Potongan berikut diambil dari vendor/laravel/framework/src/Illuminate/Validation/Rules/Regex.php — class asli yang terpakai ketika kita menulis aturan validasi regex dalam bentuk object, misalnya new Regex('/^[a-z]+$/'):
<?php
namespace Illuminate\Validation\Rules;
use Stringable;
class Regex implements Stringable
{
/**
* Create a new regex rule instance.
*/
public function __construct(protected string $pattern)
{
}
/**
* Convert the rule to a validation string.
*/
public function __toString(): string
{
return "regex:{$this->pattern}";
}
}Satu parameter protected string $pattern sekaligus menjadi property, tipe parameternya, dan field yang diisi constructor. Kita bisa menulis class dengan pola yang sama di kode kita sendiri, berikut dengan member static yang hidup di level class, bukan object:
<?php
class Penerima
{
public function __construct(
protected string $nama,
protected string $email,
) {
}
public function deskripsi(): string
{
return "{$this->nama} <{$this->email}>";
}
}
class Notifikasi
{
public static int $jumlahTerkirim = 0;
public static function kirim(Penerima $penerima): string
{
self::$jumlahTerkirim++;
return "Mengirim notifikasi ke {$penerima->deskripsi()}";
}
}
$penerima = new Penerima('Budi', '[email protected]');
echo Notifikasi::kirim($penerima) . "\n";
echo Notifikasi::kirim(new Penerima('Siti', '[email protected]')) . "\n";
echo 'Total terkirim: ' . Notifikasi::$jumlahTerkirim . "\n";Output yang Dihasilkan:
Mengirim notifikasi ke Budi <[email protected]>
Mengirim notifikasi ke Siti <[email protected]>
Total terkirim: 2Property dan method static dipanggil dengan Notifikasi::kirim(...) tanpa membuat object — pola yang sama dengan Route::get(...) dan Validator::make(...) di Laravel, di mana Route dan Validator adalah facade yang meneruskan pemanggilan ke method milik object di balik layar.
Inheritance, Abstract Class, dan Interface #
Inheritance memungkinkan sebuah class mewarisi property dan method class lain lewat extends. Abstract class mendefinisikan kerangka yang wajib dilengkapi turunannya, sedangkan interface adalah kontrak berisi daftar method yang wajib diimplementasikan. Ketiganya adalah satu paket konsep yang saling melengkapi:
<?php
interface DapatDitagih
{
public function totalTagihan(): float;
}
abstract class Proyek
{
abstract public function namaProyek(): string;
public function laporan(): string
{
return 'Proyek: ' . $this->namaProyek();
}
}
class ProyekWeb extends Proyek implements DapatDitagih
{
public function __construct(
protected string $klien,
protected float $tarif,
) {
}
public function namaProyek(): string
{
return "Pengembangan web untuk {$this->klien}";
}
public function totalTagihan(): float
{
return $this->tarif * 1.11;
}
}
$proyek = new ProyekWeb('PT Contoh', 10000000);
echo $proyek->laporan() . "\n";
echo 'Tagihan: Rp' . number_format($proyek->totalTagihan(), 0, ',', '.') . "\n";Output yang Dihasilkan:
Proyek: Pengembangan web untuk PT Contoh
Tagihan: Rp11.100.000Method laporan() di abstract class Proyek sudah bisa dipanggil oleh turunannya meskipun namaProyek() baru diimplementasikan di ProyekWeb — inilah cara framework menyediakan perilaku umum sekaligus memaksa kita mengisi detailnya. class ProyekWeb implements DapatDitagih menandakan kontrak totalTagihan() wajib ada; lupa mengimplementasikannya membuat PHP menolak memuat class.
Trait #
PHP hanya mengizinkan satu induk per class, sedangkan trait menjadi jalur reusable berikutnya: kumpulan method yang bisa “ditempelkan” ke beberapa class sekaligus dengan use di dalam tubuh class:
<?php
trait MemilikiTimestamp
{
public ?string $dibuatPada = null;
public function tandaiDibuat(): void
{
$this->dibuatPada = 'waktu-referensi';
}
}
class Catatan
{
use MemilikiTimestamp;
}
class Komentar
{
use MemilikiTimestamp;
}
$catatan = new Catatan();
$catatan->tandaiDibuat();
echo get_class($catatan) . ': ' . $catatan->dibuatPada . "\n";
$komentar = new Komentar();
$komentar->tandaiDibuat();
echo get_class($komentar) . ': ' . $komentar->dibuatPada . "\n";Output yang Dihasilkan:
Catatan: waktu-referensi
Komentar: waktu-referensiLaravel memakai trait dalam skala besar. Trait SoftDeletes menjadikan model tersembunyi alih-alih terhapus lewat kolom deleted_at, sekaligus menyediakan query withTrashed(); trait Notifiable memberi model kemampuan menerima notifikasi; trait HasFactory menyambungkan model ke factory untuk testing. Sebuah model Laravel biasanya berdiri di atas lima sampai sepuluh trait sekaligus.
Rangka Class App\Models\User #
Mari bedah class paling penting di setiap proyek Laravel. Berikut isi asli app/Models/User.php dari sandbox Course ini:
<?php
namespace App\Models;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
#[Fillable(['name', 'email', 'password'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable
{
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable;
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}Empat konsep chapter ini hadir sekaligus dalam satu berkas singkat: class User extends Authenticatable adalah inheritance; use HasFactory, Notifiable di dalam tubuh class memasang trait; casts() adalah method dengan return type array; dan Authenticatable sendiri hanyalah alias untuk Illuminate\Foundation\Auth\User — sebuah class framework yang kita bahas sejenak:
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\Access\Authorizable;
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}Class induk tersebut extends Model (inti Eloquent) sekaligus implements tiga kontrak autentikasi. Kita bisa membuktikan seluruh rantai ini secara programatik di sandbox:
<?php
use App\Models\User;
$user = User::factory()->create([
'name' => 'Budi',
'email' => '[email protected]',
]);
echo get_class($user) . "\n";
echo get_parent_class($user) . "\n";
echo get_parent_class(get_parent_class($user)) . "\n";
echo 'implements: ' . implode(', ', class_implements($user)) . "\n";
echo 'traits: ' . implode(', ', array_keys(class_uses_recursive($user))) . "\n";Output yang Dihasilkan:
App\Models\User
Illuminate\Foundation\Auth\User
Illuminate\Database\Eloquent\Model
implements: Illuminate\Contracts\Auth\CanResetPassword, Illuminate\Contracts\Auth\Access\Authorizable, Illuminate\Contracts\Auth\Authenticatable, Illuminate\Contracts\Routing\UrlRoutable, Stringable, Illuminate\Contracts\Queue\QueueableEntity, JsonSerializable, Illuminate\Contracts\Support\Jsonable, Illuminate\Contracts\Broadcasting\HasBroadcastChannel, Illuminate\Contracts\Support\CanBeEscapedWhenCastToString, ArrayAccess, Illuminate\Contracts\Support\Arrayable
traits: Illuminate\Database\Eloquent\Concerns\HasAttributes, Illuminate\Database\Eloquent\Concerns\HasEvents, Illuminate\Database\Eloquent\Concerns\HasGlobalScopes, Illuminate\Database\Eloquent\Concerns\HasRelationships, Illuminate\Database\Eloquent\Concerns\HasTimestamps, …Baris traits: di atas sengaja dipangkas — class_uses_recursive() mengembalikan lebih dari dua puluh trait karena seluruh trait milik Model juga ikut terhitung. Dengan peta ini, kode seperti $user->name atau User::factory()->create(...) tidak lagi misterius: keduanya hanya memanggil method yang diwarisi dari rantai class dan trait di atas. Rujukan lengkap tersedia di dokumentasi resmi class dan object PHP
, sedangkan contoh pemakaian trait di model ada di dokumentasi Eloquent
Laravel.
Ada satu pertanyaan yang masih menggantung: dari mana PHP tahu bahwa App\Models\User berada di berkas app/Models/User.php tanpa pernah ada require yang kita tulis? Jawabannya ada pada namespace dan autoloading — topik chapter berikutnya.