Service provider adalah kelas yang menjadi tempat seluruh pendaftaran layanan aplikasi, mulai dari binding pada service container hingga konfigurasi yang harus dijalankan ketika aplikasi dimulai. Topik ini membahas dua fase eksekusi provider, yaitu register() dan boot(), cara framework menemukan daftar providernya, serta contoh nyata penggunaan boot() untuk binding rute kustom pada aplikasi catatan. Dengan kedua fase tersebut, penempatan kode inisialisasi aplikasi mengikuti aturan yang ditetapkan framework.
Dua fase eksekusi: register() dan boot()#
Framework mengeksekusi setiap service provider dalam dua fase yang terpisah. Fase pertama memanggil register() pada semua provider secara berurutan, kemudian fase kedua memanggil boot() pada provider yang sama setelah seluruh register() selesai. Aturan pemisahan ini penting. Metode register() hanya boleh mencatat binding ke service container, sedangkan boot() boleh menggunakan layanan apa pun karena pada fase tersebut seluruh binding dari semua provider sudah terdaftar. Pembagian kerja yang sama berulang pada setiap siklus hidup aplikasi, baik saat permintaan HTTP dijalankan maupun saat perintah Artisan dijalankan.
Urutan tersebut dapat dibuktikan dengan mendaftarkan provider yang mencetak penanda pada kedua metodenya. Pendaftaran dilakukan saat aplikasi sudah berjalan, sehingga kedua metode dijalankan berurutan tanpa jeda.
# mengamati urutan register() dan boot() pada provider
cd ~/aplikasi-catatan
php artisan tinker --execute='
use Illuminate\Support\ServiceProvider;
class PenandaProvider extends ServiceProvider
{
public function register(): void
{
echo "register: binding dicatat" . PHP_EOL;
}
public function boot(): void
{
echo "boot: layanan siap dipakai" . PHP_EOL;
}
}
app()->register(PenandaProvider::class);
echo "setelah registrasi" . PHP_EOL;
'register: binding dicatat
boot: layanan siap dipakai
setelah registrasiKeluaran menunjukkan bahwa register() selalu mendahului boot(), dan keduanya selesai sebelum kode aplikasi lain dieksekusi. Pada saat framework dijalankan secara normal, pola yang sama berlaku untuk seluruh provider sekaligus, yaitu semua register() terlebih dahulu, kemudian semua boot().
Daftar provider pada aplikasi#
Framework mengetahui provider yang harus dimuat dari daftar pada berkas bootstrap/providers.php. Daftar bawaan aplikasi catatan masih berisi satu provider, yaitu AppServiceProvider yang dibuat bersama proyek.
# menampilkan daftar provider aplikasi
cd ~/aplikasi-catatan
cat bootstrap/providers.php<?php
use App\Providers\AppServiceProvider;
return [
AppServiceProvider::class,
];Daftar tersebut tidak memuat seluruh provider. Provider milik framework, misalnya provider untuk database, antrian, dan validasi, didaftarkan otomatis oleh Laravel tanpa harus dituliskan. Jumlahnya dapat diperiksa melalui konfigurasi app.providers yang telah digabungkan saat aplikasi dijalankan.
# menghitung provider framework dan provider aplikasi
cd ~/aplikasi-catatan
php artisan tinker --execute='
$semua = config("app.providers");
$aplikasi = collect($semua)->filter(fn ($p) => str_starts_with($p, "App\\"));
echo "total provider: " . count($semua) . PHP_EOL;
echo "provider aplikasi: " . implode(", ", $aplikasi->all()) . PHP_EOL;
'total provider: 25
provider aplikasi: App\Providers\AppServiceProviderAngka 25 berasal dari versi framework yang terpasang, sehingga nilainya dapat berbeda antarversi. Yang penting diperhatikan adalah polanya, yaitu banyak provider framework yang menyediakan fitur inti dan hanya segelintir provider milik aplikasi yang dikendalikan pengembang.
Membuat provider baru#
Provider baru dibuat dengan perintah make:provider. Perintah ini hanya menghasilkan berkas kerangka kelas, sedangkan pendaftarannya pada daftar provider tetap ditulis manual.
# tidak-dijalankan
# membuat provider baru bernama KategoriServiceProvider
cd ~/aplikasi-catatan
php artisan make:provider KategoriServiceProviderBerkas yang dihasilkan terletak pada app/Providers/KategoriServiceProvider.php. Keluaran asli perintah tersebut berbentuk berikut.
INFO Provider [app/Providers/KategoriServiceProvider.php] created successfully. Agar provider dijalankan oleh framework, nama kelasnya ditambahkan ke bootstrap/providers.php. Berkas aplikasi catatan menjadi seperti berikut setelah provider baru didaftarkan.
// tidak-dijalankan
<?php
use App\Providers\AppServiceProvider;
use App\Providers\KategoriServiceProvider;
return [
AppServiceProvider::class,
KategoriServiceProvider::class,
];Provider yang dibuat tetapi tidak terdaftar pada berkas ini tidak pernah dijalankan sama sekali, dan tidak ada pesan error yang muncul untuk mengingatkan hal tersebut.
Menggunakan boot() untuk binding rute#
Kegunaan nyata boot() adalah menyiapkan fitur yang membutuhkan layanan yang sudah terdaftar. Salah satu contohnya adalah binding rute kustom, yaitu aturan agar parameter rute tidak lagi diartikan sebagai primary key, melainkan dicari berdasarkan kolom lain. Pada aplikasi catatan, parameter kategori dapat diikat ke kolom nama, sehingga URL memuat nama kategori yang mudah dibaca.
// tidak-dijalankan
<?php
namespace App\Providers;
use App\Models\Category;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Route::bind("kategori", function (string $nilai) {
return Category::where("nama", $nilai)->firstOrFail();
});
}
}Metode Route::bind() mendaftarkan penyelesai kustom untuk parameter bernama kategori. Apabila kategori tidak ditemukan, firstOrFail() melemparkan error dan router mengubahnya menjadi respons 404. Perilaku lengkapnya dapat disimulasikan dengan provider yang dijalankan saat aplikasi sedang berjalan.
# menguji binding rute kustom melalui provider runtime
cd ~/aplikasi-catatan
php artisan tinker --execute='
use App\Models\Category;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Route;
class KategoriRoutingProvider extends ServiceProvider
{
public function boot(): void
{
Route::bind("kategori", function (string $nilai) {
return Category::where("nama", $nilai)->firstOrFail();
});
}
}
app()->register(KategoriRoutingProvider::class);
Category::firstOrCreate(["nama" => "Kerja"]);
Route::middleware("web")->get("/kategori/{kategori}", fn (Category $kategori) => "Kategori: " . $kategori->nama);
$r1 = app()->handle(Illuminate\Http\Request::create("/kategori/Kerja", "GET"));
echo $r1->getStatusCode() . " " . $r1->getContent() . PHP_EOL;
$r2 = app()->handle(Illuminate\Http\Request::create("/kategori/Iseng", "GET"));
echo "status untuk kategori yang tidak ada: " . $r2->getStatusCode() . PHP_EOL;
'200 Kategori: Kerja
status untuk kategori yang tidak ada: 404Permintaan pertama menemukan kategori Kerja berdasarkan kolom nama, sedangkan permintaan kedua gagal dengan status 404 karena kategori Iseng tidak ada. Perlu dicatat bahwa rute contoh menggunakan grup middleware web karena substitusi parameter model dilakukan oleh middleware SubstituteBindings yang menjadi anggota grup tersebut.
Kesalahan umum#
Kesalahan pertama adalah menuliskan binding pada boot() alih-alih register(). Kode tersebut tetap berjalan, tetapi urutan eksekusi menjadi tidak terjamin karena provider lain yang boot()-nya berjalan lebih dulu belum menerima binding dari provider yang terlambat. Simpan binding pada register() dan sisakan boot() untuk kode yang menggunakan layanan.
Kesalahan kedua adalah membuat provider tanpa mendaftarkannya pada bootstrap/providers.php. Kelasnya ada, metode-metodenya tertulis rapi, tetapi framework tidak pernah memuatnya. Pemeriksaan cepatnya adalah memanggil config("app.providers") melalui Tinker dan memastikan nama kelasnya tercantum.
Kesalahan ketiga adalah memanggil layanan lain di dalam register(), misalnya langsung mengambil objek dari container yang belum didaftarkan oleh provider sebelumnya. Karena register() dijalankan sebelum seluruh provider selesai mendaftar, resolusi semacam itu dapat gagal dengan BindingResolutionException. Pindahkan kode yang menggunakan layanan ke boot().
Kesalahan keempat adalah menjalankan pekerjaan berat pada boot() tanpa menimbang konteks eksekusinya. Metode boot() dieksekusi pada setiap proses yang menjalankan framework, termasuk perintah Artisan seperti route:list maupun config:cache, sehingga query database atau pembacaan berkas besar di dalamnya menambah beban pada operasi yang sebenarnya tidak membutuhkannya. Pekerjaan yang hanya diperlukan saat permintaan web berjalan sebaiknya dipindahkan ke middleware, ke binding yang diresolusi secara lazy, atau ke titik penggunaan sesungguhnya.
Ringkasan#
- Service provider menampung seluruh pendaftaran layanan aplikasi dan dieksekusi dalam dua fase, yaitu
register()untuk binding danboot()untuk kode yang menggunakan layanan. - Daftar provider aplikasi ada pada
bootstrap/providers.php, sedangkan provider framework didaftarkan otomatis oleh Laravel. - Perintah
make:providermenghasilkan berkas kerangka, tetapi pendaftarannya tetap ditulis manual. - Metode
boot()adalah tempat konfigurasi yang butuh layanan, misalnyaRoute::bind()untuk mengikat parameter rute ke kolom selain primary key.
Referensi#
- Service Providers — Laravel
— Dokumentasi resmi metode
register(),boot(), dan pendaftaran provider. - Routing — Laravel
— Dokumentasi binding rute kustom yang dipakai sebagai contoh
boot().