Kode Laravel yang kita tulis sehari-hari sering terasa ringkas: satu baris Route::get(...), satu pemanggilan Cache::remember(...), dan semuanya bekerja. Di balik ringkas itu ada empat konsep arsitektur yang bekerja sama: service container, service provider, facade, dan contract. Memahami keempatnya membuat “magic” Laravel menjadi bisa dijelaskan — dan bisa kita kendalikan. Chapter ini membedah satu per satu keempat konsep tersebut beserta bukti nyata bagaimana semuanya saling terhubung.
Peta Besar Arsitektur #
Cara kerja Laravel bisa diringkas dalam satu kalimat: semua fitur adalah service yang tersimpan di service container, didaftarkan oleh service provider, dan biasanya diakses melalui facade. Contract adalah kontrak berupa interface yang menjadi “bahasa resmi” antar komponen tersebut. Tabel berikut memetakan peran masing-masing:
| Konsep | Peran | Analogi |
|---|---|---|
| Service container | Menyimpan dan menyediakan objek (service) | Gudang berlabel untuk class |
| Service provider | Mendaftarkan service ke container | Petugas yang mengisi gudang saat aplikasi menyala |
| Facade | Antarmuka statis ke service di container | Papan tombol pintas ke isi gudang |
| Contract | Interface yang mendefinisikan kemampuan service | Daftar fitur yang dijanjikan |
Service Container #
Service container adalah alat untuk mengelola dependency antar class — inti dari dependency injection. Masalah yang diselesaikannya sederhana: daripada setiap class membuat sendiri objek yang dibutuhkannya, class cukup meminta dependency-nya dari container, dan container yang menyediakannya.
Container bahkan bisa bekerja tanpa konfigurasi: ketika sebuah class bertipe-hint dependency di constructor, container membaca tipe tersebut lewat reflection lalu membuat objeknya otomatis. Untuk kasus yang butuh kendali penuh, kita mendaftarkan binding — pemetaan dari abstraksi (biasanya interface) ke implementasi konkret. Perhatikan contoh berikut yang dijalankan pada aplikasi Laravel:
<?php
// Demo service container — jalankan dari root proyek Laravel
require __DIR__.'/vendor/autoload.php';
$app = require __DIR__.'/bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
interface PaymentGateway
{
public function charge(int $jumlah): string;
}
class DummyGateway implements PaymentGateway
{
public function charge(int $jumlah): string
{
return 'Membayar Rp'.number_format($jumlah, 0, ',', '.');
}
}
// Ikat (bind) interface ke implementasi konkret
app()->singleton(PaymentGateway::class, fn () => new DummyGateway());
// Mintalah container menyediakan PaymentGateway
$gateway = app(PaymentGateway::class);
echo $gateway->charge(150000).PHP_EOL;
echo app(PaymentGateway::class) === $gateway ? 'objek sama (singleton)' : 'objek berbeda';
echo PHP_EOL;Output yang Dihasilkan:
Membayar Rp150.000
objek sama (singleton)Ada dua hal penting dari output tersebut. Pertama, pemanggilan app(PaymentGateway::class) berhasil mengembalikan DummyGateway padahal kode hanya menyebut interface — container yang menerjemahkan binding untuk kita. Kedua, pemanggilan kedua mengembalikan objek yang sama: binding singleton membuat container hanya membuat satu instance untuk seluruh aplikasi. Dengan pola ini, saat production membutuhkan gateway sungguhan, kita cukup mengganti satu baris binding tanpa menyentuh kode pengguna.
Service Provider #
Service provider adalah tempat semua service didaftarkan saat aplikasi bootstrap. Dokumentasi menyebutnya sebagai pusat dari seluruh proses bootstrapping: puluhan fitur Laravel — routing, database, queue, validasi — masing-masing disiapkan oleh provider-nya sendiri. Provider punya dua method kunci: register() untuk mendaftarkan binding ke container, dan boot() yang dijalankan setelah semua provider selesai meregistrasi, sehingga aman menggunakan service lain di dalamnya.
Provider milik kita sendiri terdaftar di bootstrap/providers.php:
<?php
use App\Providers\AppServiceProvider;
return [
AppServiceProvider::class,
];Secara bawaan, satu-satunya provider aplikasi adalah AppServiceProvider yang masih kosong:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
}
}Dua method kosong tersebut adalah titik resmi untuk meletakkan binding container (di register()) dan konfigurasi lintas service (di boot()), misalnya binding PaymentGateway tadi. Perintah php artisan make:provider membuat provider baru dan otomatis mendaftarkannya ke berkas ini.
Facade #
Facade menyediakan antarmuka statis ke objek yang tersimpan di container — dokumentasi menyebutnya static proxy. Ketika kita menulis Route::get(...), sebenarnya tidak ada method statis get di class Route; class facade meneruskan pemanggilan tersebut ke objek asli dari container lewat magic method __callStatic().
Kita bisa membuktikannya secara langsung. Skrip berikut mendaftarkan route lewat facade, lalu membandingkannya dengan router yang diambil eksplisit dari container:
<?php
// Demo facade vs service container — jalankan dari root proyek Laravel
use Illuminate\Http\Request;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Route;
require __DIR__.'/vendor/autoload.php';
$app = require __DIR__.'/bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
// 1. Daftarkan route lewat facade, seperti di routes/web.php
Route::get('/arsitektur', fn () => 'Halo dari route!');
// 2. Ambil objek router langsung dari service container
$router = app(Router::class);
echo 'Class di balik facade : '.get_class(Route::getFacadeRoot()).PHP_EOL;
echo 'Class dari container : '.get_class($router).PHP_EOL;
echo 'Objek yang sama? : ';
echo Route::getFacadeRoot() === $router ? 'Ya' : 'Tidak';
echo PHP_EOL;
// 3. Route lewat facade terlihat dari router milik container
$route = $router->getRoutes()->match(Request::create('/arsitektur'));
echo 'Route yang cocok : '.$route->uri().PHP_EOL;Output yang Dihasilkan:
Class di balik facade : Illuminate\Routing\Router
Class dari container : Illuminate\Routing\Router
Objek yang sama? : Ya
Route yang cocok : arsitekturOutput tersebut menegaskan mekanisme facade: Route::get(...) dan app(Router::class) bermuara pada objek yang sama — instance Illuminate\Routing\Router yang tersimpan di container. Karena facade hanyalah pintasan, kode yang sama tetap bisa diuji dan di-mock layaknya dependency injection. Mekanisme penghubungnya ada di method getFacadeAccessor() pada tiap class facade yang mengembalikan nama binding di container.
Contract #
Contract adalah kumpulan interface yang mendefinisikan fitur inti Laravel — misalnya Illuminate\Contracts\Cache\Factory yang menjadi kontrak di balik facade Cache. Nilai utamanya: kode kita bergantung pada kemampuan (interface), bukan implementasi tertentu, sehingga implementasi bisa ditukar tanpa mengubah konsumen — persis seperti pola PaymentGateway pada contoh container di atas. Detail daftar contract tersedia di dokumentasi service container
dan facade
yang juga menjadi rujukan resmi chapter ini.
Dengan keempat konsep ini, arsitektur Laravel sudah terpetakan. Lapisan tersebut lalu memikul pola pengorganisasian kode yang paling terkenal di dunia web — Model, View, Controller — yang kita bahas pada chapter berikutnya.