Learn/flight_vs_laravel
Flight vs Laravel
Apa itu Laravel?
Laravel adalah framework lengkap yang memiliki semua fitur lengkap dan ekosistem yang berfokus pada pengembang yang luar biasa, tetapi dengan biaya dalam hal performa dan kompleksitas. Tujuan Laravel adalah agar pengembang memiliki tingkat produktivitas tertinggi dan membuat tugas-tugas umum menjadi mudah. Laravel adalah pilihan yang bagus untuk pengembang yang ingin membangun aplikasi web perusahaan yang lengkap. Itu datang dengan beberapa trade-off, khususnya dalam hal performa dan kompleksitas. Belajar dasar-dasar Laravel bisa mudah, tetapi mencapai kefasihan dalam framework ini bisa memakan waktu.
Ada juga begitu banyak modul Laravel sehingga pengembang sering merasa satu-satunya cara untuk menyelesaikan masalah adalah melalui modul-modul ini, padahal sebenarnya Anda bisa saja menggunakan pustaka lain atau menulis kode sendiri.
Kelebihan dibandingkan Flight
- Laravel memiliki ekosistem besar dari pengembang dan modul yang dapat digunakan untuk menyelesaikan masalah umum.
- Laravel memiliki ORM lengkap yang dapat digunakan untuk berinteraksi dengan database Anda.
- Laravel memiliki jumlah dokumentasi dan tutorial yang gila yang dapat digunakan untuk mempelajari framework. Itu bisa bagus untuk mendalami detail halus atau buruk karena ada begitu banyak yang harus dibaca.
- Laravel memiliki sistem autentikasi bawaan yang dapat digunakan untuk mengamankan aplikasi Anda.
- Laravel memiliki podcast, konferensi, pertemuan, video, dan sumber daya lain yang dapat digunakan untuk mempelajari framework.
- Laravel ditujukan untuk pengembang berpengalaman yang ingin membangun aplikasi web perusahaan yang lengkap.
Kekurangan dibandingkan Flight
- Laravel memiliki lebih banyak hal yang terjadi di balik layar dibandingkan Flight. Ini datang dengan biaya dramatis dalam hal performa. Lihat benchmark TechEmpower untuk informasi lebih lanjut.
- Flight ditujukan untuk pengembang yang ingin membangun aplikasi web ringan, cepat, dan mudah digunakan.
- Flight ditujukan untuk kesederhanaan dan kemudahan penggunaan.
- Salah satu fitur inti Flight adalah bahwa ia berusaha sebaik mungkin untuk mempertahankan kompatibilitas mundur. Laravel menyebabkan banyak frustrasi antara versi mayor.
- Flight ditujukan untuk pengembang yang baru memasuki dunia framework untuk pertama kalinya.
- Flight tidak memiliki dependensi, sedangkan Laravel memiliki jumlah dependensi yang mengerikan
- Flight juga bisa melakukan aplikasi tingkat perusahaan, tetapi tidak memiliki kode boilerplate sebanyak Laravel. Ini juga akan membutuhkan lebih banyak disiplin dari pengembang untuk menjaga semuanya terorganisir dan terstruktur dengan baik.
- Flight memberikan pengembang lebih banyak kendali atas aplikasi, sedangkan Laravel memiliki banyak sihir di balik layar yang bisa menjengkelkan.
Learn/migrating_to_v3
Migrasi ke v3
Kompatibilitas mundur sebagian besar telah dipertahankan, tetapi ada beberapa perubahan yang harus Anda ketahui saat migrasi dari v2 ke v3. Ada beberapa perubahan yang bertentangan terlalu banyak dengan pola desain sehingga beberapa penyesuaian harus dilakukan.
Perilaku Penyanggaan Output
v3.5.0
Penyanggaan output adalah proses di mana output yang dihasilkan oleh skrip PHP disimpan dalam penyangga (internal ke PHP) sebelum dikirim ke klien. Ini memungkinkan Anda untuk memodifikasi output sebelum dikirim ke klien.
Dalam aplikasi MVC, Controller adalah "manajer" dan mengelola apa yang dilakukan oleh view. Memiliki output yang dihasilkan di luar controller (atau dalam kasus Flight terkadang fungsi anonim) merusak pola MVC. Perubahan ini dilakukan untuk lebih selaras dengan pola MVC dan membuat framework lebih dapat diprediksi serta lebih mudah digunakan.
Di v2, penyanggaan output ditangani dengan cara yang tidak secara konsisten menutup penyangga outputnya sendiri, yang membuat pengujian unit dan streaming lebih sulit. Bagi sebagian besar pengguna, perubahan ini mungkin tidak memengaruhi Anda secara aktual. Namun, jika Anda mencetak konten di luar callable dan controller (misalnya dalam hook), kemungkinan Anda akan mengalami masalah. Mencetak konten dalam hook, dan sebelum framework benar-benar dieksekusi mungkin pernah berhasil di masa lalu, tetapi tidak akan berhasil ke depannya.
Di Mana Anda Mungkin Mengalami Masalah
// index.php
require 'vendor/autoload.php';
// hanya contoh
define('START_TIME', microtime(true));
function hello() {
echo 'Hello World';
}
Flight::map('hello', 'hello');
Flight::after('hello', function(){
// ini sebenarnya akan baik-baik saja
echo '<p>Kalimat Hello World ini disajikan oleh huruf "H"</p>';
});
Flight::before('start', function(){
// hal-hal seperti ini akan menyebabkan kesalahan
echo '<html><head><title>Halaman Saya</title></head><body>';
});
Flight::route('/', function(){
// ini sebenarnya baik-baik saja
echo 'Hello World';
// Ini juga seharusnya baik-baik saja
Flight::hello();
});
Flight::after('start', function(){
// ini akan menyebabkan kesalahan
echo '<div>Halaman Anda dimuat dalam '.(microtime(true) - START_TIME).' detik</div></body></html>';
});
Mengaktifkan Perilaku Rendering v2
Bisakah Anda tetap mempertahankan kode lama Anda apa adanya tanpa melakukan penulisan ulang agar kompatibel dengan v3? Ya, Anda bisa! Anda dapat mengaktifkan
perilaku rendering v2 dengan mengatur opsi konfigurasi flight.v2.output_buffering menjadi true. Ini akan memungkinkan Anda untuk terus
menggunakan perilaku rendering lama, tetapi disarankan untuk memperbaikinya ke depannya. Di v4 dari framework, ini akan dihapus.
// index.php
require 'vendor/autoload.php';
Flight::set('flight.v2.output_buffering', true);
Flight::before('start', function(){
// Sekarang ini akan baik-baik saja
echo '<html><head><title>Halaman Saya</title></head><body>';
});
// lebih banyak kode
Perubahan Dispatcher
v3.7.0
Jika Anda secara langsung memanggil metode statis untuk Dispatcher seperti Dispatcher::invokeMethod(), Dispatcher::execute(), dll.
Anda perlu memperbarui kode Anda agar tidak secara langsung memanggil metode-metode ini. Dispatcher telah diubah menjadi lebih berorientasi objek sehingga
Container Injeksi Dependensi dapat digunakan dengan lebih mudah. Jika Anda perlu memanggil metode mirip dengan cara Dispatcher, Anda
dapat secara manual menggunakan sesuatu seperti $result = $class->$method(...$params); atau call_user_func_array() sebagai gantinya.
Perubahan halt() stop() redirect() dan error()
v3.10.0
Perilaku default sebelum 3.10.0 adalah membersihkan baik header maupun body respons. Ini diubah menjadi hanya membersihkan body respons.
Jika Anda perlu membersihkan header juga, Anda dapat menggunakan Flight::response()->clear().
Learn/configuration
Konfigurasi
Gambaran Umum
Flight menyediakan cara sederhana untuk mengonfigurasi berbagai aspek framework agar sesuai dengan kebutuhan aplikasi Anda. Beberapa pengaturan sudah ditetapkan secara bawaan, tetapi Anda dapat menimpanya sesuai kebutuhan. Anda juga dapat mengatur variabel Anda sendiri untuk digunakan di seluruh aplikasi.
Konfigurasi berlapis yang jelas (default file + rahasia lingkungan) juga membantu alat coding AI: agen dapat belajar satu tempat untuk literal dan satu tempat untuk rahasia, alih-alih menemukan pembacaan $_ENV di dalam controller.
Pemahaman
Anda dapat menyesuaikan perilaku tertentu dari Flight dengan mengatur nilai konfigurasi melalui metode set.
Flight::set('flight.log_errors', true);
Dalam aplikasi terstruktur (termasuk skeleton), Anda biasanya memuat pengaturan proyek dari app/config/config.php dan kemudian menerapkan kunci yang relevan ke Engine (misalnya flight.base_url, flight.views.path). Anda juga dapat menyuntikkan objek konfigurasi kecil ke dalam controller alih-alih membaca global di mana-mana—lebih ramah untuk pengujian dan untuk agen yang mengikuti AGENTS.md.
Penggunaan Dasar
Opsi Konfigurasi Flight
Berikut adalah daftar semua pengaturan konfigurasi yang tersedia:
- flight.base_url
?string- Menimpa URL dasar permintaan jika Flight berjalan di subdirektori. (default: null) - flight.case_sensitive
bool- Pencocokan peka huruf besar/kecil untuk URL. (default: false) - flight.handle_errors
bool- Mengizinkan Flight untuk menangani semua kesalahan secara internal. (default: true)- Jika Anda ingin Flight menangani kesalahan alih-alih perilaku PHP bawaan, ini perlu disetel ke true.
- Jika Anda telah menginstal Tracy, Anda ingin menyetel ini ke false agar Tracy dapat menangani kesalahan.
- Jika Anda memiliki plugin APM terinstal, Anda ingin menyetel ini ke true agar APM dapat mencatat kesalahan.
- flight.log_errors
bool- Mencatat kesalahan ke file log kesalahan server web. (default: false)- Jika Anda telah menginstal Tracy, Tracy akan mencatat kesalahan berdasarkan konfigurasi Tracy, bukan konfigurasi ini.
- flight.debug
bool- Menampilkan informasi kesalahan terperinci (pesan pengecualian, kode, dan jejak tumpukan) di browser saat terjadi kesalahan. (default: false)- Jangan pernah mengaktifkan ini di produksi — ini membocorkan detail aplikasi internal. Gunakan hanya untuk pengembangan lokal atau staging.
- Ketika
false, respons umum500 Internal Server Erroryang ditampilkan sebagai gantinya. Pasangkan denganflight.log_errorsuntuk menangkap kesalahan di sisi server.
- flight.allow_method_override
bool- Mengizinkan metode HTTP untuk ditimpa melalui header permintaanX-HTTP-Method-Overrideatau bidang_methoddi badan POST. (default: true)- Menyetel ini ke
falsedisarankan untuk aplikasi yang tidak memerlukan spoofing metode berbasis formulir HTML, karena ini mencegah klien memalsukan permintaanDELETEatauPUTmelalui formulir POST standar. - Lihat Keamanan untuk detail lebih lanjut.
- Menyetel ini ke
- flight.views.path
string- Direktori yang berisi file template tampilan. (default: ./views) - flight.views.extension
string- Ekstensi file template tampilan. (default:.php; skeleton resmi menyetel ini ke.twigsaat menggunakan Twig) - flight.content_length
bool- Menyetel headerContent-Length. (default: true)- Jika Anda menggunakan Tracy, ini perlu disetel ke false agar Tracy dapat dirender dengan benar.
- flight.v2.output_buffering
bool- Menggunakan buffering keluaran lama. Lihat migrasi ke v3. (default: false)
Konfigurasi Loader
Terdapat juga pengaturan konfigurasi tambahan untuk loader. Ini memungkinkan Anda untuk memuat kelas secara otomatis dengan _ di nama kelas.
// Mengaktifkan pemuatan kelas dengan garis bawah
// Defaultnya true
Loader::$v2ClassLoading = false;
Ingat bahwa autoloading juga bergantung pada huruf besar/kecil folder yang cocok dengan namespace Anda—terutama dengan tata letak App\ + app/Controller/ pada skeleton.
Konfigurasi proyek dan .env (pola skeleton)
Inti Flight tidak memerlukan file .env. Banyak aplikasi hanya menggunakan array konfigurasi PHP. Skeleton resmi melapisi konfigurasi sehingga rahasia tetap keluar dari git sementara Runway dapat dengan aman menulis ulang konfigurasi literal:
.env/ lingkungan nyata — rahasia dan penimpaan deploy (diabaikan git).app/config/config.php— default array PHP literal (disalin dariconfig_sample.php). Sebaiknya tidak ada ekspresi$_ENV[...]di dalam file ini: alat sepertirunway config:setdapat menulis ulangnya sebagai nilai statis dan dapat memanggang rahasia ke dalam file.- Gabungkan saat bootstrap — env menang untuk kunci yang dipetakan; kode aplikasi membaca objek konfigurasi atau
$app->get(), bukan$_ENVdi controller.
Contoh bentuk config_sample.php / config.php (disederhanakan):
<?php
// Hanya literal — rahasia ada di .env untuk alur kerja skeleton
return [
'app' => [
'env' => 'development',
'debug' => true,
'base_url' => '/',
'timezone' => 'UTC',
],
'database' => [
'driver' => 'sqlite', // atau mysql, atau '' untuk menonaktifkan
'host' => 'localhost',
'dbname' => '',
'user' => '',
'password' => '',
'file_path' => __DIR__ . '/../../database.sqlite',
],
// ...
];
# .env.example → .env (skeleton)
APP_ENV=development
APP_DEBUG=true
FLIGHT_BASE_URL=/
DB_DRIVER=sqlite
# DB_PASSWORD=...
Pemisahan ini disengaja untuk proyek yang ramah AI: instruksi dapat mengatakan "default di config.php, rahasia di .env, suntikkan Config / Engine—jangan pernah menemukan akses env di controller." Aplikasi yang ada dapat mengabaikan .env sepenuhnya dan tetap menggunakan satu file konfigurasi.
Variabel
Flight memungkinkan Anda menyimpan variabel sehingga dapat digunakan di mana saja dalam aplikasi Anda.
// Simpan variabel Anda
Flight::set('id', 123);
// Di bagian lain aplikasi Anda
$id = Flight::get('id');
Untuk melihat apakah suatu variabel telah disetel, Anda dapat melakukan:
if (Flight::has('id')) {
// Lakukan sesuatu
}
Anda dapat menghapus variabel dengan melakukan:
// Menghapus variabel id
Flight::clear('id');
// Menghapus semua variabel
Flight::clear();
Catatan: Hanya karena Anda dapat mengatur variabel bukan berarti Anda harus melakukannya. Gunakan fitur ini secukupnya. Alasannya adalah karena apa pun yang disimpan di sini menjadi variabel global. Variabel global buruk karena dapat diubah dari mana saja di aplikasi Anda, sehingga sulit untuk melacak bug. Selain itu, ini dapat memperumit hal-hal seperti pengujian unit. Lebih suka injeksi konstruktor (seperti pada setup skeleton + Dice) untuk layanan dan konfigurasi yang dibutuhkan controller.
Kesalahan dan Pengecualian
Semua kesalahan dan pengecualian ditangkap oleh Flight dan diteruskan ke metode error jika flight.handle_errors disetel ke true.
Perilaku defaultnya adalah mengirim respons umum HTTP 500 Internal Server Error dengan beberapa informasi kesalahan.
Anda dapat menimpa perilaku ini sesuai kebutuhan Anda:
Flight::map('error', function (Throwable $error) {
// Tangani kesalahan
echo $error->getTraceAsString();
});
Secara default, kesalahan tidak dicatat ke server web. Anda dapat mengaktifkannya dengan mengubah konfigurasi:
Flight::set('flight.log_errors', true);
404 Not Found
Ketika URL tidak dapat ditemukan, Flight memanggil metode notFound. Perilaku defaultnya adalah mengirim respons HTTP 404 Not Found dengan pesan sederhana.
Anda dapat menimpa perilaku ini sesuai kebutuhan Anda:
Flight::map('notFound', function () {
// Tangani tidak ditemukan
});
Lihat Juga
- Instalasi - Konfigurasi skeleton,
.env, dan tata letak bootstrap. - Autoloading - Namespace dan huruf besar/kecil folder.
- Memperluas Flight - Cara memperluas dan menyesuaikan fungsionalitas inti Flight.
- Pengujian Unit - Cara menulis pengujian unit untuk aplikasi Flight Anda.
- AI & Pengalaman Pengembang -
AGENTS.mddan instruksi proyek yang konsisten. - Tracy - Plugin untuk penanganan kesalahan dan debugging tingkat lanjut.
- Ekstensi Tracy - Ekstensi untuk mengintegrasikan Tracy dengan Flight.
- APM - Plugin untuk pemantauan kinerja aplikasi dan pelacakan kesalahan.
- Keamanan - Bendera penguatan dan penanganan rahasia.
Pemecahan Masalah
- Jika Anda mengalami masalah dalam menemukan semua nilai konfigurasi Anda, Anda dapat melakukan
var_dump(Flight::get()); - Jika Runway atau alat deploy menulis ulang
config.php, pastikan rahasia tidak di-commit—simpan di.envatau lingkungan nyata saat menggunakan pola skeleton.
Changelog
- Dokumen – Mendokumentasikan konfigurasi gaya skeleton / lapisan
.envdan default ekstensi tampilan Twig untuk proyek baru. - v3.18.1 - Menambahkan opsi konfigurasi
flight.debugdanflight.allow_method_override. - v3.5.0 - Menambahkan konfigurasi untuk
flight.v2.output_bufferinguntuk mendukung perilaku buffering keluaran lama. - v2.0 - Konfigurasi inti ditambahkan.
Learn/ai
AI & Pengalaman Pengembang dengan Flight
Ringkasan
Flight dirancang untuk bekerja dengan alat coding AI—bukan melawannya. API yang kecil dan dapat diprediksi, tata letak aplikasi yang jelas di skeleton resmi, dan file instruksi khusus proyek berarti asisten seperti GitHub Copilot, Cursor, Windsurf, Claude Code, dan Gemini dapat mengikuti pola yang sama seperti yang Anda tulis secara manual.
Dengan perintah Runway bawaan untuk menghubungkan ke penyedia LLM dan menghasilkan instruksi proyek, Flight membantu Anda dan tim mendapatkan bantuan yang konsisten dan relevan tanpa harus menempelkan konteks yang sama di setiap obrolan.
Memahami
Asisten coding AI paling membantu ketika mereka memahami konteks, konvensi, dan tujuan proyek Anda. Bantuan AI Flight memungkinkan Anda untuk:
- Menghubungkan proyek Anda ke penyedia LLM populer (OpenAI, Grok, Claude, dll.)
- Menghasilkan dan memperbarui instruksi khusus proyek sehingga semua orang mendapatkan panduan yang sama
- Menjaga kode tulisan tangan dan kode yang dihasilkan AI berada dalam satu tata letak (terutama dengan skeleton)
Fitur-fitur ini tersedia di CLI inti Flight (melalui Runway) dan sudah terhubung sebelumnya di starter resmi flightphp/skeleton.
Apa yang disertakan skeleton untuk AI
Starter resmi memperlakukan AGENTS.md sebagai sumber kebenaran untuk alat AI:
| File | Peran |
|---|---|
AGENTS.md (akar proyek) |
Aturan global, alur boot, namespace, DI, "apa yang tidak boleh dilakukan" |
AGENTS.md khusus di bawah app/, migrations/, tests/, dll. |
Tip ringan khusus folder saat Anda bekerja di direktori tersebut |
SECURITY.md |
Rahasia, header, XSS/SQL, pelaporan—keamanan tetap disengaja dan terpisah |
Tidak ada file gaya rumah terpisah untuk Copilot / Cursor / Gemini / Windsurf di skeleton. Arahkan asisten Anda ke AGENTS.md di akar proyek (dan biarkan ia mengikuti tautan ke file khusus). Manusia dapat mengabaikan file-file ini sepenuhnya dan menggunakan README; tata letaknya tetap sama apa pun caranya.
Dokumen mengajarkan API; skeleton mengajarkan tata letak. Contoh singkat
Flight::di dokumentasi ini bagus untuk belajar. Di aplikasi skeleton, lebih suka kelasApp\…, injeksi konstruktor, dan$this->appdaripada fasad statis di dalam kontroler. Lihat Instalasi dan Autoloading.
Penggunaan Dasar
Menyiapkan Kredensial LLM
Perintah ai:init memandu Anda menghubungkan proyek ke penyedia LLM.
php runway ai:init
Anda akan diminta untuk:
- Memilih penyedia Anda (OpenAI, Grok, Claude, dll.)
- Memasukkan kunci API Anda
- Menetapkan URL dasar dan nama model
Ini membuat kredensial yang digunakan untuk permintaan LLM selanjutnya (misalnya menghasilkan instruksi).
Contoh:
Welcome to AI Init!
Which LLM API do you want to use? [1] openai, [2] grok, [3] claude: 1
Enter the base URL for the LLM API [https://api.openai.com]:
Enter your API key for openai: sk-...
Enter the model name you want to use (e.g. gpt-4, claude-3-opus, etc) [gpt-4o]:
Credentials saved to .runway-creds.json
Menghasilkan Instruksi AI Khusus Proyek
Perintah ai:generate-instructions membuat atau memperbarui instruksi untuk asisten coding AI, disesuaikan dengan proyek Anda.
php runway ai:generate-instructions
Anda akan menjawab beberapa pertanyaan (deskripsi, basis data, templating, keamanan, ukuran tim, dll.). Flight menggunakan penyedia LLM Anda untuk menghasilkan instruksi dan menuliskannya terutama ke:
AGENTS.mddi akar proyek (tidak bergantung pada alat; yang diharapkan oleh skeleton resmi dan sebagian besar agen modern)
Tergantung pada versi CLI dan opsi, perintah tersebut juga dapat menulis salinan khusus alat untuk alur kerja lama (misalnya file aturan Copilot, Cursor, Windsurf, atau Gemini). Untuk proyek baru dari skeleton, perlakukan AGENTS.md (plus file AGENTS.md khusus yang Anda simpan di bawah app/) sebagai satu-satunya sumber kebenaran—jangan memelihara lima file instruksi yang berbeda-beda secara manual.
Contoh:
Please describe what your project is for? My awesome API
What database are you planning on using? MySQL
What HTML templating engine will you plan on using (if any)? twig
Is security an important element of this project? (y/n) y
...
AI instructions updated successfully.
Sekarang alat AI dapat menyarankan kode yang sesuai dengan tumpukan dan tata letak nyata Anda—bukan tutorial PHP generik.
Penggunaan Lanjutan
- Sesuaikan kredensial atau jalur keluaran dengan opsi perintah (lihat
--helppada setiap perintah). - Alat bantu ini bekerja dengan penyedia LLM apa pun yang mendukung API yang kompatibel dengan OpenAI.
- Jalankan ulang
ai:generate-instructionsseiring berkembangnya proyek agar agen tetap sinkron. - Di skeleton, simpan kebijakan keamanan di
SECURITY.mddan tata letak kode diAGENTS.mdsehingga kedua dokumen tidak menjadi tempat campur aduk. - Utamakan docs.flightphp.com dan server MCP Flight saat agen membutuhkan detail API; verifikasi metode yang dibuat-buat terhadap
vendor/flightphp/core.
Lihat Juga
- Flight Skeleton – Starter resmi dengan
AGENTS.md, Twig, SimplePdo, dan Dice yang terhubung untuk struktur yang ramah AI - Instalasi – Tata letak
create-projectyang direkomendasikan - Autoloading – Huruf besar/kecil folder cocok dengan namespace (
App\Controller↔app/Controller/) - CLI Runway – CLI yang mendukung perintah
ai:*dan pembuatan kerangka - Keamanan – Default yang aman yang seharusnya tidak diperlemah oleh agen (dan manusia)
Pemecahan Masalah
- Jika Anda melihat "Missing .runway-creds.json", jalankan
php runway ai:initterlebih dahulu. - Pastikan kunci API Anda valid dan memiliki akses ke model yang dipilih.
- Jika instruksi tidak diperbarui, periksa izin file di direktori proyek Anda.
- Jika agen mengarang API Flight atau tata letak folder yang salah, arahkan ke
AGENTS.mddi akar proyek dan situs dokumentasi ini; tata letak skeleton berlaku untuk kode di bawahapp/.
Catatan Perubahan
- v3.18.4 –
ai:generate-instructionsmenulis instruksi proyek keAGENTS.mddi akar proyek. - v3.16.0 – Menambahkan perintah CLI
ai:initdanai:generate-instructionsuntuk integrasi AI.
Learn/unit_testing_and_solid_principles
Artikel ini awalnya diterbitkan di Airpair pada tahun 2015. Semua kredit diberikan kepada Airpair dan Brian Fenton yang awalnya menulis artikel ini, meskipun situs web tersebut sudah tidak tersedia lagi dan artikel hanya ada dalam Wayback Machine. Artikel ini telah ditambahkan ke situs untuk tujuan pembelajaran dan pendidikan bagi komunitas PHP secara keseluruhan.
1 Pengaturan dan konfigurasi
1.1 Tetap Terbaru
Mari kita sebutkan ini dari awal - jumlah instalasi PHP yang sedikit menyedihkan di alam liar yang tetap terbaru atau dipertahankan tetap terbaru. Baik itu karena pembatasan hosting bersama, pengaturan default yang tidak ada yang berpikir untuk mengubahnya, atau tidak ada waktu/anggaran untuk pengujian peningkatan, binary PHP yang sederhana cenderung ditinggalkan. Jadi satu praktik terbaik yang jelas yang perlu lebih ditekankan adalah selalu menggunakan versi PHP yang terbaru (5.6.x pada saat artikel ini). Selanjutnya, penting juga untuk menjadwalkan peningkatan reguler baik PHP itu sendiri maupun ekstensi atau pustaka vendor apa pun yang mungkin Anda gunakan. Peningkatan memberi Anda fitur bahasa baru, kecepatan yang ditingkatkan, penggunaan memori yang lebih rendah, dan pembaruan keamanan. Semakin sering Anda meningkatkan, semakin sedikit prosesnya menjadi menyakitkan.
1.2 Atur default yang masuk akal
PHP melakukan pekerjaan yang layak dalam menetapkan default yang baik langsung dari kotak dengan file php.ini.development dan php.ini.production, tetapi kita bisa lebih baik. Untuk satu, mereka tidak menetapkan zona waktu/tanggal untuk kita. Itu masuk akal dari perspektif distribusi, tetapi tanpa satu, PHP akan melemparkan kesalahan E_WARNING setiap kali kita memanggil fungsi terkait tanggal/waktu. Berikut adalah beberapa pengaturan yang direkomendasikan:
- date.timezone - pilih dari daftar zona waktu yang didukung
- session.savepath - jika kita menggunakan file untuk sesi dan bukan penangan penyimpanan lain, atur ini ke sesuatu di luar /tmp. Meninggalkan ini sebagai /tmp bisa berisiko di lingkungan hosting bersama karena /tmp_ biasanya memiliki izin yang luas. Bahkan dengan bit lengket yang diatur, siapa saja yang memiliki akses untuk mencantumkan isi direktori ini bisa mengetahui semua ID sesi aktif Anda.
- session.cookie_secure - hal yang jelas, nyalakan ini jika Anda menyajikan kode PHP Anda melalui HTTPS.
- session.cookie_httponly - atur ini untuk mencegah cookie sesi PHP diakses melalui JavaScript
- Lebih... gunakan alat seperti iniscan untuk menguji konfigurasi Anda terhadap kerentanan umum
1.3 Ekstensi
Ini juga ide bagus untuk menonaktifkan (atau setidaknya tidak mengaktifkan) ekstensi yang tidak akan Anda gunakan, seperti driver basis data. Untuk melihat apa yang diaktifkan, jalankan perintah phpinfo() atau pergi ke baris perintah dan jalankan ini.
$ php -i
Informasinya sama, tetapi phpinfo() memiliki pemformatan HTML yang ditambahkan. Versi CLI lebih mudah dialirkan ke grep untuk menemukan informasi spesifik meskipun. Contoh.
$ php -i | grep error_log
Satu peringatan dari metode ini meskipun: mungkin ada pengaturan PHP yang berbeda yang berlaku untuk versi yang menghadap web dan versi CLI.
2 Gunakan Composer
Ini mungkin mengejutkan tetapi salah satu praktik terbaik untuk menulis PHP modern adalah menulis lebih sedikit darinya. Meskipun benar bahwa salah satu cara terbaik untuk mahir dalam pemrograman adalah melakukannya, ada banyak masalah yang sudah teratasi di ruang PHP, seperti routing, pustaka validasi input dasar, konversi unit, lapisan abstraksi basis data, dll... Cukup kunjungi Packagist dan jelajahi. Anda mungkin menemukan bahwa bagian signifikan dari masalah yang Anda coba selesaikan sudah ditulis dan diuji.
Meskipun menggoda untuk menulis semua kode sendiri (dan tidak ada yang salah dengan menulis kerangka kerja atau pustaka Anda sendiri sebagai pengalaman belajar) Anda harus melawan perasaan Itu Tidak Diciptakan Di Sini dan menghemat banyak waktu dan sakit kepala. Ikuti doktrin PIE sebagai gantinya - Bangga Dengan Penemuan Lain. Juga, jika Anda memilih untuk menulis sendiri apa pun, jangan rilis kecuali itu melakukan sesuatu yang sangat berbeda atau lebih baik daripada penawaran yang ada.
Composer adalah manajer paket untuk PHP, mirip dengan pip di Python, gem di Ruby, dan npm di Node. Ini memungkinkan Anda mendefinisikan file JSON yang mencantumkan ketergantungan kode Anda, dan itu akan mencoba menyelesaikan persyaratan tersebut dengan mengunduh dan menginstal bundel kode yang diperlukan.
2.1 Menginstal Composer
Kami mengasumsikan ini adalah proyek lokal, jadi mari instal instance Composer hanya untuk proyek saat ini. Navigasi ke direktori proyek Anda dan jalankan ini:
$ curl -sS https://getcomposer.org/installer | php
Ingat bahwa mengalirkan unduhan apa pun langsung ke penerjemah skrip (sh, ruby, php, dll...) adalah risiko keamanan, jadi baca kode instal dan pastikan Anda nyaman dengannya sebelum menjalankan perintah seperti ini.
Untuk kemudahan (jika Anda lebih suka mengetik composer install daripada php composer.phar install), Anda bisa menggunakan perintah ini untuk menginstal salinan tunggal composer secara global:
$ mv composer.phar /usr/local/bin/composer
$ chmod +x composer
Anda mungkin perlu menjalankannya dengan sudo tergantung pada izin file Anda.
2.2 Menggunakan Composer
Composer memiliki dua kategori utama ketergantungan yang bisa dikelolanya: "require" dan "require-dev". Ketergantungan yang tercantum sebagai "require" diinstal di mana-mana, tetapi ketergantungan "require-dev" hanya diinstal saat diminta secara spesifik. Biasanya ini adalah alat untuk saat kode sedang dikembangkan aktif, seperti PHP_CodeSniffer. Baris di bawah menunjukkan contoh cara menginstal Guzzle, sebuah pustaka HTTP populer.
$ php composer.phar require guzzle/guzzle
Untuk menginstal alat hanya untuk tujuan pengembangan, tambahkan flag --dev:
$ php composer.phar require --dev 'sebastian/phpcpd'
Ini menginstal PHP Copy-Paste Detector, alat kualitas kode lain sebagai ketergantungan hanya untuk pengembangan.
2.3 Install vs update
Saat kita pertama kali menjalankan composer install itu akan menginstal pustaka dan ketergantungan mereka yang kita butuhkan, berdasarkan file composer.json. Saat selesai, composer membuat file kunci, yang dapat diprediksi disebut composer.lock. File ini berisi daftar ketergantungan yang ditemukan composer untuk kita dan versi tepatnya, dengan hash. Kemudian setiap kali mendatang kita menjalankan composer install, itu akan melihat di file kunci dan menginstal versi tepat itu.
composer update adalah binatang yang sedikit berbeda. Ini akan mengabaikan file composer.lock (jika ada) dan mencoba menemukan versi yang paling mutakhir dari setiap ketergantungan yang masih memenuhi batasan di composer.json. Ini kemudian menulis file composer.lock baru saat selesai.
2.4 Autoloading
Baik composer install maupun composer update akan menghasilkan autoloader untuk kita yang memberi tahu PHP di mana menemukan semua file yang diperlukan untuk menggunakan pustaka yang baru saja kita instal. Untuk menggunakannya, cukup tambahkan baris ini (biasanya ke file bootstrap yang dieksekusi pada setiap permintaan):
require 'vendor/autoload.php';
3 Ikuti prinsip desain yang baik
3.1 SOLID
SOLID adalah mnemonik untuk mengingatkan kita akan lima prinsip kunci dalam desain perangkat lunak berorientasi objek yang baik.
3.1.1 S - Prinsip Tanggung Jawab Tunggal
Ini menyatakan bahwa kelas hanya boleh memiliki satu tanggung jawab, atau dengan kata lain, mereka hanya boleh memiliki satu alasan untuk berubah. Ini sesuai dengan filosofi Unix dari banyak alat kecil, melakukan satu hal dengan baik. Kelas yang hanya melakukan satu hal jauh lebih mudah diuji dan di-debug, dan mereka kurang mungkin mengejutkan Anda. Anda tidak ingin panggilan metode ke kelas Validator memperbarui catatan db. Berikut adalah contoh pelanggaran SRP, seperti yang umum Anda lihat di aplikasi berdasarkan pola ActiveRecord.
class Person extends Model
{
public $name;
public $birthDate;
protected $preferences;
public function getPreferences() {}
public function save() {}
}
Jadi ini adalah model entitas yang cukup dasar. Salah satu dari hal-hal ini tidak termasuk di sini meskipun. Tanggung jawab tunggal model entitas haruslah perilaku terkait entitas yang direpresentasikannya, itu tidak boleh bertanggung jawab untuk mempertahankan dirinya sendiri.
class Person extends Model
{
public $name;
public $birthDate;
protected $preferences;
public function getPreferences() {}
}
class DataStore
{
public function save(Model $model) {}
}
Ini lebih baik. Model Person kembali hanya melakukan satu hal, dan perilaku simpan telah dipindahkan ke objek ketekunan sebagai gantinya. Perhatikan juga bahwa saya hanya memberikan petunjuk tipe pada Model, bukan Person. Kita akan kembali ke itu saat kita sampai pada bagian L dan D dari SOLID.
3.1.2 O - Prinsip Terbuka Tertutup
Ada tes luar biasa untuk ini yang cukup merangkum apa prinsip ini: pikirkan fitur untuk diimplementasikan, mungkin yang terbaru yang Anda kerjakan atau sedang kerjakan. Dapatkah Anda mengimplementasikan fitur itu di basis kode yang ada HANYA dengan menambahkan kelas baru dan tidak mengubah kelas yang ada di sistem Anda? Konfigurasi dan kode wiring Anda mendapat sedikit pengabaian, tetapi di sebagian besar sistem ini mengejutkan sulit. Anda harus bergantung banyak pada dispatch polimorfik dan sebagian besar basis kode hanya tidak diatur untuk itu. Jika Anda tertarik pada itu ada pembicaraan Google yang bagus di YouTube tentang polimorfisme dan menulis kode tanpa Ifs yang menggali lebih dalam. Sebagai bonus, pembicaraan diberikan oleh Miško Hevery, yang banyak mungkin tahu sebagai pencipta AngularJs.
3.1.3 L - Prinsip Penggantian Liskov
Prinsip ini dinamai untuk Barbara Liskov, dan dicetak di bawah:
"Objek dalam program harus dapat diganti dengan instance subtipe mereka tanpa mengubah kebenaran program itu."
Itu semua terdengar bagus dan bagus, tetapi lebih jelas diilustrasikan dengan contoh.
abstract class Shape
{
public function getHeight();
public function setHeight($height);
public function getLength();
public function setLength($length);
}
Ini akan mewakili bentuk empat sisi dasar kita. Tidak ada yang mewah di sini.
class Square extends Shape
{
protected $size;
public function getHeight() {
return $this->size;
}
public function setHeight($height) {
$this->size = $height;
}
public function getLength() {
return $this->size;
}
public function setLength($length) {
$this->size = $length;
}
}
Berikut bentuk pertama kita, Kotak. Bentuk yang cukup langsung, kan? Anda bisa mengasumsikan bahwa ada konstruktor di mana kita mengatur dimensi, tetapi Anda melihat dari implementasi ini bahwa panjang dan tinggi selalu akan sama. Kotak hanya seperti itu.
class Rectangle extends Shape
{
protected $height;
protected $length;
public function getHeight() {
return $this->height;
}
public function setHeight($height) {
$this->height = $height;
}
public function getLength() {
return $this->length;
}
public function setLength($length) {
$this->length = $length;
}
}
Jadi di sini kita memiliki bentuk yang berbeda. Masih memiliki tanda tangan metode yang sama, itu masih bentuk empat sisi, tetapi bagaimana jika kita mulai mencoba menggunakannya sebagai pengganti satu sama lain? Sekarang tiba-tiba jika kita mengubah tinggi Bentuk kita, kita tidak lagi bisa mengasumsikan bahwa panjang bentuk kita akan cocok. Kita telah melanggar kontrak yang kita miliki dengan pengguna saat kita memberi mereka bentuk Kotak kita.
Ini adalah contoh teks pelanggaran LSP dan kita memerlukan jenis prinsip ini untuk membuat penggunaan terbaik dari sistem tipe. Bahkan duck typing tidak akan memberi tahu kita jika perilaku dasarnya berbeda, dan karena kita tidak bisa tahu itu tanpa melihatnya rusak, yang terbaik adalah memastikan itu tidak berbeda sejak awal.
3.1.3 I - Prinsip Segregasi Antarmuka
Prinsip ini mengatakan untuk lebih memilih banyak antarmuka kecil dan halus dibandingkan satu yang besar. Antarmuka harus didasarkan pada perilaku daripada "ini salah satu kelas ini". Pikirkan antarmuka yang datang dengan PHP. Traversable, Countable, Serializable, hal-hal seperti itu. Mereka mengiklankan kemampuan yang dimiliki objek, bukan apa yang diwariskannya. Jadi jaga antarmuka Anda tetap kecil. Anda tidak ingin antarmuka memiliki 30 metode di atasnya, 3 adalah tujuan yang lebih baik.
3.1.4 D - Prinsip Pembalikan Ketergantungan
Anda mungkin pernah mendengar tentang ini di tempat lain yang membahas Dependency Injection, tetapi Dependency Inversion dan Dependency Injection bukan cukup hal yang sama. Dependency inversion benar-benar hanya cara mengatakan bahwa Anda harus bergantung pada abstraksi dalam sistem Anda dan bukan pada detailnya. Sekarang apa artinya itu bagi Anda sehari-hari?
Jangan langsung menggunakan mysqli_query() di seluruh kode Anda, gunakan sesuatu seperti DataStore->query() sebagai gantinya.
Inti dari prinsip ini sebenarnya tentang abstraksi. Ini lebih tentang mengatakan "gunakan adaptor basis data" daripada bergantung pada panggilan langsung ke sesuatu seperti mysqli_query. Jika Anda langsung menggunakan mysqli_query di setengah kelas Anda maka Anda mengikat segala sesuatu langsung ke basis data Anda. Tidak ada untuk atau melawan MySQL di sini, tetapi jika Anda menggunakan mysqli_query, jenis detail tingkat rendah itu harus disembunyikan hanya di satu tempat dan kemudian fungsionalitas itu harus diekspos melalui wrapper umum.
Sekarang saya tahu ini adalah contoh yang agak klise jika Anda memikirkannya, karena jumlah kali Anda akan benar-benar mengubah mesin basis data sepenuhnya setelah produk Anda diproduksi sangat, sangat rendah. Saya memilihnya karena saya pikir orang akan akrab dengan ide dari kode mereka sendiri. Juga, bahkan jika Anda memiliki basis data yang Anda ketahui akan tetap, objek wrapper abstrak itu memungkinkan Anda untuk memperbaiki bug, mengubah perilaku, atau mengimplementasikan fitur yang Anda inginkan basis data yang dipilih Anda miliki. Ini juga membuat pengujian unit mungkin di mana panggilan tingkat rendah tidak.
4 Latihan objek
Ini bukan penyelaman penuh ke prinsip-prinsip ini, tetapi dua pertama mudah diingat, memberikan nilai bagus, dan bisa segera diterapkan ke hampir semua basis kode.
4.1 Tidak lebih dari satu level indentasi per metode
Ini adalah cara membantu untuk memikirkan dekomposisi metode menjadi potongan yang lebih kecil, meninggalkan kode yang lebih jelas dan lebih mendokumentasikan diri. Semakin banyak level indentasi yang Anda miliki, semakin banyak metode yang melakukan dan semakin banyak status yang harus Anda ingat dalam pikiran Anda saat bekerja dengannya.
Segera saya tahu orang akan keberatan dengan ini, tetapi ini hanya pedoman/heuristik, bukan aturan keras dan cepat. Saya tidak mengharapkan siapa pun untuk menegakkan aturan PHP_CodeSniffer untuk ini (meskipun orang telah).
Mari kita jalankan melalui sampel cepat apa ini mungkin terlihat seperti:
public function transformToCsv($data)
{
$csvLines = array();
$csvLines[] = implode(',', array_keys($data[0]));
foreach ($data as $row) {
if (!$row) {
continue;
}
$csvLines[] = implode(',', $row);
}
return $csvLines;
}
Meskipun ini bukan kode yang buruk (teknis benar, dapat diuji, dll...) kita bisa melakukan banyak lagi untuk membuat ini jelas. Bagaimana kita mengurangi level nesting di sini?
Kita tahu kita perlu menyederhanakan isi loop foreach (atau menghapusnya sepenuhnya) jadi mari kita mulai di sana.
if (!$row) {
continue;
}
Bagian ini mudah. Yang dilakukan semua ini adalah mengabaikan baris kosong. Kita bisa shortcut proses ini seluruhnya dengan menggunakan fungsi bawaan PHP sebelum kita bahkan sampai ke loop.
$data = array_filter($data);
foreach ($data as $row) {
$csvLines[] = implode(',', $row);
}
Sekarang kita memiliki satu level nesting. Tetapi melihat ini, yang kita lakukan hanyalah menerapkan fungsi ke setiap item dalam array. Kita bahkan tidak perlu loop foreach untuk melakukan itu.
$data = array_filter($data);
$csvLines = array_map(function($row) {
return implode(',', $row);
}, $data);
Sekarang kita tidak memiliki nesting sama sekali, dan kode kemungkinan akan lebih cepat karena kita melakukan semua looping dengan fungsi C asli daripada PHP. Kita harus terlibat dalam sedikit tipuan untuk meneruskan koma ke implode meskipun, jadi Anda bisa berargumen bahwa berhenti di langkah sebelumnya jauh lebih bisa dipahami.
4.2 Coba tidak gunakan else
Ini benar-benar menangani dua ide utama. Yang pertama adalah pernyataan return ganda dari metode. Jika Anda memiliki cukup informasi untuk membuat keputusan tentang hasil metode, lanjutkan buat keputusan itu dan kembali. Yang kedua adalah ide yang dikenal sebagai Guard Clauses. Ini pada dasarnya adalah pemeriksaan validasi yang dikombinasikan dengan return awal, biasanya di dekat atas metode. Biarkan saya tunjukkan apa yang saya maksud.
public function addThreeInts($first, $second, $third) {
if (is_int($first)) {
if (is_int($second)) {
if (is_int($third)) {
$sum = $first + $second + $third;
} else {
return null;
}
} else {
return null;
}
} else {
return null;
}
return $sum;
}
Jadi ini cukup langsung lagi, itu menambahkan 3 int bersama dan mengembalikan hasilnya, atau null jika salah satu parameter bukan integer. Mengabaikan fakta bahwa kita bisa menggabungkan semua pemeriksaan itu menjadi satu baris dengan operator AND, saya pikir Anda bisa melihat bagaimana struktur if/else bersarang membuat kode lebih sulit diikuti. Sekarang lihat contoh ini sebagai gantinya.
public function addThreeInts($first, $second, $third) {
if (!is_int($first)) {
return null;
}
if (!is_int($second)) {
return null;
}
if (!is_int($third)) {
return null;
}
return $first + $second + $third;
}
Bagi saya contoh ini jauh lebih mudah diikuti. Di sini kita menggunakan klausa guard untuk memverifikasi asumsi awal kita tentang parameter yang kita lewati dan segera keluar dari metode jika mereka tidak lulus. Kita juga tidak lagi memiliki variabel perantara untuk melacak jumlah sepanjang metode. Dalam kasus ini kita telah memverifikasi bahwa kita sudah di jalur bahagia dan kita bisa langsung melakukan apa yang kita datang kemari untuk lakukan. Sekali lagi kita bisa hanya melakukan semua pemeriksaan itu dalam satu if tetapi prinsipnya harus jelas.
5 Pengujian unit
Pengujian unit adalah praktik menulis tes kecil yang memverifikasi perilaku dalam kode Anda. Mereka hampir selalu ditulis dalam bahasa yang sama dengan kode (dalam kasus ini PHP) dan dimaksudkan untuk cukup cepat untuk dijalankan kapan saja. Mereka sangat berharga sebagai alat untuk meningkatkan kode Anda. Selain manfaat yang jelas memastikan bahwa kode Anda melakukan apa yang Anda pikirkan, pengujian unit juga bisa memberikan umpan balik desain yang sangat berguna. Jika potongan kode sulit diuji, itu sering menunjukkan masalah desain. Mereka juga memberi Anda jaring pengaman terhadap regresi, dan itu memungkinkan Anda melakukan refaktor lebih sering dan mengembangkan kode Anda ke desain yang lebih bersih.
5.1 Alat
Ada beberapa alat pengujian unit di luar sana di PHP, tetapi jauh dan paling umum adalah PHPUnit. Anda bisa menginstalnya dengan mengunduh file PHAR langsung, atau menginstalnya dengan composer. Karena kita menggunakan composer untuk segala sesuatu yang lain, kita akan menunjukkan metode itu. Juga, karena PHPUnit tidak mungkin akan dikerahkan ke produksi, kita bisa menginstalnya sebagai ketergantungan dev dengan perintah berikut:
composer require --dev phpunit/phpunit
5.2 Tes adalah spesifikasi
Peran paling penting dari tes unit dalam kode Anda adalah memberikan spesifikasi yang dapat dieksekusi dari apa yang seharusnya dilakukan kode. Bahkan jika kode tes salah, atau kode memiliki bug, pengetahuan tentang apa yang seharusnya dilakukan sistem itu tak ternilai harganya.
5.3 Tulis tes Anda terlebih dahulu
Jika Anda punya kesempatan untuk melihat satu set tes yang ditulis sebelum kode dan satu yang ditulis setelah kode selesai, mereka sangat berbeda. Tes "setelah" jauh lebih khawatir dengan detail implementasi kelas dan memastikan mereka memiliki cakupan baris yang baik, sedangkan tes "sebelum" lebih tentang memverifikasi perilaku eksternal yang diinginkan. Itu benar-benar yang kita pedulikan dengan tes unit anyway, adalah memastikan kelas menunjukkan perilaku yang benar. Tes yang berfokus pada implementasi sebenarnya membuat refaktor lebih sulit karena mereka rusak jika internal kelas berubah, dan Anda baru saja kehilangan manfaat penyembunyian informasi OOP.
5.4 Apa yang membuat tes unit yang baik
Tes unit yang baik berbagi banyak karakteristik berikut:
- Cepat - harus berjalan dalam milidetik.
- Tidak ada akses jaringan - harus bisa mematikan nirkabel/mencabut dan semua tes masih lulus.
- Akses sistem file terbatas - ini menambah kecepatan dan fleksibilitas jika menerapkan kode ke lingkungan lain.
- Tidak ada akses basis data - menghindari aktivitas setup dan teardown yang mahal.
- Uji hanya satu hal sekaligus - tes unit harus memiliki hanya satu alasan untuk gagal.
- Bernama dengan baik - lihat 5.2 di atas.
- Sebagian besar objek palsu - satu-satunya "objek nyata" dalam tes unit harus menjadi objek yang kita uji dan objek nilai sederhana. Sisanya harus berupa beberapa bentuk test double
Ada alasan untuk melawan beberapa dari ini tetapi sebagai pedoman umum mereka akan melayani Anda dengan baik.
5.5 Saat pengujian menyakitkan
Pengujian unit memaksa Anda untuk merasakan sakit desain buruk di depan - Michael Feathers
Saat Anda menulis tes unit, Anda memaksa diri Anda untuk benar-benar menggunakan kelas untuk mencapai hal-hal. Jika Anda menulis tes di akhir, atau lebih buruk lagi, hanya melemparkan kode ke atas dinding untuk QA atau siapa pun untuk menulis tes, Anda tidak mendapatkan umpan balik tentang bagaimana kelas benar-benar berperilaku. Jika kita menulis tes, dan kelas itu nyeri nyata untuk digunakan, kita akan mengetahuinya saat kita menulisnya, yang hampir waktu termurah untuk memperbaikinya.
Jika kelas sulit diuji, itu cacat desain. Cacat yang berbeda menampakkan diri dalam cara yang berbeda, meskipun. Jika Anda harus melakukan banyak mocking, kelas Anda mungkin memiliki terlalu banyak ketergantungan, atau metode Anda melakukan terlalu banyak. Semakin banyak setup yang harus Anda lakukan untuk setiap tes, semakin mungkin metode Anda melakukan terlalu banyak. Jika Anda harus menulis skenario tes yang sangat rumit untuk melatih perilaku, metode kelas mungkin melakukan terlalu banyak. Jika Anda harus menggali di dalam banyak metode pribadi dan status untuk menguji hal-hal, mungkin ada kelas lain yang mencoba keluar. Pengujian unit sangat bagus dalam mengekspos "kelas gunung es" di mana 80% dari apa yang dilakukan kelas disembunyikan di kode yang dilindungi atau pribadi. Saya dulu penggemar besar membuat sebanyak mungkin dilindungi, tetapi sekarang saya sadar saya hanya membuat kelas individu saya bertanggung jawab atas terlalu banyak, dan solusi sebenarnya adalah memecah kelas menjadi potongan yang lebih kecil.
Ditulis oleh Brian Fenton - Brian Fenton telah menjadi pengembang PHP selama 8 tahun di Midwest dan Bay Area, saat ini di Thismoment. Dia fokus pada kerajinan kode dan prinsip desain. Blog di www.brianfenton.us, Twitter di @brianfenton. Saat dia tidak sibuk menjadi ayah, dia menikmati makanan, bir, gaming, dan belajar.
Learn/security
Keamanan
Tinjauan
Keamanan adalah hal yang sangat penting dalam aplikasi web. Anda perlu memastikan bahwa aplikasi Anda aman dan data pengguna Anda terlindungi. Flight menyediakan sejumlah fitur untuk membantu Anda mengamankan aplikasi web.
skeleton resmi juga menyertakan SECURITY.md khusus dan middleware keamanan header sehingga alat coding AI (dan manusia) memiliki satu tempat yang disengaja untuk secret, header, serta aturan XSS/SQL—terpisah dari gaya penulisan kode umum di AGENTS.md.
Memahami
Ada sejumlah ancaman keamanan umum yang harus Anda waspadai saat membangun aplikasi web. Beberapa ancaman yang paling umum meliputi:
- Cross Site Request Forgery (CSRF)
- Cross Site Scripting (XSS)
- SQL Injection
- Cross Origin Resource Sharing (CORS)
Templates membantu mengatasi XSS dengan melakukan escape pada output secara default (Twig dan Latte melakukan ini; manfaatkan keunggulan tersebut). Sessions dapat membantu mengatasi CSRF dengan menyimpan token CSRF di sesi pengguna seperti yang dijelaskan di bawah. Menggunakan prepared statements dengan PDO—atau helper pada SimplePdo—membantu mencegah SQL injection. CORS dapat ditangani dengan hook sederhana sebelum Flight::start() dipanggil.
Semua metode ini bekerja sama untuk membantu menjaga keamanan aplikasi web Anda. Selalu ingat untuk mempelajari dan memahami praktik terbaik keamanan. Jangan meminta asisten AI untuk "menonaktifkan CSP" atau melemahkan header hanya agar halaman dapat dimuat tanpa memahami konsekuensinya.
Penggunaan Dasar
Header
Header HTTP adalah salah satu cara termudah untuk mengamankan aplikasi web Anda. Anda dapat menggunakan header untuk mencegah clickjacking, XSS, dan serangan lainnya. Ada beberapa cara untuk menambahkan header ini ke aplikasi Anda.
Dua situs web yang bagus untuk memeriksa keamanan header Anda adalah securityheaders.com dan observatory.mozilla.org. Setelah Anda menyiapkan kode di bawah, Anda dapat dengan mudah memverifikasi bahwa header Anda berfungsi dengan kedua situs tersebut.
Skeleton menyertakan App\Middleware\SecurityHeadersMiddleware (CSP dengan nonce per permintaan, frame options, HSTS, dan lainnya). Preferensikan untuk memperluasnya secara sengaja daripada mematikan header.
Tambahkan Secara Manual
Anda dapat menambahkan header ini secara manual menggunakan metode header pada objek Flight\Response.
// Setel header X-Frame-Options untuk mencegah clickjacking
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
// Setel header Content-Security-Policy untuk mencegah XSS
// Catatan: header ini bisa menjadi sangat kompleks, jadi Anda perlu
// berkonsultasi dengan contoh-contoh di internet untuk aplikasi Anda
Flight::response()->header("Content-Security-Policy", "default-src 'self'");
// Setel header X-XSS-Protection untuk mencegah XSS
Flight::response()->header('X-XSS-Protection', '1; mode=block');
// Setel header X-Content-Type-Options untuk mencegah MIME sniffing
Flight::response()->header('X-Content-Type-Options', 'nosniff');
// Setel header Referrer-Policy untuk mengontrol seberapa banyak informasi referrer yang dikirim
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
// Setel header Strict-Transport-Security untuk memaksa HTTPS
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
// Setel header Permissions-Policy untuk mengontrol fitur dan API apa saja yang dapat digunakan
Flight::response()->header('Permissions-Policy', 'geolocation=()');
Ini dapat ditambahkan di bagian atas file routes.php atau index.php Anda.
Tambahkan sebagai Filter
Anda juga dapat menambahkannya dalam filter/hook seperti berikut:
// Tambahkan header dalam sebuah filter
Flight::before('start', function() {
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
Flight::response()->header("Content-Security-Policy", "default-src 'self'");
Flight::response()->header('X-XSS-Protection', '1; mode=block');
Flight::response()->header('X-Content-Type-Options', 'nosniff');
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
Flight::response()->header('Permissions-Policy', 'geolocation=()');
});
Tambahkan sebagai Middleware
Anda juga dapat menambahkannya sebagai class middleware yang memberikan fleksibilitas terbesar untuk menentukan route mana yang akan diterapkan. Secara umum, header ini harus diterapkan ke semua respons HTML dan API.
Jalur dan namespace ala skeleton (kapitalisasi folder harus sesuai dengan App\Middleware):
// app/Middleware/SecurityHeadersMiddleware.php
namespace App\Middleware;
use flight\Engine;
class SecurityHeadersMiddleware
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function before(array $params): void
{
$response = $this->app->response();
// Preferensikan nonce CSP dari bootstrap ketika Anda memiliki skrip inline (skeleton menetapkan csp_nonce)
$nonce = $this->app->get('csp_nonce');
$csp = $nonce
? "default-src 'self'; script-src 'self' 'nonce-{$nonce}'; style-src 'self' 'nonce-{$nonce}'"
: "default-src 'self'";
$response->header('X-Frame-Options', 'SAMEORIGIN');
$response->header('Content-Security-Policy', $csp);
$response->header('X-XSS-Protection', '1; mode=block');
$response->header('X-Content-Type-Options', 'nosniff');
$response->header('Referrer-Policy', 'no-referrer-when-downgrade');
$response->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
$response->header('Permissions-Policy', 'geolocation=()');
}
}
// app/config/routes.php — grup string kosong = middleware global untuk semua route
use App\Middleware\SecurityHeadersMiddleware;
use flight\net\Router;
$router->group('', function (Router $router) {
$router->get('/users', [ \App\Controller\UserController::class, 'getUsers' ]);
// lebih banyak route
}, [SecurityHeadersMiddleware::class]);
Proyek lama mungkin masih menggunakan app/middlewares dan app\middlewares; itu berfungsi jika folder sesuai. Aplikasi skeleton baru menggunakan app/Middleware/ dan App\Middleware. Lihat Autoloading.
Cross Site Request Forgery (CSRF)
Cross Site Request Forgery (CSRF) adalah jenis serangan di mana situs web berbahaya dapat membuat browser pengguna mengirim permintaan ke situs web Anda. Ini dapat digunakan untuk melakukan tindakan di situs web Anda tanpa sepengetahuan pengguna. Flight tidak menyediakan mekanisme perlindungan CSRF bawaan, tetapi Anda dapat dengan mudah menerapkannya sendiri menggunakan middleware.
Pengaturan
Pertama, Anda perlu menghasilkan token CSRF dan menyimpannya di sesi pengguna. Anda kemudian dapat menggunakan token ini di form Anda dan memeriksanya saat form dikirim. Kita akan menggunakan plugin flightphp/session untuk mengelola sesi.
// Hasilkan token CSRF dan simpan di sesi pengguna
// (dengan asumsi Anda telah membuat objek sesi dan melampirkannya ke Flight)
// lihat dokumentasi sesi untuk informasi lebih lanjut
Flight::register('session', flight\Session::class);
// Anda hanya perlu menghasilkan satu token per sesi (sehingga berfungsi
// di beberapa tab dan permintaan untuk pengguna yang sama)
if(Flight::session()->get('csrf_token') === null) {
Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) );
}
Menggunakan Template PHP Flight Default
<!-- Gunakan token CSRF di form Anda -->
<form method="post">
<input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
<!-- field form lainnya -->
</form>
Menggunakan Twig (default skeleton)
Daftarkan fungsi Twig atau teruskan token ke setiap tampilan form. Contoh minimal dengan global + field form:
// Saat mengonfigurasi Twig (misalnya services.php)
$twig->addGlobal('csrf_token', $app->session()->get('csrf_token'));
{# app/views/form.twig #}
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
{# field lainnya #}
</form>
Menggunakan Latte
Anda juga dapat mengatur fungsi khusus untuk menampilkan token CSRF di template Latte Anda.
Flight::map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// konfigurasi lainnya...
// Atur fungsi khusus untuk menampilkan token CSRF
$latte->addFunction('csrf', function() {
$csrfToken = Flight::session()->get('csrf_token');
return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">');
});
$latte->render($finalPath, $data, $block);
});
Dan sekarang di template Latte Anda dapat menggunakan fungsi csrf() untuk menampilkan token CSRF.
<form method="post">
{csrf()}
<!-- field form lainnya -->
</form>
Memeriksa Token CSRF
Anda dapat memeriksa token CSRF menggunakan beberapa metode.
Middleware
// app/Middleware/CsrfMiddleware.php
namespace App\Middleware;
use flight\Engine;
class CsrfMiddleware
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function before(array $params): void
{
if($this->app->request()->method == 'POST') {
$token = $this->app->request()->data->csrf_token;
// Validasi token
if($token !== $this->app->session()->get('csrf_token')) {
$this->app->halt(403, 'Token CSRF tidak valid');
}
}
}
}
// routes.php
use App\Middleware\CsrfMiddleware;
$router->group('', function ($router) {
$router->get('/users', [ \App\Controller\UserController::class, 'getUsers' ]);
// lebih banyak route
}, [CsrfMiddleware::class]);
Event Filters
// Middleware ini memeriksa apakah permintaan adalah POST dan jika ya, ia memeriksa apakah token CSRF valid
Flight::before('start', function() {
if(Flight::request()->method == 'POST') {
// ambil token csrf dari nilai form
$token = Flight::request()->data->csrf_token;
if($token !== Flight::session()->get('csrf_token')) {
Flight::halt(403, 'Token CSRF tidak valid');
// atau untuk respons JSON
Flight::jsonHalt(['error' => 'Token CSRF tidak valid'], 403);
}
}
});
Cross Site Scripting (XSS)
Cross Site Scripting (XSS) adalah jenis serangan di mana input form berbahaya dapat menyuntikkan kode ke situs web Anda. Sebagian besar peluang ini berasal dari nilai form yang akan diisi oleh pengguna akhir Anda. Anda tidak boleh pernah mempercayai output dari pengguna Anda! Selalu anggap semuanya adalah peretas terbaik di dunia. Mereka dapat menyuntikkan JavaScript atau HTML berbahaya ke halaman Anda. Kode ini dapat digunakan untuk mencuri informasi dari pengguna Anda atau melakukan tindakan di situs web Anda. Dengan menggunakan class view Flight atau mesin template seperti Twig atau Latte, Anda dapat dengan mudah melakukan escape output untuk mencegah serangan XSS.
// Anggap pengguna cerdas dan mencoba menggunakan ini sebagai nama mereka
$name = '<script>alert("XSS")</script>';
// Ini akan melakukan escape pada output
Flight::view()->set('name', $name);
// Ini akan menghasilkan output: <script>alert("XSS")</script>
// Twig (default skeleton) dan Latte melakukan auto-escape secara default — preferensikan daripada echo PHP mentah
Flight::render('template', ['name' => $name]);
// Twig: {{ name }} → sudah di-escape
// Hindari |raw / output yang tidak di-escape kecuali kontennya sepenuhnya tepercaya
SQL Injection
SQL Injection adalah jenis serangan di mana pengguna berbahaya dapat menyuntikkan kode SQL ke database Anda. Ini dapat digunakan untuk mencuri informasi dari database Anda atau melakukan tindakan pada database Anda. Sekali lagi, Anda tidak boleh pernah mempercayai input dari pengguna Anda! Selalu anggap mereka haus akan data. Gunakan prepared statements—helper SimplePdo menjadikan ini jalur default.
// Dengan asumsi Anda mendaftarkan Flight::db() sebagai SimplePdo (atau menginjeksi SimplePdo di controller)
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();
// SimplePdo (disarankan) — satu baris dengan parameter terikat
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);
// Ide yang sama dengan placeholder ?
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);
Di controller bergaya skeleton, preferensikan injeksi konstruktor SimplePdo daripada Flight::db() sehingga pengujian dan kode yang dihasilkan AI tetap konsisten (DIC).
Contoh Tidak Aman
Di bawah ini adalah alasan mengapa kita menggunakan SQL prepared statements untuk melindungi dari contoh sederhana seperti di bawah:
// pengguna akhir mengisi form web.
// untuk nilai form, peretas memasukkan sesuatu seperti ini:
$username = "' OR 1=1; -- ";
$sql = "SELECT * FROM users WHERE username = '$username' LIMIT 5";
$users = Flight::db()->fetchAll($sql);
// Setelah query dibuat, hasilnya terlihat seperti ini
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5
// Terlihat aneh, tetapi ini adalah query yang valid dan akan berhasil. Faktanya,
// ini adalah serangan SQL injection yang sangat umum yang akan mengembalikan semua pengguna.
var_dump($users); // ini akan menampilkan semua pengguna di database, bukan hanya satu username tersebut
Secret dan Konfigurasi
- Letakkan secret di
.env(atau environment asli), bukan diconfig.phpcontoh yang di-commit. - Aturan skeleton: default literal di
config.php; gabungkan env saat bootstrap; jangan membaca$_ENVdi dalam controller—injeksi konfigurasi sebagai gantinya. Lihat Configuration. - Jangan pernah meng-commit API keys, password database, atau kunci enkripsi sesi. Arahkan alat AI ke
SECURITY.mdsehingga mereka tidak membuat jalan pintas yang tidak aman.
Validasi Callback JSONP
Jika Anda menggunakan metode Flight::jsonp(), perlu diketahui bahwa Flight memvalidasi nama parameter callback JSONP terhadap regex whitelist yang ketat (/^[A-Za-z_$][\w$.]{0,127}$/). Nama callback apa pun yang tidak cocok dengan pola ini akan menyebabkan Flight melempar exception, mencegah injeksi JavaScript arbitrer melalui nilai callback berbahaya.
Validasi ini sudah tertanam dan tidak memerlukan konfigurasi tambahan, tetapi perlu diketahui saat men-debug error tak terduga dari endpoint JSONP.
CORS
Cross-Origin Resource Sharing (CORS) adalah mekanisme yang memungkinkan banyak sumber daya (misalnya, font, JavaScript, dll.) di halaman web untuk
diminta dari domain lain di luar domain asal sumber daya tersebut. Flight tidak memiliki fungsionalitas bawaan,
tetapi ini dapat dengan mudah ditangani dengan hook yang berjalan sebelum metode Flight::start() dipanggil.
// app/Utils/CorsUtil.php (skeleton: folder Utils PascalCase → App\Utils)
namespace App\Utils;
use flight\Engine;
class CorsUtil
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function set(array $params = []): void
{
$request = $this->app->request();
$response = $this->app->response();
if ($request->getVar('HTTP_ORIGIN') !== '') {
$this->allowOrigins();
$response->header('Access-Control-Allow-Credentials', 'true');
$response->header('Access-Control-Max-Age', '86400');
}
if ($request->method === 'OPTIONS') {
if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_METHOD') !== '') {
$response->header(
'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD'
);
}
if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS') !== '') {
$response->header(
"Access-Control-Allow-Headers",
$request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS')
);
}
$response->status(200);
$response->send();
exit;
}
}
private function allowOrigins(): void
{
// sesuaikan host yang diizinkan di sini.
$allowed = [
'capacitor://localhost',
'ionic://localhost',
'http://localhost',
'http://localhost:4200',
'http://localhost:8080',
'http://localhost:8100',
];
$request = $this->app->request();
if (in_array($request->getVar('HTTP_ORIGIN'), $allowed, true) === true) {
$response = $this->app->response();
$response->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN'));
}
}
}
// bootstrap / routes — jalankan sebelum start
$app = Flight::app();
$cors = new \App\Utils\CorsUtil($app);
$app->before('start', [ $cors, 'set' ]);
Penguatan Konfigurasi Flight
Flight mengekspos beberapa pengaturan engine yang memiliki implikasi keamanan langsung. Mengatur ini dengan benar adalah salah satu cara termudah untuk memperkuat aplikasi Anda.
flight.allow_method_override
Secara default, Flight mengizinkan klien untuk menimpa metode HTTP dari sebuah permintaan menggunakan header X-HTTP-Method-Override atau field _method di body POST. Meskipun ini berguna untuk form HTML yang hanya dapat mengirim GET/POST, ini bisa berbahaya jika Anda tidak mengharapkannya — penyerang dapat memalsukan permintaan DELETE atau PUT melalui form biasa.
Jika aplikasi Anda tidak bergantung pada perilaku ini (misalnya, Anda membangun API yang dikonsumsi oleh klien modern atau frontend JavaScript yang dapat mengirim kata kerja HTTP apa pun), Anda harus menonaktifkannya:
// Di index.php atau file bootstrap Anda, sebelum Flight::start()
Flight::set('flight.allow_method_override', false);
Nilai defaultnya adalah true untuk kompatibilitas mundur, tetapi sangat disarankan untuk mengaturnya ke false untuk aplikasi apa pun yang tidak secara eksplisit membutuhkan fitur override.
flight.debug
Flight memiliki pengaturan flight.debug yang mengontrol apakah informasi error terperinci (pesan exception, kode, dan stack trace lengkap) ditampilkan di browser ketika exception yang tidak tertangani terjadi. Defaultnya adalah false, yang berarti hanya pesan 500 Internal Server Error generik yang ditampilkan — tidak ada detail internal yang bocor ke klien.
Jangan pernah mengaktifkan ini di server produksi. Gunakan hanya secara lokal atau di lingkungan staging:
// Aman untuk pengembangan lokal saja — JANGAN di produksi
Flight::set('flight.debug', true);
Ketika flight.debug adalah false (default), Anda masih dapat menangkap error dengan mengaktifkan flight.log_errors:
// Catat error di sisi server tanpa mengeksposnya ke klien
Flight::set('flight.debug', false);
Flight::set('flight.log_errors', true);
Konfigurasi produksi yang direkomendasikan
// index.php atau diterapkan dari konfigurasi app / bootstrap
Flight::set('flight.allow_method_override', false);
Flight::set('flight.debug', false);
Flight::set('flight.log_errors', true);
Penanganan Error
Sembunyikan detail error yang sensitif di produksi untuk menghindari kebocoran informasi kepada penyerang. Di produksi, catat error alih-alih menampilkannya dengan display_errors diatur ke 0.
// Di bootstrap.php atau index.php Anda
// tambahkan ini ke app/config/config.php Anda
$environment = ENVIRONMENT;
if ($environment === 'production') {
ini_set('display_errors', 0); // Nonaktifkan tampilan error
ini_set('log_errors', 1); // Catat error sebagai gantinya
ini_set('error_log', '/path/to/error.log');
}
// Di routes atau controller Anda
// Gunakan Flight::halt() untuk respons error yang terkontrol
Flight::halt(403, 'Akses ditolak');
Sanitasi Input
Jangan pernah mempercayai input pengguna. Sanitasi menggunakan filter_var sebelum diproses untuk mencegah data berbahaya masuk. Preferensikan membaca input melalui $app->request() (atau Flight::request()) daripada $_GET / $_POST mentah di kode aplikasi.
// Anggap ada permintaan $_POST dengan $_POST['input'] dan $_POST['email']
// Sanitasi input string
$clean_input = filter_var(Flight::request()->data->input, FILTER_SANITIZE_STRING);
// Sanitasi email
$clean_email = filter_var(Flight::request()->data->email, FILTER_SANITIZE_EMAIL);
Hashing Password
Simpan password dengan aman dan verifikasi dengan aman menggunakan fungsi bawaan PHP seperti password_hash dan password_verify. Password tidak boleh pernah disimpan dalam teks biasa, juga tidak boleh dienkripsi dengan metode yang dapat dibalik. Hashing memastikan bahwa bahkan jika database Anda disusupi, password sebenarnya tetap terlindungi.
$password = Flight::request()->data->password;
// Hash password saat menyimpannya (misalnya, saat registrasi)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
// Verifikasi password (misalnya, saat login)
if (password_verify($password, $stored_hash)) {
// Password cocok
}
Pembatasan Laju (Rate Limiting)
Lindungi dari serangan brute force atau serangan denial-of-service dengan membatasi laju permintaan menggunakan cache.
// Dengan asumsi Anda memiliki flightphp/cache yang terinstal dan terdaftar
// Menggunakan flightphp/cache dalam filter
Flight::before('start', function() {
$cache = Flight::cache();
$ip = Flight::request()->ip;
$key = "rate_limit_{$ip}";
$attempts = (int) $cache->retrieve($key);
if ($attempts >= 10) {
Flight::halt(429, 'Terlalu banyak permintaan');
}
$cache->set($key, $attempts + 1, 60); // Atur ulang setelah 60 detik
});
Lihat Juga
- Sessions - Cara mengelola sesi pengguna dengan aman.
- Templates - Twig/Latte auto-escape dan XSS.
- SimplePdo - Helper database dengan prepared statements.
- PdoWrapper - Tidak digunakan lagi; gunakan SimplePdo untuk kode baru.
- Middleware - Cara menggunakan middleware untuk menyederhanakan proses penambahan header keamanan.
- Configuration -
.envvs konfigurasi literal, flag produksi. - AI & Developer Experience - Jaga kebijakan keamanan di
SECURITY.mduntuk agen. - Responses - Cara menyesuaikan respons HTTP dengan header yang aman.
- Requests - Cara menangani dan membersihkan input pengguna.
- filter_var - Fungsi PHP untuk sanitasi input.
- password_hash - Fungsi PHP untuk hashing password yang aman.
- password_verify - Fungsi PHP untuk memverifikasi password yang di-hash.
Pemecahan Masalah
- Lihat bagian "Lihat Juga" di atas untuk informasi pemecahan masalah terkait komponen Framework Flight.
- Jika CSP memblokir skrip Anda, tambahkan nonce (pola skeleton) atau daftar putih origin spesifik—jangan atur
script-src *tanpa rencana.
Changelog
- Docs – Skeleton
App\Middleware, catatan Twig CSRF/XSS, SimplePdo, secret/.env, danSECURITY.mduntuk proyek yang ramah AI. - v3.18.1 - Menambahkan bagian Penguatan Konfigurasi Flight yang mencakup
flight.allow_method_override,flight.debug, dan validasi callback JSONP. - v3.1.0 - Menambahkan bagian tentang CORS, Penanganan Error, Sanitasi Input, Hashing Password, dan Pembatasan Laju.
- v2.0 - Menambahkan escape untuk tampilan default guna mencegah XSS.
Learn/routing
Routing
Ikhtisar
Routing di Flight PHP memetakan pola URL ke fungsi callback atau metode kelas, memungkinkan penanganan permintaan yang cepat dan sederhana. Ini dirancang dengan overhead minimal, ramah untuk pemula, dan dapat diperluas tanpa dependensi eksternal.
Pemahaman
Routing adalah mekanisme inti yang menghubungkan permintaan HTTP ke logika aplikasi Anda di Flight. Dengan mendefinisikan rute, Anda menentukan bagaimana URL yang berbeda memicu kode tertentu, baik melalui fungsi, metode kelas, atau aksi kontroler. Sistem routing Flight fleksibel, mendukung pola dasar, parameter bernama, ekspresi reguler, dan fitur lanjutan seperti injeksi dependensi dan routing resourceful. Pendekatan ini menjaga kode Anda tetap terorganisir dan mudah dipelihara, sambil tetap cepat dan sederhana untuk pemula serta dapat diperluas untuk pengguna tingkat lanjut.
Catatan: Ingin memahami lebih lanjut tentang routing? Lihat halaman "mengapa framework?" untuk penjelasan yang lebih mendalam.
Penggunaan Dasar
Mendefinisikan Rute Sederhana
Routing dasar di Flight dilakukan dengan mencocokkan pola URL dengan fungsi callback atau array dari kelas dan metode.
Flight::route('/', function(){
echo 'hello world!';
});
Rute dicocokkan sesuai urutan pendefinisiannya. Rute pertama yang cocok dengan permintaan akan dipanggil.
Menggunakan Fungsi sebagai Callback
Callback dapat berupa objek apa pun yang dapat dipanggil (callable). Jadi Anda dapat menggunakan fungsi biasa:
function hello() {
echo 'hello world!';
}
Flight::route('/', 'hello');
Menggunakan Kelas dan Metode sebagai Kontroler
Anda dapat menggunakan metode (statis atau tidak) dari sebuah kelas:
class GreetingController {
public function hello() {
echo 'hello world!';
}
}
Flight::route('/', [ 'GreetingController','hello' ]);
// atau
Flight::route('/', [ GreetingController::class, 'hello' ]); // metode yang disarankan
// atau
Flight::route('/', [ 'GreetingController::hello' ]);
// atau
Flight::route('/', [ 'GreetingController->hello' ]);
Atau dengan membuat objek terlebih dahulu lalu memanggil metodenya:
use flight\Engine;
// GreetingController.php
class GreetingController
{
protected Engine $app
public function __construct(Engine $app) {
$this->app = $app;
$this->name = 'John Doe';
}
public function hello() {
echo "Hello, {$this->name}!";
}
}
// index.php
$app = Flight::app();
$greeting = new GreetingController($app);
Flight::route('/', [ $greeting, 'hello' ]);
Catatan: Secara default ketika sebuah kontroler dipanggil di dalam framework, kelas
flight\Engineselalu diinjeksikan kecuali Anda menentukan melalui wadah injeksi dependensi
Routing Khusus Metode
Secara default, pola rute dicocokkan dengan semua metode permintaan. Anda dapat merespons metode tertentu dengan menempatkan pengidentifikasi sebelum URL.
Flight::route('GET /', function () {
echo 'Saya menerima permintaan GET.';
});
Flight::route('POST /', function () {
echo 'Saya menerima permintaan POST.';
});
// Anda tidak dapat menggunakan Flight::get() untuk rute karena itu adalah metode
// untuk mendapatkan variabel, bukan membuat rute.
Flight::post('/', function() { /* kode */ });
Flight::patch('/', function() { /* kode */ });
Flight::put('/', function() { /* kode */ });
Flight::delete('/', function() { /* kode */ });
Anda juga dapat memetakan beberapa metode ke satu callback dengan menggunakan pemisah |:
Flight::route('GET|POST /', function () {
echo 'Saya menerima permintaan GET atau POST.';
});
Penanganan Khusus untuk Permintaan HEAD dan OPTIONS
Flight menyediakan penanganan bawaan untuk permintaan HTTP HEAD dan OPTIONS:
Permintaan HEAD
- Permintaan HEAD diperlakukan sama seperti permintaan
GET, tetapi Flight secara otomatis menghapus body respons sebelum mengirimkannya ke klien. - Ini berarti Anda dapat mendefinisikan rute untuk
GET, dan permintaan HEAD ke URL yang sama hanya akan mengembalikan header (tanpa konten), sesuai dengan standar HTTP.
Flight::route('GET /info', function() {
echo 'Ini adalah beberapa info!';
});
// Permintaan HEAD ke /info akan mengembalikan header yang sama, tetapi tanpa body.
Permintaan OPTIONS
Permintaan OPTIONS ditangani secara otomatis oleh Flight untuk setiap rute yang didefinisikan.
- Ketika permintaan OPTIONS diterima, Flight merespons dengan status
204 No Contentdan headerAllowyang mencantumkan semua metode HTTP yang didukung untuk rute tersebut. - Anda tidak perlu mendefinisikan rute terpisah untuk OPTIONS.
// Untuk rute yang didefinisikan sebagai:
Flight::route('GET|POST /users', function() { /* ... */ });
// Permintaan OPTIONS ke /users akan merespons dengan:
//
// Status: 204 No Content
// Allow: GET, POST, HEAD, OPTIONS
Menggunakan Objek Router
Selain itu, Anda dapat mengambil objek Router yang memiliki beberapa metode pembantu untuk Anda gunakan:
$router = Flight::router();
// memetakan semua metode sama seperti Flight::route()
$router->map('/', function() {
echo 'hello world!';
});
// Permintaan GET
$router->get('/users', function() {
echo 'users';
});
$router->post('/users', function() { /* kode */});
$router->put('/users/update/@id', function() { /* kode */});
$router->delete('/users/@id', function() { /* kode */});
$router->patch('/users/@id', function() { /* kode */});
Ekspresi Reguler (Regex)
Anda dapat menggunakan ekspresi reguler di dalam rute Anda:
Flight::route('/user/[0-9]+', function () {
// Ini akan cocok dengan /user/1234
});
Meskipun metode ini tersedia, disarankan untuk menggunakan parameter bernama, atau parameter bernama dengan ekspresi reguler, karena lebih mudah dibaca dan dipelihara.
Parameter Bernama
Anda dapat menentukan parameter bernama di dalam rute Anda yang akan diteruskan ke fungsi callback Anda. Ini lebih untuk keterbacaan rute daripada apa pun lainnya. Silakan lihat bagian tentang catatan penting di bawah ini.
Flight::route('/@name/@id', function (string $name, string $id) {
echo "hello, $name ($id)!";
});
Anda juga dapat menyertakan ekspresi reguler dengan parameter bernama Anda menggunakan
pemisah ::
Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
// Ini akan cocok dengan /bob/123
// Tetapi tidak akan cocok dengan /bob/12345
});
Catatan: Pencocokan grup regex
()dengan parameter posisional tidak didukung. Contoh::'\(
Catatan Penting
Meskipun pada contoh di atas, tampaknya @name terikat langsung dengan variabel $name, sebenarnya tidak demikian. Urutan parameter dalam fungsi callback adalah yang menentukan apa yang diteruskan ke fungsi tersebut. Jika Anda menukar urutan parameter dalam fungsi callback, variabel juga akan tertukar. Berikut contohnya:
Flight::route('/@name/@id', function (string $id, string $name) {
echo "hello, $name ($id)!";
});
Dan jika Anda mengunjungi URL berikut: /bob/123, hasilnya akan menjadi hello, 123 (bob)!.
Harap berhati-hati saat Anda menyiapkan rute dan fungsi callback Anda!
Parameter Opsional
Anda dapat menentukan parameter bernama yang opsional untuk dicocokkan dengan membungkus segmen dalam tanda kurung.
Flight::route(
'/blog(/@year(/@month(/@day)))',
function(?string $year, ?string $month, ?string $day) {
// Ini akan cocok dengan URL berikut:
// /blog/2012/12/10
// /blog/2012/12
// /blog/2012
// /blog
}
);
Parameter opsional apa pun yang tidak cocok akan diteruskan sebagai NULL.
Routing Wildcard
Pencocokan hanya dilakukan pada segmen URL individual. Jika Anda ingin mencocokkan beberapa
segmen, Anda dapat menggunakan wildcard *.
Flight::route('/blog/*', function () {
// Ini akan cocok dengan /blog/2000/02/01
});
Untuk mengarahkan semua permintaan ke satu callback, Anda dapat melakukannya:
Flight::route('*', function () {
// Lakukan sesuatu
});
Penanganan 404 Tidak Ditemukan
Secara default, jika URL tidak dapat ditemukan, Flight akan mengirimkan respons HTTP 404 Not Found yang sangat sederhana dan polos.
Jika Anda menginginkan respons 404 yang lebih khusus, Anda dapat memetakan metode notFound Anda sendiri:
Flight::map('notFound', function() {
$url = Flight::request()->url;
// Anda juga dapat menggunakan Flight::render() dengan template khusus.
$output = <<<HTML
<h1>404 Tidak Ditemukan Kustom Saya</h1>
<h3>Halaman yang Anda minta {$url} tidak dapat ditemukan.</h3>
HTML;
$this->response()
->clearBody()
->status(404)
->write($output)
->send();
});
Penanganan Metode Tidak Ditemukan
Secara default, jika URL ditemukan tetapi metode tidak diizinkan, Flight akan mengirimkan respons HTTP 405 Method Not Allowed yang sangat sederhana dan polos (Contoh: Metode Tidak Diizinkan. Metode yang Diizinkan adalah: GET, POST). Ini juga akan menyertakan header Allow dengan metode yang diizinkan untuk URL tersebut.
Jika Anda menginginkan respons 405 yang lebih khusus, Anda dapat memetakan metode methodNotFound Anda sendiri:
use flight\net\Route;
Flight::map('methodNotFound', function(Route $route) {
$url = Flight::request()->url;
$methods = implode(', ', $route->methods);
// Anda juga dapat menggunakan Flight::render() dengan template khusus.
$output = <<<HTML
<h1>405 Metode Tidak Diizinkan Kustom Saya</h1>
<h3>Metode yang Anda minta untuk {$url} tidak diizinkan.</h3>
<p>Metode yang Diizinkan adalah: {$methods}</p>
HTML;
$this->response()
->clearBody()
->status(405)
->setHeader('Allow', $methods)
->write($output)
->send();
});
Penggunaan Lanjutan
Injeksi Dependensi dalam Rute
Jika Anda ingin menggunakan injeksi dependensi melalui wadah (PSR-11, PHP-DI, Dice, dll), satu-satunya jenis rute yang tersedia adalah membuat objek secara langsung sendiri dan menggunakan wadah untuk membuat objek Anda atau Anda dapat menggunakan string untuk mendefinisikan kelas dan metode yang akan dipanggil. Anda dapat mengunjungi halaman Injeksi Dependensi untuk informasi lebih lanjut.
Berikut contoh singkatnya:
use flight\database\SimplePdo;
// Greeting.php
class Greeting
{
protected SimplePdo $db;
public function __construct(SimplePdo $db) {
$this->db = $db;
}
public function hello(int $id) {
// lakukan sesuatu dengan $this->db
$name = $this->db->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
echo "Hello, world! Nama saya adalah {$name}!";
}
}
// index.php
// Siapkan wadah dengan parameter apa pun yang Anda butuhkan
// Lihat halaman Injeksi Dependensi untuk informasi lebih lanjut tentang PSR-11
$dice = new \Dice\Dice();
// Jangan lupa untuk menetapkan ulang variabel dengan '$dice = '!!!!!
$dice = $dice->addRule(SimplePdo::class, [
'shared' => true,
'constructParams' => [
'mysql:host=localhost;dbname=test',
'root',
'password'
]
]);
// Daftarkan penangan wadah
Flight::registerContainerHandler(function($class, $params) use ($dice) {
return $dice->create($class, $params);
});
// Rute seperti biasa
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// atau
Flight::route('/hello/@id', 'Greeting->hello');
// atau
Flight::route('/hello/@id', 'Greeting::hello');
Flight::start();
Meneruskan Eksekusi ke Rute Berikutnya
Usang
Anda dapat meneruskan eksekusi ke rute berikutnya yang cocok dengan mengembalikan true dari
fungsi callback Anda.
Flight::route('/user/@name', function (string $name) {
// Periksa beberapa kondisi
if ($name !== "Bob") {
// Lanjutkan ke rute berikutnya
return true;
}
});
Flight::route('/user/*', function () {
// Ini akan dipanggil
});
Sekarang disarankan untuk menggunakan middleware untuk menangani kasus penggunaan yang kompleks seperti ini.
Alias Rute
Dengan menetapkan alias ke sebuah rute, Anda nantinya dapat memanggil alias tersebut di dalam aplikasi Anda secara dinamis untuk dibuat kemudian di kode Anda (contoh: tautan dalam template HTML, atau menghasilkan URL pengalihan).
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// atau
Flight::route('/users/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
// nanti di suatu tempat dalam kode
class UserController {
public function update() {
// kode untuk menyimpan pengguna...
$id = $user['id']; // 5 sebagai contoh
$redirectUrl = Flight::getUrl('user_view', [ 'id' => $id ]); // akan mengembalikan '/users/5'
Flight::redirect($redirectUrl);
}
}
Ini sangat membantu jika URL Anda berubah. Pada contoh di atas, misalkan pengguna dipindahkan ke /admin/users/@id.
Dengan alias yang terpasang pada rute, Anda tidak perlu lagi mencari semua URL lama di kode Anda dan mengubahnya karena alias sekarang akan mengembalikan /admin/users/5 seperti pada contoh di atas.
Alias rute juga tetap berfungsi dalam grup:
Flight::group('/users', function() {
Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// atau
Flight::route('/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
});
Memeriksa Informasi Rute
Jika Anda ingin memeriksa informasi rute yang cocok, ada 2 cara yang dapat Anda lakukan:
- Anda dapat menggunakan properti
executedRoutepada objekFlight::router(). - Anda dapat meminta objek rute untuk diteruskan ke callback Anda dengan memberikan
truesebagai parameter ketiga dalam metode rute. Objek rute akan selalu menjadi parameter terakhir yang diteruskan ke fungsi callback Anda.
executedRoute
Flight::route('/', function() {
$route = Flight::router()->executedRoute;
// Lakukan sesuatu dengan $route
// Array metode HTTP yang dicocokkan
$route->methods;
// Array parameter bernama
$route->params;
// Ekspresi reguler yang cocok
$route->regex;
// Berisi konten dari '*' apa pun yang digunakan dalam pola URL
$route->splat;
// Menampilkan jalur url....jika Anda benar-benar membutuhkannya
$route->pattern;
// Menampilkan middleware apa yang ditetapkan untuk ini
$route->middleware;
// Menampilkan alias yang ditetapkan untuk rute ini
$route->alias;
});
Catatan: Properti
executedRoutehanya akan diatur setelah sebuah rute dieksekusi. Jika Anda mencoba mengaksesnya sebelum rute dieksekusi, nilainya akanNULL. Anda juga dapat menggunakan executedRoute di dalam middleware!
Berikan true pada definisi rute
Flight::route('/', function(\flight\net\Route $route) {
// Array metode HTTP yang dicocokkan
$route->methods;
// Array parameter bernama
$route->params;
// Ekspresi reguler yang cocok
$route->regex;
// Berisi konten dari '*' apa pun yang digunakan dalam pola URL
$route->splat;
// Menampilkan jalur url....jika Anda benar-benar membutuhkannya
$route->pattern;
// Menampilkan middleware apa yang ditetapkan untuk ini
$route->middleware;
// Menampilkan alias yang ditetapkan untuk rute ini
$route->alias;
}, true);// <-- Parameter true ini yang membuat itu terjadi
Pengelompokan Rute dan Middleware
Mungkin ada saatnya Anda ingin mengelompokkan rute yang terkait (seperti /api/v1).
Anda dapat melakukannya dengan menggunakan metode group:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Cocok dengan /api/v1/users
});
Flight::route('/posts', function () {
// Cocok dengan /api/v1/posts
});
});
Anda bahkan dapat menumpuk grup di dalam grup:
Flight::group('/api', function () {
Flight::group('/v1', function () {
// Flight::get() mendapatkan variabel, bukan membuat rute! Lihat konteks objek di bawah
Flight::route('GET /users', function () {
// Cocok dengan GET /api/v1/users
});
Flight::post('/posts', function () {
// Cocok dengan POST /api/v1/posts
});
Flight::put('/posts/1', function () {
// Cocok dengan PUT /api/v1/posts
});
});
Flight::group('/v2', function () {
// Flight::get() mendapatkan variabel, bukan membuat rute! Lihat konteks objek di bawah
Flight::route('GET /users', function () {
// Cocok dengan GET /api/v2/users
});
});
});
Pengelompokan dengan Konteks Objek
Anda tetap dapat menggunakan pengelompokan rute dengan objek Engine dengan cara berikut:
$app = Flight::app();
$app->group('/api/v1', function (Router $router) {
// gunakan variabel $router
$router->get('/users', function () {
// Cocok dengan GET /api/v1/users
});
$router->post('/posts', function () {
// Cocok dengan POST /api/v1/posts
});
});
Catatan: Ini adalah metode yang disarankan untuk mendefinisikan rute dan grup dengan objek
$router.
Pengelompokan dengan Middleware
Anda juga dapat menetapkan middleware ke sekelompok rute:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Cocok dengan /api/v1/users
});
}, [ MyAuthMiddleware::class ]); // atau [ new MyAuthMiddleware() ] jika Anda ingin menggunakan sebuah instance
Lihat detail lebih lanjut di halaman grup middleware.
Routing Resource
Anda dapat membuat satu set rute untuk sebuah resource menggunakan metode resource. Ini akan membuat
satu set rute untuk sebuah resource yang mengikuti konvensi RESTful.
Untuk membuat resource, lakukan hal berikut:
Flight::resource('/users', UsersController::class);
Dan apa yang akan terjadi di latar belakang adalah ia akan membuat rute-rute berikut:
[
'index' => 'GET /users',
'create' => 'GET /users/create',
'store' => 'POST /users',
'show' => 'GET /users/@id',
'edit' => 'GET /users/@id/edit',
'update' => 'PUT /users/@id',
'destroy' => 'DELETE /users/@id'
]
Dan kontroler Anda akan menggunakan metode-metode berikut:
class UsersController
{
public function index(): void
{
}
public function show(string $id): void
{
}
public function create(): void
{
}
public function store(): void
{
}
public function edit(string $id): void
{
}
public function update(string $id): void
{
}
public function destroy(string $id): void
{
}
}
Catatan: Anda dapat melihat rute yang baru ditambahkan dengan
runwaydengan menjalankanphp runway routes.
Menyesuaikan Rute Resource
Ada beberapa opsi untuk mengonfigurasi rute resource.
Alias Dasar (Alias Base)
Anda dapat mengonfigurasi aliasBase. Secara default alias adalah bagian terakhir dari URL yang ditentukan.
Misalnya /users/ akan menghasilkan aliasBase dari users. Ketika rute-rute ini dibuat,
aliasnya adalah users.index, users.create, dst. Jika Anda ingin mengubah aliasnya, atur aliasBase
ke nilai yang Anda inginkan.
Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);
Only dan Except
Anda juga dapat menentukan rute mana yang ingin Anda buat dengan menggunakan opsi only dan except.
// Whitelist hanya metode ini dan blacklist sisanya
Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
// Blacklist hanya metode ini dan whitelist sisanya
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);
Ini pada dasarnya adalah opsi whitelisting dan blacklisting sehingga Anda dapat menentukan rute mana yang ingin dibuat.
Middleware
Anda juga dapat menentukan middleware yang akan dijalankan pada setiap rute yang dibuat oleh metode resource.
Flight::resource('/users', UsersController::class, [ 'middleware' => [ MyAuthMiddleware::class ] ]);
Respons Streaming
Anda sekarang dapat melakukan streaming respons ke klien menggunakan stream() atau streamWithHeaders().
Ini berguna untuk mengirim file besar, proses yang berjalan lama, atau menghasilkan respons yang besar.
Streaming rute ditangani sedikit berbeda dari rute biasa.
Catatan: Respons streaming hanya tersedia jika Anda telah mengatur
flight.v2.output_bufferingmenjadifalse.
Streaming dengan Header Manual
Anda dapat melakukan streaming respons ke klien dengan menggunakan metode stream() pada sebuah rute. Jika Anda
melakukan ini, Anda harus mengatur semua header secara manual sebelum Anda mengeluarkan apa pun ke klien.
Ini dilakukan dengan fungsi PHP header() atau metode Flight::response()->setRealHeader().
Flight::route('/@filename', function($filename) {
$response = Flight::response();
// tentu saja Anda harus membersihkan jalur file dan sebagainya.
$fileNameSafe = basename($filename);
// Jika Anda memiliki header tambahan yang perlu diatur di sini setelah rute dieksekusi
// Anda harus mendefinisikannya sebelum apa pun dicetak.
// Semuanya harus berupa panggilan mentah ke fungsi header() atau
// panggilan ke Flight::response()->setRealHeader()
header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
// atau
$response->setRealHeader('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
$filePath = '/some/path/to/files/'.$fileNameSafe;
if (!is_readable($filePath)) {
Flight::halt(404, 'File tidak ditemukan');
}
// atur panjang konten secara manual jika Anda mau
header('Content-Length: '.filesize($filePath));
// atau
$response->setRealHeader('Content-Length: '.filesize($filePath));
// Streaming file ke klien saat dibaca
readfile($filePath);
// Ini adalah baris ajaibnya
})->stream();
Streaming dengan Header
Anda juga dapat menggunakan metode streamWithHeaders() untuk mengatur header sebelum Anda memulai streaming.
Flight::route('/stream-users', function() {
// Anda dapat menambahkan header tambahan apa pun di sini
// Anda hanya harus menggunakan header() atau Flight::response()->setRealHeader()
// bagaimanapun Anda mengambil data Anda, hanya sebagai contoh...
$users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");
echo '{';
$user_count = count($users);
while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) {
echo json_encode($user);
if(--$user_count > 0) {
echo ',';
}
// Ini diperlukan untuk mengirim data ke klien
ob_flush();
}
echo '}';
// Ini adalah cara Anda mengatur header sebelum memulai streaming.
})->streamWithHeaders([
'Content-Type' => 'application/json',
'Content-Disposition' => 'attachment; filename="users.json"',
// kode status opsional, defaultnya 200
'status' => 200
]);
Lihat Juga
- Middleware - Menggunakan middleware dengan rute untuk autentikasi, pencatatan, dll.
- Injeksi Dependensi - Menyederhanakan pembuatan dan pengelolaan objek dalam rute.
- Mengapa Framework? - Memahami manfaat menggunakan framework seperti Flight.
- Ekstensi - Cara memperluas Flight dengan fungsionalitas Anda sendiri termasuk metode
notFound. - php.net: preg_match - Fungsi PHP untuk pencocokan ekspresi reguler.
Pemecahan Masalah
- Parameter rute dicocokkan berdasarkan urutan, bukan berdasarkan nama. Pastikan urutan parameter callback sesuai dengan definisi rute.
- Menggunakan
Flight::get()tidak mendefinisikan rute; gunakanFlight::route('GET /...')untuk routing atau konteks objek Router dalam grup (misalnya$router->get(...)). - Properti executedRoute hanya diatur setelah rute dieksekusi; nilainya NULL sebelum eksekusi.
- Streaming memerlukan fungsionalitas buffering output lama Flight untuk dinonaktifkan (
flight.v2.output_buffering = false). - Untuk injeksi dependensi, hanya definisi rute tertentu yang mendukung pembuatan instance berbasis wadah.
404 Tidak Ditemukan atau Perilaku Rute yang Tidak Terduga
Jika Anda melihat kesalahan 404 Not Found (tetapi Anda bersumpah demi hidup Anda bahwa itu benar-benar ada dan itu bukan salah ketik), ini sebenarnya bisa menjadi masalah dengan Anda mengembalikan nilai di endpoint rute Anda alih-alih hanya mencetaknya. Alasan untuk ini disengaja tetapi bisa mengecoh beberapa pengembang.
Flight::route('/hello', function(){
// Ini dapat menyebabkan kesalahan 404 Not Found
return 'Hello World';
});
// Yang mungkin Anda inginkan
Flight::route('/hello', function(){
echo 'Hello World';
});
Alasannya adalah karena mekanisme khusus yang dibangun ke dalam router yang memperlakukan keluaran yang dikembalikan sebagai sinyal untuk "lanjut ke rute berikutnya". Anda dapat melihat perilaku yang didokumentasikan di bagian Routing.
Riwayat Perubahan
- v3: Menambahkan routing resource, alias rute, dan dukungan streaming, grup rute, dan dukungan middleware.
- v1: Sebagian besar fitur dasar tersedia.
Learn/learn
Belajar tentang Flight
Flight adalah framework PHP yang cepat, sederhana, dan dapat diperluas. Ia cukup serbaguna dan dapat digunakan untuk membangun berbagai jenis aplikasi web. Dibangun dengan mengedepankan kesederhanaan dan ditulis dengan cara yang mudah dipahami dan digunakan—oleh manusia dan oleh asisten coding AI.
Catatan: Anda akan melihat contoh yang menggunakan
Flight::sebagai variabel statis dan beberapa yang menggunakan objek Engine$app->. Keduanya dapat digunakan secara bergantian.$appdan$this->appdalam controller/middleware adalah pendekatan yang direkomendasikan oleh tim Flight (dan apa yang distandarisasi oleh skeleton resmi +AGENTS.mduntuk proyek baru).
Komponen Inti
Routing
Pelajari cara mengelola rute untuk aplikasi web Anda. Ini juga mencakup pengelompokan rute, parameter rute, dan middleware.
Middleware
Pelajari cara menggunakan middleware untuk memfilter permintaan dan respons dalam aplikasi Anda.
Autoloading
Pelajari cara memuat otomatis (autoload) kelas-kelas Anda sendiri. Kapitalisasi folder harus sesuai dengan namespace Anda—skeleton menggunakan App\ dan folder PascalCase seperti app/Controller/.
Requests
Pelajari cara menangani permintaan dan respons dalam aplikasi Anda.
Responses
Pelajari cara mengirim respons kepada pengguna Anda.
HTML Templates
Pelajari cara merender HTML dengan Twig (default skeleton), Latte, atau mesin lainnya—tidak hanya tampilan PHP bawaan.
Security
Pelajari cara mengamankan aplikasi Anda dari ancaman keamanan umum.
Configuration
Pelajari cara mengonfigurasi framework untuk aplikasi Anda.
Event Manager
Pelajari cara menggunakan sistem event untuk menambahkan event kustom ke aplikasi Anda.
Extending Flight
Pelajari cara memperluas framework dengan menambahkan metode dan kelas Anda sendiri.
Method Hooks and Filtering
Pelajari cara menambahkan kait (hook) event ke metode Anda dan metode internal framework.
Dependency Injection Container (DIC)
Pelajari cara menggunakan kontainer injeksi dependensi (DIC) untuk mengelola dependensi aplikasi Anda.
Kelas Utilitas
Collections
Koleksi digunakan untuk menyimpan data dan dapat diakses sebagai array atau sebagai objek untuk kemudahan penggunaan.
JSON Wrapper
Ini memiliki beberapa fungsi sederhana untuk membuat encoding dan decoding JSON Anda konsisten.
SimplePdo
PDO terkadang dapat menambah sakit kepala lebih dari yang diperlukan. SimplePdo adalah kelas pembantu PDO modern dengan metode yang mudah seperti insert(), update(), delete(), dan transaction() untuk membuat operasi database jauh lebih mudah.
PdoWrapper (Tidak digunakan lagi)
Pembungkus PDO asli sudah tidak digunakan lagi (deprecated) sejak v3.18.0. Silakan gunakan SimplePdo sebagai gantinya.
Uploaded File Handler
Kelas sederhana untuk membantu mengelola file yang diunggah dan memindahkannya ke lokasi permanen.
Konsep Penting
Why a Framework?
Berikut adalah artikel singkat tentang mengapa Anda sebaiknya menggunakan framework. Sangat baik untuk memahami manfaat menggunakan framework sebelum Anda mulai menggunakannya.
Selain itu, tutorial yang sangat baik telah dibuat oleh @lubiana. Meskipun tidak membahas secara detail tentang Flight secara khusus, panduan ini akan membantu Anda memahami beberapa konsep utama seputar framework dan mengapa konsep-konsep tersebut bermanfaat untuk digunakan. Anda dapat menemukan tutorialnya di sini.
Flight Dibandingkan dengan Framework Lain
Jika Anda bermigrasi dari framework lain seperti Laravel, Slim, Fat-Free, atau Symfony ke Flight, halaman ini akan membantu Anda memahami perbedaan antara keduanya.
Topik Lainnya
Unit Testing
Ikuti panduan ini untuk mempelajari cara melakukan unit testing pada kode Flight Anda agar menjadi sangat kokoh.
AI & Pengalaman Pengembang
Flight dibangun untuk dipasangkan dengan LLM coding: AGENTS.md, perintah Runway ai:*, dan satu tata letak skeleton yang jelas sehingga agen tetap sesuai pola.
Migrasi v2 -> v3
Kompatibilitas mundur (backwards compatibility) sebagian besar tetap dipertahankan, tetapi ada beberapa perubahan yang perlu Anda ketahui saat bermigrasi dari v2 ke v3.
Learn/unit_testing
Pengujian Unit
Ringkasan
Pengujian unit di Flight membantu Anda memastikan aplikasi Anda berperilaku seperti yang diharapkan, menemukan bug sejak dini, dan membuat basis kode Anda lebih mudah dipelihara. Flight dirancang untuk bekerja dengan lancar dengan PHPUnit, kerangka pengujian PHP yang paling populer.
Pemahaman
Pengujian unit memeriksa perilaku bagian-bagian kecil dari aplikasi Anda (seperti pengontrol atau layanan) secara terpisah. Di Flight, ini berarti menguji bagaimana rute, pengontrol, dan logika Anda merespons berbagai masukan—tanpa bergantung pada status global atau layanan eksternal nyata.
Prinsip-prinsip utama:
- Uji perilaku, bukan implementasi: Fokus pada apa yang dilakukan kode Anda, bukan bagaimana kode itu melakukannya.
- Hindari status global: Gunakan injeksi ketergantungan alih-alih
Flight::set()atauFlight::get(). - Tirukan layanan eksternal: Ganti hal-hal seperti basis data atau pengirim email dengan objek pengganti (test doubles).
- Jaga pengujian tetap cepat dan fokus: Pengujian unit tidak boleh mengakses basis data atau API nyata.
Penggunaan Dasar
Menyiapkan PHPUnit
- Instal PHPUnit dengan Composer:
composer require --dev phpunit/phpunit - Buat direktori
testsdi akar proyek Anda. - Tambahkan skrip pengujian ke
composer.jsonAnda:"scripts": { "test": "phpunit --configuration phpunit.xml" } - Buat file
phpunit.xml:<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="vendor/autoload.php"> <testsuites> <testsuite name="Flight Tests"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
Sekarang Anda dapat menjalankan pengujian Anda dengan composer test.
Menguji Penangan Rute Sederhana
Misalkan Anda memiliki rute yang memvalidasi email:
// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);
// UserController.php
class UserController {
protected $app;
public function __construct(flight\Engine $app) {
$this->app = $app;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->app->json(['status' => 'error', 'message' => 'Invalid email']);
}
return $this->app->json(['status' => 'success', 'message' => 'Valid email']);
}
}
Pengujian sederhana untuk pengontrol ini:
use PHPUnit\Framework\TestCase;
use flight\Engine;
class UserControllerTest extends TestCase {
public function testValidEmailReturnsSuccess() {
$app = new Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserController($app);
$controller->register();
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Valid email', $output['message']);
}
public function testInvalidEmailReturnsError() {
$app = new Engine();
$app->request()->data->email = 'invalid-email';
$controller = new UserController($app);
$controller->register();
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('Invalid email', $output['message']);
}
}
Tips:
- Simulasikan data POST menggunakan
$app->request()->data. - Hindari menggunakan statis
Flight::dalam pengujian Anda—gunakan instance$app.
Menggunakan Injeksi Ketergantungan untuk Pengontrol yang Dapat Diuji
Suntikkan ketergantungan (seperti basis data atau pengirim email) ke dalam pengontrol Anda agar mudah ditiru (mock) dalam pengujian:
use flight\database\SimplePdo;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct($app, $db, $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->app->json(['status' => 'error', 'message' => 'Invalid email']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'User registered']);
}
}
Dan pengujian dengan mock:
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
$mockDb = $this->createMock(flight\database\SimplePdo::class);
$mockDb->method('runQuery')->willReturn(true);
$mockMailer = new class {
public $sentEmail = null;
public function sendWelcome($email) { $this->sentEmail = $email; return true; }
};
$app = new flight\Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserController($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
}
Penggunaan Lanjutan
- Mocking: Gunakan mock bawaan PHPUnit atau kelas anonim untuk menggantikan ketergantungan.
- Menguji pengontrol secara langsung: Buat instance pengontrol dengan
Enginebaru dan mock ketergantungan. - Hindari mocking berlebihan: Biarkan logika nyata berjalan jika memungkinkan; hanya mock layanan eksternal.
Lihat Juga
- Panduan Pengujian Unit - Panduan komprehensif tentang praktik terbaik pengujian unit.
- Kontainer Injeksi Ketergantungan - Cara menggunakan DIC untuk mengelola ketergantungan dan meningkatkan kemampuan pengujian.
- Memperluas - Cara menambahkan helper Anda sendiri atau mengganti kelas inti.
- SimplePdo - Menyederhanakan interaksi basis data dan lebih mudah ditiru dalam pengujian.
- Permintaan - Menangani permintaan HTTP di Flight.
- Respons - Mengirim respons kepada pengguna.
- Pengujian Unit dan Prinsip SOLID - Pelajari bagaimana prinsip SOLID dapat meningkatkan pengujian unit Anda.
Pemecahan Masalah
- Hindari menggunakan status global (
Flight::set(),$_SESSION, dll.) dalam kode dan pengujian Anda. - Jika pengujian Anda lambat, Anda mungkin menulis pengujian integrasi—mock layanan eksternal untuk menjaga pengujian unit tetap cepat.
- Jika penyiapan pengujian rumit, pertimbangkan untuk memfaktorkan ulang kode Anda menggunakan injeksi ketergantungan.
Catatan Perubahan
- v3.15.0 - Menambahkan contoh untuk injeksi ketergantungan dan mocking.
Learn/flight_vs_symfony
Flight vs Symfony
Apa itu Symfony?
Symfony adalah sekumpulan komponen PHP yang dapat digunakan kembali dan framework PHP untuk proyek web.
Landasan standar di mana aplikasi PHP terbaik dibangun. Pilih salah satu dari 50 komponen mandiri yang tersedia untuk aplikasi Anda sendiri.
Percepat pembuatan dan pemeliharaan aplikasi web PHP Anda. Akhiri tugas pengkodean yang repetitif dan nikmati kekuatan mengendalikan kode Anda.
Kelebihan dibandingkan Flight
- Symfony memiliki ekosistem yang besar dari pengembang dan modul yang dapat digunakan untuk menyelesaikan masalah umum.
- Symfony memiliki ORM yang dilengkapi dengan fitur lengkap (Doctrine) yang dapat digunakan untuk berinteraksi dengan basis data Anda.
- Symfony memiliki banyak dokumentasi dan tutorial yang dapat digunakan untuk mempelajari framework.
- Symfony memiliki podcast, konferensi, pertemuan, video, dan sumber daya lainnya yang dapat digunakan untuk mempelajari framework.
- Symfony ditujukan untuk pengembang berpengalaman yang ingin membangun aplikasi web perusahaan yang kaya fitur.
Kekurangan dibandingkan Flight
- Symfony memiliki lebih banyak yang terjadi di bawah permukaan dibandingkan dengan Flight. Ini datang dengan biaya dramatis dalam hal kinerja. Lihat pengujian TechEmpower untuk informasi lebih lanjut.
- Flight ditujukan untuk pengembang yang ingin membangun aplikasi web yang ringan, cepat, dan mudah digunakan.
- Flight ditujukan untuk kesederhanaan dan kemudahan penggunaan.
- Salah satu fitur inti Flight adalah bahwa ia melakukan yang terbaik untuk mempertahankan kompatibilitas mundur.
- Flight tidak memiliki ketergantungan, sementara Symfony memiliki sejumlah ketergantungan
- Flight ditujukan untuk pengembang yang baru memasuki dunia framework untuk pertama kalinya.
- Flight juga dapat digunakan untuk aplikasi tingkat enterprise, tetapi tidak memiliki sebanyak contoh dan tutorial seperti Symfony. Ini juga akan memerlukan lebih banyak disiplin dari pihak pengembang untuk menjaga agar segala sesuatunya terorganisir dan terstruktur dengan baik.
- Flight memberikan pengembang lebih banyak kontrol atas aplikasi, sementara Symfony dapat menyisipkan sedikit keajaiban di belakang layar.
Learn/flight_vs_another_framework
Membandingkan Flight dengan Framework Lain
Jika Anda bermigrasi dari framework lain seperti Laravel, Slim, Fat-Free, atau Symfony ke Flight, halaman ini akan membantu Anda memahami perbedaan antara keduanya.
Laravel
Laravel adalah framework yang penuh fitur yang memiliki segala fasilitas dan ekosistem yang menakjubkan yang berfokus pada pengembang, tetapi dengan biaya dalam kinerja dan kompleksitas.
Lihat perbandingan antara Laravel dan Flight.
Slim
Slim adalah micro-framework yang mirip dengan Flight. Ini dirancang agar ringan dan mudah digunakan, tetapi bisa sedikit lebih kompleks daripada Flight.
Lihat perbandingan antara Slim dan Flight.
Fat-Free
Fat-Free adalah framework full-stack dalam paket yang jauh lebih kecil. Meskipun memiliki semua alat dalam kotak perkakas, ia memiliki arsitektur data yang dapat membuat beberapa proyek menjadi lebih kompleks daripada yang seharusnya.
Lihat perbandingan antara Fat-Free dan Flight.
Symfony
Symfony adalah framework modular tingkat perusahaan yang dirancang untuk fleksibel dan skalabel. Untuk proyek yang lebih kecil atau pengembang yang lebih baru, Symfony bisa sedikit membingungkan.
Lihat perbandingan antara Symfony dan Flight.
Learn/pdo_wrapper
Kelas Pembantu PDO PdoWrapper
PERINGATAN
Usang:
PdoWrappersudah usang sejak Flight v3.18.0. Ini tidak akan dihapus di versi mendatang, tetapi akan dipelihara untuk kompatibilitas mundur. Silakan gunakan SimplePdo sebagai gantinya, yang menawarkan fungsionalitas yang sama ditambah metode pembantu tambahan untuk operasi database umum.
Gambaran Umum
Kelas PdoWrapper di Flight adalah pembantu yang ramah untuk bekerja dengan database menggunakan PDO. Ini menyederhanakan tugas database umum, menambahkan beberapa metode yang berguna untuk mengambil hasil, dan mengembalikan hasil sebagai Collections untuk akses yang mudah. Ini juga mendukung pencatatan query dan pemantauan kinerja aplikasi (APM) untuk kasus penggunaan lanjutan.
Pemahaman
Bekerja dengan database di PHP bisa agak verbose, terutama saat menggunakan PDO secara langsung. PdoWrapper memperluas PDO dan menambahkan metode yang membuat querying, fetching, dan penanganan hasil jauh lebih mudah. Alih-alih mengelola pernyataan yang disiapkan dan mode fetch, Anda mendapatkan metode sederhana untuk tugas umum, dan setiap baris dikembalikan sebagai Collection, sehingga Anda bisa menggunakan notasi array atau objek.
Anda bisa mendaftarkan PdoWrapper sebagai layanan bersama di Flight, dan kemudian menggunakannya di mana saja di aplikasi Anda melalui Flight::db().
Penggunaan Dasar
Mendaftarkan Pembantu PDO
Pertama, daftarkan kelas PdoWrapper dengan Flight:
Flight::register('db', \flight\database\PdoWrapper::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
]);
Sekarang Anda bisa menggunakan Flight::db() di mana saja untuk mendapatkan koneksi database Anda.
Menjalankan Query
runQuery()
function runQuery(string $sql, array $params = []): PDOStatement
Gunakan ini untuk INSERT, UPDATE, atau saat Anda ingin mengambil hasil secara manual:
$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM users WHERE status = ?", ['active']);
while ($row = $statement->fetch()) {
// $row adalah array
}
Anda juga bisa menggunakannya untuk penulisan:
$db->runQuery("INSERT INTO users (name) VALUES (?)", ['Alice']);
$db->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 1]);
fetchField()
function fetchField(string $sql, array $params = []): mixed
Dapatkan satu nilai tunggal dari database:
$count = Flight::db()->fetchField("SELECT COUNT(*) FROM users WHERE status = ?", ['active']);
fetchRow()
function fetchRow(string $sql, array $params = []): Collection
Dapatkan satu baris sebagai Collection (akses array/objek):
$user = Flight::db()->fetchRow("SELECT * FROM users WHERE id = ?", [123]);
echo $user['name'];
// atau
echo $user->name;
fetchAll()
function fetchAll(string $sql, array $params = []): array<Collection>
Dapatkan semua baris sebagai array dari Collections:
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE status = ?", ['active']);
foreach ($users as $user) {
echo $user['name'];
// atau
echo $user->name;
}
Menggunakan Placeholder IN()
Anda bisa menggunakan satu ? tunggal dalam klausa IN() dan meneruskan array atau string yang dipisahkan koma:
$ids = [1, 2, 3];
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", [$ids]);
// atau
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", ['1,2,3']);
Penggunaan Lanjutan
Pencatatan Query & APM
Jika Anda ingin melacak kinerja query, aktifkan pelacakan APM saat mendaftarkan:
Flight::register('db', \flight\database\PdoWrapper::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [/* options */], true // parameter terakhir mengaktifkan APM
]);
Setelah menjalankan query, Anda bisa mencatatnya secara manual tetapi APM akan mencatatnya secara otomatis jika diaktifkan:
Flight::db()->logQueries();
Ini akan memicu event (flight.db.queries) dengan metrik koneksi dan query, yang bisa Anda dengarkan menggunakan sistem event Flight.
Contoh Lengkap
Flight::route('/users', function () {
// Dapatkan semua pengguna
$users = Flight::db()->fetchAll('SELECT * FROM users');
// Stream semua pengguna
$statement = Flight::db()->runQuery('SELECT * FROM users');
while ($user = $statement->fetch()) {
echo $user['name'];
}
// Dapatkan satu pengguna
$user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);
// Dapatkan satu nilai
$count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');
// Sintaks IN() khusus
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', ['1,2,3,4,5']);
// Sisipkan pengguna baru
Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']);
$insert_id = Flight::db()->lastInsertId();
// Perbarui pengguna
Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 123]);
// Hapus pengguna
Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]);
// Dapatkan jumlah baris yang terpengaruh
$statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Bob', 'Sally']);
$affected_rows = $statement->rowCount();
});
Lihat Juga
- Collections - Pelajari cara menggunakan kelas Collection untuk akses data yang mudah.
Pemecahan Masalah
- Jika Anda mendapatkan kesalahan tentang koneksi database, periksa DSN, nama pengguna, kata sandi, dan opsi Anda.
- Semua baris dikembalikan sebagai Collections—jika Anda membutuhkan array biasa, gunakan
$collection->getData(). - Untuk query
IN (?), pastikan untuk meneruskan array atau string yang dipisahkan koma.
Changelog
- v3.2.0 - Rilis awal PdoWrapper dengan metode query dan fetch dasar.
Learn/dependency_injection_container
Kontainer Injeksi Dependensi
Ikhtisar
Kontainer Injeksi Dependensi (DIC) adalah peningkatan yang ampuh yang memungkinkan Anda mengelola dependensi aplikasi Anda. Ini juga merupakan salah satu alasan terbesar mengapa Flight dapat bekerja dengan baik dengan alat coding AI dan pengujian unit: kontroler mengambil apa yang mereka butuhkan di konstruktor alih-alih mengakses global.
Pemahaman
Injeksi Dependensi (DI) adalah konsep kunci dalam kerangka kerja PHP modern dan digunakan untuk mengelola instansiasi dan konfigurasi objek. Beberapa contoh pustaka DIC adalah: flightphp/container, Dice, Pimple, PHP-DI, dan league/container.
DIC adalah cara yang canggih untuk membuat dan mengelola kelas Anda di satu lokasi terpusat. Ini berguna ketika Anda perlu mengirim objek yang sama ke beberapa kelas (kontroler, middleware, perintah, dan sebagainya).
skeleton flightphp/skeleton resmi menghubungkan Dice di app/config/services.php, mengganti instance flight\Engine yang digunakan bersama, dan menyelesaikan target rute seperti [App\Controller\HomeController::class, 'index']. Prefer pola itu untuk proyek baru sehingga manusia dan agen mengedit tempat yang sama.
Penggunaan Dasar
Cara lama dalam melakukan sesuatu mungkin terlihat seperti ini:
require 'vendor/autoload.php';
// kelas untuk mengelola pengguna dari database
class UserController {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function view(int $id) {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
print_r($stmt->fetch());
}
}
// di file routes.php Anda
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$UserController = new UserController($db);
Flight::route('/user/@id', [ $UserController, 'view' ]);
// rute UserController lainnya...
Flight::start();
Anda dapat melihat dari kode di atas bahwa kita membuat objek PDO baru dan mengirimkannya ke kelas UserController kita. Ini baik-baik saja untuk aplikasi kecil, tetapi seiring aplikasi Anda berkembang, Anda akan menemukan bahwa Anda membuat atau mengirimkan objek PDO yang sama di banyak tempat. Di sinilah DIC menjadi berguna.
Berikut adalah contoh yang sama menggunakan DIC (menggunakan Dice):
require 'vendor/autoload.php';
// kelas yang sama seperti di atas. Tidak ada yang berubah
class UserController {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function view(int $id) {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
print_r($stmt->fetch());
}
}
// buat kontainer baru
$container = new \Dice\Dice;
// tambahkan aturan untuk memberi tahu kontainer cara membuat objek PDO
// jangan lupa untuk menetapkannya kembali ke dirinya sendiri seperti di bawah ini!
$container = $container->addRule('PDO', [
// shared berarti objek yang sama akan dikembalikan setiap kali
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// Ini mendaftarkan penangan kontainer sehingga Flight tahu untuk menggunakannya.
Flight::registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
// sekarang kita dapat menggunakan kontainer untuk membuat UserController kita
Flight::route('/user/@id', [ UserController::class, 'view' ]);
Flight::start();
Saya yakin Anda mungkin berpikir bahwa ada banyak kode tambahan yang ditambahkan ke contoh ini.
Keajaibannya muncul ketika Anda memiliki kontroler lain yang membutuhkan objek PDO.
// Jika semua kontroler Anda memiliki konstruktor yang membutuhkan objek PDO
// setiap rute di bawah ini secara otomatis akan mendapatkannya diinjeksikan!!!
Flight::route('/company/@id', [ CompanyController::class, 'view' ]);
Flight::route('/organization/@id', [ OrganizationController::class, 'view' ]);
Flight::route('/category/@id', [ CategoryController::class, 'view' ]);
Flight::route('/settings', [ SettingsController::class, 'view' ]);
Bonus tambahan dari menggunakan DIC adalah pengujian unit menjadi jauh lebih mudah. Anda dapat membuat objek mock dan mengirimkannya ke kelas Anda. Ini adalah manfaat besar saat Anda menulis tes untuk aplikasi Anda—dan ketika asisten AI membuat kontroler, injeksi konstruktor memberinya pola yang jelas dan konsisten untuk diikuti (panduan pengujian unit).
Membuat penangan DIC terpusat
Anda dapat membuat penangan DIC terpusat di file layanan Anda dengan memperluas aplikasi Anda. Berikut adalah contohnya:
// services.php
// buat kontainer baru
$container = new \Dice\Dice;
// jangan lupa untuk menetapkannya kembali ke dirinya sendiri seperti di bawah ini!
$container = $container->addRule('PDO', [
// shared berarti objek yang sama akan dikembalikan setiap kali
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// sekarang kita dapat membuat metode yang dapat dipetakan untuk membuat objek apa pun.
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// Ini mendaftarkan penangan kontainer sehingga Flight tahu untuk menggunakannya untuk kontroler/middleware
Flight::registerContainerHandler(function($class, $params) {
return Flight::make($class, $params);
});
// katakanlah kita memiliki kelas contoh berikut yang mengambil objek PDO di konstruktor
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// kode yang mengirim email
}
}
// Dan akhirnya Anda dapat membuat objek menggunakan injeksi dependensi
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();
flightphp/container
Flight memiliki plugin yang menyediakan kontainer sederhana yang sesuai dengan PSR-11 yang dapat Anda gunakan untuk menangani injeksi dependensi Anda. Berikut adalah contoh cepat cara menggunakannya:
// index.php misalnya
require 'vendor/autoload.php';
use flight\Container;
$container = new Container;
$container->set(PDO::class, fn(): PDO => new PDO('sqlite::memory:'));
Flight::registerContainerHandler([$container, 'get']);
class TestController {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function index() {
var_dump($this->pdo);
// akan menampilkan ini dengan benar!
}
}
Flight::route('GET /', [TestController::class, 'index']);
Flight::start();
Penggunaan Lanjutan flightphp/container
Anda juga dapat menyelesaikan dependensi secara rekursif. Berikut adalah contohnya:
<?php
require 'vendor/autoload.php';
use flight\Container;
class User {}
interface UserRepository {
function find(int $id): ?User;
}
class PdoUserRepository implements UserRepository {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function find(int $id): ?User {
// Implementasi ...
return null;
}
}
$container = new Container;
$container->set(PDO::class, static fn(): PDO => new PDO('sqlite::memory:'));
$container->set(UserRepository::class, PdoUserRepository::class);
$userRepository = $container->get(UserRepository::class);
var_dump($userRepository);
/*
object(PdoUserRepository)#4 (1) {
["pdo":"PdoUserRepository":private]=>
object(PDO)#3 (0) {
}
}
*/
DICE
Anda juga dapat membuat penangan DIC sendiri. Ini berguna jika Anda memiliki kontainer khusus yang ingin Anda gunakan yang tidak sesuai dengan PSR-11 (Dice). Lihat bagian penggunaan dasar untuk cara melakukannya.
Selain itu, ada beberapa default yang membantu yang akan membuat hidup Anda lebih mudah saat menggunakan Flight.
Instance Engine (diperlukan untuk injeksi $app)
Jika Anda mengetik-hint flight\Engine pada kontroler atau middleware, Dice tidak boleh membuat Engine baru. Ganti dengan instance yang sama dari bootstrap. Inilah yang dilakukan skeleton resmi, dan ini adalah pola yang diharapkan AGENTS.md untuk kontroler yang dihasilkan AI:
// Di suatu tempat di bootstrap / services.php Anda
use flight\Engine;
use flight\database\SimplePdo;
$app = Flight::app(); // atau $engine = Flight::app();
$container = new \Dice\Dice;
$container = $container->addRule('*', [
'substitutions' => [
// Penting: gunakan kembali Engine yang sudah di-bootstrap — jangan biarkan Dice `new Engine()`
Engine::class => $app,
// Prefer SimplePdo untuk kode baru
// SimplePdo::class => $db,
// Config::class => $config,
// \Twig\Environment::class => $twig,
]
]);
$app->registerContainerHandler(function ($class, $params) use ($container) {
return $container->create($class, $params);
});
// Helper opsional untuk kode non-rute
$app->map('make', function ($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// app/Controller/MyController.php (tata letak skeleton — huruf besar/kecil folder cocok dengan namespace)
namespace App\Controller;
use flight\Engine;
class MyController
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function index(): void
{
// Tidak ada fasad Flight:: di lapisan aplikasi — lebih mudah diuji dan lebih jelas untuk alat AI
$this->app->render('welcome', ['message' => 'Hello']);
}
}
// app/config/routes.php
use App\Controller\MyController;
$router->get('/', [MyController::class, 'index']);
Jika Anda melewatkan substitusi Engine, Dice dapat membuat Engine kedua dan kontroler Anda tidak akan berbagi rute, konfigurasi, atau render Twig yang dipetakan dari bootstrap.
Menambahkan layanan bersama lainnya (SimplePdo, Config, Twig)
use flight\database\SimplePdo;
use flight\Engine;
// Setelah Anda membuat $db, $config, $twig di services.php:
$substitutions = [
Engine::class => $app,
SimplePdo::class => $db,
// App\Utils\Config::class => $config,
// \Twig\Environment::class => $twig,
];
$container = $container->addRule('*', [
'substitutions' => $substitutions,
]);
Kemudian kontroler dapat mengambil SimplePdo $db (atau tipe konfigurasi Anda) di konstruktor dan tidak pernah memanggil Flight::db(). Itu sesuai dengan panduan pengujian unit dan gaya rumah skeleton.
Menambahkan kelas lainnya
Jika Anda memiliki kelas lain yang ingin ditambahkan ke kontainer, dengan Dice itu mudah karena mereka akan diselesaikan secara otomatis oleh kontainer. Berikut adalah contohnya:
$container = new \Dice\Dice;
// Jika Anda tidak perlu menginjeksikan dependensi apa pun ke dalam kelas Anda
// Anda tidak perlu mendefinisikan apa pun!
Flight::registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
class MyCustomClass {
public function parseThing() {
return 'thing';
}
}
class UserController {
protected MyCustomClass $MyCustomClass;
public function __construct(MyCustomClass $MyCustomClass) {
$this->MyCustomClass = $MyCustomClass;
}
public function index() {
echo $this->MyCustomClass->parseThing();
}
}
Flight::route('/user', 'UserController->index');
PSR-11
Flight juga dapat menggunakan kontainer apa pun yang sesuai dengan PSR-11. Ini berarti Anda dapat menggunakan kontainer apa pun yang mengimplementasikan antarmuka PSR-11. Berikut adalah contoh menggunakan kontainer PSR-11 dari League:
require 'vendor/autoload.php';
use flight\database\SimplePdo;
// Ide UserController yang sama seperti di atas, ketik-hint SimplePdo alih-alih PDO mentah
$container = new \League\Container\Container();
$container->add(UserController::class)->addArgument(SimplePdo::class);
$container->add(SimplePdo::class)
->addArgument('mysql:host=localhost;dbname=test')
->addArgument('user')
->addArgument('pass');
Flight::registerContainerHandler($container);
Flight::route('/user', [ 'UserController', 'view' ]);
Flight::start();
Ini bisa sedikit lebih panjang daripada contoh Dice sebelumnya, tetapi tetap menyelesaikan pekerjaan dengan manfaat yang sama!
Lihat Juga
- Instalasi - Tata letak skeleton dan di mana
services.phpberada. - Autoloading - Namespace
App\dan huruf besar/kecil folder. - Memperluas Flight - Pelajari cara menambahkan injeksi dependensi ke kelas Anda sendiri dengan memperluas kerangka kerja.
- Konfigurasi - Pelajari cara mengonfigurasi Flight untuk aplikasi Anda.
- Routing - Pelajari cara mendefinisikan rute untuk aplikasi Anda dan cara kerja injeksi dependensi dengan kontroler.
- Middleware - Pelajari cara membuat middleware untuk aplikasi Anda dan cara kerja injeksi dependensi dengan middleware.
- Pengujian Unit - Mengapa injeksi konstruktor lebih baik daripada global
Flight::. - AI & Pengalaman Pengembang - Satu pola DI untuk manusia dan agen.
- SimplePdo - Helper basis data yang disarankan untuk injeksi.
Pemecahan Masalah
- Jika Anda mengalami masalah dengan kontainer Anda, pastikan Anda memberikan nama kelas yang benar ke kontainer.
- Kontroler yang ketik-hint
Enginetetapi mendapatkan aplikasi "kosong": tambahkan substitusi Engine (lihat di atas). Dice tidak bolehnewEngine kedua. - Kelas tidak ditemukan untuk
App\Controller\…: periksa huruf besar/kecil folder di bawahapp/Controller/— lihat Autoloading. - Penangan harus mengembalikan objek yang dibuat dari
registerContainerHandler(jangan memanggilFlight::make()tanpareturn).
Log Perubahan
- Docs – Dokumentasikan skeleton Dice + substitusi Engine, SimplePdo, dan tata letak
App\Controlleruntuk proyek yang ramah AI. - v3.7.0 - Menambahkan kemampuan untuk mendaftarkan penangan DIC ke Flight.
Learn/middleware
Middleware
Ikhtisar
Flight mendukung middleware rute dan grup rute. Middleware adalah bagian dari aplikasi Anda di mana kode dieksekusi sebelum (atau setelah) callback rute. Ini adalah cara yang bagus untuk menambahkan pemeriksaan autentikasi API dalam kode Anda, atau untuk memvalidasi bahwa pengguna memiliki izin untuk mengakses rute.
Pemahaman
Middleware dapat sangat menyederhanakan aplikasi Anda. Alih-alih pewarisan kelas abstrak yang kompleks atau override metode, middleware memungkinkan Anda mengontrol rute dengan menetapkan logika aplikasi kustom terhadapnya. Anda dapat membayangkan middleware seperti sebuah sandwich. Anda memiliki roti di luar, dan kemudian lapisan topik seperti selada, tomat, daging dan keju. Kemudian bayangkan seperti setiap permintaan adalah seperti menggigit sandwich di mana Anda makan lapisan luar terlebih dahulu dan bekerja menuju inti.
Berikut adalah visualisasi bagaimana middleware bekerja. Kemudian kami akan menunjukkan kepada Anda contoh praktis bagaimana ini berfungsi.
Permintaan pengguna di URL /api ---->
Middleware->before() dieksekusi ----->
Callable/method yang terpasang ke /api dieksekusi dan respons dihasilkan ------>
Middleware->after() dieksekusi ----->
Pengguna menerima respons dari server
Dan berikut adalah contoh praktis:
Pengguna menavigasi ke URL /dashboard
LoggedInMiddleware->before() dieksekusi
before() memeriksa sesi login yang valid
jika ya lakukan tidak ada dan lanjutkan eksekusi
jika tidak arahkan pengguna ke /login
Callable/method yang terpasang ke /api dieksekusi dan respons dihasilkan
LoggedInMiddleware->after() tidak memiliki apa pun yang didefinisikan sehingga membiarkan eksekusi berlanjut
Pengguna menerima HTML dashboard dari server
Urutan Eksekusi
Fungsi middleware dieksekusi dalam urutan mereka ditambahkan ke rute. Eksekusi mirip dengan bagaimana Slim Framework menangani ini.
Metode before() dieksekusi dalam urutan ditambahkan, dan metode after() dieksekusi dalam urutan terbalik.
Contoh: Middleware1->before(), Middleware2->before(), Middleware2->after(), Middleware1->after().
Penggunaan Dasar
Anda dapat menggunakan middleware sebagai metode callable apa pun termasuk fungsi anonim atau kelas (direkomendasikan)
Fungsi Anonim
Berikut adalah contoh sederhana:
Flight::route('/path', function() { echo ' Here I am!'; })->addMiddleware(function() {
echo 'Middleware first!';
});
Flight::start();
// Ini akan menghasilkan "Middleware first! Here I am!"
Catatan: Saat menggunakan fungsi anonim, satu-satunya metode yang diinterpretasikan adalah metode
before(). Anda tidak bisa mendefinisikan perilakuafter()dengan kelas anonim.
Menggunakan Kelas
Middleware dapat (dan harus) didaftarkan sebagai kelas. Jika Anda membutuhkan fungsionalitas "after", Anda harus menggunakan kelas.
class MyMiddleware {
public function before($params) {
echo 'Middleware first!';
}
public function after($params) {
echo 'Middleware last!';
}
}
$MyMiddleware = new MyMiddleware();
Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware($MyMiddleware);
// juga ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]);
Flight::start();
// Ini akan menampilkan "Middleware first! Here I am! Middleware last!"
Anda juga hanya bisa mendefinisikan nama kelas middleware dan itu akan menginstansiasi kelas.
Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware(MyMiddleware::class);
Catatan: Jika Anda hanya memasukkan nama middleware, itu akan secara otomatis dieksekusi oleh dependency injection container dan middleware akan dieksekusi dengan parameter yang dibutuhkan. Jika Anda tidak memiliki dependency injection container yang terdaftar, itu akan memasukkan instance
flight\Engineke dalam__construct(Engine $app)secara default.
Menggunakan Rute dengan Parameter
Jika Anda membutuhkan parameter dari rute Anda, mereka akan diteruskan dalam satu array ke fungsi middleware Anda. (function($params) { ... } atau public function before($params) { ... }). Alasan untuk ini adalah bahwa Anda dapat menyusun parameter Anda menjadi grup dan dalam beberapa grup tersebut, parameter Anda mungkin muncul dalam urutan yang berbeda yang akan merusak fungsi middleware dengan merujuk ke parameter yang salah. Dengan cara ini, Anda dapat mengaksesnya berdasarkan nama bukan posisi.
use flight\Engine;
class RouteSecurityMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$clientId = $params['clientId'];
// jobId mungkin atau mungkin tidak diteruskan
$jobId = $params['jobId'] ?? 0;
// mungkin jika tidak ada ID pekerjaan, Anda tidak perlu mencari apa pun.
if($jobId === 0) {
return;
}
// lakukan pencarian semacamnya di database Anda
$isValid = !!$this->app->db()->fetchField("SELECT 1 FROM client_jobs WHERE client_id = ? AND job_id = ?", [ $clientId, $jobId ]);
if($isValid !== true) {
$this->app->halt(400, 'You are blocked, muahahaha!');
}
}
}
// routes.php
$router->group('/client/@clientId/job/@jobId', function(Router $router) {
// Grup ini di bawah masih mendapatkan middleware parent
// Tapi parameter diteruskan dalam satu array tunggal
// di middleware.
$router->group('/job/@jobId', function(Router $router) {
$router->get('', [ JobController::class, 'view' ]);
$router->put('', [ JobController::class, 'update' ]);
$router->delete('', [ JobController::class, 'delete' ]);
// lebih banyak rute...
});
}, [ RouteSecurityMiddleware::class ]);
Mengelompokkan Rute dengan Middleware
Anda dapat menambahkan grup rute, dan kemudian setiap rute dalam grup itu akan memiliki middleware yang sama juga. Ini berguna jika Anda perlu mengelompokkan banyak rute berdasarkan middleware Auth untuk memeriksa kunci API di header.
// ditambahkan di akhir metode grup
Flight::group('/api', function() {
// Rute "kosong" ini sebenarnya akan cocok dengan /api
Flight::route('', function() { echo 'api'; }, false, 'api');
// Ini akan cocok dengan /api/users
Flight::route('/users', function() { echo 'users'; }, false, 'users');
// Ini akan cocok dengan /api/users/1234
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);
Jika Anda ingin menerapkan middleware global ke semua rute Anda, Anda dapat menambahkan grup "kosong":
// ditambahkan di akhir metode grup
Flight::group('', function() {
// Ini masih /users
Flight::route('/users', function() { echo 'users'; }, false, 'users');
// Dan ini masih /users/1234
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ ApiAuthMiddleware::class ]); // atau [ new ApiAuthMiddleware() ], hal yang sama
Kasus Penggunaan Umum
Validasi Kunci API
Jika Anda ingin melindungi rute /api Anda dengan memverifikasi kunci API yang benar, Anda dapat dengan mudah menanganinya dengan middleware.
use flight\Engine;
class ApiMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$authorizationHeader = $this->app->request()->getHeader('Authorization');
$apiKey = str_replace('Bearer ', '', $authorizationHeader);
// lakukan pencarian di database Anda untuk kunci api
$apiKeyHash = hash('sha256', $apiKey);
$hasValidApiKey = !!$this->db()->fetchField("SELECT 1 FROM api_keys WHERE hash = ? AND valid_date >= NOW()", [ $apiKeyHash ]);
if($hasValidApiKey !== true) {
$this->app->jsonHalt(['error' => 'Invalid API Key']);
}
}
}
// routes.php
$router->group('/api', function(Router $router) {
$router->get('/users', [ ApiController::class, 'getUsers' ]);
$router->get('/companies', [ ApiController::class, 'getCompanies' ]);
// lebih banyak rute...
}, [ ApiMiddleware::class ]);
Sekarang semua rute API Anda dilindungi oleh middleware validasi kunci API yang Anda siapkan! Jika Anda memasukkan lebih banyak rute ke dalam grup router, mereka akan langsung memiliki perlindungan yang sama!
Validasi Login
Apakah Anda ingin melindungi beberapa rute agar hanya tersedia untuk pengguna yang login? Itu dapat dengan mudah dicapai dengan middleware!
use flight\Engine;
class LoggedInMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$session = $this->app->session();
if($session->get('logged_in') !== true) {
$this->app->redirect('/login');
exit;
}
}
}
// routes.php
$router->group('/admin', function(Router $router) {
$router->get('/dashboard', [ DashboardController::class, 'index' ]);
$router->get('/clients', [ ClientController::class, 'index' ]);
// lebih banyak rute...
}, [ LoggedInMiddleware::class ]);
Validasi Parameter Rute
Apakah Anda ingin melindungi pengguna Anda dari mengubah nilai di URL untuk mengakses data yang seharusnya tidak mereka akses? Itu dapat diselesaikan dengan middleware!
use flight\Engine;
class RouteSecurityMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$clientId = $params['clientId'];
$jobId = $params['jobId'];
// lakukan pencarian semacamnya di database Anda
$isValid = !!$this->app->db()->fetchField("SELECT 1 FROM client_jobs WHERE client_id = ? AND job_id = ?", [ $clientId, $jobId ]);
if($isValid !== true) {
$this->app->halt(400, 'You are blocked, muahahaha!');
}
}
}
// routes.php
$router->group('/client/@clientId/job/@jobId', function(Router $router) {
$router->get('', [ JobController::class, 'view' ]);
$router->put('', [ JobController::class, 'update' ]);
$router->delete('', [ JobController::class, 'delete' ]);
// lebih banyak rute...
}, [ RouteSecurityMiddleware::class ]);
Menangani Eksekusi Middleware
Misalkan Anda memiliki middleware auth dan Anda ingin mengarahkan pengguna ke halaman login jika mereka tidak terautentikasi. Anda memiliki beberapa opsi yang tersedia:
- Anda dapat mengembalikan false dari fungsi middleware dan Flight akan secara otomatis mengembalikan kesalahan 403 Forbidden, tapi tidak ada kustomisasi.
- Anda dapat mengarahkan pengguna ke halaman login menggunakan
Flight::redirect(). - Anda dapat membuat kesalahan kustom dalam middleware dan menghentikan eksekusi rute.
Sederhana dan Langsung
Berikut adalah contoh sederhana return false; :
class MyMiddleware {
public function before($params) {
$hasUserKey = Flight::session()->exists('user');
if ($hasUserKey === false) {
return false;
}
// karena itu benar, semuanya terus berjalan
}
}
Contoh Pengalihan
Berikut adalah contoh mengarahkan pengguna ke halaman login:
class MyMiddleware {
public function before($params) {
$hasUserKey = Flight::session()->exists('user');
if ($hasUserKey === false) {
Flight::redirect('/login');
exit;
}
}
}
Contoh Kesalahan Kustom
Misalkan Anda perlu melempar kesalahan JSON karena Anda membangun API. Anda dapat melakukannya seperti ini:
class MyMiddleware {
public function before($params) {
$authorization = Flight::request()->getHeader('Authorization');
if(empty($authorization)) {
Flight::jsonHalt(['error' => 'You must be logged in to access this page.'], 403);
// atau
Flight::json(['error' => 'You must be logged in to access this page.'], 403);
exit;
// atau
Flight::halt(403, json_encode(['error' => 'You must be logged in to access this page.']);
}
}
}
Lihat Juga
- Routing - Cara memetakan rute ke controller dan merender tampilan.
- Requests - Memahami cara menangani permintaan masuk.
- Responses - Cara menyesuaikan respons HTTP.
- Dependency Injection - Menyederhanakan pembuatan dan pengelolaan objek di rute.
- Mengapa Framework? - Memahami manfaat menggunakan framework seperti Flight.
- Contoh Strategi Eksekusi Middleware
Pemecahan Masalah
- Jika Anda memiliki pengalihan di middleware Anda, tapi aplikasi Anda tampaknya tidak mengalihkan, pastikan Anda menambahkan pernyataan
exit;di middleware Anda.
Changelog
- v3.1: Menambahkan dukungan untuk middleware.
Learn/filtering
Penyaringan
Gambaran Umum
Flight memungkinkan Anda menyaring metode yang dipetakan sebelum dan sesudah mereka dipanggil.
Pemahaman
Tidak ada hook yang telah ditentukan sebelumnya yang perlu Anda hafal. Anda dapat menyaring metode kerangka kerja default apa pun serta metode kustom apa pun yang telah Anda petakan.
Fungsi filter terlihat seperti ini:
/**
* @param array $params Parameter yang diteruskan ke metode yang disaring.
* @param string $output (hanya penyanggaan output v2) Output dari metode yang disaring.
* @return bool Kembalikan true/void atau jangan kembalikan untuk melanjutkan rantai, false untuk memutus rantai.
*/
function (array &$params, string &$output): bool {
// Kode filter
}
Dengan menggunakan variabel yang diteruskan, Anda dapat memanipulasi parameter input dan/atau output.
Anda dapat menjalankan filter sebelum metode dengan melakukan:
Flight::before('start', function (array &$params, string &$output): bool {
// Lakukan sesuatu
});
Anda dapat menjalankan filter setelah metode dengan melakukan:
Flight::after('start', function (array &$params, string &$output): bool {
// Lakukan sesuatu
});
Anda dapat menambahkan sebanyak filter yang Anda inginkan ke metode apa pun. Mereka akan dipanggil dalam urutan yang mereka dinyatakan.
Berikut adalah contoh proses penyaringan:
// Petakan metode kustom
Flight::map('hello', function (string $name) {
return "Hello, $name!";
});
// Tambahkan filter sebelum
Flight::before('hello', function (array &$params, string &$output): bool {
// Manipulasi parameter
$params[0] = 'Fred';
return true;
});
// Tambahkan filter setelah
Flight::after('hello', function (array &$params, string &$output): bool {
// Manipulasi output
$output .= " Have a nice day!";
return true;
});
// Panggil metode kustom
echo Flight::hello('Bob');
Ini seharusnya menampilkan:
Hello Fred! Have a nice day!
Jika Anda telah mendefinisikan beberapa filter, Anda dapat memutus rantai dengan mengembalikan false
dalam fungsi filter mana pun:
Flight::before('start', function (array &$params, string &$output): bool {
echo 'one';
return true;
});
Flight::before('start', function (array &$params, string &$output): bool {
echo 'two';
// Ini akan mengakhiri rantai
return false;
});
// Ini tidak akan dipanggil
Flight::before('start', function (array &$params, string &$output): bool {
echo 'three';
return true;
});
Catatan: Metode inti seperti
mapdanregistertidak dapat disaring karena mereka dipanggil secara langsung dan tidak dipanggil secara dinamis. Lihat Memperluas Flight untuk informasi lebih lanjut.
Lihat Juga
Pemecahan Masalah
- Pastikan Anda mengembalikan
falsedari fungsi filter Anda jika Anda ingin rantai berhenti. Jika Anda tidak mengembalikan apa pun, rantai akan berlanjut.
Log Perubahan
- v2.0 - Rilis Awal.
Learn/requests
Permintaan
Gambaran Umum
Flight merangkum permintaan HTTP ke dalam satu objek, yang dapat diakses dengan melakukan:
$request = Flight::request();
Pemahaman
Permintaan HTTP adalah salah satu aspek inti yang perlu dipahami tentang siklus hidup HTTP. Pengguna melakukan tindakan pada peramban web atau klien HTTP, dan mereka mengirim serangkaian header, body, URL, dll ke proyek Anda. Anda dapat menangkap header ini (bahasa peramban, jenis kompresi yang dapat ditangani, agen pengguna, dll) dan menangkap body serta URL yang dikirim ke aplikasi Flight Anda. Permintaan ini sangat penting agar aplikasi Anda memahami apa yang harus dilakukan selanjutnya.
Penggunaan Dasar
PHP memiliki beberapa super global termasuk $_GET, $_POST, $_REQUEST, $_SERVER, $_FILES, dan $_COOKIE. Flight mengabstraksikan ini menjadi Collections yang berguna. Anda dapat mengakses properti query, data, cookies, dan files sebagai array atau objek.
Catatan: Sangat TIDAK DISARANKAN menggunakan super global ini dalam proyek Anda dan seharusnya dirujuk melalui objek
request().
Catatan: Tidak ada abstraksi yang tersedia untuk
$_ENV.
$_GET
Anda dapat mengakses array $_GET melalui properti query:
// GET /search?keyword=something
Flight::route('/search', function(){
$keyword = Flight::request()->query['keyword'];
// atau
$keyword = Flight::request()->query->keyword;
echo "Anda sedang mencari: $keyword";
// query database atau sesuatu yang lain dengan $keyword
});
$_POST
Anda dapat mengakses array $_POST melalui properti data:
Flight::route('POST /submit', function(){
$name = Flight::request()->data['name'];
$email = Flight::request()->data['email'];
// atau
$name = Flight::request()->data->name;
$email = Flight::request()->data->email;
echo "Anda mengirimkan: $name, $email";
// simpan ke database atau sesuatu yang lain dengan $name dan $email
});
$_COOKIE
Anda dapat mengakses array $_COOKIE melalui properti cookies:
Flight::route('GET /login', function(){
$savedLogin = Flight::request()->cookies['myLoginCookie'];
// atau
$savedLogin = Flight::request()->cookies->myLoginCookie;
// periksa apakah benar-benar tersimpan atau tidak dan jika ya, login otomatis mereka
if($savedLogin) {
Flight::redirect('/dashboard');
return;
}
});
Untuk bantuan dalam mengatur nilai cookie baru, lihat overclokk/cookie
$_SERVER
Ada jalan pintas yang tersedia untuk mengakses array $_SERVER melalui metode getVar():
$host = Flight::request()->getVar('HTTP_HOST');
$_FILES
Anda dapat mengakses file yang diunggah melalui properti files:
// akses mentah ke properti $_FILES. Lihat di bawah untuk pendekatan yang direkomendasikan
$uploadedFile = Flight::request()->files['myFile'];
// atau
$uploadedFile = Flight::request()->files->myFile;
Lihat Uploaded File Handler untuk info lebih lanjut.
Pemrosesan Unggahan File
v3.12.0
Anda dapat memproses unggahan file menggunakan framework dengan beberapa metode bantu. Ini pada dasarnya berarti menarik data file dari permintaan, dan memindahkannya ke lokasi baru.
Flight::route('POST /upload', function(){
// Jika Anda memiliki field input seperti <input type="file" name="myFile">
$uploadedFileData = Flight::request()->getUploadedFiles();
$uploadedFile = $uploadedFileData['myFile'];
$uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
});
Jika Anda memiliki beberapa file yang diunggah, Anda dapat melooping melalui mereka:
Flight::route('POST /upload', function(){
// Jika Anda memiliki field input seperti <input type="file" name="myFiles[]">
$uploadedFiles = Flight::request()->getUploadedFiles()['myFiles'];
foreach ($uploadedFiles as $uploadedFile) {
$uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
}
});
Catatan Keamanan: Selalu validasi dan sanitasi input pengguna, terutama saat menangani unggahan file. Selalu validasi jenis ekstensi yang akan diizinkan diunggah, tetapi Anda juga harus memvalidasi "magic bytes" file untuk memastikan itu benar-benar jenis file yang diklaim pengguna. Ada artikel dan library yang tersedia untuk membantu dengan ini.
Body Permintaan
Untuk mendapatkan body permintaan HTTP mentah, misalnya saat menangani permintaan POST/PUT, Anda dapat melakukan:
Flight::route('POST /users/xml', function(){
$xmlBody = Flight::request()->getBody();
// lakukan sesuatu dengan XML yang dikirim.
});
Body JSON
Jika Anda menerima permintaan dengan jenis konten application/json dan data contoh {"id": 123}
itu akan tersedia dari properti data:
$id = Flight::request()->data->id;
Header Permintaan
Anda dapat mengakses header permintaan menggunakan metode getHeader() atau getHeaders():
// Mungkin Anda membutuhkan header Authorization
$host = Flight::request()->getHeader('Authorization');
// atau
$host = Flight::request()->header('Authorization');
// Jika Anda perlu mengambil semua header
$headers = Flight::request()->getHeaders();
// atau
$headers = Flight::request()->headers();
Metode Permintaan
Anda dapat mengakses metode permintaan menggunakan properti method atau metode getMethod():
$method = Flight::request()->method; // sebenarnya diisi oleh getMethod()
$method = Flight::request()->getMethod();
Catatan: Metode getMethod() pertama-tama menarik metode dari $_SERVER['REQUEST_METHOD'], kemudian dapat ditimpa
oleh $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] jika ada atau $_REQUEST['_method'] jika ada.
Properti Objek Permintaan
Objek permintaan menyediakan properti berikut:
- body - Body permintaan HTTP mentah
- url - URL yang diminta
- base - Subdirektori induk dari URL
- method - Metode permintaan (GET, POST, PUT, DELETE)
- referrer - URL referrer
- ip - Alamat IP klien
- ajax - Apakah permintaan adalah permintaan AJAX
- scheme - Protokol server (http, https)
- user_agent - Informasi peramban
- type - Jenis konten
- length - Panjang konten
- query - Parameter string query
- data - Data POST atau data JSON
- cookies - Data cookie
- files - File yang diunggah
- secure - Apakah koneksi aman
- accept - Parameter accept HTTP
- proxy_ip - Alamat IP proxy klien. Memindai array
$_SERVERuntukHTTP_CLIENT_IP,HTTP_X_FORWARDED_FOR,HTTP_X_FORWARDED,HTTP_X_CLUSTER_CLIENT_IP,HTTP_FORWARDED_FOR,HTTP_FORWARDEDdalam urutan itu. - host - Nama host permintaan
- servername - SERVER_NAME dari
$_SERVER
Metode Bantu
Ada beberapa metode bantu untuk menyusun bagian-bagian URL, atau menangani header tertentu.
URL Lengkap
Anda dapat mengakses URL permintaan lengkap menggunakan metode getFullUrl():
$url = Flight::request()->getFullUrl();
// https://example.com/some/path?foo=bar
URL Dasar
Anda dapat mengakses URL dasar menggunakan metode getBaseUrl():
// http://example.com/path/to/something/cool?query=yes+thanks
$url = Flight::request()->getBaseUrl();
// https://example.com
// Perhatikan, tidak ada slash akhir.
Parsing Query
Anda dapat meneruskan URL ke metode parseQuery() untuk mem-parsing string query menjadi array asosiatif:
$query = Flight::request()->parseQuery('https://example.com/some/path?foo=bar');
// ['foo' => 'bar']
Negosiasi Jenis Konten Accept
v3.17.2
Anda dapat menggunakan metode negotiateContentType() untuk menentukan jenis konten terbaik untuk merespons berdasarkan header Accept yang dikirim oleh klien.
// Contoh header Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
// Yang di bawah ini mendefinisikan apa yang Anda dukung.
$availableTypes = ['application/json', 'application/xml'];
$typeToServe = Flight::request()->negotiateContentType($availableTypes);
if ($typeToServe === 'application/json') {
// Layani respons JSON
} elseif ($typeToServe === 'application/xml') {
// Layani respons XML
} else {
// Default ke sesuatu yang lain atau lempar kesalahan
}
Catatan: Jika tidak ada jenis yang tersedia ditemukan dalam header
Accept, metode akan mengembalikannull. Jika tidak ada headerAcceptyang didefinisikan, metode akan mengembalikan jenis pertama dalam array$availableTypes.
Lihat Juga
- Routing - Lihat cara memetakan rute ke controller dan merender tampilan.
- Responses - Cara menyesuaikan respons HTTP.
- Why a Framework? - Bagaimana permintaan cocok ke dalam gambaran besar.
- Collections - Bekerja dengan kumpulan data.
- Uploaded File Handler - Menangani unggahan file.
Pemecahan Masalah
request()->ipdanrequest()->proxy_ipbisa berbeda jika webserver Anda berada di belakang proxy, load balancer, dll.
Changelog
- v3.17.2 - Menambahkan negotiateContentType()
- v3.12.0 - Menambahkan kemampuan menangani unggahan file melalui objek request.
- v1.0 - Rilis awal.
Learn/why_frameworks
Mengapa Kerangka?
Beberapa pemrogram sangat menentang penggunaan kerangka. Mereka berpendapat bahwa kerangka itu berlebihan, lambat, dan sulit dipelajari. Mereka mengatakan bahwa kerangka tidak diperlukan dan bahwa Anda dapat menulis kode yang lebih baik tanpa mereka. Tentu ada beberapa poin yang valid mengenai kekurangan menggunakan kerangka. Namun, ada juga banyak keuntungan dalam menggunakan kerangka.
Alasan untuk Menggunakan Kerangka
Berikut adalah beberapa alasan mengapa Anda mungkin ingin mempertimbangkan untuk menggunakan kerangka:
- Pengembangan Cepat: Kerangka menyediakan banyak fungsionalitas langsung dari kotaknya. Ini berarti Anda dapat membangun aplikasi web lebih cepat. Anda tidak perlu menulis sebanyak itu karena kerangka menyediakan banyak fungsionalitas yang Anda butuhkan.
- Konsistensi: Kerangka menyediakan cara yang konsisten untuk melakukan hal-hal. Ini memudahkan Anda untuk memahami cara kerja kode dan memudahkan pengembang lain untuk memahami kode Anda. Jika Anda memiliki skrip demi skrip, Anda mungkin kehilangan konsistensi antara skrip, terutama jika Anda bekerja dengan tim pengembang.
- Keamanan: Kerangka menyediakan fitur keamanan yang membantu melindungi aplikasi web Anda dari ancaman keamanan umum. Ini berarti Anda tidak perlu khawatir sebanyak itu tentang keamanan karena kerangka mengurus banyak hal untuk Anda.
- Komunitas: Kerangka memiliki komunitas besar pengembang yang berkontribusi pada kerangka. Ini berarti Anda dapat mendapatkan bantuan dari pengembang lain ketika Anda memiliki pertanyaan atau masalah. Ini juga berarti bahwa ada banyak sumber daya yang tersedia untuk membantu Anda belajar cara menggunakan kerangka.
- Praktik Terbaik: Kerangka dibangun menggunakan praktik terbaik. Ini berarti Anda dapat belajar dari kerangka dan menggunakan praktik terbaik yang sama dalam kode Anda sendiri. Ini dapat membantu Anda menjadi pemrogram yang lebih baik. Kadang-kadang Anda tidak tahu apa yang tidak Anda ketahui dan itu dapat merugikan Anda pada akhirnya.
- Ekstensibilitas: Kerangka dirancang untuk diperluas. Ini berarti Anda dapat menambahkan fungsionalitas Anda sendiri ke dalam kerangka. Ini memungkinkan Anda untuk membangun aplikasi web yang disesuaikan dengan kebutuhan spesifik Anda.
Flight adalah micro-framework. Ini berarti bahwa ia kecil dan ringan. Ia tidak menyediakan sebanyak fungsionalitas seperti kerangka besar seperti Laravel atau Symfony. Namun, ia menyediakan banyak fungsionalitas yang Anda butuhkan untuk membangun aplikasi web. Ini juga mudah dipelajari dan digunakan. Ini membuatnya menjadi pilihan yang baik untuk membangun aplikasi web dengan cepat dan mudah. Jika Anda baru mengenal kerangka, Flight adalah kerangka pemula yang hebat untuk mulai digunakan. Ini akan membantu Anda belajar tentang keuntungan menggunakan kerangka tanpa membebani Anda dengan terlalu banyak kompleksitas. Setelah Anda memiliki beberapa pengalaman dengan Flight, akan lebih mudah untuk beralih ke kerangka yang lebih kompleks seperti Laravel atau Symfony, namun Flight masih dapat membuat aplikasi yang berhasil dan tangguh.
Apa itu Routing?
Routing adalah inti dari kerangka Flight, tetapi apa itu sebenarnya? Routing adalah proses mengambil URL dan mencocokkannya dengan fungsi tertentu di kode Anda.
Inilah cara Anda dapat membuat situs web Anda melakukan hal-hal yang berbeda berdasarkan URL yang diminta. Misalnya, Anda mungkin ingin menampilkan profil pengguna ketika mereka
mengunjungi /user/1234, tetapi menampilkan daftar semua pengguna ketika mereka mengunjungi /users. Semua ini dilakukan melalui routing.
Ini mungkin bekerja seperti ini:
- Seorang pengguna pergi ke peramban Anda dan mengetik
http://example.com/user/1234. - Server menerima permintaan dan melihat URL dan meneruskannya ke kode aplikasi Flight Anda.
- Katakanlah di kode Flight Anda Anda memiliki sesuatu seperti
Flight::route('/user/@id', [ 'UserController', 'viewUserProfile' ]);. Kode aplikasi Flight Anda melihat URL dan melihat bahwa itu cocok dengan jalur yang telah Anda definisikan, dan kemudian menjalankan kode yang telah Anda definisikan untuk jalur tersebut. - Router Flight kemudian akan dijalankan dan memanggil metode
viewUserProfile($id)dalam kelasUserController, dengan melewatkan1234sebagai argumen$iddalam metode tersebut. - Kode dalam metode
viewUserProfile()Anda kemudian akan dijalankan dan melakukan apa yang telah Anda katakan untuk dilakukan. Anda mungkin akan mengeluarkan beberapa HTML untuk halaman profil pengguna, atau jika ini adalah API RESTful, Anda mungkin akan mengeluarkan respons JSON dengan informasi pengguna. - Flight membungkus ini dengan rapi, menghasilkan header respons dan mengirimkannya kembali ke peramban pengguna.
- Pengguna merasa senang dan memberi diri mereka pelukan hangat!
Dan Mengapa Ini Penting?
Memiliki router terpusat yang baik sebenarnya dapat membuat hidup Anda jauh lebih mudah! Ini mungkin sulit dilihat pada awalnya. Berikut adalah beberapa alasan mengapa:
- Routing Terpusat: Anda dapat menyimpan semua jalur Anda di satu tempat. Ini memudahkan untuk melihat jalur mana yang Anda miliki dan apa yang mereka lakukan. Ini juga memudahkan untuk mengubahnya jika Anda perlu.
- Parameter Jalur: Anda dapat menggunakan parameter jalur untuk melewatkan data ke metode jalur Anda. Ini adalah cara yang bagus untuk menjaga kode Anda tetap bersih dan teratur.
- Kelompok Jalur: Anda dapat mengelompokkan jalur bersama. Ini bagus untuk menjaga kode Anda teratur dan untuk menerapkan middleware ke sekelompok jalur.
- Alias Jalur: Anda dapat menetapkan alias ke sebuah jalur, sehingga URL dapat dibuat secara dinamis nanti di kode Anda (seperti template misalnya). Contoh: alih-alih menghardcode
/user/1234di kode Anda, Anda bisa merujuk ke aliasuser_viewdan melewatkanidsebagai parameter. Ini sangat memudahkan jika Anda memutuskan untuk mengubahnya menjadi/admin/user/1234nanti. Anda tidak perlu mengubah semua URL yang Anda hardcode, cukup URL yang terhubung ke jalur. - Middleware Jalur: Anda dapat menambahkan middleware ke jalur Anda. Middleware sangat kuat dalam menambahkan perilaku tertentu ke aplikasi Anda, seperti mengotentikasi bahwa pengguna tertentu dapat mengakses jalur atau kelompok jalur.
Saya yakin Anda sudah familiar dengan cara skrip demi skrip untuk membuat situs web. Anda mungkin memiliki file bernama index.php yang memiliki banyak pernyataan if
untuk memeriksa URL dan kemudian menjalankan fungsi tertentu berdasarkan URL tersebut. Ini adalah bentuk routing, tetapi tidak sangat teratur dan dapat
menjadi tidak terkendali dengan cepat. Sistem routing Flight adalah cara yang jauh lebih teratur dan kuat untuk menangani routing.
Ini?
// /user/view_profile.php?id=1234
if ($_GET['id']) {
$id = $_GET['id'];
viewUserProfile($id);
}
// /user/edit_profile.php?id=1234
if ($_GET['id']) {
$id = $_GET['id'];
editUserProfile($id);
}
// dll...
Atau ini?
// index.php
Flight::route('/user/@id', [ 'UserController', 'viewUserProfile' ]);
Flight::route('/user/@id/edit', [ 'UserController', 'editUserProfile' ]);
// Mungkin di dalam app/controllers/UserController.php Anda
class UserController {
public function viewUserProfile($id) {
// lakukan sesuatu
}
public function editUserProfile($id) {
// lakukan sesuatu
}
}
Semoga Anda mulai melihat manfaat menggunakan sistem routing terpusat. Ini jauh lebih mudah untuk dikelola dan dipahami dalam jangka panjang!
Permintaan dan Respons
Flight menyediakan cara yang sederhana dan mudah untuk menangani permintaan dan respons. Ini adalah inti dari apa yang dilakukan kerangka web. Ini menerima permintaan dari peramban pengguna, memprosesnya, dan kemudian mengirim kembali respons. Ini adalah cara Anda dapat membangun aplikasi web yang melakukan hal-hal seperti menampilkan profil pengguna, memungkinkan pengguna masuk, atau memungkinkan pengguna membuat posting blog baru.
Permintaan
Permintaan adalah apa yang dikirim peramban pengguna ke server Anda ketika mereka mengunjungi situs web Anda. Permintaan ini mengandung informasi tentang apa yang ingin dilakukan pengguna. Misalnya, mungkin berisi informasi tentang URL apa yang ingin dikunjungi pengguna, data apa yang ingin dikirim pengguna ke server Anda, atau jenis data apa yang ingin diterima pengguna dari server Anda. Penting untuk diketahui bahwa permintaan bersifat read-only. Anda tidak dapat mengubah permintaan, tetapi Anda dapat membacanya.
Flight menyediakan cara yang sederhana untuk mengakses informasi tentang permintaan tersebut. Anda dapat mengakses informasi tentang permintaan menggunakan metode Flight::request()
. Metode ini mengembalikan objek Request yang berisi informasi tentang permintaan. Anda dapat menggunakan objek ini untuk mengakses informasi tentang
permintaan, seperti URL, metode, atau data yang dikirim pengguna ke server Anda.
Respons
Respons adalah apa yang dikirim server Anda kembali ke peramban pengguna ketika mereka mengunjungi situs web Anda. Respons ini berisi informasi tentang apa yang ingin dilakukan server Anda. Misalnya, mungkin berisi informasi tentang jenis data apa yang ingin dikirim server Anda kepada pengguna, jenis data apa yang ingin diterima server Anda dari pengguna, atau jenis data apa yang ingin disimpan server Anda di komputer pengguna.
Flight menyediakan cara yang sederhana untuk mengirim respons ke peramban pengguna. Anda dapat mengirim respons menggunakan metode Flight::response(). Metode ini
mengambil objek Response sebagai argumen dan mengirimkan respons ke peramban pengguna. Anda dapat menggunakan objek ini untuk mengirim respons kepada peramban pengguna,
seperti HTML, JSON, atau file. Flight membantu Anda secara otomatis menghasilkan beberapa bagian dari respons untuk mempermudah, tetapi pada akhirnya Anda memiliki
kendali atas apa yang Anda kirim kembali kepada pengguna.
Learn/responses
Respons
Gambaran Umum
Flight membantu menghasilkan sebagian header respons untuk Anda, tetapi Anda memegang sebagian besar kendali atas apa yang Anda kirim kembali ke pengguna. Sebagian besar waktu Anda akan mengakses objek response() secara langsung, tetapi Flight memiliki beberapa metode pembantu untuk mengatur beberapa header respons untuk Anda.
Pemahaman
Setelah pengguna mengirimkan permintaan mereka ke aplikasi Anda, Anda perlu menghasilkan respons yang tepat untuk mereka. Mereka telah mengirimkan informasi seperti bahasa yang mereka sukai, apakah mereka dapat menangani jenis kompresi tertentu, agen pengguna mereka, dll., dan setelah memproses semuanya, saatnya mengirimkan respons yang tepat kembali kepada mereka. Ini bisa berupa pengaturan header, mengeluarkan body HTML atau JSON untuk mereka, atau mengarahkan mereka ke halaman.
Penggunaan Dasar
Mengirim Body Respons
Flight menggunakan ob_start() untuk membuffer output. Ini berarti Anda dapat menggunakan echo atau print untuk mengirim respons ke pengguna dan Flight akan menangkapnya dan mengirimkannya kembali ke pengguna dengan header yang sesuai.
// Ini akan mengirim "Hello, World!" ke browser pengguna
Flight::route('/', function() {
echo "Hello, World!";
});
// HTTP/1.1 200 OK
// Content-Type: text/html
//
// Hello, World!
Sebagai alternatif, Anda dapat memanggil metode write() untuk menambahkan ke body juga.
// Ini akan mengirim "Hello, World!" ke browser pengguna
Flight::route('/', function() {
// verbose, tapi kadang-kadang diperlukan saat Anda membutuhkannya
Flight::response()->write("Hello, World!");
// jika Anda ingin mengambil body yang telah Anda atur pada titik ini
// Anda bisa melakukannya seperti ini
$body = Flight::response()->getBody();
});
JSON
Flight menyediakan dukungan untuk mengirim respons JSON dan JSONP. Untuk mengirim respons JSON, Anda meneruskan beberapa data yang akan dikodekan JSON:
Flight::route('/@companyId/users', function(int $companyId) {
// entah bagaimana ambil pengguna Anda dari database misalnya
$users = Flight::db()->fetchAll("SELECT id, first_name, last_name FROM users WHERE company_id = ?", [ $companyId ]);
Flight::json($users);
});
// [{"id":1,"first_name":"Bob","last_name":"Jones"}, /* more users */ ]
Catatan: Secara default, Flight akan mengirim header
Content-Type: application/jsondengan respons. Ini juga akan menggunakan flagJSON_THROW_ON_ERRORdanJSON_UNESCAPED_SLASHESsaat mengkodekan JSON.
JSON dengan Kode Status
Anda juga dapat meneruskan kode status sebagai argumen kedua:
Flight::json(['id' => 123], 201);
JSON dengan Pretty Print
Anda juga dapat meneruskan argumen ke posisi terakhir untuk mengaktifkan pretty printing:
Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);
Mengubah Urutan Argumen JSON
Flight::json() adalah metode yang sangat lama, tetapi tujuan Flight adalah mempertahankan kompatibilitas mundur
untuk proyek. Sebenarnya sangat sederhana jika Anda ingin mengubah urutan argumen untuk menggunakan sintaks yang lebih sederhana,
Anda hanya perlu memetakan ulang metode JSON seperti metode Flight lainnya:
Flight::map('json', function($data, $code = 200, $options = 0) {
// sekarang Anda tidak perlu `true, 'utf-8'` saat menggunakan metode json()!
Flight::_json($data, $code, true, 'utf-8', $options);
}
// Dan sekarang bisa digunakan seperti ini
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);
JSON dan Menghentikan Eksekusi
v3.10.0
Jika Anda ingin mengirim respons JSON dan menghentikan eksekusi, Anda dapat menggunakan metode jsonHalt().
Ini berguna untuk kasus di mana Anda memeriksa mungkin jenis otorisasi tertentu dan jika
pengguna tidak diotorisasi, Anda dapat mengirim respons JSON segera, membersihkan konten body
yang ada dan menghentikan eksekusi.
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// Periksa apakah pengguna diotorisasi
if($authorized === false) {
Flight::jsonHalt(['error' => 'Unauthorized'], 401);
// no exit; needed here.
}
// Lanjutkan dengan sisa route
});
Sebelum v3.10.0, Anda harus melakukan sesuatu seperti ini:
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// Periksa apakah pengguna diotorisasi
if($authorized === false) {
Flight::halt(401, json_encode(['error' => 'Unauthorized']));
}
// Lanjutkan dengan sisa route
});
Membersihkan Body Respons
Jika Anda ingin membersihkan body respons, Anda dapat menggunakan metode clearBody:
Flight::route('/', function() {
if($someCondition) {
Flight::response()->write("Hello, World!");
} else {
Flight::response()->clearBody();
}
});
Kasus penggunaan di atas mungkin tidak umum, namun bisa lebih umum jika ini digunakan dalam middleware.
Menjalankan Callback pada Body Respons
Anda dapat menjalankan callback pada body respons dengan menggunakan metode addResponseBodyCallback:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
});
// Ini akan mengompresi gzip semua respons untuk route apa pun
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
Anda dapat menambahkan beberapa callback dan mereka akan dijalankan dalam urutan yang ditambahkan. Karena ini dapat menerima callable apa pun, ini dapat menerima array kelas [ $class, 'method' ], closure $strReplace = function($body) { str_replace('hi', 'there', $body); };, atau nama fungsi 'minify' jika Anda memiliki fungsi untuk meminify kode html Anda misalnya.
Catatan: Callback route tidak akan bekerja jika Anda menggunakan opsi konfigurasi flight.v2.output_buffering.
Callback Route Spesifik
Jika Anda ingin ini hanya berlaku untuk route spesifik, Anda dapat menambahkan callback di route itu sendiri:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
// Ini akan mengompresi gzip hanya respons untuk route ini
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
});
Opsi Middleware
Anda juga dapat menggunakan middleware untuk menerapkan callback ke semua route melalui middleware:
// MinifyMiddleware.php
class MinifyMiddleware {
public function before() {
// Terapkan callback di sini pada objek response().
Flight::response()->addResponseBodyCallback(function($body) {
return $this->minify($body);
});
}
protected function minify(string $body): string {
// minify body entah bagaimana
return $body;
}
}
// index.php
Flight::group('/users', function() {
Flight::route('', function() { /* ... */ });
Flight::route('/@id', function($id) { /* ... */ });
}, [ new MinifyMiddleware() ]);
Kode Status
Anda dapat mengatur kode status respons dengan menggunakan metode status:
Flight::route('/@id', function($id) {
if($id == 123) {
Flight::response()->status(200);
echo "Hello, World!";
} else {
Flight::response()->status(403);
echo "Forbidden";
}
});
Jika Anda ingin mendapatkan kode status saat ini, Anda dapat menggunakan metode status tanpa argumen apa pun:
Flight::response()->status(); // 200
Mengatur Header Respons
Anda dapat mengatur header seperti tipe konten respons dengan menggunakan metode header:
// Ini akan mengirim "Hello, World!" ke browser pengguna dalam teks biasa
Flight::route('/', function() {
Flight::response()->header('Content-Type', 'text/plain');
// atau
Flight::response()->setHeader('Content-Type', 'text/plain');
echo "Hello, World!";
});
Redirect
Anda dapat mengarahkan ulang permintaan saat ini dengan menggunakan metode redirect() dan meneruskan
URL baru:
Flight::route('/login', function() {
$username = Flight::request()->data->username;
$password = Flight::request()->data->password;
$passwordConfirm = Flight::request()->data->password_confirm;
if($password !== $passwordConfirm) {
Flight::redirect('/new/location');
return; // ini diperlukan agar fungsionalitas di bawah tidak dieksekusi
}
// tambahkan pengguna baru...
Flight::db()->runQuery("INSERT INTO users ....");
Flight::redirect('/admin/dashboard');
});
Catatan: Secara default Flight mengirim kode status HTTP 303 ("See Other"). Anda dapat secara opsional mengatur kode kustom:
Flight::redirect('/new/location', 301); // permanen
Menghentikan Eksekusi Route
Anda dapat menghentikan framework dan segera keluar pada titik mana pun dengan memanggil metode halt:
Flight::halt();
Anda juga dapat menentukan kode status HTTP dan pesan opsional:
Flight::halt(200, 'Be right back...');
Memanggil halt akan membuang konten respons apa pun hingga titik itu dan menghentikan semua eksekusi.
Jika Anda ingin menghentikan framework dan mengeluarkan respons saat ini, gunakan metode stop:
Flight::stop($httpStatusCode = null);
Catatan:
Flight::stop()memiliki perilaku aneh seperti itu akan mengeluarkan respons tetapi melanjutkan eksekusi skrip Anda yang mungkin bukan yang Anda inginkan. Anda dapat menggunakanexitataureturnsetelah memanggilFlight::stop()untuk mencegah eksekusi lebih lanjut, tetapi umumnya disarankan untuk menggunakanFlight::halt().
Ini akan menyimpan kunci dan nilai header ke objek respons. Pada akhir siklus hidup permintaan ini akan membangun header dan mengirim respons.
Penggunaan Lanjutan
Mengirim Header Segera
Mungkin ada saat-saat ketika Anda perlu melakukan sesuatu yang kustom dengan header dan Anda perlu mengirim header
pada baris kode yang sama yang Anda kerjakan. Jika Anda mengatur route yang di-stream,
ini yang Anda butuhkan. Itu dapat dicapai melalui response()->setRealHeader().
Flight::route('/', function() {
Flight::response()->setRealHeader('Content-Type: text/plain');
echo 'Streaming response...';
sleep(5);
echo 'Done!';
})->stream();
JSONP
Untuk permintaan JSONP, Anda dapat secara opsional meneruskan nama parameter query yang Anda gunakan untuk mendefinisikan fungsi callback Anda:
Flight::jsonp(['id' => 123], 'q');
Jadi, saat membuat permintaan GET menggunakan ?q=my_func, Anda seharusnya menerima output:
my_func({"id":123});
Jika Anda tidak meneruskan nama parameter query, itu akan default ke jsonp.
Catatan: Jika Anda masih menggunakan permintaan JSONP pada 2025 dan seterusnya, lompat ke chat dan beri tahu kami mengapa! Kami suka mendengar cerita pertempuran/horor yang bagus!
Membersihkan Data Respons
Anda dapat membersihkan body respons dan header dengan menggunakan metode clear(). Ini akan membersihkan
header apa pun yang ditetapkan ke respons, membersihkan body respons, dan mengatur kode status ke 200.
Flight::response()->clear();
Membersihkan Hanya Body Respons
Jika Anda hanya ingin membersihkan body respons, Anda dapat menggunakan metode clearBody():
// Ini masih akan mempertahankan header apa pun yang diatur pada objek response().
Flight::response()->clearBody();
Penyimpanan Cache HTTP
Flight menyediakan dukungan bawaan untuk caching tingkat HTTP. Jika kondisi caching
terpenuhi, Flight akan mengembalikan respons HTTP 304 Not Modified. Saat berikutnya klien
meminta sumber daya yang sama, mereka akan diminta untuk menggunakan versi cache lokal mereka.
Caching Tingkat Route
Jika Anda ingin menyimpan cache seluruh respons Anda, Anda dapat menggunakan metode cache() dan meneruskan waktu untuk cache.
// Ini akan menyimpan cache respons selama 5 menit
Flight::route('/news', function () {
Flight::response()->cache(time() + 300);
echo 'This content will be cached.';
});
// Alternatifnya, Anda dapat menggunakan string yang akan Anda teruskan
// ke metode strtotime()
Flight::route('/news', function () {
Flight::response()->cache('+5 minutes');
echo 'This content will be cached.';
});
Last-Modified
Anda dapat menggunakan metode lastModified dan meneruskan timestamp UNIX untuk mengatur tanggal
dan waktu halaman terakhir dimodifikasi. Klien akan terus menggunakan cache mereka hingga
nilai last modified berubah.
Flight::route('/news', function () {
Flight::lastModified(1234567890);
echo 'This content will be cached.';
});
ETag
Caching ETag mirip dengan Last-Modified, kecuali Anda dapat menentukan id apa pun yang
Anda inginkan untuk sumber daya:
Flight::route('/news', function () {
Flight::etag('my-unique-id');
echo 'This content will be cached.';
});
Ingatlah bahwa memanggil lastModified atau etag akan sama-sama mengatur dan memeriksa
nilai cache. Jika nilai cache sama antara permintaan, Flight akan segera
mengirim respons HTTP 304 dan menghentikan pemrosesan.
Mengunduh File
v3.12.0
Ada metode pembantu untuk streaming file ke pengguna akhir. Anda dapat menggunakan metode download dan meneruskan path.
Flight::route('/download', function () {
Flight::download('/path/to/file.txt');
// Mulai v3.17.1 Anda dapat menentukan nama file kustom untuk unduhan
Flight::download('/path/to/file.txt', 'custom_name.txt');
});
Lihat Juga
- Routing - Cara memetakan route ke controller dan merender view.
- Requests - Memahami cara menangani permintaan masuk.
- Middleware - Menggunakan middleware dengan route untuk autentikasi, logging, dll.
- Mengapa Framework? - Memahami manfaat menggunakan framework seperti Flight.
- Extending - Cara memperluas Flight dengan fungsionalitas Anda sendiri.
Pemecahan Masalah
- Jika Anda mengalami masalah dengan redirect yang tidak bekerja, pastikan Anda menambahkan
return;ke metode. stop()danhalt()bukan hal yang sama.halt()akan menghentikan eksekusi segera, sementarastop()akan memungkinkan eksekusi berlanjut.
Changelog
- v3.17.1 - Menambahkan
$fileNameke metodedownloadFile(). - v3.12.0 - Menambahkan metode pembantu downloadFile.
- v3.10.0 - Menambahkan
jsonHalt. - v1.0 - Rilis awal.
Learn/events
Pengelola Acara
sejak v3.15.0
Gambaran Umum
Acara memungkinkan Anda mendaftarkan dan memicu perilaku khusus dalam aplikasi Anda. Dengan penambahan Flight::onEvent() dan Flight::triggerEvent(), Anda sekarang dapat menghubungkan ke momen kunci dalam siklus hidup aplikasi Anda atau mendefinisikan acara Anda sendiri (seperti notifikasi dan email) untuk membuat kode Anda lebih modular dan dapat diperluas. Metode-metode ini adalah bagian dari metode yang dapat dipetakan milik Flight, yang berarti Anda dapat menimpa perilakunya sesuai kebutuhan Anda.
Pemahaman
Acara memungkinkan Anda memisahkan berbagai bagian aplikasi Anda sehingga mereka tidak terlalu bergantung satu sama lain. Pemisahan ini—sering disebut decoupling—membuat kode Anda lebih mudah untuk diperbarui, diperluas, atau di-debug. Alih-alih menulis semuanya dalam satu blok besar, Anda dapat membagi logika Anda menjadi potongan-potongan kecil yang independen yang merespons tindakan tertentu (acara).
Bayangkan Anda sedang membangun aplikasi blog:
- Ketika pengguna memposting komentar, Anda mungkin ingin:
- Menyimpan komentar ke database.
- Mengirim email ke pemilik blog.
- Mencatat tindakan untuk keamanan.
Tanpa acara, Anda akan memasukkan semuanya ke dalam satu fungsi. Dengan acara, Anda dapat membaginya: satu bagian menyimpan komentar, bagian lain memicu acara seperti 'comment.posted', dan pendengar terpisah menangani email dan pencatatan. Ini membuat kode Anda lebih bersih dan memungkinkan Anda menambahkan atau menghapus fitur (seperti notifikasi) tanpa menyentuh logika inti.
Kasus Penggunaan Umum
Untuk sebagian besar, acara bagus untuk hal-hal yang opsional, tetapi bukan bagian inti mutlak dari sistem Anda. Misalnya, berikut adalah hal-hal yang baik untuk dimiliki tetapi jika mereka gagal karena alasan tertentu, aplikasi Anda masih harus berfungsi:
- Pencatatan: Mencatat tindakan seperti login atau kesalahan tanpa mengacaukan kode utama Anda.
- Notifikasi: Mengirim email atau peringatan ketika sesuatu terjadi.
- Pembaruan Cache: Memperbarui cache atau memberi tahu sistem lain tentang perubahan.
Namun, katakanlah Anda memiliki fitur lupa kata sandi. Itu harus menjadi bagian dari fungsionalitas inti Anda dan bukan acara karena jika email itu tidak terkirim, pengguna Anda tidak dapat mereset kata sandi mereka dan menggunakan aplikasi Anda.
Penggunaan Dasar
Sistem acara Flight dibangun di sekitar dua metode utama: Flight::onEvent() untuk mendaftarkan pendengar acara dan Flight::triggerEvent() untuk memicu acara. Berikut adalah cara Anda dapat menggunakannya:
Mendaftarkan Pendengar Acara
Untuk mendengarkan acara, gunakan Flight::onEvent(). Metode ini memungkinkan Anda mendefinisikan apa yang harus terjadi ketika acara terjadi.
Flight::onEvent(string $event, callable $callback): void
$event: Nama untuk acara Anda (misalnya,'user.login').$callback: Fungsi yang dijalankan ketika acara dipicu.
Anda "berlangganan" ke acara dengan memberi tahu Flight apa yang harus dilakukan ketika itu terjadi. Callback dapat menerima argumen yang diteruskan dari pemicu acara.
Sistem acara Flight bersifat sinkron, yang berarti setiap pendengar acara dieksekusi secara berurutan, satu demi satu. Ketika Anda memicu acara, semua pendengar yang terdaftar untuk acara itu akan berjalan hingga selesai sebelum kode Anda melanjutkan. Ini penting untuk dipahami karena berbeda dari sistem acara asinkron di mana pendengar mungkin berjalan secara paralel atau pada waktu yang kemudian.
Contoh Sederhana
Flight::onEvent('user.login', function ($username) {
echo "Selamat datang kembali, $username!";
// Anda dapat mengirim email jika login dari lokasi baru
});
Di sini, ketika acara 'user.login' dipicu, itu akan menyapa pengguna dengan nama dan juga dapat menyertakan logika untuk mengirim email jika diperlukan.
Catatan: Callback dapat berupa fungsi, fungsi anonim, atau metode dari kelas.
Memicu Acara
Untuk membuat acara terjadi, gunakan Flight::triggerEvent(). Ini memberi tahu Flight untuk menjalankan semua pendengar yang terdaftar untuk acara itu, sambil meneruskan data apa pun yang Anda berikan.
Flight::triggerEvent(string $event, ...$args): void
$event: Nama acara yang Anda picu (harus cocok dengan acara yang terdaftar)....$args: Argumen opsional untuk dikirim ke pendengar (bisa berupa jumlah argumen berapa pun).
Contoh Sederhana
$username = 'alice';
Flight::triggerEvent('user.login', $username);
Ini memicu acara 'user.login' dan mengirim 'alice' ke pendengar yang kita definisikan sebelumnya, yang akan menghasilkan: Selamat datang kembali, alice!.
- Jika tidak ada pendengar yang terdaftar, tidak ada yang terjadi—aplikasi Anda tidak akan rusak.
- Gunakan operator spread (
...) untuk meneruskan beberapa argumen secara fleksibel.
Menghentikan Acara
Jika pendengar mengembalikan false, tidak ada pendengar tambahan untuk acara itu yang akan dieksekusi. Ini memungkinkan Anda menghentikan rantai acara berdasarkan kondisi tertentu. Ingat, urutan pendengar penting, karena yang pertama mengembalikan false akan menghentikan yang lainnya dari berjalan.
Contoh:
Flight::onEvent('user.login', function ($username) {
if (isBanned($username)) {
logoutUser($username);
return false; // Menghentikan pendengar selanjutnya
}
});
Flight::onEvent('user.login', function ($username) {
sendWelcomeEmail($username); // ini tidak pernah dikirim
});
Menimpa Metode Acara
Flight::onEvent() dan Flight::triggerEvent() tersedia untuk diperluas, yang berarti Anda dapat mendefinisikan ulang cara kerjanya. Ini bagus untuk pengguna lanjutan yang ingin menyesuaikan sistem acara, seperti menambahkan pencatatan atau mengubah cara acara didistribusikan.
Contoh: Menyesuaikan onEvent
Flight::map('onEvent', function (string $event, callable $callback) {
// Catat setiap pendaftaran acara
error_log("Pendengar acara baru ditambahkan untuk: $event");
// Panggil perilaku default (asumsi sistem acara internal)
Flight::_onEvent($event, $callback);
});
Sekarang, setiap kali Anda mendaftarkan acara, itu akan mencatatnya sebelum melanjutkan.
Mengapa Menimpa?
- Tambahkan debugging atau pemantauan.
- Batasi acara di lingkungan tertentu (misalnya, nonaktifkan saat pengujian).
- Integrasikan dengan pustaka acara yang berbeda.
Di Mana Menempatkan Acara Anda
Jika Anda baru dengan konsep acara di proyek Anda, Anda mungkin bertanya-tanya: di mana saya mendaftarkan semua acara ini di aplikasi saya? Kesederhanaan Flight berarti tidak ada aturan ketat—Anda dapat menempatkannya di mana pun yang masuk akal untuk proyek Anda. Namun, menjaga agar mereka terorganisir membantu Anda mempertahankan kode Anda saat aplikasi Anda berkembang. Berikut adalah beberapa opsi praktis dan praktik terbaik, disesuaikan dengan sifat ringan Flight:
Opsi 1: Di File index.php Utama Anda
Untuk aplikasi kecil atau prototipe cepat, Anda dapat mendaftarkan acara langsung di file index.php Anda bersama dengan rute Anda. Ini menjaga semuanya di satu tempat, yang baik ketika kesederhanaan adalah prioritas Anda.
require 'vendor/autoload.php';
// Daftarkan acara
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
});
// Definisikan rute
Flight::route('/login', function () {
$username = 'bob';
Flight::triggerEvent('user.login', $username);
echo "Logged in!";
});
Flight::start();
- Kelebihan: Sederhana, tidak ada file tambahan, bagus untuk proyek kecil.
- Kekurangan: Dapat menjadi berantakan saat aplikasi Anda berkembang dengan lebih banyak acara dan rute.
Opsi 2: File events.php Terpisah
Untuk aplikasi yang sedikit lebih besar, pertimbangkan untuk memindahkan pendaftaran acara ke file khusus seperti app/config/events.php. Sertakan file ini di index.php Anda sebelum rute Anda. Ini meniru cara rute sering diorganisir di app/config/routes.php dalam proyek Flight.
// app/config/events.php
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
});
Flight::onEvent('user.registered', function ($email, $name) {
echo "Email sent to $email: Welcome, $name!";
});
// index.php
require 'vendor/autoload.php';
require 'app/config/events.php';
Flight::route('/login', function () {
$username = 'bob';
Flight::triggerEvent('user.login', $username);
echo "Logged in!";
});
Flight::start();
- Kelebihan: Menjaga
index.phpfokus pada routing, mengorganisir acara secara logis, mudah ditemukan dan diedit. - Kekurangan: Menambahkan sedikit struktur, yang mungkin terasa berlebihan untuk aplikasi sangat kecil.
Opsi 3: Dekat dengan Tempat Mereka Dipicu
Pendekatan lain adalah mendaftarkan acara dekat dengan tempat mereka dipicu, seperti di dalam controller atau definisi rute. Ini bekerja dengan baik jika acara spesifik untuk satu bagian dari aplikasi Anda.
Flight::route('/signup', function () {
// Daftarkan acara di sini
Flight::onEvent('user.registered', function ($email) {
echo "Welcome email sent to $email!";
});
$email = 'jane@example.com';
Flight::triggerEvent('user.registered', $email);
echo "Signed up!";
});
- Kelebihan: Menjaga kode terkait bersama, bagus untuk fitur terisolasi.
- Kekurangan: Menyebarkan pendaftaran acara, membuat lebih sulit untuk melihat semua acara sekaligus; berisiko pendaftaran duplikat jika tidak hati-hati.
Praktik Terbaik untuk Flight
- Mulai Sederhana: Untuk aplikasi kecil, letakkan acara di
index.php. Ini cepat dan selaras dengan minimalisme Flight. - Tumbuh Cerdas: Saat aplikasi Anda berkembang (misalnya, lebih dari 5-10 acara), gunakan file
app/config/events.php. Ini adalah langkah alami, seperti mengorganisir rute, dan menjaga kode Anda rapi tanpa menambahkan framework kompleks. - Hindari Over-Engineering: Jangan buat kelas “pengelola acara” lengkap atau direktori kecuali aplikasi Anda menjadi sangat besar—Flight berkembang dengan kesederhanaan, jadi jaga agar tetap ringan.
Tips: Kelompokkan berdasarkan Tujuan
Di events.php, kelompokkan acara terkait (misalnya, semua acara terkait pengguna bersama) dengan komentar untuk kejelasan:
// app/config/events.php
// Acara Pengguna
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in");
});
Flight::onEvent('user.registered', function ($email) {
echo "Welcome to $email!";
});
// Acara Halaman
Flight::onEvent('page.updated', function ($pageId) {
Flight::cache()->delete("page_$pageId");
});
Struktur ini skalabel dengan baik dan tetap ramah pemula.
Contoh Dunia Nyata
Mari kita jelajahi beberapa skenario dunia nyata untuk menunjukkan bagaimana acara bekerja dan mengapa mereka membantu.
Contoh 1: Mencatat Login Pengguna
// Langkah 1: Daftarkan pendengar
Flight::onEvent('user.login', function ($username) {
$time = date('Y-m-d H:i:s');
error_log("$username logged in at $time");
});
// Langkah 2: Picu di aplikasi Anda
Flight::route('/login', function () {
$username = 'bob'; // Berpura-pura ini berasal dari formulir
Flight::triggerEvent('user.login', $username);
echo "Hi, $username!";
});
Mengapa Berguna: Kode login tidak perlu tahu tentang pencatatan—ia hanya memicu acara. Anda dapat menambahkan lebih banyak pendengar (misalnya, kirim email selamat datang) nanti tanpa mengubah rute.
Contoh 2: Memberi Pemberitahuan Tentang Pengguna Baru
// Pendengar untuk pendaftaran baru
Flight::onEvent('user.registered', function ($email, $name) {
// Simulasikan pengiriman email
echo "Email sent to $email: Welcome, $name!";
});
// Picu ketika seseorang mendaftar
Flight::route('/signup', function () {
$email = 'jane@example.com';
$name = 'Jane';
Flight::triggerEvent('user.registered', $email, $name);
echo "Thanks for signing up!";
});
Mengapa Berguna: Logika pendaftaran fokus pada pembuatan pengguna, sementara acara menangani notifikasi. Anda dapat menambahkan lebih banyak pendengar (misalnya, catat pendaftaran) nanti.
Contoh 3: Membersihkan Cache
// Pendengar untuk membersihkan cache
Flight::onEvent('page.updated', function ($pageId) {
// jika menggunakan plugin flightphp/cache
Flight::cache()->delete("page_$pageId");
echo "Cache cleared for page $pageId.";
});
// Picu ketika halaman diedit
Flight::route('/edit-page/(@id)', function ($pageId) {
// Berpura-pura kita memperbarui halaman
Flight::triggerEvent('page.updated', $pageId);
echo "Page $pageId updated.";
});
Mengapa Berguna: Kode pengeditan tidak peduli dengan caching—ia hanya memberi sinyal pembaruan. Bagian lain dari aplikasi dapat bereaksi sesuai kebutuhan.
Praktik Terbaik
- Berikan Nama Acara dengan Jelas: Gunakan nama spesifik seperti
'user.login'atau'page.updated'sehingga jelas apa yang mereka lakukan. - Jaga Pendengar Sederhana: Jangan letakkan tugas lambat atau kompleks di pendengar—jaga aplikasi Anda tetap cepat.
- Uji Acara Anda: Picu mereka secara manual untuk memastikan pendengar bekerja seperti yang diharapkan.
- Gunakan Acara dengan Bijak: Mereka bagus untuk decoupling, tetapi terlalu banyak dapat membuat kode Anda sulit diikuti—gunakan mereka ketika masuk akal.
Sistem acara di Flight PHP, dengan Flight::onEvent() dan Flight::triggerEvent(), memberi Anda cara sederhana namun kuat untuk membangun aplikasi fleksibel. Dengan membiarkan berbagai bagian aplikasi Anda berbicara satu sama lain melalui acara, Anda dapat menjaga kode Anda terorganisir, dapat digunakan kembali, dan mudah diperluas. Apakah Anda mencatat tindakan, mengirim notifikasi, atau mengelola pembaruan, acara membantu Anda melakukannya tanpa mengacaukan logika Anda. Plus, dengan kemampuan untuk menimpa metode ini, Anda memiliki kebebasan untuk menyesuaikan sistem sesuai kebutuhan Anda. Mulai kecil dengan satu acara, dan lihat bagaimana itu mengubah struktur aplikasi Anda!
Acara Bawaan
Flight PHP dilengkapi dengan beberapa acara bawaan yang dapat Anda gunakan untuk menghubungkan ke siklus hidup framework. Acara ini dipicu pada titik tertentu dalam siklus permintaan/respons, memungkinkan Anda mengeksekusi logika khusus ketika tindakan tertentu terjadi.
Daftar Acara Bawaan
- flight.request.received:
function(Request $request)Dipicu ketika permintaan diterima, diurai, dan diproses. - flight.error:
function(Throwable $exception)Dipicu ketika kesalahan terjadi selama siklus hidup permintaan. - flight.redirect:
function(string $url, int $status_code)Dipicu ketika pengalihan dimulai. - flight.cache.checked:
function(string $cache_key, bool $hit, float $executionTime)Dipicu ketika cache diperiksa untuk kunci tertentu dan apakah cache hit atau miss. - flight.middleware.before:
function(Route $route)Dipicu setelah middleware before dieksekusi. - flight.middleware.after:
function(Route $route)Dipicu setelah middleware after dieksekusi. - flight.middleware.executed:
function(Route $route, $middleware, string $method, float $executionTime)Dipicu setelah middleware apa pun dieksekusi - flight.route.matched:
function(Route $route)Dipicu ketika rute cocok, tetapi belum dijalankan. - flight.route.executed:
function(Route $route, float $executionTime)Dipicu setelah rute dieksekusi dan diproses.$executionTimeadalah waktu yang dibutuhkan untuk mengeksekusi rute (memanggil controller, dll). - flight.view.rendered:
function(string $template_file_path, float $executionTime)Dipicu setelah tampilan dirender.$executionTimeadalah waktu yang dibutuhkan untuk merender template. Catatan: Jika Anda menimpa metoderender, Anda perlu memicu ulang acara ini. - flight.response.sent:
function(Response $response, float $executionTime)Dipicu setelah respons dikirim ke klien.$executionTimeadalah waktu yang dibutuhkan untuk membangun respons.
Lihat Juga
- Memperluas Flight - Cara memperluas dan menyesuaikan fungsionalitas inti Flight.
- Cache - Contoh menggunakan acara untuk membersihkan cache ketika halaman diperbarui.
Pemecahan Masalah
- Jika Anda tidak melihat pendengar acara Anda dipanggil, pastikan Anda mendaftarkannya sebelum memicu acara. Urutan pendaftaran penting.
Changelog
- v3.15.0 - Menambahkan acara ke Flight.
Learn/templates
Tampilan HTML dan Template
Ringkasan
Flight menyediakan beberapa fungsionalitas templating HTML dasar secara bawaan. Templating adalah cara yang sangat efektif untuk memisahkan logika aplikasi Anda dari lapisan presentasi. Mesin khusus (Twig, Latte, dll.) juga memberikan alat bantu pengodean AI sintaks yang familiar dan terbatas sehingga mereka cenderung tidak membuang logika bisnis ke dalam HTML Anda.
Pemahaman
Saat Anda membangun aplikasi, kemungkinan Anda akan memiliki HTML yang ingin Anda kirim kembali ke pengguna akhir. PHP sendiri adalah bahasa templating, tetapi sangat mudah untuk membungkus logika bisnis seperti panggilan database, panggilan API, dll. ke dalam file HTML Anda dan membuat pengujian serta pemisahan menjadi proses yang sangat sulit. Dengan mendorong data ke dalam template dan membiarkan template merender dirinya sendiri, menjadi lebih mudah untuk memisahkan dan menguji unit kode Anda. Anda akan berterima kasih kepada kami jika menggunakan template!
Penggunaan Dasar
Flight memungkinkan Anda untuk mengganti mesin tampilan default cukup dengan memetakan render (atau mendaftarkan kelas tampilan). Gulir ke bawah untuk Twig, Latte, Smarty, Blade, dan lainnya.
Default Skeleton: flightphp/skeleton resmi menggunakan Twig saja di bawah
app/views/(*.twig). Controller memanggil$this->app->render('welcome', $data)(ekstensi opsional). Itu adalah pilihan aplikasi untuk proyek baru—bukan keharusan dari inti Flight. Latte dan mesin lainnya tetap didukung penuh.
Twig
default skeleton
Twig adalah mesin template yang fleksibel, cepat, dan aman yang digunakan oleh Symfony dan banyak proyek PHP lainnya. Alat bantu pengodean AI cenderung sangat mengenal Twig, dan secara default melakukan auto-escape pada output yang membantu melindungi dari XSS.
Instalasi
composer require twig/twig
(Sudah termasuk saat Anda composer create-project flightphp/skeleton.)
Konfigurasi Dasar
Timpa metode render untuk menggunakan Twig alih-alih renderer PHP default:
// timpa metode render untuk menggunakan Twig daripada renderer PHP default
Flight::map('render', function(string $template, array $data): void {
$loader = new \Twig\Loader\FilesystemLoader(Flight::get('flight.views.path'));
$twig = new \Twig\Environment($loader, [
// Di mana Twig menyimpan template yang dikompilasi
'cache' => __DIR__ . '/../cache/twig',
'auto_reload' => true,
]);
// Izinkan "welcome" atau "welcome.twig"
if (substr($template, -5) !== '.twig') {
$template .= '.twig';
}
echo $twig->render($template, $data);
});
Di skeleton, pemasangan ini berada di app/config/services.php (lingkungan Twig bersama, jalur cache, global seperti base_url / nonce CSP). Sebaiknya injeksi Engine dan panggil $app->render() dari controller agar kode tetap ramah AI dan pengujian.
Menggunakan Twig di Flight
Sekarang Anda dapat merender dengan Twig, Anda dapat melakukan sesuatu seperti ini:
{# app/views/home.twig #}
<html>
<head>
<title>{% if title %}{{ title }} - {% endif %}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
// routes.php
Flight::route('/@name', function ($name) {
Flight::render('home.twig', [
'title' => 'Home Page',
'name' => $name
]);
});
Saat Anda mengunjungi /Bob di browser, outputnya akan menjadi:
<html>
<head>
<title>Home Page - My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, Bob!</h1>
</body>
</html>
Bacaan Lebih Lanjut
Contoh yang lebih lengkap tentang penggunaan Twig dengan layout ditunjukkan di bagian plugin keren dari dokumentasi ini. Untuk metrik waktu render pada bar Tracy, lihat panel Twig di Ekstensi Tracy.
Anda dapat mempelajari lebih lanjut tentang kemampuan penuh Twig dengan membaca dokumentasi resmi.
Latte
alternatif bagus
Latte adalah mesin berfitur lengkap dengan sintaks mirip PHP. Ini tetap menjadi pilihan yang sangat baik untuk aplikasi Flight; skeleton hanya menstandarkan pada Twig untuk satu default bersama (terutama membantu saat alat AI menghasilkan template).
Instalasi
composer require latte/latte
Konfigurasi Dasar
Ide utamanya adalah Anda menimpa metode render untuk menggunakan Latte alih-alih renderer PHP default.
// timpa metode render untuk menggunakan latte daripada renderer PHP default
Flight::map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Di mana latte secara khusus menyimpan cache-nya
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
$latte->render($finalPath, $data, $block);
});
Menggunakan Latte di Flight
Sekarang Anda dapat merender dengan Latte, Anda dapat melakukan sesuatu seperti ini:
<!-- app/views/home.latte -->
<html>
<head>
<title>{$title ? $title . ' - '}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, {$name}!</h1>
</body>
</html>
// routes.php
Flight::route('/@name', function ($name) {
Flight::render('home.latte', [
'title' => 'Home Page',
'name' => $name
]);
});
Saat Anda mengunjungi /Bob di browser, outputnya akan menjadi:
<html>
<head>
<title>Home Page - My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, Bob!</h1>
</body>
</html>
Bacaan Lebih Lanjut
Contoh yang lebih kompleks tentang penggunaan Latte dengan layout ditunjukkan di bagian plugin keren dari dokumentasi ini.
Anda dapat mempelajari lebih lanjut tentang kemampuan penuh Latte termasuk terjemahan dan kemampuan bahasa dengan membaca dokumentasi resmi.
Mesin Tampilan Bawaan
usang
Catatan: Ini masih merupakan fungsionalitas default dan secara teknis masih berfungsi.
Untuk menampilkan template tampilan, panggil metode render dengan nama
file template dan data template opsional:
Flight::render('hello.php', ['name' => 'Bob']);
Data template yang Anda berikan secara otomatis disuntikkan ke dalam template dan dapat
dirujuk seperti variabel lokal. File template hanyalah file PHP. Jika
isi file template hello.php adalah:
Hello, <?= $name ?>!
Outputnya akan menjadi:
Hello, Bob!
Anda juga dapat mengatur variabel tampilan secara manual dengan menggunakan metode set:
Flight::view()->set('name', 'Bob');
Variabel name sekarang tersedia di semua tampilan Anda. Jadi Anda cukup melakukan:
Flight::render('hello');
Perhatikan bahwa saat menentukan nama template dalam metode render, Anda dapat
menghilangkan ekstensi .php.
Secara default Flight akan mencari direktori views untuk file template. Anda dapat
mengatur jalur alternatif untuk template Anda dengan mengatur konfigurasi berikut:
Flight::set('flight.views.path', '/path/to/views');
Layout
Umumnya situs web memiliki satu file template layout dengan konten yang
bergantian. Untuk merender konten yang akan digunakan dalam layout, Anda dapat memberikan
parameter opsional ke metode render.
Flight::render('header', ['heading' => 'Hello'], 'headerContent');
Flight::render('body', ['body' => 'World'], 'bodyContent');
Tampilan Anda kemudian akan memiliki variabel tersimpan bernama headerContent dan bodyContent.
Anda kemudian dapat merender layout Anda dengan melakukan:
Flight::render('layout', ['title' => 'Home Page']);
Jika file template terlihat seperti ini:
header.php:
<h1><?= $heading ?></h1>
body.php:
<div><?= $body ?></div>
layout.php:
<html>
<head>
<title><?= $title ?></title>
</head>
<body>
<?= $headerContent ?>
<?= $bodyContent ?>
</body>
</html>
Outputnya akan menjadi:
<html>
<head>
<title>Home Page</title>
</head>
<body>
<h1>Hello</h1>
<div>World</div>
</body>
</html>
Smarty
Berikut cara menggunakan mesin template Smarty untuk tampilan Anda:
// Muat pustaka Smarty
require './Smarty/libs/Smarty.class.php';
// Daftarkan Smarty sebagai kelas tampilan
// Juga berikan fungsi callback untuk mengonfigurasi Smarty saat dimuat
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
$smarty->setTemplateDir('./templates/');
$smarty->setCompileDir('./templates_c/');
$smarty->setConfigDir('./config/');
$smarty->setCacheDir('./cache/');
});
// Tetapkan data template
Flight::view()->assign('name', 'Bob');
// Tampilkan template
Flight::view()->display('hello.tpl');
Untuk kelengkapan, Anda juga harus menimpa metode render default Flight:
Flight::map('render', function(string $template, array $data): void {
Flight::view()->assign($data);
Flight::view()->display($template);
});
Blade
Berikut cara menggunakan mesin template Blade untuk tampilan Anda:
Pertama, Anda perlu menginstal pustaka BladeOne melalui Composer:
composer require eftec/bladeone
Kemudian, Anda dapat mengonfigurasi BladeOne sebagai kelas tampilan di Flight:
<?php
// Muat pustaka BladeOne
use eftec\bladeone\BladeOne;
// Daftarkan BladeOne sebagai kelas tampilan
// Juga berikan fungsi callback untuk mengonfigurasi BladeOne saat dimuat
Flight::register('view', BladeOne::class, [], function (BladeOne $blade) {
$views = __DIR__ . '/../views';
$cache = __DIR__ . '/../cache';
$blade->setPath($views);
$blade->setCompiledPath($cache);
});
// Tetapkan data template
Flight::view()->share('name', 'Bob');
// Tampilkan template
echo Flight::view()->run('hello', []);
Untuk kelengkapan, Anda juga harus menimpa metode render default Flight:
<?php
Flight::map('render', function(string $template, array $data): void {
echo Flight::view()->run($template, $data);
});
Dalam contoh ini, file template hello.blade.php mungkin terlihat seperti ini:
<?php
Hello, {{ $name }}!
Outputnya akan menjadi:
Hello, Bob!
Lihat Juga
- Instalasi - Tata letak skeleton (
app/views/*.twig) untuk proyek baru. - Ekstensi - Cara menimpa metode
renderuntuk menggunakan mesin template yang berbeda. - Routing - Cara memetakan rute ke controller dan merender tampilan.
- Respons - Cara menyesuaikan respons HTTP.
- Keamanan - Auto-escaping dan XSS.
- AI & Pengalaman Pengembang - Mengapa satu default mesin tampilan membantu agen pengodean.
- Mengapa Framework? - Bagaimana template masuk ke gambaran besar.
Pemecahan Masalah
- Jika Anda memiliki pengalihan di middleware, tetapi aplikasi Anda tampaknya tidak mengalihkan, pastikan Anda menambahkan pernyataan
exit;di middleware Anda. - Jika Twig tidak dapat menemukan template, periksa
flight.views.pathdan pastikan file tersebut ada di jalur itu dengan ekstensi yang diharapkan (skeleton:app/views/).
Changelog
- Dokumen – Twig didokumentasikan sebagai default skeleton resmi; Latte tetap menjadi alternatif kelas satu.
- v2.0 - Rilis awal.
Learn/simple_pdo
Kelas Pembantu SimplePdo PDO
Gambaran Umum
Kelas SimplePdo di Flight adalah pembantu modern dan kaya fitur untuk bekerja dengan database menggunakan PDO. Ia memperluas PdoWrapper dan menambahkan metode pembantu yang nyaman untuk operasi database umum seperti insert(), update(), delete(), dan transaksi. Ia menyederhanakan tugas database, mengembalikan hasil sebagai Collections untuk akses mudah, dan mendukung pencatatan query serta pemantauan kinerja aplikasi (APM) untuk kasus penggunaan lanjutan.
Pemahaman
Kelas SimplePdo dirancang untuk membuat bekerja dengan database di PHP menjadi jauh lebih mudah. Daripada mengelola pernyataan yang disiapkan, mode pengambilan, dan operasi SQL yang verbose, Anda mendapatkan metode yang bersih dan sederhana untuk tugas umum. Setiap baris dikembalikan sebagai Collection, sehingga Anda dapat menggunakan notasi array ($row['name']) dan notasi objek ($row->name).
Kelas ini adalah superset dari PdoWrapper, yang berarti ia mencakup semua fungsionalitas PdoWrapper ditambah metode pembantu tambahan yang membuat kode Anda lebih bersih dan mudah dipelihara. Jika Anda saat ini menggunakan PdoWrapper, upgrade ke SimplePdo adalah hal yang sederhana karena ia memperluas PdoWrapper.
Anda dapat mendaftarkan SimplePdo sebagai layanan bersama di Flight, dan kemudian menggunakannya di mana saja di aplikasi Anda melalui Flight::db().
Penggunaan Dasar
Mendaftarkan SimplePdo
Pertama, daftarkan kelas SimplePdo dengan Flight:
Flight::register('db', \flight\database\SimplePdo::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
]);
CATATAN
Jika Anda tidak menentukan
PDO::ATTR_DEFAULT_FETCH_MODE,SimplePdoakan secara otomatis mengaturnya kePDO::FETCH_ASSOCuntuk Anda.
Sekarang Anda dapat menggunakan Flight::db() di mana saja untuk mendapatkan koneksi database Anda.
Menjalankan Query
runQuery()
function runQuery(string $sql, array $params = []): PDOStatement
Gunakan ini untuk INSERT, UPDATE, atau ketika Anda ingin mengambil hasil secara manual:
$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM users WHERE status = ?", ['active']);
while ($row = $statement->fetch()) {
// $row adalah array
}
Anda juga dapat menggunakannya untuk penulisan:
$db->runQuery("INSERT INTO users (name) VALUES (?)", ['Alice']);
$db->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 1]);
fetchField()
function fetchField(string $sql, array $params = []): mixed
Dapatkan satu nilai tunggal dari database:
$count = Flight::db()->fetchField("SELECT COUNT(*) FROM users WHERE status = ?", ['active']);
fetchRow()
function fetchRow(string $sql, array $params = []): ?Collection
Dapatkan satu baris sebagai Collection (akses array/objek):
$user = Flight::db()->fetchRow("SELECT * FROM users WHERE id = ?", [123]);
echo $user['name'];
// atau
echo $user->name;
TIPS
SimplePdosecara otomatis menambahkanLIMIT 1ke queryfetchRow()jika belum ada, membuat query Anda lebih efisien.
fetchAll()
function fetchAll(string $sql, array $params = []): array<Collection>
Dapatkan semua baris sebagai array dari Collections:
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE status = ?", ['active']);
foreach ($users as $user) {
echo $user['name'];
// atau
echo $user->name;
}
fetchColumn()
function fetchColumn(string $sql, array $params = []): array
Ambil satu kolom sebagai array:
$ids = Flight::db()->fetchColumn("SELECT id FROM users WHERE active = ?", [1]);
// Mengembalikan: [1, 2, 3, 4, 5]
fetchPairs()
function fetchPairs(string $sql, array $params = []): array
Ambil hasil sebagai pasangan kunci-nilai (kolom pertama sebagai kunci, kedua sebagai nilai):
$userNames = Flight::db()->fetchPairs("SELECT id, name FROM users");
// Mengembalikan: [1 => 'John', 2 => 'Jane', 3 => 'Bob']
Menggunakan Placeholder IN()
Anda dapat menggunakan satu ? tunggal dalam klausa IN() dan meneruskan array:
$ids = [1, 2, 3];
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", [$ids]);
Metode Pembantu
Salah satu keunggulan utama SimplePdo dibandingkan PdoWrapper adalah penambahan metode pembantu yang nyaman untuk operasi database umum.
insert()
function insert(string $table, array $data): string
Masukkan satu atau lebih baris dan kembalikan ID sisipan terakhir.
Sisipan tunggal:
$id = Flight::db()->insert('users', [
'name' => 'John',
'email' => 'john@example.com'
]);
Sisipan massal:
$id = Flight::db()->insert('users', [
['name' => 'John', 'email' => 'john@example.com'],
['name' => 'Jane', 'email' => 'jane@example.com'],
]);
update()
function update(string $table, array $data, string $where, array $whereParams = []): int
Perbarui baris dan kembalikan jumlah baris yang terpengaruh:
$affected = Flight::db()->update(
'users',
['name' => 'Jane', 'email' => 'jane@example.com'],
'id = ?',
[1]
);
CATATAN
rowCount()SQLite mengembalikan jumlah baris di mana data benar-benar berubah. Jika Anda memperbarui baris dengan nilai yang sama yang sudah dimilikinya,rowCount()akan mengembalikan 0. Ini berbeda dari perilaku MySQL saat menggunakanPDO::MYSQL_ATTR_FOUND_ROWS.
delete()
function delete(string $table, string $where, array $whereParams = []): int
Hapus baris dan kembalikan jumlah baris yang dihapus:
$deleted = Flight::db()->delete('users', 'id = ?', [1]);
transaction()
function transaction(callable $callback): mixed
Jalankan callback dalam transaksi. Transaksi secara otomatis melakukan commit pada sukses atau rollback pada kesalahan:
$result = Flight::db()->transaction(function($db) {
$db->insert('users', ['name' => 'John']);
$db->insert('logs', ['action' => 'user_created']);
return $db->lastInsertId();
});
Jika ada pengecualian yang dilemparkan dalam callback, transaksi secara otomatis di-rollback dan pengecualian dilemparkan kembali.
Penggunaan Lanjutan
Pencatatan Query & APM
Jika Anda ingin melacak kinerja query, aktifkan pelacakan APM saat mendaftarkan:
Flight::register('db', \flight\database\SimplePdo::class, [
'mysql:host=localhost;dbname=cool_db_name',
'user',
'pass',
[/* opsi PDO */],
[
'trackApmQueries' => true,
'maxQueryMetrics' => 1000
]
]);
Setelah menjalankan query, Anda dapat mencatatnya secara manual, tetapi APM akan mencatatnya secara otomatis jika diaktifkan:
Flight::db()->logQueries();
Ini akan memicu event (flight.db.queries) dengan metrik koneksi dan query, yang dapat Anda dengarkan menggunakan sistem event Flight.
Contoh Lengkap
Flight::route('/users', function () {
// Dapatkan semua pengguna
$users = Flight::db()->fetchAll('SELECT * FROM users');
// Stream semua pengguna
$statement = Flight::db()->runQuery('SELECT * FROM users');
while ($user = $statement->fetch()) {
echo $user['name'];
}
// Dapatkan satu pengguna
$user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);
// Dapatkan satu nilai
$count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');
// Dapatkan satu kolom
$ids = Flight::db()->fetchColumn('SELECT id FROM users');
// Dapatkan pasangan kunci-nilai
$userNames = Flight::db()->fetchPairs('SELECT id, name FROM users');
// Sintaks IN() khusus
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
// Sisipkan pengguna baru
$id = Flight::db()->insert('users', [
'name' => 'Bob',
'email' => 'bob@example.com'
]);
// Sisipkan massal pengguna
Flight::db()->insert('users', [
['name' => 'Bob', 'email' => 'bob@example.com'],
['name' => 'Jane', 'email' => 'jane@example.com']
]);
// Perbarui pengguna
$affected = Flight::db()->update('users', ['name' => 'Bob'], 'id = ?', [123]);
// Hapus pengguna
$deleted = Flight::db()->delete('users', 'id = ?', [123]);
// Gunakan transaksi
$result = Flight::db()->transaction(function($db) {
$db->insert('users', ['name' => 'John', 'email' => 'john@example.com']);
$db->insert('audit_log', ['action' => 'user_created']);
return $db->lastInsertId();
});
});
Migrasi dari PdoWrapper
Jika Anda saat ini menggunakan PdoWrapper, migrasi ke SimplePdo adalah hal yang sederhana:
-
Perbarui pendaftaran Anda:
// Lama Flight::register('db', \flight\database\PdoWrapper::class, [ /* ... */ ]); // Baru Flight::register('db', \flight\database\SimplePdo::class, [ /* ... */ ]); -
Semua metode
PdoWrapperyang ada bekerja diSimplePdo- Tidak ada perubahan yang merusak. Kode Anda yang ada akan terus bekerja. -
Secara opsional gunakan metode pembantu baru - Mulai gunakan
insert(),update(),delete(), dantransaction()untuk menyederhanakan kode Anda.
Lihat Juga
- Collections - Pelajari cara menggunakan kelas Collection untuk akses data yang mudah.
- PdoWrapper - Kelas pembantu PDO lama (deprecated).
Pemecahan Masalah
- Jika Anda mendapatkan kesalahan tentang koneksi database, periksa DSN, nama pengguna, kata sandi, dan opsi Anda.
- Semua baris dikembalikan sebagai Collections—jika Anda membutuhkan array biasa, gunakan
$collection->getData(). - Untuk query
IN (?), pastikan untuk meneruskan array. - Jika Anda mengalami masalah memori dengan pencatatan query di proses yang berjalan lama, sesuaikan opsi
maxQueryMetrics.
Changelog
- v3.18.0 - Rilis awal SimplePdo dengan metode pembantu untuk insert, update, delete, dan transaksi.
Learn/collections
Collections
Ikhtisar
Kelas Collection di Flight adalah utilitas yang berguna untuk mengelola kumpulan data. Ini memungkinkan Anda mengakses dan memanipulasi data menggunakan notasi array maupun objek, sehingga kode Anda lebih bersih dan lebih fleksibel.
Pemahaman
Sebuah Collection pada dasarnya adalah pembungkus di sekitar array, tetapi dengan kekuatan ekstra. Anda dapat menggunakannya seperti array, melakukan perulangan di atasnya, menghitung itemnya, dan bahkan mengakses item seolah-olah itu properti objek. Ini sangat berguna ketika Anda ingin meneruskan data terstruktur dalam aplikasi Anda, atau ketika Anda ingin membuat kode Anda sedikit lebih mudah dibaca.
Collection mengimplementasikan beberapa antarmuka PHP:
ArrayAccess(sehingga Anda dapat menggunakan sintaks array)Iterator(sehingga Anda dapat melakukan perulangan denganforeach)Countable(sehingga Anda dapat menggunakancount())JsonSerializable(sehingga Anda dapat dengan mudah mengonversinya ke JSON)
Penggunaan Dasar
Membuat Collection
Anda dapat membuat sebuah Collection dengan cukup melewatkan array ke konstruktornya:
use flight\util\Collection;
$data = [
'name' => 'Flight',
'version' => 3,
'features' => ['routing', 'views', 'extending']
];
$collection = new Collection($data);
Mengakses Item
Anda dapat mengakses item menggunakan notasi array atau objek:
// Notasi array
echo $collection['name']; // Keluaran: FlightPHP
// Notasi objek
echo $collection->version; // Keluaran: 3
Jika Anda mencoba mengakses kunci yang tidak ada, Anda akan mendapatkan null alih-alih error.
Mengatur Item
Anda juga dapat mengatur item menggunakan salah satu notasi:
// Notasi array
$collection['author'] = 'Mike Cao';
// Notasi objek
$collection->license = 'MIT';
Memeriksa dan Menghapus Item
Periksa apakah sebuah item ada:
if (isset($collection['name'])) {
// Lakukan sesuatu
}
if (isset($collection->version)) {
// Lakukan sesuatu
}
Hapus sebuah item:
unset($collection['author']);
unset($collection->license);
Iterasi pada Collection
Collection bersifat iterable, sehingga Anda dapat menggunakannya dalam perulangan foreach:
foreach ($collection as $key => $value) {
echo "$key: $value\n";
}
Menghitung Item
Anda dapat menghitung jumlah item dalam sebuah Collection:
echo count($collection); // Keluaran: 4
Mendapatkan Semua Kunci atau Data
Dapatkan semua kunci:
$keys = $collection->keys(); // ['name', 'version', 'features', 'license']
Dapatkan semua data sebagai array:
$data = $collection->getData();
Mengosongkan Collection
Hapus semua item:
$collection->clear();
Serialisasi JSON
Collection dapat dengan mudah dikonversi menjadi JSON:
echo json_encode($collection);
// Keluaran: {"name":"FlightPHP","version":3,"features":["routing","views","extending"],"license":"MIT"}
Penggunaan Lanjutan
Anda dapat mengganti seluruh array data internal jika diperlukan:
$collection->setData(['foo' => 'bar']);
Collection sangat berguna ketika Anda ingin meneruskan data terstruktur antar komponen, atau ketika Anda ingin menyediakan antarmuka yang lebih berorientasi objek untuk data array.
Lihat Juga
- Requests - Pelajari cara menangani permintaan HTTP dan bagaimana Collection dapat digunakan untuk mengelola data permintaan.
- SimplePdo - Helper database yang mengembalikan baris kueri sebagai Collection.
Pemecahan Masalah
- Jika Anda mencoba mengakses kunci yang tidak ada, Anda akan mendapatkan
nullalih-alih error. - Ingat bahwa Collection tidak bersifat rekursif: array bersarang tidak secara otomatis dikonversi menjadi Collection.
- Jika Anda perlu mengatur ulang Collection, gunakan
$collection->clear()atau$collection->setData([]).
Log Perubahan
- v3.0 - Peningkatan type hints dan dukungan PHP 8+.
- v1.0 - Rilis awal dari kelas Collection.
Learn/flight_vs_fat_free
Flight vs Fat-Free
Apa itu Fat-Free?
Fat-Free (dikenal dengan penuh kasih sayang sebagai F3) adalah mikro-framework PHP yang kuat namun mudah digunakan, dirancang untuk membantu Anda membangun aplikasi web yang dinamis dan tangguh - dengan cepat!
Flight dibandingkan dengan Fat-Free dalam banyak hal dan mungkin merupakan kerabat terdekat dalam hal fitur dan kesederhanaan. Fat-Free memiliki banyak fitur yang tidak dimiliki Flight, tetapi juga memiliki banyak fitur yang dimiliki Flight. Fat-Free mulai menunjukkan usianya dan tidak sepopuler dulu.
Pembaruan menjadi semakin jarang dan komunitas tidak seaktif dulu. Kodenya cukup sederhana, tetapi terkadang kurangnya disiplin sintaks dapat membuatnya sulit dibaca dan dipahami. Ia memang bekerja untuk PHP 8.3, tetapi kodenya sendiri masih terlihat seperti hidup di PHP 5.3.
Kelebihan dibandingkan Flight
- Fat-Free memiliki bintang di GitHub sedikit lebih banyak daripada Flight.
- Fat-Free memiliki beberapa dokumentasi yang layak, tetapi masih kurang jelas di beberapa area.
- Fat-Free memiliki beberapa sumber daya yang jarang seperti tutorial YouTube dan artikel online yang dapat digunakan untuk mempelajari framework ini.
- Fat-Free memiliki beberapa plugin yang membantu bawaan yang terkadang berguna.
- Fat-Free memiliki ORM bawaan yang disebut Mapper yang dapat digunakan untuk berinteraksi dengan database Anda. Flight memiliki active-record.
- Fat-Free memiliki Sessions, Caching, dan lokalisasi bawaan. Flight mengharuskan Anda menggunakan library pihak ketiga, tetapi hal ini tercakup dalam dokumentasi.
- Fat-Free memiliki sekelompok kecil plugin buatan komunitas yang dapat digunakan untuk memperluas framework. Flight memiliki beberapa yang tercakup dalam halaman dokumentasi dan contoh.
- Fat-Free seperti Flight tidak memiliki dependensi.
- Fat-Free seperti Flight diarahkan untuk memberikan kendali kepada pengembang atas aplikasi mereka dan pengalaman pengembang yang sederhana.
- Fat-Free mempertahankan kompatibilitas mundur seperti yang dilakukan Flight (sebagian karena pembaruan semakin jarang).
- Fat-Free seperti Flight ditujukan untuk pengembang yang baru pertama kali menjelajah ke dunia framework.
- Fat-Free memiliki mesin template bawaan yang lebih kuat daripada mesin template Flight. Flight merekomendasikan Latte untuk mencapai hal ini.
- Fat-Free memiliki perintah CLI unik bertipe "route" di mana Anda dapat membangun aplikasi CLI di dalam Fat-Free itu sendiri dan memperlakukannya seperti permintaan
GET. Flight mencapai hal ini dengan runway.
Kekurangan dibandingkan Flight
- Fat-Free memiliki beberapa tes implementasi dan bahkan memiliki kelas test sendiri yang sangat mendasar. Namun, tidak 100% diuji unit seperti Flight.
- Anda harus menggunakan mesin pencari seperti Google untuk benar-benar mencari situs dokumentasi.
- Flight memiliki mode gelap di situs dokumentasi mereka. (mic drop)
- Fat-Free memiliki beberapa modul yang sangat tidak terawat.
- Flight memiliki SimplePdo untuk akses database, yang sedikit lebih sederhana daripada kelas
DB\SQLbawaan Fat-Free (dan lebih disukai daripada PdoWrapper yang sudah tidak digunakan lagi). - Flight memiliki plugin permissions yang dapat digunakan untuk mengamankan aplikasi Anda. Fat Free mengharuskan Anda menggunakan library pihak ketiga.
- Flight memiliki ORM yang disebut active-record yang terasa lebih seperti ORM daripada Mapper milik Fat-Free.
Manfaat tambahan dari
active-recordadalah Anda dapat mendefinisikan hubungan antar rekaman untuk penggabungan otomatis, sedangkan Mapper Fat-Free mengharuskan Anda membuat SQL views. - Anehnya, Fat-Free tidak memiliki root namespace. Flight diberi namespace sepenuhnya agar tidak bertabrakan dengan kode Anda sendiri.
Kelas
Cacheadalah pelanggar terbesar di sini. - Fat-Free tidak memiliki middleware. Sebagai gantinya, ada hook
beforeroutedanafterrouteyang dapat digunakan untuk menyaring permintaan dan respons di kontroler. - Fat-Free tidak dapat mengelompokkan rute.
- Fat-Free memiliki penangan wadah injeksi dependensi, tetapi dokumentasinya sangat minim tentang cara menggunakannya.
- Proses debugging bisa menjadi sedikit rumit karena pada dasarnya semuanya disimpan dalam apa yang disebut
HIVE.
Learn/extending
Memperluas
Gambaran Umum
Flight dirancang sebagai kerangka kerja yang dapat diperluas. Kerangka kerja ini dilengkapi dengan seperangkat metode dan komponen default, tetapi memungkinkan Anda untuk memetakan metode Anda sendiri, mendaftarkan kelas Anda sendiri, atau bahkan menimpa kelas dan metode yang ada.
Pemahaman
Ada 2 cara yang dapat Anda gunakan untuk memperluas fungsionalitas Flight:
- Pemetaan Metode - Ini digunakan untuk membuat metode kustom sederhana yang dapat Anda panggil dari mana saja di aplikasi Anda. Ini biasanya digunakan untuk fungsi utilitas yang ingin Anda panggil dari mana saja di kode Anda.
- Pendaftaran Kelas - Ini digunakan untuk mendaftarkan kelas Anda sendiri dengan Flight. Ini biasanya digunakan untuk kelas yang memiliki dependensi atau memerlukan konfigurasi.
Anda juga dapat menimpa metode kerangka kerja yang ada untuk mengubah perilaku defaultnya agar lebih sesuai dengan kebutuhan proyek Anda.
Jika Anda mencari DIC (Dependency Injection Container), kunjungi halaman Dependency Injection Container.
Penggunaan Dasar
Menimpa Metode Kerangka Kerja
Flight memungkinkan Anda menimpa fungsionalitas defaultnya agar sesuai dengan kebutuhan Anda sendiri, tanpa harus memodifikasi kode apa pun. Anda dapat melihat semua metode yang dapat ditimpa di bawah.
Misalnya, ketika Flight tidak dapat mencocokkan URL dengan rute, ia memanggil metode notFound
yang mengirim respons HTTP 404 generik. Anda dapat menimpa perilaku ini
dengan menggunakan metode map:
Flight::map('notFound', function() {
// Tampilkan halaman 404 kustom
include 'errors/404.html';
});
Flight juga memungkinkan Anda untuk mengganti komponen inti kerangka kerja. Misalnya, Anda dapat mengganti kelas Router default dengan kelas kustom Anda sendiri:
// buat kelas Router kustom Anda
class MyRouter extends \flight\net\Router {
// timpa metode di sini
// misalnya pintasan untuk permintaan GET untuk menghapus
// fitur rute pass
public function get($pattern, $callback, $alias = '') {
return parent::get($pattern, $callback, false, $alias);
}
}
// Daftarkan kelas kustom Anda
Flight::register('router', MyRouter::class);
// Saat Flight memuat instance Router, ia akan memuat kelas Anda
$myRouter = Flight::router();
$myRouter->get('/hello', function() {
echo "Hello World!";
}, 'hello_alias');
Namun, metode kerangka kerja seperti map dan register tidak dapat ditimpa. Anda akan
mendapat kesalahan jika mencoba melakukannya (lihat lagi di bawah untuk daftar metode).
Metode Kerangka Kerja yang Dapat Dipetakan
Berikut adalah kumpulan lengkap metode untuk kerangka kerja. Ini terdiri dari metode inti, yang merupakan metode statis biasa, dan metode yang dapat diperluas, yang merupakan metode yang dipetakan yang dapat difilter atau ditimpa.
Metode Inti
Metode ini adalah inti dari kerangka kerja dan tidak dapat ditimpa.
Flight::map(string $name, callable $callback, bool $pass_route = false) // Membuat metode kerangka kerja kustom.
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // Mendaftarkan kelas ke metode kerangka kerja.
Flight::unregister(string $name) // Membatalkan pendaftaran kelas ke metode kerangka kerja.
Flight::before(string $name, callable $callback) // Menambahkan filter sebelum metode kerangka kerja.
Flight::after(string $name, callable $callback) // Menambahkan filter setelah metode kerangka kerja.
Flight::path(string $path) // Menambahkan jalur untuk autoloading kelas.
Flight::get(string $key) // Mendapatkan variabel yang ditetapkan oleh Flight::set().
Flight::set(string $key, mixed $value) // Mengatur variabel dalam mesin Flight.
Flight::has(string $key) // Memeriksa apakah variabel telah ditetapkan.
Flight::clear(array|string $key = []) // Membersihkan variabel.
Flight::init() // Menginisialisasi kerangka kerja ke pengaturan defaultnya.
Flight::app() // Mendapatkan instance objek aplikasi
Flight::request() // Mendapatkan instance objek permintaan
Flight::response() // Mendapatkan instance objek respons
Flight::router() // Mendapatkan instance objek router
Flight::view() // Mendapatkan instance objek tampilan
Metode yang Dapat Diperluas
Flight::start() // Memulai kerangka kerja.
Flight::stop() // Menghentikan kerangka kerja dan mengirim respons.
Flight::halt(int $code = 200, string $message = '') // Menghentikan kerangka kerja dengan kode status dan pesan opsional.
Flight::route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Memetakan pola URL ke callback.
Flight::post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Memetakan pola URL permintaan POST ke callback.
Flight::put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Memetakan pola URL permintaan PUT ke callback.
Flight::patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Memetakan pola URL permintaan PATCH ke callback.
Flight::delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Memetakan pola URL permintaan DELETE ke callback.
Flight::group(string $pattern, callable $callback) // Membuat pengelompokan untuk url, pola harus berupa string.
Flight::getUrl(string $name, array $params = []) // Menghasilkan URL berdasarkan alias rute.
Flight::redirect(string $url, int $code) // Mengarahkan ke URL lain.
Flight::download(string $filePath) // Mengunduh file.
Flight::render(string $file, array $data, ?string $key = null) // Merender file template.
Flight::error(Throwable $error) // Mengirim respons HTTP 500.
Flight::notFound() // Mengirim respons HTTP 404.
Flight::etag(string $id, string $type = 'string') // Melakukan caching HTTP ETag.
Flight::lastModified(int $time) // Melakukan caching HTTP last modified.
Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Mengirim respons JSON.
Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Mengirim respons JSONP.
Flight::jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Mengirim respons JSON dan menghentikan kerangka kerja.
Flight::onEvent(string $event, callable $callback) // Mendaftarkan pendengar acara.
Flight::triggerEvent(string $event, ...$args) // Memicu acara.
Metode kustom apa pun yang ditambahkan dengan map dan register juga dapat difilter. Untuk contoh tentang cara memfilter metode ini, lihat panduan Filtering Methods.
Kelas Kerangka Kerja yang Dapat Diperluas
Ada beberapa kelas yang dapat Anda timpa fungsionalitasnya dengan memperluasnya dan mendaftarkan kelas Anda sendiri. Kelas-kelas ini adalah:
Flight::app() // Kelas Aplikasi - perluas kelas flight\Engine
Flight::request() // Kelas Permintaan - perluas kelas flight\net\Request
Flight::response() // Kelas Respons - perluas kelas flight\net\Response
Flight::router() // Kelas Router - perluas kelas flight\net\Router
Flight::view() // Kelas Tampilan - perluas kelas flight\template\View
Flight::eventDispatcher() // Kelas Event Dispatcher - perluas kelas flight\core\Dispatcher
Pemetaan Metode Kustom
Untuk memetakan metode kustom sederhana Anda sendiri, gunakan fungsi map:
// Petakan metode Anda
Flight::map('hello', function (string $name) {
echo "hello $name!";
});
// Panggil metode kustom Anda
Flight::hello('Bob');
Meskipun mungkin untuk membuat metode kustom sederhana, disarankan untuk hanya membuat fungsi standar di PHP. Ini memiliki autocomplete di IDE dan lebih mudah dibaca. Setara dengan kode di atas adalah:
function hello(string $name) {
echo "hello $name!";
}
hello('Bob');
Ini digunakan lebih banyak ketika Anda perlu meneruskan variabel ke metode Anda untuk mendapatkan nilai
yang diharapkan. Menggunakan metode register() seperti di bawah ini lebih untuk meneruskan konfigurasi
dan kemudian memanggil kelas yang telah dikonfigurasi sebelumnya.
Pendaftaran Kelas Kustom
Untuk mendaftarkan kelas Anda sendiri dan mengonfigurasinya, gunakan fungsi register. Keuntungan yang dimiliki ini dibandingkan map() adalah Anda dapat menggunakan kembali kelas yang sama ketika Anda memanggil fungsi ini (akan membantu dengan Flight::db() untuk berbagi instance yang sama).
// Daftarkan kelas Anda
Flight::register('user', User::class);
// Dapatkan instance kelas Anda
$user = Flight::user();
Metode register juga memungkinkan Anda untuk meneruskan parameter ke konstruktor kelas Anda. Jadi ketika Anda memuat kelas kustom Anda, ia akan datang sudah diinisialisasi. Anda dapat mendefinisikan parameter konstruktor dengan meneruskan array tambahan. Berikut adalah contoh memuat koneksi database:
// Daftarkan kelas dengan parameter konstruktor
Flight::register('db', PDO::class, ['mysql:host=localhost;dbname=test', 'user', 'pass']);
// Dapatkan instance kelas Anda
// Ini akan membuat objek dengan parameter yang didefinisikan
//
// new PDO('mysql:host=localhost;dbname=test','user','pass');
//
$db = Flight::db();
// dan jika Anda membutuhkannya nanti di kode Anda, Anda hanya memanggil metode yang sama lagi
class SomeController {
public function __construct() {
$this->db = Flight::db();
}
}
Jika Anda meneruskan parameter callback tambahan, ia akan dieksekusi segera setelah konstruksi kelas. Ini memungkinkan Anda untuk melakukan prosedur penyiapan apa pun untuk objek baru Anda. Fungsi callback mengambil satu parameter, instance objek baru.
// Callback akan diteruskan objek yang dibuat
Flight::register(
'db',
PDO::class,
['mysql:host=localhost;dbname=test', 'user', 'pass'],
function (PDO $db) {
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
);
Secara default, setiap kali Anda memuat kelas Anda, Anda akan mendapatkan instance yang dibagikan.
Untuk mendapatkan instance baru dari kelas, cukup teruskan false sebagai parameter:
// Instance kelas yang dibagikan
$shared = Flight::db();
// Instance kelas yang baru
$new = Flight::db(false);
Catatan: Ingatlah bahwa metode yang dipetakan memiliki prioritas atas kelas yang terdaftar. Jika Anda menyatakan keduanya menggunakan nama yang sama, hanya metode yang dipetakan yang akan dipanggil.
Contoh
Berikut adalah beberapa contoh tentang bagaimana Anda dapat memperluas Flight dengan fungsionalitas yang tidak ada di inti.
Logging
Flight tidak memiliki sistem logging bawaan, namun, sangat mudah untuk menggunakan perpustakaan logging dengan Flight. Berikut adalah contoh menggunakan perpustakaan Monolog:
// services.php
// Daftarkan logger dengan Flight
Flight::register('log', Monolog\Logger::class, [ 'name' ], function(Monolog\Logger $log) {
$log->pushHandler(new Monolog\Handler\StreamHandler('path/to/your.log', Monolog\Logger::WARNING));
});
Sekarang setelah terdaftar, Anda dapat menggunakannya di aplikasi Anda:
// Di controller atau rute Anda
Flight::log()->warning('This is a warning message');
Ini akan mencatat pesan ke file log yang Anda tentukan. Bagaimana jika Anda ingin mencatat sesuatu ketika terjadi
kesalahan? Anda dapat menggunakan metode error:
// Di controller atau rute Anda
Flight::map('error', function(Throwable $ex) {
Flight::log()->error($ex->getMessage());
// Tampilkan halaman kesalahan kustom Anda
include 'errors/500.html';
});
Anda juga dapat membuat sistem APM (Application Performance Monitoring) dasar
menggunakan metode before dan after:
// Di file services.php Anda
Flight::before('start', function() {
Flight::set('start_time', microtime(true));
});
Flight::after('start', function() {
$end = microtime(true);
$start = Flight::get('start_time');
Flight::log()->info('Request '.Flight::request()->url.' took ' . round($end - $start, 4) . ' seconds');
// Anda juga dapat menambahkan header permintaan atau respons Anda
// untuk mencatatnya juga (berhati-hatilah karena ini akan menjadi banyak data
// jika Anda memiliki banyak permintaan)
Flight::log()->info('Request Headers: ' . json_encode(Flight::request()->headers));
Flight::log()->info('Response Headers: ' . json_encode(Flight::response()->headers));
});
Caching
Flight tidak memiliki sistem caching bawaan, namun, sangat mudah untuk menggunakan perpustakaan caching dengan Flight. Berikut adalah contoh menggunakan PHP File Cache library:
// services.php
// Daftarkan cache dengan Flight
Flight::register('cache', \flight\Cache::class, [ __DIR__ . '/../cache/' ], function(\flight\Cache $cache) {
$cache->setDevMode(ENVIRONMENT === 'development');
});
Sekarang setelah terdaftar, Anda dapat menggunakannya di aplikasi Anda:
// Di controller atau rute Anda
$data = Flight::cache()->get('my_cache_key');
if (empty($data)) {
// Lakukan pemrosesan untuk mendapatkan data
$data = [ 'some' => 'data' ];
Flight::cache()->set('my_cache_key', $data, 3600); // cache untuk 1 jam
}
Instantiation Objek DIC yang Mudah
Jika Anda menggunakan DIC (Dependency Injection Container) di aplikasi Anda, Anda dapat menggunakan Flight untuk membantu Anda menginisialisasi objek Anda. Berikut adalah contoh menggunakan perpustakaan Dice:
// services.php
// buat container baru
$container = new \Dice\Dice;
// jangan lupa untuk menugaskan ulang ke dirinya sendiri seperti di bawah!
$container = $container->addRule('PDO', [
// shared berarti bahwa objek yang sama akan dikembalikan setiap kali
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// sekarang kita dapat membuat metode yang dapat dipetakan untuk membuat objek apa pun.
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// Ini mendaftarkan penangan container sehingga Flight tahu untuk menggunakannya untuk controller/middleware
Flight::registerContainerHandler(function($class, $params) {
Flight::make($class, $params);
});
// katakanlah kita memiliki kelas sampel berikut yang mengambil objek PDO di konstruktor
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// kode yang mengirim email
}
}
// Dan akhirnya Anda dapat membuat objek menggunakan dependency injection
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();
Keren, bukan?
Lihat Juga
- Dependency Injection Container - Cara menggunakan DIC dengan Flight.
- File Cache - Contoh menggunakan perpustakaan caching dengan Flight.
Pemecahan Masalah
- Ingatlah bahwa metode yang dipetakan memiliki prioritas atas kelas yang terdaftar. Jika Anda menyatakan keduanya menggunakan nama yang sama, hanya metode yang dipetakan yang akan dipanggil.
Changelog
- v2.0 - Rilis Awal.
Learn/json
JSON Wrapper
Gambaran Umum
Kelas Json di Flight menyediakan cara sederhana dan konsisten untuk mengkodekan dan mendekodekan data JSON dalam aplikasi Anda. Ini membungkus fungsi JSON asli PHP dengan penanganan kesalahan yang lebih baik dan beberapa pengaturan default yang membantu, membuatnya lebih mudah dan aman untuk bekerja dengan JSON.
Pemahaman
Bekerja dengan JSON sangat umum di aplikasi PHP modern, terutama saat membangun API atau menangani permintaan AJAX. Kelas Json memusatkan semua pengkodean dan dekodean JSON Anda, sehingga Anda tidak perlu khawatir tentang kasus tepi yang aneh atau kesalahan kriptik dari fungsi bawaan PHP.
Fitur utama:
- Penanganan kesalahan yang konsisten (melemparkan pengecualian saat gagal)
- Opsi default untuk pengkodean/dekodean (seperti garis miring yang tidak di-escape)
- Metode utilitas untuk pencetakan cantik dan validasi
Penggunaan Dasar
Mengkodekan Data ke JSON
Untuk mengonversi data PHP ke string JSON, gunakan Json::encode():
use flight\util\Json;
$data = [
'framework' => 'Flight',
'version' => 3,
'features' => ['routing', 'views', 'extending']
];
$json = Json::encode($data);
echo $json;
// Output: {"framework":"Flight","version":3,"features":["routing","views","extending"]}
Jika pengkodean gagal, Anda akan mendapatkan pengecualian dengan pesan kesalahan yang membantu.
Pencetakan Cantik
Ingin JSON Anda mudah dibaca oleh manusia? Gunakan prettyPrint():
echo Json::prettyPrint($data);
/*
{
"framework": "Flight",
"version": 3,
"features": [
"routing",
"views",
"extending"
]
}
*/
Mendekodekan String JSON
Untuk mengonversi string JSON kembali ke data PHP, gunakan Json::decode():
$json = '{"framework":"Flight","version":3}';
$data = Json::decode($json);
echo $data->framework; // Output: Flight
Jika Anda ingin array asosiatif daripada objek, berikan true sebagai argumen kedua:
$data = Json::decode($json, true);
echo $data['framework']; // Output: Flight
Jika dekodean gagal, Anda akan mendapatkan pengecualian dengan pesan kesalahan yang jelas.
Memvalidasi JSON
Periksa apakah string adalah JSON yang valid:
if (Json::isValid($json)) {
// Itu valid!
} else {
// Bukan JSON yang valid
}
Mendapatkan Kesalahan Terakhir
Jika Anda ingin memeriksa pesan kesalahan JSON terakhir (dari fungsi PHP asli):
$error = Json::getLastError();
if ($error !== '') {
echo "Last JSON error: $error";
}
Penggunaan Lanjutan
Anda dapat menyesuaikan opsi pengkodean dan dekodean jika Anda membutuhkan kontrol lebih (lihat opsi json_encode PHP):
// Encode dengan opsi HEX_TAG
$json = Json::encode($data, JSON_HEX_TAG);
// Decode dengan kedalaman khusus
$data = Json::decode($json, false, 1024);
Lihat Juga
- Collections - Untuk bekerja dengan data terstruktur yang dapat dengan mudah dikonversi ke JSON.
- Configuration - Cara mengonfigurasi aplikasi Flight Anda.
- Extending - Cara menambahkan utilitas sendiri atau menimpa kelas inti.
Pemecahan Masalah
- Jika pengkodean atau dekodean gagal, pengecualian dilemparkan—bungkus panggilan Anda dalam try/catch jika Anda ingin menangani kesalahan dengan anggun.
- Jika Anda mendapatkan hasil yang tidak diharapkan, periksa data Anda untuk referensi melingkar atau karakter non-UTF8.
- Gunakan
Json::isValid()untuk memeriksa apakah string adalah JSON yang valid sebelum mendekode.
Changelog
- v3.16.0 - Ditambahkan kelas utilitas pembungkus JSON.
Learn/flight_vs_slim
Flight vs Slim
Apa itu Slim?
Slim adalah kerangka kerja mikro PHP yang membantu Anda dengan cepat menulis aplikasi web dan API yang sederhana namun powerful.
Banyak inspirasi untuk beberapa fitur v3 Flight sebenarnya berasal dari Slim. Pengelompokan rute (grouping routes), dan mengeksekusi middleware dalam urutan tertentu adalah dua fitur yang terinspirasi dari Slim. Slim v3 dirilis dengan fokus pada kesederhanaan, tetapi ada ulasan beragam mengenai v4.
Kelebihan dibandingkan Flight
- Slim memiliki komunitas pengembang yang lebih besar, yang pada gilirannya membuat modul-modul berguna untuk membantu Anda tidak perlu menciptakan ulang roda.
- Slim mengikuti banyak antarmuka dan standar yang umum di komunitas PHP, yang meningkatkan interoperabilitas.
- Slim memiliki dokumentasi dan tutorial yang layak yang dapat digunakan untuk mempelajari kerangka kerja ini (tidak sebanding dengan Laravel atau Symfony).
- Slim memiliki berbagai sumber daya seperti tutorial YouTube dan artikel online yang dapat digunakan untuk mempelajari kerangka kerja ini.
- Slim memungkinkan Anda menggunakan komponen apa pun yang Anda inginkan untuk menangani fitur routing inti karena sesuai dengan PSR-7.
Kekurangan dibandingkan Flight
- Anehnya, Slim tidak secepat yang Anda kira untuk sebuah kerangka kerja mikro. Lihat tolok ukur TechEmpower untuk informasi lebih lanjut.
- Flight diarahkan untuk pengembang yang ingin membangun aplikasi web yang ringan, cepat, dan mudah digunakan.
- Flight tidak memiliki dependensi, sedangkan Slim memiliki beberapa dependensi yang harus Anda instal.
- Flight diarahkan untuk kesederhanaan dan kemudahan penggunaan.
- Salah satu fitur inti Flight adalah ia berusaha semaksimal mungkin untuk menjaga kompatibilitas mundur. Slim v3 ke v4 adalah perubahan yang merusak (breaking change).
- Flight ditujukan untuk pengembang yang baru pertama kali menjelajahi dunia kerangka kerja.
- Flight juga dapat menangani aplikasi tingkat enterprise, tetapi tidak memiliki banyak contoh dan tutorial seperti Slim. Ini juga akan membutuhkan lebih banyak disiplin dari pihak pengembang untuk menjaga hal-hal tetap terorganisir dan terstruktur dengan baik.
- Flight memberi pengembang lebih banyak kontrol atas aplikasi, sedangkan Slim dapat menyelipkan beberapa sihir di belakang layar.
- Flight memiliki SimplePdo untuk akses database (lebih disukai daripada PdoWrapper yang sudah usang). Slim mengharuskan Anda menggunakan pustaka pihak ketiga.
- Flight memiliki plugin izin yang dapat digunakan untuk mengamankan aplikasi Anda. Slim mengharuskan Anda menggunakan pustaka pihak ketiga.
- Flight memiliki ORM bernama active-record yang dapat digunakan untuk berinteraksi dengan database Anda. Slim mengharuskan Anda menggunakan pustaka pihak ketiga.
- Flight memiliki aplikasi CLI bernama runway yang dapat digunakan untuk menjalankan aplikasi Anda dari baris perintah. Slim tidak memilikinya.
Learn/autoloading
Autoloading
Ringkasan
Autoloading adalah konsep dalam PHP di mana Anda menentukan direktori atau direktori-direktori untuk memuat kelas. Ini jauh lebih bermanfaat daripada menggunakan require atau include untuk memuat kelas. Ini juga merupakan persyaratan untuk menggunakan paket Composer.
Membuat autoloading benar itu penting untuk pengembangan berbantuan AI juga: agen menempatkan file di tempat namespace menunjuk. Jika huruf besar/kecil folder dan namespace tidak sesuai, kesalahan class-not-found akan muncul di Linux bahkan ketika hal-hal "berfungsi" di disk Mac yang tidak peka huruf besar/kecil.
Memahami
Secara default, kelas Flight apa pun di-autoload secara otomatis berkat Composer. Untuk kelas aplikasi Anda, ada dua pendekatan umum:
- Composer PSR-4 (yang digunakan oleh skeleton resmi): memetakan prefiks namespace ke direktori di
composer.json, lalucomposer dump-autoload. Flight::path(): mengarahkan loader Flight ke direktori-direktori (berguna untuk aplikasi sederhana atau saat Anda tidak menggunakan Composer untuk kode aplikasi).
Menggunakan autoloader menyederhanakan kode Anda banyak. Alih-alih dinding include / require di bagian atas setiap file, kelas dimuat saat pertama kali Anda menggunakannya.
Sensitivitas huruf besar/kecil (baca ini dua kali)
Namespace harus cocok dengan struktur direktori dan huruf besar/kecil direktori tersebut.
| Berfungsi | Rusak di Linux |
|---|---|
App\Controller\HomeController → app/Controller/HomeController.php |
App\Controller\… dengan folder app/controllers/ |
app\controllers\MyController → app/controllers/MyController.php |
Mencampur App\ dengan controllers huruf kecil |
Namespace PHP tidak peka huruf besar/kecil dalam beberapa konteks, tetapi Composer dan sistem file tidak. Skeleton resmi menstandarkan pada:
- Composer:
"App\\": "app/" - Folder:
Controller,Middleware,Model,Utils(PascalCase), bukancontrollers/middlewares
Dokumen lama dan contoh komunitas terkadang menggunakan app\controllers huruf kecil. Itu masih berfungsi jika folder Anda huruf kecil—tetapi proyek skeleton baru menggunakan App\ + folder PascalCase. Pilih satu konvensi per proyek dan patuhi agar manusia dan alat AI tidak menemukan tata letak kedua.
Skeleton (direkomendasikan untuk proyek baru)
Setelah composer create-project flightphp/skeleton, kode aplikasi di-autoload melalui Composer—tidak perlu Flight::path() untuk kelas App\:
{
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
}
// app/Controller/HomeController.php
namespace App\Controller;
use flight\Engine;
class HomeController
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function index(): void
{
$this->app->render('welcome', ['message' => 'Hello!']);
}
}
// app/config/routes.php — Dice menyelesaikan App\Controller\… melalui container
$router->get('/', [HomeController::class, 'index']);
Lihat Instalasi untuk pohon lengkap dan AI & pengalaman pengembang untuk cara AGENTS.md mendokumentasikan tata letak ini untuk asisten pengkodean.
Penggunaan Dasar (Flight::path())
Mari kita asumsikan kita memiliki pohon direktori seperti berikut:
# Contoh path
/home/user/project/my-flight-project/
├── app
│ ├── cache
│ ├── config
│ ├── controllers - berisi controller untuk proyek ini
│ ├── translations
│ ├── UTILS - berisi kelas untuk aplikasi ini saja (ini sengaja huruf kapital semua untuk contoh nanti)
│ └── views
└── public
└── css
└── js
└── index.php
Anda mungkin telah memperhatikan bahwa ini mirip dengan pohon aplikasi pada umumnya (situs dokumentasi itu sendiri menggunakan tata letak terstruktur). controllers huruf kecil di sini adalah pilihan yang valid—hanya saja bukan default skeleton saat ini.
Anda dapat menentukan setiap direktori untuk dimuat seperti ini:
/**
* public/index.php
*/
// Tambahkan path ke autoloader
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
/**
* app/controllers/MyController.php
*/
// tidak perlu namespace
// Semua kelas yang di-autoload disarankan menggunakan Pascal Case (setiap kata diawali huruf kapital, tanpa spasi)
class MyController {
public function index() {
// lakukan sesuatu
}
}
Namespace dengan Flight::path()
Jika Anda memiliki namespace, sebenarnya ini menjadi sangat mudah untuk diterapkan. Anda harus menggunakan metode Flight::path() untuk menentukan direktori root (bukan document root atau folder public/) dari aplikasi Anda.
/**
* public/index.php
*/
// Tambahkan path ke autoloader
Flight::path(__DIR__.'/../');
Sekarang ini adalah contoh tampilan controller Anda. Perhatikan contoh di bawah ini, tetapi perhatikan komentar untuk informasi penting.
/**
* app/controllers/MyController.php
*/
// namespace diperlukan
// namespace sama dengan struktur direktori
// namespace harus mengikuti huruf besar/kecil yang sama dengan struktur direktori
// namespace dan direktori tidak boleh memiliki garis bawah (kecuali Loader::setV2ClassLoading(false) diatur)
namespace app\controllers;
// Semua kelas yang di-autoload disarankan menggunakan Pascal Case (setiap kata diawali huruf kapital, tanpa spasi)
// Mulai 3.7.2, Anda dapat menggunakan Pascal_Snake_Case untuk nama kelas Anda dengan menjalankan Loader::setV2ClassLoading(false);
class MyController {
public function index() {
// lakukan sesuatu
}
}
Dan jika Anda ingin meng-autoload kelas di direktori utils Anda, Anda pada dasarnya melakukan hal yang sama:
/**
* app/UTILS/ArrayHelperUtil.php
*/
// namespace harus cocok dengan struktur direktori dan huruf besar/kecil (perhatikan direktori UTILS semuanya huruf kapital
// seperti di pohon file di atas)
namespace app\UTILS;
class ArrayHelperUtil {
public function changeArrayCase(array $array) {
// lakukan sesuatu
}
}
Namespace gaya skeleton (aturan yang sama, huruf besar/kecil berbeda)
/**
* app/Controller/MyController.php
*/
namespace App\Controller;
class MyController {
// ...
}
Aturannya tidak berubah—hanya huruf besar/kecil folder/namespace yang dipilih skeleton. Huruf besar/kecil apa pun yang digunakan folder Anda, baris namespace Anda harus cocok.
Garis Bawah dalam Nama Kelas
Mulai 3.7.2, Anda dapat menggunakan Pascal_Snake_Case untuk nama kelas Anda dengan menjalankan Loader::setV2ClassLoading(false);.
Ini akan memungkinkan Anda menggunakan garis bawah dalam nama kelas.
Ini tidak disarankan, tetapi tersedia bagi mereka yang membutuhkannya.
use flight\core\Loader;
/**
* public/index.php
*/
// Tambahkan path ke autoloader
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);
/**
* app/controllers/My_Controller.php
*/
// tidak perlu namespace
class My_Controller {
public function index() {
// lakukan sesuatu
}
}
Lihat Juga
- Instalasi - Pohon skeleton dan default
App\untuk proyek baru. - Routing - Cara memetakan rute ke controller dan merender tampilan.
- Dependency Injection - Cara controller mendapatkan
Enginedan layanan. - AI & Pengalaman Pengembang - Jaga agen tetap selaras dengan tata letak Anda melalui
AGENTS.md. - Mengapa Framework? - Memahami manfaat menggunakan framework seperti Flight.
Pemecahan Masalah
- Jika Anda tidak dapat mengetahui mengapa kelas ber-namespace Anda tidak ditemukan, ingat: dengan
Flight::path(), arahkan ke root proyek (atau basis yang benar untuk namespace Anda), bukan hanya folder bersarang yang Anda lupa untuk dicerminkan di namespace. - Dengan Composer PSR-4, jalankan
composer dump-autoloadsetelah mengubah pemetaancomposer.json. - Di CI Linux atau produksi, huruf besar/kecil folder yang salah adalah kegagalan "berfungsi di mesin saya" yang sangat umum.
Kelas Tidak Ditemukan (autoloading tidak berfungsi)
Mungkin ada beberapa alasan untuk hal ini. Di bawah ini adalah beberapa contoh.
Nama File Salah
Yang paling umum adalah nama kelas tidak cocok dengan nama file.
Jika Anda memiliki kelas bernama MyClass maka file harus bernama MyClass.php. Jika Anda memiliki kelas bernama MyClass dan file bernama myclass.php
maka autoloader tidak akan dapat menemukannya.
Namespace atau Huruf Besar/Kecil Folder Salah
Jika Anda menggunakan namespace, maka namespace harus cocok dengan struktur direktori termasuk huruf besar/kecil.
// ...kode...
// jika MyController Anda ada di app/Controller (skeleton) dan ber-namespace App\Controller
// ini tidak akan berfungsi:
Flight::route('/hello', 'MyController->hello');
// Gaya skeleton:
use App\Controller\MyController;
Flight::route('/hello', [ MyController::class, 'hello' ]);
// Tata letak huruf kecil lama (hanya jika folder Anda benar-benar app/controllers):
use app\controllers\MyController;
Flight::route('/hello', [ MyController::class, 'hello' ]);
// atau sepenuhnya memenuhi syarat:
Flight::route('/hello', [ 'App\Controller\MyController', 'hello' ]);
path() tidak ditentukan (kode aplikasi non-Composer)
Jika Anda mengandalkan Flight::path() alih-alih Composer untuk kelas aplikasi, tentukan path sebelum rute yang merujuk ke kelas tersebut (sering kali di awal bootstrap / public/index.php):
// Tambahkan path ke autoloader (root proyek untuk aplikasi ber-namespace)
Flight::path(__DIR__.'/../');
Skeleton resmi terutama menggunakan Composer PSR-4 untuk App\, jadi Anda biasanya tidak perlu Flight::path() untuk controller dan model di sana.
Changelog
- Dokumen – Mendokumentasikan skeleton
App\+ folder PascalCase dan jebakan sensitivitas huruf besar/kecil untuk manusia dan alat AI. - v3.7.2 - Anda dapat menggunakan Pascal_Snake_Case untuk nama kelas Anda dengan menjalankan
Loader::setV2ClassLoading(false); - v2.0 - Fungsionalitas Autoload ditambahkan.
Learn/uploaded_file
Penanganan File yang Diunggah
Gambaran Umum
Kelas UploadedFile di Flight memudahkan dan aman untuk menangani unggahan file dalam aplikasi Anda. Ini membungkus detail proses unggahan file PHP, memberikan cara yang sederhana dan berorientasi objek untuk mengakses informasi file dan memindahkan file yang diunggah.
Pemahaman
Ketika pengguna mengunggah file melalui formulir, PHP menyimpan informasi tentang file tersebut di superglobal $_FILES. Di Flight, Anda jarang berinteraksi langsung dengan $_FILES. Sebaliknya, objek Request milik Flight (dapat diakses melalui Flight::request()) menyediakan metode getUploadedFiles() yang mengembalikan array objek UploadedFile, membuat penanganan file jauh lebih nyaman dan kuat.
Kelas UploadedFile menyediakan metode untuk:
- Mendapatkan nama file asli, tipe MIME, ukuran, dan lokasi sementara
- Memeriksa kesalahan unggahan
- Memindahkan file yang diunggah ke lokasi permanen
Kelas ini membantu Anda menghindari kesalahan umum dengan unggahan file, seperti penanganan kesalahan atau memindahkan file dengan aman.
Penggunaan Dasar
Mengakses File yang Diunggah dari Permintaan
Cara yang direkomendasikan untuk mengakses file yang diunggah adalah melalui objek permintaan:
Flight::route('POST /upload', function() {
// Untuk field formulir bernama <input type="file" name="myFile">
$uploadedFiles = Flight::request()->getUploadedFiles();
$file = $uploadedFiles['myFile'];
// Sekarang Anda dapat menggunakan metode UploadedFile
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "File berhasil diunggah!";
} else {
echo "Unggahan gagal: " . $file->getError();
}
});
Menangani Beberapa Unggahan File
Jika formulir Anda menggunakan name="myFiles[]" untuk beberapa unggahan, Anda akan mendapatkan array objek UploadedFile:
Flight::route('POST /upload', function() {
// Untuk field formulir bernama <input type="file" name="myFiles[]">
$uploadedFiles = Flight::request()->getUploadedFiles();
foreach ($uploadedFiles['myFiles'] as $file) {
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "Diunggah: " . $file->getClientFilename() . "<br>";
} else {
echo "Gagal mengunggah: " . $file->getClientFilename() . "<br>";
}
}
});
Membuat Instance UploadedFile Secara Manual
Biasanya, Anda tidak akan membuat UploadedFile secara manual, tetapi Anda bisa jika diperlukan:
use flight\net\UploadedFile;
$file = new UploadedFile(
$_FILES['myfile']['name'],
$_FILES['myfile']['type'],
$_FILES['myfile']['size'],
$_FILES['myfile']['tmp_name'],
$_FILES['myfile']['error']
);
Mengakses Informasi File
Anda dapat dengan mudah mendapatkan detail tentang file yang diunggah:
echo $file->getClientFilename(); // Nama file asli dari komputer pengguna
echo $file->getClientMediaType(); // Tipe MIME (misalnya, image/png)
echo $file->getSize(); // Ukuran file dalam byte
echo $file->getTempName(); // Jalur file sementara di server
echo $file->getError(); // Kode kesalahan unggahan (0 berarti tidak ada kesalahan)
Memindahkan File yang Diunggah
Setelah memvalidasi file, pindahkan ke lokasi permanen:
try {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "File berhasil diunggah!";
} catch (Exception $e) {
echo "Unggahan gagal: " . $e->getMessage();
}
Metode moveTo() akan melempar pengecualian jika ada yang salah (seperti kesalahan unggahan atau masalah izin).
Menangani Kesalahan Unggahan
Jika ada masalah selama unggahan, Anda dapat mendapatkan pesan kesalahan yang dapat dibaca oleh manusia:
if ($file->getError() !== UPLOAD_ERR_OK) {
// Anda dapat menggunakan kode kesalahan atau menangkap pengecualian dari moveTo()
echo "Ada kesalahan saat mengunggah file.";
}
Lihat Juga
- Requests - Pelajari cara mengakses file yang diunggah dari permintaan HTTP dan lihat lebih banyak contoh unggahan file.
- Configuration - Cara mengonfigurasi batas unggahan dan direktori di PHP.
- Extending - Cara menyesuaikan atau memperluas kelas inti Flight.
Pemecahan Masalah
- Selalu periksa
$file->getError()sebelum memindahkan file. - Pastikan direktori unggahan Anda dapat ditulis oleh server web.
- Jika
moveTo()gagal, periksa pesan pengecualian untuk detail. - Pengaturan
upload_max_filesizedanpost_max_sizePHP dapat membatasi unggahan file. - Untuk beberapa unggahan file, selalu loop melalui array objek
UploadedFile.
Changelog
- v3.12.0 - Menambahkan kelas
UploadedFileke objek permintaan untuk penanganan file yang lebih mudah.
Guides/unit_testing
Pengujian Unit di Flight PHP dengan PHPUnit
Panduan ini memperkenalkan pengujian unit di Flight PHP menggunakan PHPUnit, ditujukan untuk pemula yang ingin memahami mengapa pengujian unit itu penting dan bagaimana menerapkannya secara praktis. Kami akan fokus pada pengujian perilaku—memastikan aplikasi Anda melakukan apa yang Anda harapkan, seperti mengirim email atau menyimpan data—bukan pada perhitungan sepele. Kami akan mulai dengan route handler sederhana dan berlanjut ke controller yang lebih kompleks, dengan menyertakan dependency injection (DI) dan mocking layanan pihak ketiga.
Mengapa Harus Pengujian Unit?
Pengujian unit memastikan kode Anda berperilaku sesuai yang diharapkan, menangkap bug sebelum masuk ke produksi. Ini sangat berharga di Flight, di mana routing yang ringan dan fleksibilitas dapat menyebabkan interaksi yang kompleks. Bagi pengembang individu atau tim, pengujian unit bertindak sebagai jaring pengaman, mendokumentasikan perilaku yang diharapkan dan mencegah regresi saat Anda meninjau kembali kode di kemudian hari. Pengujian unit juga meningkatkan desain: kode yang sulit diuji sering kali menandakan kelas yang terlalu kompleks atau terikat erat.
Berbeda dengan contoh sederhana (misalnya, menguji x * y = z), kami akan fokus pada perilaku dunia nyata, seperti memvalidasi input, menyimpan data, atau memicu tindakan seperti email. Tujuan kami adalah membuat pengujian mudah dipahami dan bermakna.
Prinsip Panduan Umum
- Uji Perilaku, Bukan Implementasi: Fokus pada hasil (misalnya, "email terkirim" atau "data tersimpan") daripada detail internal. Ini membuat pengujian tahan terhadap refactoring.
- Berhenti menggunakan
Flight::: Metode statis Flight sangat nyaman, tetapi membuat pengujian sulit. Anda harus terbiasa menggunakan variabel$appdari$app = Flight::app();.$appmemiliki semua metode yang sama denganFlight::. Anda tetap dapat menggunakan$app->route()atau$this->app->json()di controller Anda, dll. Anda juga harus menggunakan router Flight yang asli dengan$router = $app->router()dan kemudian Anda dapat menggunakan$router->get(),$router->post(),$router->group(), dll. Lihat Routing. - Jaga Pengujian Tetap Cepat: Pengujian yang cepat mendorong eksekusi yang sering. Hindari operasi lambat seperti panggilan basis data dalam pengujian unit. Jika Anda memiliki pengujian yang lambat, itu adalah tanda bahwa Anda sedang menulis pengujian integrasi, bukan pengujian unit. Pengujian integrasi adalah saat Anda benar-benar melibatkan basis data nyata, panggilan HTTP nyata, pengiriman email nyata, dll. Itu memiliki tempatnya, tetapi lambat dan bisa rapuh, artinya kadang gagal karena alasan yang tidak diketahui.
- Gunakan Nama yang Deskriptif: Nama pengujian harus menggambarkan dengan jelas perilaku yang diuji. Ini meningkatkan keterbacaan dan kemudahan pemeliharaan.
- Hindari Global Seperti Menghindari Wabah: Minimalkan penggunaan
$app->set()dan$app->get(), karena mereka bertindak seperti state global, mengharuskan mock di setiap pengujian. Lebih suka DI atau wadah DI (lihat Dependency Injection Container). Bahkan menggunakan metode$app->map()secara teknis adalah "global" dan harus dihindari demi DI. Gunakan pustaka sesi seperti flightphp/session sehingga Anda dapat mock objek sesi dalam pengujian Anda. Jangan memanggil$_SESSIONlangsung dalam kode Anda karena itu memasukkan variabel global ke dalam kode Anda, membuat pengujian menjadi sulit. - Gunakan Dependency Injection: Suntikkan dependensi (misalnya,
PDO, pengirim email) ke dalam controller untuk mengisolasi logika dan menyederhanakan mocking. Jika Anda memiliki kelas dengan terlalu banyak dependensi, pertimbangkan untuk refactoring menjadi kelas-kelas yang lebih kecil yang masing-masing memiliki satu tanggung jawab mengikuti prinsip SOLID. - Mock Layanan Pihak Ketiga: Mock basis data, klien HTTP (cURL), atau layanan email untuk menghindari panggilan eksternal. Uji satu atau dua lapisan ke dalam, tetapi biarkan logika inti Anda berjalan. Misalnya, jika aplikasi Anda mengirim pesan teks, Anda TIDAK ingin benar-benar mengirim pesan teks setiap kali menjalankan pengujian karena biayanya akan bertambah (dan akan lebih lambat). Sebagai gantinya, mock layanan pesan teks dan cukup verifikasi bahwa kode Anda memanggil layanan pesan teks dengan parameter yang benar.
- Targetkan Cakupan Tinggi, Bukan Kesempurnaan: Cakupan baris 100% itu bagus, tetapi tidak benar-benar berarti bahwa semua yang ada di kode Anda diuji sebagaimana mestinya (silahkan riset branch/path coverage di PHPUnit). Prioritaskan perilaku penting (misalnya, pendaftaran pengguna, respons API, dan menangkap respons yang gagal).
- Gunakan Controller untuk Routes: Dalam definisi route Anda, gunakan controller bukan closure.
flight\Engine $appdisuntikkan ke setiap controller melalui konstruktor secara default. Dalam pengujian, gunakan$app = new Flight\Engine()untuk membuat instance Flight dalam pengujian, suntikkan ke controller Anda, dan panggil metode secara langsung (misalnya,$controller->register()). Lihat Extending Flight dan Routing. - Pilih gaya mocking dan pertahankan: PHPUnit mendukung beberapa gaya mocking (misalnya, prophecy, mock bawaan), atau Anda dapat menggunakan kelas anonim yang memiliki manfaat sendiri seperti penyelesaian kode, gagal jika Anda mengubah definisi metode, dll. Tetaplah konsisten di seluruh pengujian Anda. Lihat PHPUnit Mock Objects.
- Gunakan visibilitas
protecteduntuk metode/properti yang ingin Anda uji di subclass: Ini memungkinkan Anda untuk menimpanya di subclass pengujian tanpa membuatnya public, ini sangat berguna untuk mock kelas anonim.
Menyiapkan PHPUnit
Pertama, siapkan PHPUnit di proyek Flight PHP Anda menggunakan Composer untuk pengujian yang mudah. Lihat panduan Memulai PHPUnit untuk detail lebih lanjut.
-
Di direktori proyek Anda, jalankan:
composer require --dev phpunit/phpunitIni menginstal PHPUnit terbaru sebagai dependensi pengembangan.
-
Buat direktori
testsdi akar proyek Anda untuk file pengujian. -
Tambahkan skrip pengujian ke
composer.jsonuntuk kenyamanan:// konten composer.json lainnya "scripts": { "test": "phpunit --configuration phpunit.xml" } -
Buat file
phpunit.xmldi akar:<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="vendor/autoload.php"> <testsuites> <testsuite name="Flight Tests"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
Sekarang setelah pengujian Anda dibuat, Anda dapat menjalankan composer test untuk mengeksekusi pengujian.
Menguji Penangan Route Sederhana
Mari kita mulai dengan route dasar yang memvalidasi input email pengguna. Kita akan menguji perilakunya: mengembalikan pesan sukses untuk email yang valid dan pesan error untuk email yang tidak valid. Untuk validasi email, kita menggunakan filter_var.
// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);
// UserController.php
class UserController {
protected $app;
public function __construct(flight\Engine $app) {
$this->app = $app;
}
public function register() {
$email = $this->app->request()->data->email;
$responseArray = [];
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$responseArray = ['status' => 'error', 'message' => 'Invalid email'];
} else {
$responseArray = ['status' => 'success', 'message' => 'Valid email'];
}
$this->app->json($responseArray);
}
}
Untuk menguji ini, buat file pengujian. Lihat Unit Testing dan Prinsip SOLID untuk lebih lanjut tentang menyusun pengujian:
// tests/UserControllerTest.php
use PHPUnit\Framework\TestCase;
use Flight;
use flight\Engine;
class UserControllerTest extends TestCase {
public function testValidEmailReturnsSuccess() {
$app = new Engine();
$request = $app->request();
$request->data->email = 'test@example.com'; // Simulasikan data POST
$UserController = new UserController($app);
$UserController->register($request->data->email);
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Valid email', $output['message']);
}
public function testInvalidEmailReturnsError() {
$app = new Engine();
$request = $app->request();
$request->data->email = 'invalid-email'; // Simulasikan data POST
$UserController = new UserController($app);
$UserController->register($request->data->email);
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('Invalid email', $output['message']);
}
}
Poin-Poin Penting:
- Kita mensimulasikan data POST menggunakan kelas request. Jangan gunakan global seperti
$_POST,$_GET, dll karena itu membuat pengujian lebih rumit (Anda harus selalu mengatur ulang nilai-nilai tersebut atau pengujian lain bisa gagal). - Semua controller secara default akan memiliki instance
flight\Engineyang disuntikkan ke dalamnya bahkan tanpa pengaturan wadah DI. Ini membuat pengujian controller secara langsung menjadi lebih mudah. - Tidak ada penggunaan
Flight::sama sekali, membuat kode lebih mudah diuji. - Pengujian memverifikasi perilaku: status dan pesan yang benar untuk email valid/tidak valid.
Jalankan composer test untuk memverifikasi bahwa route berperilaku sesuai yang diharapkan. Untuk lebih lanjut tentang requests dan responses di Flight, lihat dokumen terkait.
Menggunakan Dependency Injection untuk Controller yang Dapat Diuji
Untuk skenario yang lebih kompleks, gunakan dependency injection (DI) untuk membuat controller dapat diuji. Hindari global Flight (misalnya, Flight::set(), Flight::map(), Flight::register()) karena mereka bertindak seperti state global, mengharuskan mock untuk setiap pengujian. Sebagai gantinya, gunakan wadah DI Flight, DICE, PHP-DI atau DI manual.
Mari kita gunakan flight\database\SimplePdo alih-alih PDO mentah. Helper ini jauh lebih mudah untuk di-mock dan diuji unit (dan lebih disukai daripada PdoWrapper yang sudah usang).
Berikut adalah controller yang menyimpan pengguna ke basis data dan mengirim email selamat datang:
use flight\database\SimplePdo;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, SimplePdo $db, MailerInterface $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// menambahkan return di sini membantu pengujian unit untuk menghentikan eksekusi
return $this->app->jsonHalt(['status' => 'error', 'message' => 'Invalid email']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'User registered']);
}
}
Poin-Poin Penting:
- Controller bergantung pada instance
SimplePdodanMailerInterface(sebuah layanan email pihak ketiga pura-pura). - Dependensi disuntikkan melalui konstruktor, menghindari global.
Menguji Controller dengan Mock
Sekarang, mari kita uji perilaku UserController: memvalidasi email, menyimpan ke basis data, dan mengirim email. Kita akan mock basis data dan mailer untuk mengisolasi controller.
// tests/UserControllerDICTest.php
use flight\database\SimplePdo;
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
// Terkadang mencampur gaya mocking diperlukan
// Di sini kita menggunakan mock bawaan PHPUnit untuk PDOStatement
$statementMock = $this->createMock(PDOStatement::class);
$statementMock->method('execute')->willReturn(true);
// Menggunakan kelas anonim untuk mock SimplePdo
$mockDb = new class($statementMock) extends SimplePdo {
protected $statementMock;
public function __construct($statementMock) {
$this->statementMock = $statementMock;
}
// Saat kita mock dengan cara ini, kita tidak benar-benar melakukan panggilan basis data.
// Kita dapat mengatur ini lebih lanjut untuk mengubah mock PDOStatement guna mensimulasikan kegagalan, dll.
public function runQuery(string $sql, array $params = []): PDOStatement {
return $this->statementMock;
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
$this->sentEmail = $email;
return true;
}
};
$app = new Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserControllerDIC($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
public function testInvalidEmailSkipsSaveAndEmail() {
$mockDb = new class() extends SimplePdo {
// Sebuah konstruktor kosong melewati konstruktor induk
public function __construct() {}
public function runQuery(string $sql, array $params = []): PDOStatement {
throw new Exception('Should not be called');
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
throw new Exception('Should not be called');
}
};
$app = new Engine();
$app->request()->data->email = 'invalid-email';
// Perlu memetakan jsonHalt untuk menghindari keluar
$app->map('jsonHalt', function($data) use ($app) {
$app->json($data, 400);
});
$controller = new UserControllerDIC($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('error', $result['status']);
$this->assertEquals('Invalid email', $result['message']);
}
}
Poin-Poin Penting:
- Kita mock
SimplePdodanMailerInterfaceuntuk menghindari panggilan basis data atau email sungguhan. - Pengujian memverifikasi perilaku: email valid memicu penyisipan basis data dan pengiriman email; email tidak valid melewati keduanya.
- Mock dependensi pihak ketiga (misalnya,
SimplePdo,MailerInterface), membiarkan logika controller berjalan.
Terlalu Banyak Mocking
Berhati-hatilah untuk tidak terlalu banyak mem-mock kode Anda. Saya akan memberikan contoh di bawah ini tentang mengapa ini bisa menjadi hal yang buruk menggunakan UserController kita. Kita akan mengubah pemeriksaan itu menjadi metode bernama isEmailValid (menggunakan filter_var) dan penambahan baru lainnya menjadi metode terpisah bernama registerUser.
use flight\database\SimplePdo;
use flight\Engine;
// UserControllerDICV2.php
class UserControllerDICV2 {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, SimplePdo $db, MailerInterface $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!$this->isEmailValid($email)) {
// menambahkan return di sini membantu pengujian unit untuk menghentikan eksekusi
return $this->app->jsonHalt(['status' => 'error', 'message' => 'Invalid email']);
}
$this->registerUser($email);
$this->app->json(['status' => 'success', 'message' => 'User registered']);
}
protected function isEmailValid($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
protected function registerUser($email) {
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
}
}
Dan sekarang pengujian unit yang terlalu di-mock yang sebenarnya tidak menguji apa pun:
use PHPUnit\Framework\TestCase;
class UserControllerTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
$app = new Engine();
$app->request()->data->email = 'test@example.com';
// kita melewatkan dependency injection tambahan di sini karena itu "mudah"
$controller = new class($app) extends UserControllerDICV2 {
protected $app;
// Lewati dependensi di konstruktor
public function __construct($app) {
$this->app = $app;
}
// Kita akan memaksa ini menjadi valid.
protected function isEmailValid($email) {
return true; // Selalu kembalikan true, melewati validasi sungguhan
}
// Lewati panggilan DB dan mailer yang sebenarnya
protected function registerUser($email) {
return false;
}
};
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
}
}
Hore, kita punya pengujian unit dan semuanya lulus! Tetapi tunggu, bagaimana jika saya benar-benar mengubah cara kerja internal isEmailValid atau registerUser? Pengujian saya masih akan lulus karena saya sudah mem-mock semua fungsionalitas. Biarkan saya menunjukkan maksud saya.
// UserControllerDICV2.php
class UserControllerDICV2 {
// ... metode lainnya ...
protected function isEmailValid($email) {
// Logika yang diubah
$validEmail = filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
// Sekarang hanya boleh memiliki domain tertentu
$validDomain = strpos($email, '@example.com') !== false;
return $validEmail && $validDomain;
}
}
Jika saya menjalankan pengujian unit di atas, semuanya tetap lulus! Tetapi karena saya tidak menguji perilaku (benar-benar membiarkan sebagian kode berjalan), saya berpotensi membuat bug yang siap terjadi di produksi. Pengujian harus dimodifikasi untuk memperhitungkan perilaku baru, dan juga kebalikan dari saat perilaku tidak sesuai yang kita harapkan.
Contoh Lengkap
Anda dapat menemukan contoh lengkap proyek Flight PHP dengan pengujian unit di GitHub: n0nag0n/flight-unit-tests-guide. Untuk pemahaman yang lebih mendalam, lihat Unit Testing dan Prinsip SOLID.
Kesalahan Umum
- Terlalu Banyak Mocking: Jangan mem-mock setiap dependensi; biarkan sebagian logika (misalnya, validasi controller) berjalan untuk menguji perilaku nyata. Lihat Unit Testing dan Prinsip SOLID.
- State Global: Menggunakan variabel global PHP (misalnya,
$_SESSION,$_COOKIE) secara berlebihan membuat pengujian rapuh. Hal yang sama berlaku untukFlight::. Refactor untuk mengoper dependensi secara eksplisit. - Setup yang Rumit: Jika setup pengujian merepotkan, kelas Anda mungkin memiliki terlalu banyak dependensi atau tanggung jawab yang melanggar prinsip SOLID.
Meningkatkan Skala dengan Pengujian Unit
Pengujian unit sangat berguna dalam proyek yang lebih besar atau saat meninjau kembali kode setelah berbulan-bulan. Pengujian unit mendokumentasikan perilaku dan menangkap regresi, menghemat Anda dari belajar ulang aplikasi. Untuk pengembang solo, uji jalur kritis (misalnya, pendaftaran pengguna, pemrosesan pembayaran). Untuk tim, pengujian memastikan perilaku yang konsisten di seluruh kontribusi. Lihat Mengapa Framework? untuk lebih lanjut tentang manfaat menggunakan framework dan pengujian.
Kontribusikan tips pengujian Anda sendiri ke repositori dokumentasi Flight PHP!
Ditulis oleh n0nag0n 2025
Guides/blog
Membangun Blog Sederhana dengan Flight PHP
Panduan ini memandu Anda membuat blog dasar menggunakan framework PHP Flight. Anda akan menyiapkan proyek, mendefinisikan rute, mengelola posting dengan JSON, dan merendernya dengan mesin templat Latte—semuanya menunjukkan kesederhanaan dan fleksibilitas Flight. Pada akhirnya, Anda akan memiliki blog yang berfungsi dengan beranda, halaman posting individu, dan formulir pembuatan.
Prasyarat
- PHP 7.4+: Terinstal di sistem Anda.
- Composer: Untuk manajemen dependensi.
- Editor Teks: Editor apa pun seperti VS Code atau PHPStorm.
- Pengetahuan dasar tentang PHP dan pengembangan web.
Langkah 1: Siapkan Proyek Anda
Mulailah dengan membuat direktori proyek baru dan menginstal Flight melalui Composer.
-
Buat Direktori:
mkdir flight-blog cd flight-blog -
Instal Flight:
composer require flightphp/core -
Buat Direktori Publik: Flight menggunakan satu titik masuk (
index.php). Buat folderpublic/untuk itu:mkdir public -
index.phpDasar: Buatpublic/index.phpdengan rute "hello world" sederhana:<?php require '../vendor/autoload.php'; Flight::route('/', function () { echo 'Hello, Flight!'; }); Flight::start(); -
Jalankan Server Bawaan: Uji pengaturan Anda dengan server pengembangan PHP:
php -S localhost:8000 -t public/Kunjungi
http://localhost:8000untuk melihat "Hello, Flight!".
Langkah 2: Atur Struktur Proyek Anda
Untuk pengaturan yang rapi, susun proyek Anda seperti ini:
flight-blog/
├── app/
│ ├── config/
│ └── views/
├── data/
├── public/
│ └── index.php
├── vendor/
└── composer.json
app/config/: File konfigurasi (misalnya, events, routes).app/views/: Templat untuk merender halaman.data/: File JSON untuk menyimpan posting blog.public/: Root web denganindex.php.
Langkah 3: Instal dan Konfigurasi Latte
Latte adalah mesin templat ringan yang terintegrasi dengan baik dengan Flight.
-
Instal Latte:
composer require latte/latte -
Konfigurasi Latte di Flight: Perbarui
public/index.phpuntuk mendaftarkan Latte sebagai mesin tampilan:<?php require '../vendor/autoload.php'; use Latte\Engine; Flight::register('view', Engine::class, [], function ($latte) { $latte->setTempDirectory(__DIR__ . '/../cache/'); $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/')); }); Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => 'My Blog']); }); Flight::start(); -
Buat Templat Layout: Di
app/views/layout.latte:<!DOCTYPE html> <html> <head> <title>{$title}</title> </head> <body> <header> <h1>My Blog</h1> <nav> <a href="/">Beranda</a> | <a href="/create">Buat Posting</a> </nav> </header> <main> {block content}{/block} </main> <footer> <p>© {date('Y')} Flight Blog</p> </footer> </body> </html> -
Buat Templat Beranda: Di
app/views/home.latte:{extends 'layout.latte'} {block content} <h2>{$title}</h2> <ul> {foreach $posts as $post} <li><a href="/post/{$post['slug']}">{$post['title']}</a></li> {/foreach} </ul> {/block}Mulai ulang server jika Anda keluar dan kunjungi
http://localhost:8000untuk melihat halaman yang dirender. -
Buat File Data:
Gunakan file JSON untuk mensimulasikan database agar sederhana.
Di
data/posts.json:[ { "slug": "first-post", "title": "My First Post", "content": "This is my very first blog post with Flight PHP!" } ]
Langkah 4: Definisikan Rute
Pisahkan rute Anda ke dalam file konfigurasi untuk organisasi yang lebih baik.
-
Buat
routes.php: Diapp/config/routes.php:<?php Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => 'My Blog']); }); Flight::route('/post/@slug', function ($slug) { Flight::view()->render('post.latte', ['title' => 'Post: ' . $slug, 'slug' => $slug]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Buat Posting']); }); -
Perbarui
index.php: Sertakan file rute:<?php require '../vendor/autoload.php'; use Latte\Engine; Flight::register('view', Engine::class, [], function ($latte) { $latte->setTempDirectory(__DIR__ . '/../cache/'); $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/')); }); require '../app/config/routes.php'; Flight::start();
Langkah 5: Simpan dan Ambil Posting Blog
Tambahkan metode untuk memuat dan menyimpan posting.
-
Tambahkan Metode Posts: Di
index.php, tambahkan metode untuk memuat posting:Flight::map('posts', function () { $file = __DIR__ . '/../data/posts.json'; return json_decode(file_get_contents($file), true); }); -
Perbarui Rute: Ubah
app/config/routes.phpuntuk menggunakan posting:<?php Flight::route('/', function () { $posts = Flight::posts(); Flight::view()->render('home.latte', [ 'title' => 'My Blog', 'posts' => $posts ]); }); Flight::route('/post/@slug', function ($slug) { $posts = Flight::posts(); $post = array_filter($posts, fn($p) => $p['slug'] === $slug); $post = reset($post) ?: null; if (!$post) { Flight::notFound(); return; } Flight::view()->render('post.latte', [ 'title' => $post['title'], 'post' => $post ]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Buat Posting']); });
Langkah 6: Buat Templat
Perbarui templat Anda untuk menampilkan posting.
-
Halaman Posting (
app/views/post.latte):{extends 'layout.latte'} {block content} <h2>{$post['title']}</h2> <div class="post-content"> <p>{$post['content']}</p> </div> {/block}
Langkah 7: Tambahkan Pembuatan Posting
Tangani pengiriman formulir untuk menambahkan posting baru.
-
Buat Formulir (
app/views/create.latte):{extends 'layout.latte'} {block content} <h2>{$title}</h2> <form method="POST" action="/create"> <div class="form-group"> <label for="title">Judul:</label> <input type="text" name="title" id="title" required> </div> <div class="form-group"> <label for="content">Konten:</label> <textarea name="content" id="content" required></textarea> </div> <button type="submit">Simpan Posting</button> </form> {/block} -
Tambahkan Rute POST: Di
app/config/routes.php:Flight::route('POST /create', function () { $request = Flight::request(); $title = $request->data['title']; $content = $request->data['content']; $slug = strtolower(str_replace(' ', '-', $title)); $posts = Flight::posts(); $posts[] = ['slug' => $slug, 'title' => $title, 'content' => $content]; file_put_contents(__DIR__ . '/../../data/posts.json', json_encode($posts, JSON_PRETTY_PRINT)); Flight::redirect('/'); }); -
Uji Coba:
- Kunjungi
http://localhost:8000/create. - Kirim posting baru (misalnya, "Second Post" dengan beberapa konten).
- Periksa beranda untuk melihatnya terdaftar.
- Kunjungi
Langkah 8: Tingkatkan dengan Penanganan Kesalahan
Timpa metode notFound untuk pengalaman 404 yang lebih baik.
Di index.php:
Flight::map('notFound', function () {
Flight::view()->render('404.latte', ['title' => 'Halaman Tidak Ditemukan']);
});
Buat app/views/404.latte:
{extends 'layout.latte'}
{block content}
<h2>404 - {$title}</h2>
<p>Maaf, halaman itu tidak ada!</p>
{/block}
Langkah Berikutnya
- Tambahkan Gaya: Gunakan CSS di templat Anda untuk tampilan yang lebih baik.
- Database: Ganti
posts.jsondengan database seperti SQLite menggunakan SimplePdo. - Validasi: Tambahkan pemeriksaan untuk slug duplikat atau input kosong.
- Middleware: Terapkan autentikasi untuk pembuatan posting.
Kesimpulan
Anda telah membangun blog sederhana dengan Flight PHP! Panduan ini menunjukkan fitur inti seperti routing, templating dengan Latte, dan penanganan pengiriman formulir—semuanya dengan tetap ringan. Jelajahi dokumentasi Flight untuk fitur yang lebih lanjut guna membawa blog Anda lebih jauh!
License
Lisensi MIT (MIT)
Hak Cipta © 2024 @mikecao, @n0nag0n
Izin diberikan di sini, tanpa biaya, kepada setiap orang yang mendapatkan salinan perangkat lunak ini dan dokumentasi terkait (disebut "Perangkat Lunak"), untuk berurusan dengan Perangkat Lunak tanpa pembatasan, termasuk tanpa batasan hak untuk menggunakan, menyalin, memodifikasi, menggabungkan, menerbitkan, mendistribusikan, sublisensikan, dan/atau menjual salinan Perangkat Lunak, dan untuk mengizinkan orang-orang yang menerima Perangkat Lunak untuk melakukannya, dengan syarat berikut:
Pemberitahuan hak cipta di atas dan pemberitahuan izin ini harus disertakan dalam semua salinan atau bagian substansial dari Perangkat Lunak.
PERANGKAT LUNAK DIBERIKAN "SEBAGAIMANA ADANYA", TANPA JAMINAN DALAM BENTUK APAPUN, TERSURAT ATAU TERSIRAT, TERMASUK NAMUN TIDAK TERBATAS PADA JAMINAN KELAYAKAN PERDAGANGAN, KECOCOKAN UNTUK TUJUAN TERTENTU DAN TIDAK MELANGGAR. DALAM HAL APA PUN PENULIS ATAU PEMEGANG HAK CIPTA TIDAK BERTANGGUNG JAWAB ATAS CLAIM, KERUSAKAN ATAU TANGGUNG JAWAB LAINNYA, BAIK DALAM TINDAKAN KONTRAK, TORT ATAU SEBALIKNYA, YANG TIMBUL DARI, DARI ATAU SEHUBUNGAN DENGAN PERANGKAT LUNAK ATAU PENGGUNAAN ATAU TRANSAKSI LAINNYA DALAM PERANGKAT LUNAK.
About
Flight PHP Framework
Flight adalah framework PHP yang cepat, sederhana, dan dapat diperluas—dibangun untuk developer yang ingin menyelesaikan tugas dengan cepat tanpa ribet. Baik Anda membangun aplikasi web klasik, API berkecepatan tinggi, atau bekerja sama dengan asisten coding AI, desain ringan dan mudah dipahami dari Flight menjadikannya pilihan yang sempurna. Flight dirancang agar ramping, tetapi juga mampu menangani kebutuhan arsitektur enterprise.
Mengapa Memilih Flight?
- Ramah Pemula: Flight adalah titik awal yang bagus untuk developer PHP baru. Struktur yang jelas dan sintaks sederhana membantu Anda belajar pengembangan web tanpa terjebak dalam boilerplate.
- Disukai Profesional: Developer berpengalaman menyukai Flight karena fleksibilitas dan kontrolnya. Anda dapat menskalakan dari prototipe kecil hingga aplikasi lengkap tanpa perlu berganti framework.
- Kompatibel Mundur: Kami menghargai waktu Anda. Flight v3 adalah augmentasi dari v2, dengan hampir semua API yang sama. Kami percaya pada evolusi, bukan revolusi—tidak ada lagi "merusak dunia" setiap kali versi mayor dirilis.
- Tanpa Dependensi: Inti Flight benar-benar bebas dependensi—tidak ada polyfill, tidak ada paket eksternal, bahkan tidak ada antarmuka PSR. Ini berarti lebih sedikit vektor serangan, jejak yang lebih kecil, dan tidak ada perubahan yang merusak secara tiba-tiba dari dependensi upstream. Plugin opsional mungkin memiliki dependensi, tetapi inti akan selalu tetap ramping dan aman.
- Ramah AI: Permukaan API yang kecil dari Flight dan skeleton resmi (satu layout,
AGENTS.md, constructor injection) memudahkan tools coding AI untuk tetap on-pattern. Kode yang sama baik saat Anda mengetik setiap baris atau bekerja sama dengan agen. Pelajari lebih lanjut tentang menggunakan AI dengan Flight.
Gambaran Video
Mulai Cepat
Untuk instalasi bare bones yang cepat, instal dengan Composer:
composer require flightphp/core
Atau Anda dapat mengunduh zip dari repo di sini. Kemudian Anda akan memiliki file index.php dasar seperti berikut:
<?php
// jika diinstal dengan composer
require 'vendor/autoload.php';
// atau jika diinstal secara manual dengan file zip
// require 'flight/Flight.php';
Flight::route('/', function() {
echo 'hello world!';
});
Flight::route('/json', function() {
Flight::json([
'hello' => 'world'
]);
});
Flight::start();
Itu saja! Anda memiliki aplikasi Flight dasar. Sekarang Anda dapat menjalankan file ini dengan php -S localhost:8000 dan mengunjungi http://localhost:8000 di browser Anda untuk melihat hasilnya.
Contoh Flight:: yang singkat seperti ini bagus untuk pembelajaran dan aplikasi mikro. Untuk layout proyek lengkap yang digunakan bersama oleh manusia dan tools AI, gunakan skeleton di bawah ini.
Aplikasi Skeleton/Boilerplate
Ada starter resmi untuk membantu Anda memulai proyek Flight baru. Ini menyiapkan struktur, konfigurasi, skrip Composer, dan instruksi yang ramah AI sejak awal.
Lihat flightphp/skeleton untuk proyek siap pakai, atau kunjungi halaman contoh untuk inspirasi. Ingin detail workflow AI? Jelajahi AI & pengalaman developer.
Yang Anda dapatkan (tingkat tinggi):
- Namespace
App\dengan folder PascalCase (app/Controller/,app/Middleware/,app/Model/, …)—huruf besar kecil folder harus sesuai dengan namespace (lihat Autoloading) - Injeksi Dice +
Engineagar controller tetap dapat diuji (lebih suka$this->appdaripadaFlight::dalam kode aplikasi) - View Twig, contoh SimplePdo + ActiveRecord, Runway migrate
AGENTS.mdroot (plus salinan scoped) danSECURITY.mduntuk asisten dan kebijakan keamanan
Menginstal Aplikasi Skeleton
Cukup mudah!
# Buat proyek baru
composer create-project flightphp/skeleton my-project/
# Masuk ke direktori proyek baru Anda
cd my-project/
# Jalankan server pengembangan lokal untuk memulai!
composer start
Ini membuat struktur proyek, menyalin config_sample.php → config.php (dan .env.example → .env jika ada), dan Anda siap untuk mulai. Data contoh opsional:
php runway migrate
# kemudian kunjungi /posts dan /api/posts
Performa Tinggi
Flight adalah salah satu framework PHP tercepat yang ada. Inti yang ringan berarti overhead lebih sedikit dan kecepatan lebih tinggi—sempurna untuk aplikasi tradisional dan workflow modern yang dibantu AI. Anda dapat melihat semua benchmark di TechEmpower
Lihat benchmark di bawah ini dengan beberapa framework PHP populer lainnya.
| Framework | Plaintext Reqs/sec | JSON Reqs/sec |
|---|---|---|
| Flight | 190,421 | 182,491 |
| Yii | 145,749 | 131,434 |
| Fat-Free | 139,238 | 133,952 |
| Slim | 89,588 | 87,348 |
| Phalcon | 95,911 | 87,675 |
| Symfony | 65,053 | 63,237 |
| Lumen | 40,572 | 39,700 |
| Laravel | 26,657 | 26,901 |
| CodeIgniter | 20,628 | 19,901 |
Flight dan AI
Penasaran bagaimana Flight berpasangan dengan coding LLM? Temukan bagaimana AGENTS.md, perintah Runway ai:*, dan layout skeleton menjaga asisten tetap on track.
Stabilitas dan Kompatibilitas Mundur
Kami menghargai waktu Anda. Kami semua telah melihat framework yang benar-benar mengubah dirinya setiap beberapa tahun, meninggalkan developer dengan kode yang rusak dan migrasi yang mahal. Flight berbeda. Flight v3 dirancang sebagai augmentasi dari v2, yang berarti API yang Anda kenal dan sukai tidak dihilangkan. Bahkan, sebagian besar proyek v2 akan bekerja tanpa perubahan apapun di v3.
Kami berkomitmen untuk menjaga Flight tetap stabil sehingga Anda dapat fokus membangun aplikasi Anda, bukan memperbaiki framework Anda. Skeleton bisa bersifat opinionated untuk proyek baru; API inti tetap familiar untuk semua orang lain.
Komunitas
Kami di Matrix Chat
Dan Discord
Berkontribusi
Ada dua cara Anda dapat berkontribusi pada Flight:
- Berkontribusi pada framework inti dengan mengunjungi repositori core.
- Membantu membuat dokumentasi menjadi lebih baik! Situs dokumentasi ini di-hosting di Github. Jika Anda menemukan kesalahan atau ingin memperbaiki sesuatu, silakan ajukan pull request. Kami menyukai pembaruan dan ide baru—terutama seputar AI dan teknologi baru!
Persyaratan
Flight memerlukan PHP 7.4 atau yang lebih baru.
Catatan: PHP 7.4 didukung karena pada saat penulisan ini (2024) PHP 7.4 adalah versi default untuk beberapa distribusi Linux LTS. Memaksa perpindahan ke PHP >8 akan menyebabkan banyak masalah bagi pengguna tersebut. Framework ini juga mendukung PHP >8.
Lisensi
Flight dirilis di bawah lisensi MIT.
Awesome-plugins/php_cookie
Cookies
overclokk/cookie adalah perpustakaan sederhana untuk mengelola cookie dalam aplikasi Anda.
Instalasi
Instalasi sangat sederhana dengan composer.
composer require overclokk/cookie
Penggunaan
Penggunaan semudah mendaftarkan metode baru pada kelas Flight.
use Overclokk\Cookie\Cookie;
/*
* Set di file bootstrap atau public/index.php Anda
*/
Flight::register('cookie', Cookie::class);
/**
* ExampleController.php
*/
class ExampleController {
public function login() {
// Set sebuah cookie
// Anda ingin ini menjadi false agar Anda mendapatkan instance baru
// gunakan komentar di bawah jika Anda ingin autocomplete
/** @var \Overclokk\Cookie\Cookie $cookie */
$cookie = Flight::cookie(false);
$cookie->set(
'stay_logged_in', // nama cookie
'1', // nilai yang ingin Anda atur
86400, // jumlah detik cookie harus bertahan
'/', // jalur yang akan tersedia untuk cookie
'example.com', // domain yang akan tersedia untuk cookie
true, // cookie hanya akan ditransmisikan melalui koneksi HTTPS yang aman
true // cookie hanya akan tersedia melalui protokol HTTP
);
// opsional, jika Anda ingin mempertahankan nilai default
// dan memiliki cara cepat untuk mengatur cookie untuk waktu yang lama
$cookie->forever('stay_logged_in', '1');
}
public function home() {
// Periksa apakah Anda memiliki cookie
if (Flight::cookie()->has('stay_logged_in')) {
// tempatkan mereka di area dasbor misalnya.
Flight::redirect('/dashboard');
}
}
}Awesome-plugins/php_encryption
Enkripsi PHP
defuse/php-encryption adalah perpustakaan yang dapat digunakan untuk mengenkripsi dan mendekripsi data. Memulai dan menjalankan cukup sederhana untuk mulai mengenkripsi dan mendekripsi data. Mereka memiliki tutorial yang sangat membantu menjelaskan dasar-dasar cara menggunakan perpustakaan serta implikasi keamanan penting terkait enkripsi.
Instalasi
Instalasi sangat sederhana dengan composer.
composer require defuse/php-encryption
Pengaturan
Kemudian Anda perlu menghasilkan kunci enkripsi.
vendor/bin/generate-defuse-key
Ini akan menghasilkan kunci yang perlu Anda simpan dengan aman. Anda bisa menyimpan kunci di file app/config/config.php Anda di array di bagian bawah file. Meskipun itu bukan tempat yang sempurna, paling tidak itu adalah sesuatu.
Penggunaan
Sekarang Anda memiliki perpustakaan dan kunci enkripsi, Anda dapat mulai mengenkripsi dan mendekripsi data.
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
/*
* Tetapkan di file bootstrap atau public/index.php Anda
*/
// Metode enkripsi
Flight::map('encrypt', function($raw_data) {
$encryption_key = /* $config['encryption_key'] atau file_get_contents tempat Anda meletakkan kunci */;
return Crypto::encrypt($raw_data, Key::loadFromAsciiSafeString($encryption_key));
});
// Metode dekripsi
Flight::map('decrypt', function($encrypted_data) {
$encryption_key = /* $config['encryption_key'] atau file_get_contents tempat Anda meletakkan kunci */;
try {
$raw_data = Crypto::decrypt($encrypted_data, Key::loadFromAsciiSafeString($encryption_key));
} catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
// Sebuah serangan! Entah kunci yang salah dimuat, atau ciphertext telah
// berubah sejak dibuat -- baik rusak di database atau
// sengaja dimodifikasi oleh Eve yang mencoba melakukan serangan.
// ... tangani kasus ini dengan cara yang sesuai untuk aplikasi Anda ...
}
return $raw_data;
});
Flight::route('/encrypt', function() {
$encrypted_data = Flight::encrypt('Ini adalah rahasia');
echo $encrypted_data;
});
Flight::route('/decrypt', function() {
$encrypted_data = '...'; // Ambil data terenkripsi dari suatu tempat
$decrypted_data = Flight::decrypt($encrypted_data);
echo $decrypted_data;
});Awesome-plugins/php_file_cache
flightphp/cache
Kelas caching PHP dalam file yang ringan, sederhana, dan berdiri sendiri yang di-fork dari Wruczek/PHP-File-Cache
Keuntungan
- Ringan, berdiri sendiri, dan sederhana
- Semua kode dalam satu file - tidak ada driver yang tidak perlu
- Aman - setiap file cache yang dihasilkan memiliki header PHP dengan die, sehingga akses langsung tidak mungkin dilakukan meskipun seseorang mengetahui jalur dan server Anda tidak dikonfigurasi dengan benar
- Didokumentasikan dan diuji dengan baik
- Menangani konkurensi dengan benar melalui flock
- Mendukung PHP 7.4+
- Gratis di bawah lisensi MIT
Situs dokumentasi ini menggunakan library ini untuk meng-cache setiap halaman!
Klik di sini untuk melihat kode.
Instalasi
Instal melalui composer:
composer require flightphp/cache
Penggunaan
Penggunaan cukup mudah. Ini menyimpan file cache di direktori cache.
use flight\Cache;
$app = Flight::app();
// Anda melewatkan direktori tempat cache akan disimpan ke dalam konstruktor
$app->register('cache', Cache::class, [ __DIR__ . '/../cache/' ], function(Cache $cache) {
// Ini memastikan bahwa cache hanya digunakan saat dalam mode produksi
// ENVIRONMENT adalah konstanta yang diatur dalam file bootstrap Anda atau di tempat lain dalam aplikasi Anda
$cache->setDevMode(ENVIRONMENT === 'development');
});
Mendapatkan Nilai Cache
Anda menggunakan metode get() untuk mendapatkan nilai yang di-cache. Jika Anda menginginkan metode yang nyaman yang akan menyegarkan cache jika sudah kedaluwarsa, Anda dapat menggunakan refreshIfExpired().
// Dapatkan instance cache
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
return date("H:i:s"); // mengembalikan data yang akan di-cache
}, 10); // 10 detik
// atau
$data = $cache->get('simple-cache-test');
if(empty($data)) {
$data = date("H:i:s");
$cache->set('simple-cache-test', $data, 10); // 10 detik
}
Menyimpan Nilai Cache
Anda menggunakan metode set() untuk menyimpan nilai dalam cache.
Flight::cache()->set('simple-cache-test', 'my cached data', 10); // 10 detik
Menghapus Nilai Cache
Anda menggunakan metode delete() untuk menghapus nilai dalam cache.
Flight::cache()->delete('simple-cache-test');
Memeriksa apakah Nilai Cache Ada
Anda menggunakan metode exists() untuk memeriksa apakah nilai ada dalam cache.
if(Flight::cache()->exists('simple-cache-test')) {
// lakukan sesuatu
}
Membersihkan Cache
Anda menggunakan metode flush() untuk membersihkan seluruh cache.
Flight::cache()->flush();
Mengambil meta data dengan cache
Jika Anda ingin mengambil timestamp dan meta data lainnya tentang entri cache, pastikan Anda melewatkan true sebagai parameter yang benar.
$data = $cache->refreshIfExpired("simple-cache-meta-test", function () {
echo "Refreshing data!" . PHP_EOL;
return date("H:i:s"); // mengembalikan data yang akan di-cache
}, 10, true); // true = mengembalikan dengan metadata
// atau
$data = $cache->get("simple-cache-meta-test", true); // true = mengembalikan dengan metadata
/*
Contoh item cache yang diambil dengan metadata:
{
"time":1511667506, <-- unix timestamp saat disimpan
"expire":10, <-- waktu kedaluwarsa dalam detik
"data":"04:38:26", <-- data yang tidak di-serialize
"permanent":false
}
Menggunakan metadata, kita dapat, misalnya, menghitung kapan item disimpan atau kapan kedaluwarsa
Kita juga dapat mengakses data itu sendiri dengan kunci "data"
*/
$expiresin = ($data["time"] + $data["expire"]) - time(); // mendapatkan unix timestamp ketika data kedaluwarsa dan mengurangi timestamp saat ini darinya
$cacheddate = $data["data"]; // kita mengakses data itu sendiri dengan kunci "data"
echo "Penyimpanan cache terbaru: $cacheddate, kedaluwarsa dalam $expiresin detik";
Kode Sumber
Kunjungi https://github.com/flightphp/cache untuk melihat kode.
Awesome-plugins/permissions
FlightPHP/Permissions
Ini adalah modul izin yang dapat digunakan dalam proyek Anda jika Anda memiliki beberapa peran dalam aplikasi Anda dan setiap peran memiliki fungsionalitas yang sedikit berbeda. Modul ini memungkinkan Anda untuk mendefinisikan izin untuk setiap peran dan kemudian memeriksa apakah pengguna saat ini memiliki izin untuk mengakses halaman tertentu atau melakukan tindakan tertentu.
Klik di sini untuk repositori di GitHub.
Instalasi
Jalankan composer require flightphp/permissions dan Anda siap!
Penggunaan
Pertama Anda perlu menyiapkan izin Anda, kemudian Anda memberitahu aplikasi Anda apa arti izin tersebut. Pada akhirnya Anda akan memeriksa izin Anda dengan $Permissions->has(), ->can(), atau is(). has() dan can() memiliki fungsionalitas yang sama, tetapi dinamai berbeda untuk membuat kode Anda lebih mudah dibaca.
Contoh Dasar
Mari kita asumsikan Anda memiliki fitur dalam aplikasi Anda yang memeriksa apakah pengguna sudah masuk. Anda dapat membuat objek izin seperti ini:
// index.php
require 'vendor/autoload.php';
// some code
// then you probably have something that tells you who the current role is of the person
// likely you have something where you pull the current role
// from a session variable which defines this
// after someone logs in, otherwise they will have a 'guest' or 'public' role.
$current_role = 'admin';
// setup permissions
$permission = new \flight\Permission($current_role);
$permission->defineRule('loggedIn', function($current_role) {
return $current_role !== 'guest';
});
// You'll probably want to persist this object in Flight somewhere
Flight::set('permission', $permission);
Kemudian di suatu pengontrol, Anda mungkin memiliki sesuatu seperti ini.
<?php
// some controller
class SomeController {
public function someAction() {
$permission = Flight::get('permission');
if ($permission->has('loggedIn')) {
// do something
} else {
// do something else
}
}
}
Anda juga dapat menggunakan ini untuk melacak apakah mereka memiliki izin untuk melakukan sesuatu dalam aplikasi Anda. Misalnya, jika Anda memiliki cara bagi pengguna untuk berinteraksi dengan posting di perangkat lunak Anda, Anda dapat memeriksa apakah mereka memiliki izin untuk melakukan tindakan tertentu.
$current_role = 'admin';
// setup permissions
$permission = new \flight\Permission($current_role);
$permission->defineRule('post', function($current_role) {
if($current_role === 'admin') {
$permissions = ['create', 'read', 'update', 'delete'];
} else if($current_role === 'editor') {
$permissions = ['create', 'read', 'update'];
} else if($current_role === 'author') {
$permissions = ['create', 'read'];
} else if($current_role === 'contributor') {
$permissions = ['create'];
} else {
$permissions = [];
}
return $permissions;
});
Flight::set('permission', $permission);
Kemudian di suatu pengontrol...
class PostController {
public function create() {
$permission = Flight::get('permission');
if ($permission->can('post.create')) {
// do something
} else {
// do something else
}
}
}
Menyuntikkan dependensi
Anda dapat menyuntikkan dependensi ke dalam closure yang mendefinisikan izin. Ini berguna jika Anda memiliki semacam toggle, id, atau titik data lain yang ingin Anda periksa. Hal yang sama berlaku untuk panggilan tipe Class->Method, kecuali Anda mendefinisikan argumen dalam metode.
Closures
$Permission->defineRule('order', function(string $current_role, MyDependency $MyDependency = null) {
// ... code
});
// in your controller file
public function createOrder() {
$MyDependency = Flight::myDependency();
$permission = Flight::get('permission');
if ($permission->can('order.create', $MyDependency)) {
// do something
} else {
// do something else
}
}
Classes
namespace MyApp;
class Permissions {
public function order(string $current_role, MyDependency $MyDependency = null) {
// ... code
}
}
Pintasan untuk mengatur izin dengan kelas
Anda juga dapat menggunakan kelas untuk mendefinisikan izin Anda. Ini berguna jika Anda memiliki banyak izin dan ingin menjaga kode Anda tetap bersih. Anda dapat melakukan sesuatu seperti ini:
<?php
// bootstrap code
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRule('order', 'MyApp\Permissions->order');
// myapp/Permissions.php
namespace MyApp;
class Permissions {
public function order(string $current_role, int $user_id) {
// Assuming you set this up beforehand
/** @var \flight\database\SimplePdo $db */
$db = Flight::db();
$allowed_permissions = [ 'read' ]; // everyone can view an order
if($current_role === 'manager') {
$allowed_permissions[] = 'create'; // managers can create orders
}
$some_special_toggle_from_db = $db->fetchField('SELECT some_special_toggle FROM settings WHERE id = ?', [ $user_id ]);
if($some_special_toggle_from_db) {
$allowed_permissions[] = 'update'; // if the user has a special toggle, they can update orders
}
if($current_role === 'admin') {
$allowed_permissions[] = 'delete'; // admins can delete orders
}
return $allowed_permissions;
}
}
Hal yang keren adalah ada juga pintasan yang dapat Anda gunakan (yang juga dapat di-cache!!!) di mana Anda hanya memberitahu kelas izin untuk memetakan semua metode dalam sebuah kelas menjadi izin. Jadi jika Anda memiliki metode bernama order() dan metode bernama company(), ini akan secara otomatis dipetakan sehingga Anda hanya dapat menjalankan $Permissions->has('order.read') atau $Permissions->has('company.read') dan itu akan berhasil. Mendefinisikan ini sangat rumit, jadi ikuti saya di sini. Anda hanya perlu melakukan ini:
Buat kelas izin yang ingin Anda kelompokkan bersama.
class MyPermissions {
public function order(string $current_role, int $order_id = 0): array {
// code to determine permissions
return $permissions_array;
}
public function company(string $current_role, int $company_id): array {
// code to determine permissions
return $permissions_array;
}
}
Kemudian buat izin dapat ditemukan menggunakan library ini.
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class);
Flight::set('permissions', $Permissions);
Akhirnya, panggil izin dalam basis kode Anda untuk memeriksa apakah pengguna diizinkan untuk melakukan izin tertentu.
class SomeController {
public function createOrder() {
if(Flight::get('permissions')->can('order.create') === false) {
die('You can\'t create an order. Sorry!');
}
}
}
Caching
Untuk mengaktifkan caching, lihat library sederhana wruczak/phpfilecache. Contoh mengaktifkan ini di bawah ini.
// this $app can be part of your code, or
// you can just pass null and it will
// pull from Flight::app() in the constructor
$app = Flight::app();
// For now it accepts this as a file cache. Others can easily
// be added in the future.
$Cache = new Wruczek\PhpFileCache\PhpFileCache;
$Permissions = new \flight\Permission($current_role, $app, $Cache);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class, 3600); // 3600 is how many seconds to cache this for. Leave this off to not use caching
Dan Anda siap!
Awesome-plugins/simple_job_queue
Antrean Pekerjaan Sederhana
Antrean Pekerjaan Sederhana adalah sebuah pustaka yang dapat digunakan untuk memproses pekerjaan secara asinkron. Ini dapat digunakan dengan beanstalkd, MySQL/MariaDB, SQLite, dan PostgreSQL.
Instal
composer require n0nag0n/simple-job-queue
Penggunaan
Agar ini dapat berfungsi, Anda memerlukan cara untuk menambahkan pekerjaan ke antrean dan cara untuk memproses pekerjaan (pekerja). Berikut adalah contoh tentang cara menambahkan pekerjaan ke antrean dan cara memproses pekerjaan.
Menambahkan ke Flight
Menambahkan ini ke Flight sangat sederhana dan dilakukan dengan menggunakan metode register(). Berikut adalah contoh cara menambahkan ini ke Flight.
<?php
require 'vendor/autoload.php';
// Ubah ['mysql'] menjadi ['beanstalkd'] jika Anda ingin menggunakan beanstalkd
Flight::register('queue', n0nag0n\Job_Queue::class, ['mysql'], function($Job_Queue) {
// jika Anda sudah memiliki koneksi PDO di Flight::db();
$Job_Queue->addQueueConnection(Flight::db());
// atau jika Anda menggunakan beanstalkd/Pheanstalk
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);
});
Menambahkan pekerjaan baru
Saat Anda menambahkan pekerjaan, Anda perlu menentukan sebuah pipeline (antrean). Ini sebanding dengan sebuah saluran di RabbitMQ atau sebuah tabung di beanstalkd.
<?php
Flight::queue()->selectPipeline('send_important_emails');
Flight::queue()->addJob(json_encode([ 'something' => 'that', 'ends' => 'up', 'a' => 'string' ]));
Menjalankan seorang pekerja
Berikut adalah contoh file tentang cara menjalankan seorang pekerja.
<?php
require 'vendor/autoload.php';
$Job_Queue = new n0nag0n\Job_Queue('mysql');
// Koneksi PDO
$PDO = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'user', 'pass');
$Job_Queue->addQueueConnection($PDO);
// atau jika Anda menggunakan beanstalkd/Pheanstalk
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);
$Job_Queue->watchPipeline('send_important_emails');
while(true) {
$job = $Job_Queue->getNextJobAndReserve();
// sesuaikan dengan apa pun yang membuat Anda tidur lebih nyenyak di malam hari (hanya untuk antrean basis data, beanstalkd tidak memerlukan pernyataan if ini)
if(empty($job)) {
usleep(500000);
continue;
}
echo "Memproses {$job['id']}\n";
$payload = json_decode($job['payload'], true);
try {
$result = doSomethingThatDoesSomething($payload);
if($result === true) {
$Job_Queue->deleteJob($job);
} else {
// ini mengeluarkannya dari antrean siap dan menempatkannya dalam antrean lain yang dapat diambil dan "dikejutkan" nanti.
$Job_Queue->buryJob($job);
}
} catch(Exception $e) {
$Job_Queue->buryJob($job);
}
}
Menangani Proses Panjang dengan Supervisord
Supervisord adalah sistem kontrol proses yang memastikan bahwa proses pekerja Anda tetap berjalan terus-menerus. Berikut adalah panduan yang lebih lengkap tentang cara mengaturnya dengan pekerja Antrean Pekerjaan Sederhana Anda:
Menginstal Supervisord
# Di Ubuntu/Debian
sudo apt-get install supervisor
# Di CentOS/RHEL
sudo yum install supervisor
# Di macOS dengan Homebrew
brew install supervisor
Membuat Skrip Pekerja
Pertama, simpan kode pekerja Anda ke dalam file PHP yang didedikasikan:
<?php
require 'vendor/autoload.php';
$Job_Queue = new n0nag0n\Job_Queue('mysql');
// Koneksi PDO
$PDO = new PDO('mysql:dbname=your_database;host=127.0.0.1', 'username', 'password');
$Job_Queue->addQueueConnection($PDO);
// Tentukan pipeline untuk diawasi
$Job_Queue->watchPipeline('send_important_emails');
// Catat awal pekerja
echo date('Y-m-d H:i:s') . " - Pekerja dimulai\n";
while(true) {
$job = $Job_Queue->getNextJobAndReserve();
if(empty($job)) {
usleep(500000); // Tidur selama 0.5 detik
continue;
}
echo date('Y-m-d H:i:s') . " - Memproses pekerjaan {$job['id']}\n";
$payload = json_decode($job['payload'], true);
try {
$result = doSomethingThatDoesSomething($payload);
if($result === true) {
$Job_Queue->deleteJob($job);
echo date('Y-m-d H:i:s') . " - Pekerjaan {$job['id']} berhasil diselesaikan\n";
} else {
$Job_Queue->buryJob($job);
echo date('Y-m-d H:i:s') . " - Pekerjaan {$job['id']} gagal, dibuang\n";
}
} catch(Exception $e) {
$Job_Queue->buryJob($job);
echo date('Y-m-d H:i:s') . " - Pengecualian saat memproses pekerjaan {$job['id']}: {$e->getMessage()}\n";
}
}
Mengkonfigurasi Supervisord
Buat file konfigurasi untuk pekerja Anda:
[program:email_worker]
command=php /path/to/worker.php
directory=/path/to/project
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/simple_job_queue_err.log
stdout_logfile=/var/log/simple_job_queue.log
user=www-data
numprocs=2
process_name=%(program_name)s_%(process_num)02d
Opsi Konfigurasi Utama:
command: Perintah untuk menjalankan pekerja Andadirectory: Direktori kerja untuk pekerjaautostart: Mulai secara otomatis saat supervisord dimulaiautorestart: Mulai ulang secara otomatis jika proses keluarstartretries: Jumlah kali untuk mencoba memulai jika gagalstderr_logfile/stdout_logfile: Lokasi file loguser: Pengguna sistem untuk menjalankan prosesnumprocs: Jumlah instance pekerja yang akan dijalankanprocess_name: Format penamaan untuk beberapa proses pekerja
Mengelola Pekerja dengan Supervisorctl
Setelah membuat atau mengubah konfigurasi:
# Muat ulang konfigurasi supervisor
sudo supervisorctl reread
sudo supervisorctl update
# Kontrol proses pekerja tertentu
sudo supervisorctl start email_worker:*
sudo supervisorctl stop email_worker:*
sudo supervisorctl restart email_worker:*
sudo supervisorctl status email_worker:*
Menjalankan Beberapa Pipeline
Untuk beberapa pipeline, buat file pekerja dan konfigurasi terpisah:
[program:email_worker]
command=php /path/to/email_worker.php
# ... konfigurasi lainnya ...
[program:notification_worker]
command=php /path/to/notification_worker.php
# ... konfigurasi lainnya ...
Memantau dan Log
Periksa log untuk memantau aktivitas pekerja:
# Lihat log
sudo tail -f /var/log/simple_job_queue.log
# Periksa status
sudo supervisorctl status
Pengaturan ini memastikan pekerja pekerjaan Anda terus berjalan meskipun setelah kerusakan, reboot server, atau masalah lainnya, menjadikan sistem antrean Anda andal untuk lingkungan produksi.
Awesome-plugins/jwt
Firebase JWT - Autentikasi JSON Web Token
JWT (JSON Web Tokens) adalah cara yang ringkas dan aman untuk URL untuk merepresentasikan klaim antara aplikasi Anda dan klien. Mereka sempurna untuk autentikasi API tanpa state—tidak perlu penyimpanan sesi di sisi server! Panduan ini menunjukkan cara mengintegrasikan Firebase JWT dengan Flight untuk autentikasi berbasis token yang aman.
Kunjungi repositori Github untuk dokumentasi lengkap dan detail.
Apa itu JWT?
JSON Web Token adalah string yang berisi tiga bagian:
- Header: Metadata tentang token (algoritma, tipe)
- Payload: Data Anda (ID pengguna, peran, kedaluwarsa, dll.)
- Signature: Tanda tangan kriptografis untuk memverifikasi keaslian
Contoh JWT: eyJ0eXAiOiJKV1QiLCJhbGc... (terlihat seperti omong kosong, tapi itu data terstruktur!)
Mengapa Menggunakan JWT?
- Tanpa State: Tidak perlu penyimpanan sesi di sisi server—sempurna untuk microservices dan API
- Skalabel: Bekerja dengan baik dengan load balancer karena tidak ada persyaratan affinity sesi
- Cross-Domain: Dapat digunakan di berbagai domain dan layanan
- Ramah Mobile: Bagus untuk aplikasi mobile di mana cookie mungkin tidak bekerja dengan baik
- Standar: Pendekatan standar industri (RFC 7519)
Instalasi
Instal melalui Composer:
composer require firebase/php-jwt
Penggunaan Dasar
Berikut contoh cepat untuk membuat dan memverifikasi JWT:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// Kunci rahasia Anda (JAGA INI AMAN!)
$secretKey = 'your-256-bit-secret-key-here-keep-it-safe';
// Buat token
$payload = [
'user_id' => 123,
'username' => 'johndoe',
'role' => 'admin',
'iat' => time(), // Diterbitkan pada
'exp' => time() + 3600 // Kedaluwarsa dalam 1 jam
];
$jwt = JWT::encode($payload, $secretKey, 'HS256');
echo "Token: " . $jwt;
// Verifikasi dan dekode token
try {
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
echo "User ID: " . $decoded->user_id;
} catch (Exception $e) {
echo "Token tidak valid: " . $e->getMessage();
}
Middleware JWT untuk Flight (Pendekatan yang Direkomendasikan)
Cara paling umum dan berguna untuk menggunakan JWT dengan Flight adalah sebagai middleware untuk melindungi rute API Anda. Berikut contoh lengkap yang siap produksi:
Langkah 1: Buat Kelas Middleware JWT
// app/middleware/JwtMiddleware.php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use flight\Engine;
class JwtMiddleware {
protected Engine $app;
protected string $secretKey;
public function __construct(Engine $app) {
$this->app = $app;
// Simpan kunci rahasia Anda di app/config/config.php, BUKAN hardcoded!
$this->secretKey = $app->get('config')['jwt_secret'];
}
public function before(array $params) {
$authHeader = $this->app->request()->getHeader('Authorization');
// Periksa apakah header Authorization ada
if (empty($authHeader)) {
$this->app->jsonHalt(['error' => 'Tidak ada token otorisasi yang disediakan'], 401);
}
// Ekstrak token dari format "Bearer <token>"
if (!preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
$this->app->jsonHalt(['error' => 'Format otorisasi tidak valid. Gunakan: Bearer <token>'], 401);
}
$jwt = $matches[1];
try {
// Dekode dan verifikasi token
$decoded = JWT::decode($jwt, new Key($this->secretKey, 'HS256'));
// Simpan data pengguna di request untuk digunakan di handler rute
$this->app->request()->data->user = $decoded;
} catch (ExpiredException $e) {
$this->app->jsonHalt(['error' => 'Token telah kedaluwarsa'], 401);
} catch (SignatureInvalidException $e) {
$this->app->jsonHalt(['error' => 'Tanda tangan token tidak valid'], 401);
} catch (Exception $e) {
$this->app->jsonHalt(['error' => 'Token tidak valid: ' . $e->getMessage()], 401);
}
}
}
Langkah 2: Daftarkan Kunci Rahasia JWT di Konfigurasi Anda
// app/config/config.php
return [
'jwt_secret' => getenv('JWT_SECRET') ?: 'your-fallback-secret-for-development'
];
// app/config/bootstrap.php atau index.php
// pastikan untuk menambahkan baris ini jika Anda ingin mengekspos konfigurasi ke aplikasi
$app->set('config', $config);
Catatan Keamanan: Jangan pernah hardcoded kunci rahasia Anda! Gunakan variabel lingkungan di produksi.
Langkah 3: Lindungi Rute Anda dengan Middleware
// Lindungi satu rute
Flight::route('GET /api/user/profile', function() {
$user = Flight::request()->data->user; // Ditetapkan oleh middleware
Flight::json([
'user_id' => $user->user_id,
'username' => $user->username,
'role' => $user->role
]);
})->addMiddleware(JwtMiddleware::class);
// Lindungi seluruh grup rute (lebih umum!)
Flight::group('/api', function() {
Flight::route('GET /users', function() { /* ... */ });
Flight::route('GET /posts', function() { /* ... */ });
Flight::route('POST /posts', function() { /* ... */ });
Flight::route('DELETE /posts/@id', function($id) { /* ... */ });
}, [ JwtMiddleware::class ]); // Semua rute di grup ini dilindungi!
Untuk detail lebih lanjut tentang middleware, lihat dokumentasi middleware.
Kasus Penggunaan Umum
1. Endpoint Login (Pembuatan Token)
Buat rute yang menghasilkan JWT setelah autentikasi berhasil:
Flight::route('POST /api/login', function() {
$data = Flight::request()->data;
$username = $data->username ?? '';
$password = $data->password ?? '';
// Validasi kredensial (contoh - gunakan logika Anda sendiri!)
$user = validateUserCredentials($username, $password);
if (!$user) {
Flight::jsonHalt(['error' => 'Kredensial tidak valid'], 401);
}
// Hasilkan JWT
$secretKey = Flight::get('config')['jwt_secret'];
$payload = [
'user_id' => $user->id,
'username' => $user->username,
'role' => $user->role,
'iat' => time(),
'exp' => time() + (60 * 60) // Kedaluwarsa 1 jam
];
$jwt = JWT::encode($payload, $secretKey, 'HS256');
Flight::json([
'success' => true,
'token' => $jwt,
'expires_in' => 3600
]);
});
function validateUserCredentials($username, $password) {
// Pencarian database dan verifikasi kata sandi Anda di sini
// Contoh:
$db = Flight::db();
$user = $db->fetchRow("SELECT * FROM users WHERE username = ?", [$username]);
if ($user && password_verify($password, $user['password_hash'])) {
return (object) [
'id' => $user['id'],
'username' => $user['username'],
'role' => $user['role']
];
}
return null;
}
2. Alur Pembaruan Token
Implementasikan sistem token pembaruan untuk sesi yang panjang:
Flight::route('POST /api/login', function() {
// ... validasi kredensial ...
$secretKey = Flight::get('config')['jwt_secret'];
$refreshSecret = Flight::get('config')['jwt_refresh_secret'];
// Token akses jangka pendek (15 menit)
$accessToken = JWT::encode([
'user_id' => $user->id,
'type' => 'access',
'iat' => time(),
'exp' => time() + (15 * 60)
], $secretKey, 'HS256');
// Token pembaruan jangka panjang (7 hari)
$refreshToken = JWT::encode([
'user_id' => $user->id,
'type' => 'refresh',
'iat' => time(),
'exp' => time() + (7 * 24 * 60 * 60)
], $refreshSecret, 'HS256');
Flight::json([
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_in' => 900
]);
});
Flight::route('POST /api/refresh', function() {
$refreshToken = Flight::request()->data->refresh_token ?? '';
$refreshSecret = Flight::get('config')['jwt_refresh_secret'];
try {
$decoded = JWT::decode($refreshToken, new Key($refreshSecret, 'HS256'));
// Verifikasi ini adalah token pembaruan
if ($decoded->type !== 'refresh') {
Flight::jsonHalt(['error' => 'Tipe token tidak valid'], 401);
}
// Hasilkan token akses baru
$secretKey = Flight::get('config')['jwt_secret'];
$accessToken = JWT::encode([
'user_id' => $decoded->user_id,
'type' => 'access',
'iat' => time(),
'exp' => time() + (15 * 60)
], $secretKey, 'HS256');
Flight::json([
'access_token' => $accessToken,
'expires_in' => 900
]);
} catch (Exception $e) {
Flight::jsonHalt(['error' => 'Token pembaruan tidak valid'], 401);
}
});
3. Kontrol Akses Berdasarkan Peran
Perluas middleware Anda untuk memeriksa peran pengguna:
class JwtRoleMiddleware {
protected Engine $app;
protected array $allowedRoles;
public function __construct(Engine $app, array $allowedRoles = []) {
$this->app = $app;
$this->allowedRoles = $allowedRoles;
}
public function before(array $params) {
// Asumsikan JwtMiddleware sudah berjalan dan menetapkan data pengguna
$user = $this->app->request()->data->user ?? null;
if (!$user) {
$this->app->jsonHalt(['error' => 'Autentikasi diperlukan'], 401);
}
// Periksa apakah pengguna memiliki peran yang diperlukan
if (!empty($this->allowedRoles) && !in_array($user->role, $this->allowedRoles)) {
$this->app->jsonHalt(['error' => 'Izin tidak mencukupi'], 403);
}
}
}
// Penggunaan: Rute hanya untuk admin
Flight::route('DELETE /api/users/@id', function($id) {
// Logika hapus pengguna
})->addMiddleware([
JwtMiddleware::class,
new JwtRoleMiddleware(Flight::app(), ['admin'])
]);
4. API Publik dengan Batasan Tingkat Berdasarkan Pengguna
Gunakan JWT untuk melacak dan membatasi tingkat pengguna tanpa sesi:
class RateLimitMiddleware {
public function before(array $params) {
$user = Flight::request()->data->user ?? null;
$userId = $user ? $user->user_id : Flight::request()->ip;
$cacheKey = "rate_limit:$userId";
// Pastikan Anda menyiapkan layanan cache di app/config/services.php
$requests = Flight::cache()->get($cacheKey, 0);
if ($requests >= 100) { // 100 permintaan per jam
Flight::jsonHalt(['error' => 'Batas tingkat terlampaui'], 429);
}
Flight::cache()->set($cacheKey, $requests + 1, 3600);
}
}
Praktik Terbaik Keamanan
1. Gunakan Kunci Rahasia yang Kuat
// Hasilkan kunci rahasia yang aman (jalankan sekali, simpan ke file .env)
$secretKey = base64_encode(random_bytes(32));
echo $secretKey; // Simpan ini di file .env Anda!
2. Simpan Rahasia di Variabel Lingkungan
// Jangan pernah commit rahasia ke kontrol versi!
// Gunakan file .env dan library seperti vlucas/phpdotenv
// File .env:
// JWT_SECRET=your-base64-encoded-secret-here
// JWT_REFRESH_SECRET=another-base64-encoded-secret-here
// Anda juga dapat menggunakan file app/config/config.php untuk menyimpan rahasia Anda
// pastikan file konfigurasi tidak di-commit ke kontrol versi
// return [
// 'jwt_secret' => 'your-base64-encoded-secret-here',
// 'jwt_refresh_secret' => 'another-base64-encoded-secret-here',
// ];
// Di aplikasi Anda:
$secretKey = getenv('JWT_SECRET');
3. Tetapkan Waktu Kedaluwarsa yang Sesuai
// Praktik baik: token akses jangka pendek
'exp' => time() + (15 * 60) // 15 menit
// Untuk token pembaruan: kedaluwarsa lebih panjang
'exp' => time() + (7 * 24 * 60 * 60) // 7 hari
4. Gunakan HTTPS di Produksi
JWT harus selalu dikirim melalui HTTPS. Jangan pernah kirim token melalui HTTP biasa di produksi!
5. Validasi Klaim Token
Selalu validasi klaim yang Anda pedulikan:
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
// Periksa kedaluwarsa ditangani secara otomatis oleh library
// Tapi Anda dapat menambahkan validasi kustom:
if ($decoded->iat > time()) {
throw new Exception('Token digunakan sebelum diterbitkan');
}
if (isset($decoded->nbf) && $decoded->nbf > time()) {
throw new Exception('Token belum valid');
}
6. Pertimbangkan Daftar Hitam Token untuk Logout
Untuk keamanan ekstra, pertahankan daftar hitam token yang dibatalkan:
Flight::route('POST /api/logout', function() {
$authHeader = Flight::request()->getHeader('Authorization');
preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches);
$jwt = $matches[1];
// Ekstrak kedaluwarsa token
$decoded = Flight::request()->data->user;
$ttl = $decoded->exp - time();
// Simpan di cache/redis hingga kedaluwarsa
Flight::cache()->set("blacklist:$jwt", true, $ttl);
Flight::json(['message' => 'Berhasil logout']);
});
// Tambahkan ke JwtMiddleware Anda:
public function before(array $params) {
// ... ekstrak JWT ...
// Periksa daftar hitam
if (Flight::cache()->get("blacklist:$jwt")) {
$this->app->jsonHalt(['error' => 'Token telah dicabut'], 401);
}
// ... verifikasi token ...
}
Algoritma dan Tipe Kunci
Firebase JWT mendukung beberapa algoritma:
Algoritma Simetris (HMAC)
- HS256 (Direkomendasikan untuk sebagian besar aplikasi): Menggunakan satu kunci rahasia
- HS384, HS512: Varian yang lebih kuat
$jwt = JWT::encode($payload, $secretKey, 'HS256');
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
Algoritma Asimetris (RSA/ECDSA)
- RS256, RS384, RS512: Menggunakan pasangan kunci publik/privat
- ES256, ES384, ES512: Varian kurva eliptik
// Hasilkan kunci: openssl genrsa -out private.key 2048
// openssl rsa -in private.key -pubout -out public.key
$privateKey = file_get_contents('/path/to/private.key');
$publicKey = file_get_contents('/path/to/public.key');
// Enkode dengan kunci privat
$jwt = JWT::encode($payload, $privateKey, 'RS256');
// Dekode dengan kunci publik
$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
Kapan menggunakan RSA: Gunakan RSA ketika Anda perlu mendistribusikan kunci publik untuk verifikasi (misalnya, microservices, integrasi pihak ketiga). Untuk aplikasi tunggal, HS256 lebih sederhana dan cukup.
Pemecahan Masalah
Kesalahan "Token kedaluwarsa"
Klaim exp token Anda di masa lalu. Terbitkan token baru atau implementasikan pembaruan token.
"Verifikasi tanda tangan gagal"
- Anda menggunakan kunci rahasia yang berbeda untuk dekode daripada yang digunakan untuk encode
- Token telah dimanipulasi
- Penyimpangan jam antara server (tambahkan buffer leeway)
use Firebase\JWT\JWT;
JWT::$leeway = 60; // Izinkan 60 detik penyimpangan jam
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
Token Tidak Dikirim dalam Permintaan
Pastikan klien Anda mengirim header Authorization:
// Contoh JavaScript
fetch('/api/users', {
headers: {
'Authorization': 'Bearer ' + token
}
});
Metode
Library Firebase JWT menyediakan metode inti ini:
JWT::encode(array $payload, string $key, string $alg): Membuat JWT dari payloadJWT::decode(string $jwt, Key $key): Mendekode dan memverifikasi JWTJWT::urlsafeB64Encode(string $input): Encoding Base64 aman URLJWT::urlsafeB64Decode(string $input): Decoding Base64 aman URLJWT::$leeway: Properti statis untuk menetapkan leeway waktu untuk validasi (dalam detik)
Mengapa Menggunakan Library Ini?
- Standar Industri: Firebase JWT adalah library JWT paling populer dan tepercaya secara luas untuk PHP
- Pemeliharaan Aktif: Dipelihara oleh tim Google/Firebase
- Fokus Keamanan: Pembaruan rutin dan patch keamanan
- API Sederhana: Mudah dipahami dan diimplementasikan
- Didokumentasikan dengan Baik: Dokumentasi ekstensif dan dukungan komunitas
- Fleksibel: Mendukung beberapa algoritma dan opsi yang dapat dikonfigurasi
Lihat Juga
- Repositori Github Firebase JWT
- JWT.io - Debug dan dekode JWT
- RFC 7519 - Spesifikasi resmi JWT
- Dokumentasi Middleware Flight
- Plugin Sesi Flight - Untuk autentikasi berbasis sesi tradisional
Lisensi
Library Firebase JWT dilisensikan di bawah Lisensi BSD 3-Clause. Lihat repositori Github untuk detail.
Awesome-plugins/n0nag0n_wordpress
Integrasi WordPress: n0nag0n/wordpress-integration-for-flight-framework
Ingin menggunakan Flight PHP di dalam situs WordPress Anda? Plugin ini membuatnya sangat mudah! Dengan n0nag0n/wordpress-integration-for-flight-framework, Anda dapat menjalankan aplikasi Flight penuh tepat di samping instalasi WordPress Anda—sempurna untuk membangun API khusus, microservices, atau bahkan aplikasi lengkap tanpa meninggalkan kenyamanan WordPress.
Apa yang Dilakukannya?
- Mengintegrasikan Flight PHP dengan WordPress secara mulus
- Arahkan permintaan ke Flight atau WordPress berdasarkan pola URL
- Atur kode Anda dengan controller, model, dan views (MVC)
- Mudah atur struktur folder Flight yang direkomendasikan
- Gunakan koneksi database WordPress atau milik Anda sendiri
- Sesuaikan interaksi antara Flight dan WordPress
- Antarmuka admin sederhana untuk konfigurasi
Instalasi
- Unggah folder
flight-integrationke direktori/wp-content/plugins/Anda. - Aktifkan plugin di admin WordPress (menu Plugins).
- Buka Pengaturan > Flight Framework untuk mengonfigurasi plugin.
- Atur jalur vendor ke instalasi Flight Anda (atau gunakan Composer untuk menginstal Flight).
- Konfigurasi jalur folder aplikasi Anda dan buat struktur folder (plugin dapat membantu dengan ini!).
- Mulailah membangun aplikasi Flight Anda!
Contoh Penggunaan
Contoh Rute Dasar
Di file app/config/routes.php Anda:
Flight::route('GET /api/hello', function() {
Flight::json(['message' => 'Hello World!']);
});
Contoh Controller
Buat controller di app/controllers/ApiController.php:
namespace app\controllers;
use Flight;
class ApiController {
public function getUsers() {
// Anda dapat menggunakan fungsi WordPress di dalam Flight!
$users = get_users();
$result = [];
foreach($users as $user) {
$result[] = [
'id' => $user->ID,
'name' => $user->display_name,
'email' => $user->user_email
];
}
Flight::json($result);
}
}
Kemudian, di routes.php Anda:
Flight::route('GET /api/users', [app\controllers\ApiController::class, 'getUsers']);
FAQ
T: Apakah saya perlu mengetahui Flight untuk menggunakan plugin ini?
J: Ya, ini untuk pengembang yang ingin menggunakan Flight dalam WordPress. Pengetahuan dasar tentang routing dan penanganan permintaan Flight direkomendasikan.
T: Apakah ini akan memperlambat situs WordPress saya?
J: Tidak! Plugin hanya memproses permintaan yang sesuai dengan rute Flight Anda. Semua permintaan lainnya tetap ke WordPress seperti biasa.
T: Bisakah saya menggunakan fungsi WordPress di aplikasi Flight saya?
J: Tentu saja! Anda memiliki akses penuh ke semua fungsi WordPress, hooks, dan globals dari dalam rute dan controller Flight Anda.
T: Bagaimana cara membuat rute khusus?
J: Tentukan rute Anda di file config/routes.php di folder aplikasi Anda. Lihat file sampel yang dibuat oleh generator struktur folder untuk contoh.
Changelog
1.0.0
Rilis awal.
Untuk informasi lebih lanjut, periksa GitHub repo.
Awesome-plugins/ghost_session
Ghostff/Session
Manajer Sesi PHP (non-blocking, flash, segment, enkripsi sesi). Menggunakan PHP open_ssl untuk enkripsi/dekripsi data sesi opsional. Mendukung File, MySQL, Redis, dan Memcached.
Klik di sini untuk melihat kode.
Instalasi
Instal dengan composer.
composer require ghostff/session
Konfigurasi Dasar
Anda tidak diharuskan untuk mengirimkan apa pun untuk menggunakan pengaturan default dengan sesi Anda. Anda dapat membaca tentang pengaturan lebih lanjut di Github Readme.
use Ghostff\Session\Session;
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('session', Session::class);
// satu hal yang perlu diingat adalah bahwa Anda harus melakukan commit sesi pada setiap pemuatan halaman
// atau Anda perlu menjalankan auto_commit dalam konfigurasi Anda.
Contoh Sederhana
Berikut adalah contoh sederhana tentang bagaimana Anda mungkin menggunakan ini.
Flight::route('POST /login', function() {
$session = Flight::session();
// lakukan logika login Anda di sini
// validasi kata sandi, dll.
// jika login berhasil
$session->set('is_logged_in', true);
$session->set('user', $user);
// setiap kali Anda menulis ke sesi, Anda harus melakukan commit secara sengaja.
$session->commit();
});
// Periksa ini bisa ada di logika halaman terbatas, atau dibungkus dengan middleware.
Flight::route('/some-restricted-page', function() {
$session = Flight::session();
if(!$session->get('is_logged_in')) {
Flight::redirect('/login');
}
// lakukan logika halaman terbatas Anda di sini
});
// versi middleware
Flight::route('/some-restricted-page', function() {
// logika halaman reguler
})->addMiddleware(function() {
$session = Flight::session();
if(!$session->get('is_logged_in')) {
Flight::redirect('/login');
}
});
Contoh Lebih Kompleks
Berikut adalah contoh lebih kompleks tentang bagaimana Anda mungkin menggunakan ini.
use Ghostff\Session\Session;
require 'vendor/autoload.php';
$app = Flight::app();
// atur jalur khusus ke file konfigurasi sesi Anda sebagai argumen pertama
// atau berikan array khusus
$app->register('session', Session::class, [
[
// jika Anda ingin menyimpan data sesi di database (bagus jika Anda ingin sesuatu seperti, "keluarkan saya dari semua perangkat" fungsionalitas)
Session::CONFIG_DRIVER => Ghostff\Session\Drivers\MySql::class,
Session::CONFIG_ENCRYPT_DATA => true,
Session::CONFIG_SALT_KEY => hash('sha256', 'my-super-S3CR3T-salt'), // silakan ubah ini menjadi sesuatu yang lain
Session::CONFIG_AUTO_COMMIT => true, // hanya lakukan ini jika diperlukan dan/atau sulit untuk commit() sesi Anda.
// selain itu Anda bisa melakukan Flight::after('start', function() { Flight::session()->commit(); });
Session::CONFIG_MYSQL_DS => [
'driver' => 'mysql', # Pengandar basis data untuk PDO dns misalnya (mysql:host=...;dbname=...)
'host' => '127.0.0.1', # Host basis data
'db_name' => 'my_app_database', # Nama basis data
'db_table' => 'sessions', # Tabel basis data
'db_user' => 'root', # Nama pengguna basis data
'db_pass' => '', # Kata sandi basis data
'persistent_conn'=> false, # Hindari biaya overhead dari membangun koneksi baru setiap kali skrip perlu berbicara ke basis data, menghasilkan aplikasi web yang lebih cepat. CARI BACKSIDE SENDIRI
]
]
]);
Bantuan! Data Sesi Saya Tidak Bertahan!
Apakah Anda mengatur data sesi dan itu tidak bertahan antara permintaan? Anda mungkin lupa untuk melakukan commit data sesi Anda. Anda bisa melakukan ini dengan memanggil $session->commit() setelah Anda mengatur data sesi Anda.
Flight::route('POST /login', function() {
$session = Flight::session();
// lakukan logika login Anda di sini
// validasi kata sandi, dll.
// jika login berhasil
$session->set('is_logged_in', true);
$session->set('user', $user);
// setiap kali Anda menulis ke sesi, Anda harus melakukan commit secara sengaja.
$session->commit();
});
Cara lain untuk mengatasi ini adalah ketika Anda mengatur layanan sesi Anda, Anda harus mengatur auto_commit ke true dalam konfigurasi Anda. Ini akan secara otomatis melakukan commit data sesi Anda setelah setiap permintaan.
$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
$session->updateConfiguration([
Session::CONFIG_AUTO_COMMIT => true,
]);
}
);
Selain itu, Anda bisa melakukan Flight::after('start', function() { Flight::session()->commit(); }); untuk melakukan commit data sesi Anda setelah setiap permintaan.
Dokumentasi
Kunjungi Github Readme untuk dokumentasi lengkap. Opsi konfigurasi didokumentasikan dengan baik di file default_config.php itu sendiri. Kode ini sederhana untuk dipahami jika Anda ingin menelusuri paket ini sendiri.
Awesome-plugins/mcp
Server MCP FlightPHP
Server MCP FlightPHP memberikan akses instan dan terstruktur kepada asisten pengkodean AI yang kompatibel dengan MCP ke seluruh dokumentasi FlightPHP — routing, middleware, plugin, panduan, dan lainnya. Alih-alih AI Anda menghasilkan detail API secara halusinasi atau menebak tanda tangan metode, ia mengambil dokumen asli sesuai permintaan. Tidak ada kunci API, tidak diperlukan instalasi untuk versi yang dihosting.
Kunjungi repositori Github untuk kode sumber lengkap dan detail.
Mulai Cepat
Server ini dihosting secara publik dan siap digunakan:
https://mcp.flightphp.com/mcp
Cukup tambahkan URL tersebut ke ekstensi pengkodean AI Anda. Tidak ada pendaftaran, tidak ada kredensial. Lihat bagian Konfigurasi IDE di bawah untuk konfigurasi copy-paste untuk alat paling populer.
Apa yang Dilakukannya
Setelah terhubung, asisten AI Anda dapat:
- Telusuri semua dokumen yang tersedia — daftarkan setiap topik inti, panduan, dan halaman plugin
- Ambil halaman dokumentasi apa pun — ambil konten lengkap untuk routing, middleware, permintaan, keamanan, dan lainnya
- Cari dokumen plugin — dapatkan dokumentasi lengkap untuk ActiveRecord, Session, Tracy, Runway, dan semua plugin resmi lainnya
- Ikuti panduan langkah demi langkah — akses walkthrough lengkap untuk membangun blog, REST API, dan aplikasi yang diuji
- Cari di seluruhnya — temukan halaman relevan di seluruh dokumen inti, panduan, dan plugin sekaligus
Poin Kunci
- Pengaturan nol — server yang dihosting di
https://mcp.flightphp.com/mcptidak memerlukan instalasi atau kunci API. - Selalu terkini — server mengambil dokumen secara langsung dari docs.flightphp.com, sehingga selalu terbaru.
- Bekerja di mana saja — alat apa pun yang mendukung transport HTTP Streamable MCP dapat terhubung.
- Dapat dihosting sendiri — jalankan instance Anda sendiri dengan PHP >= 8.1 dan Composer jika Anda lebih suka.
Konfigurasi IDE / Ekstensi AI
Server menggunakan transport HTTP Streamable. Pilih ekstensi Anda di bawah dan tempelkan konfigurasi.
Claude Code (CLI)
Jalankan perintah berikut untuk menambahkannya ke proyek Anda:
claude mcp add --transport http flightphp-docs https://mcp.flightphp.com/mcp
Atau tambahkan secara manual ke .mcp.json proyek Anda:
{
"mcpServers": {
"flightphp-docs": {
"type": "http",
"url": "https://mcp.flightphp.com/mcp"
}
}
}
GitHub Copilot (VS Code)
Tambahkan ke .vscode/mcp.json di workspace Anda:
{
"servers": {
"flightphp-docs": {
"type": "http",
"url": "https://mcp.flightphp.com/mcp"
}
}
}
Kilo Code (VS Code)
Tambahkan ke settings.json VS Code Anda:
{
"kilocode.mcpServers": {
"flightphp-docs": {
"url": "https://mcp.flightphp.com/mcp",
"transport": "streamable-http"
}
}
}
Continue.dev (VS Code / JetBrains)
Tambahkan ke ~/.continue/config.json:
{
"mcpServers": [
{
"name": "flightphp-docs",
"transport": {
"type": "http",
"url": "https://mcp.flightphp.com/mcp"
}
}
]
}
Alat yang Tersedia
Server MCP mengekspos alat berikut ke asisten AI Anda:
| Alat | Deskripsi |
|---|---|
list_docs_pages |
Daftarkan semua topik dokumentasi inti yang tersedia dengan slug dan deskripsi |
get_docs_page |
Ambil halaman dokumen inti berdasarkan slug topik (mis. routing, middleware, security) |
list_guide_pages |
Daftarkan semua panduan langkah demi langkah yang tersedia |
get_guide_page |
Ambil panduan lengkap berdasarkan slug (mis. blog, unit-testing) |
list_plugin_pages |
Daftarkan semua halaman plugin dan ekstensi yang tersedia |
get_plugin_docs |
Ambil dokumentasi plugin lengkap berdasarkan slug (mis. active-record, session, jwt) |
search_docs |
Cari di seluruh dokumen, panduan, dan plugin untuk kata kunci atau topik |
fetch_url |
Ambil halaman apa pun secara langsung berdasarkan URL lengkap docs.flightphp.com |
Self-Hosting
Lebih suka menjalankan instance Anda sendiri? Anda memerlukan PHP >= 8.1 dan Composer.
git clone https://github.com/flightphp/mcp.git
cd mcp
composer install
php server.php
Server dimulai di http://0.0.0.0:8890/mcp secara default. Perbarui konfigurasi IDE Anda untuk mengarah ke alamat lokal Anda:
{
"mcpServers": {
"flightphp-docs": {
"type": "http",
"url": "http://localhost:8890/mcp"
}
}
}Awesome-plugins/async
Async
Async adalah paket kecil untuk framework Flight yang memungkinkan Anda menjalankan aplikasi Flight di dalam server dan runtime asinkron seperti Swoole, AdapterMan, ReactPHP, Amp, RoadRunner, Workerman, dll. Secara default, ia menyertakan adapter untuk Swoole dan AdapterMan.
Tujuan: mengembangkan dan mendebug dengan PHP-FPM (atau server bawaan) dan beralih ke Swoole (atau driver asinkron lainnya) untuk produksi dengan perubahan minimal.
Persyaratan
- PHP 7.4 atau lebih tinggi
- Framework Flight 3.16.1 atau lebih tinggi
- Ekstensi Swoole
Instalasi
Instal melalui composer:
composer require flightphp/async
Jika Anda berencana menjalankan dengan Swoole, instal ekstensi tersebut:
# menggunakan pecl
pecl install swoole
# atau openswoole
pecl install openswoole
# atau dengan pengelola paket (contoh Debian/Ubuntu)
sudo apt-get install php-swoole
Contoh Cepat Swoole
Berikut adalah pengaturan minimal yang menunjukkan cara mendukung baik PHP-FPM (atau server bawaan) maupun Swoole menggunakan kode dasar yang sama.
File yang Anda butuhkan dalam proyek Anda:
- index.php
- swoole_server.php
- SwooleServerDriver.php
index.php
File ini adalah saklar sederhana yang memaksa aplikasi berjalan dalam mode PHP untuk pengembangan.
// index.php
<?php
define('NOT_SWOOLE', true);
include 'swoole_server.php';
swoole_server.php
File ini memulai aplikasi Flight Anda dan akan memulai driver Swoole ketika NOT_SWOOLE tidak didefinisikan.
// swoole_server.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
$app = Flight::app();
$app->route('/', function() use ($app) {
$app->json(['hello' => 'world']);
});
if (!defined('NOT_SWOOLE')) {
// Require kelas SwooleServerDriver ketika berjalan dalam mode Swoole.
require_once __DIR__ . '/SwooleServerDriver.php';
Swoole\Runtime::enableCoroutine();
$Swoole_Server = new SwooleServerDriver('127.0.0.1', 9501, $app);
$Swoole_Server->start();
} else {
$app->start();
}
SwooleServerDriver.php
Driver ringkas yang menunjukkan cara menjembatani permintaan Swoole ke Flight menggunakan AsyncBridge dan adapter Swoole.
// SwooleServerDriver.php
<?php
use flight\adapter\SwooleAsyncRequest;
use flight\adapter\SwooleAsyncResponse;
use flight\AsyncBridge;
use flight\Engine;
use Swoole\HTTP\Server as SwooleServer;
use Swoole\HTTP\Request as SwooleRequest;
use Swoole\HTTP\Response as SwooleResponse;
class SwooleServerDriver {
protected $Swoole;
protected $app;
public function __construct(string $host, int $port, Engine $app) {
$this->Swoole = new SwooleServer($host, $port);
$this->app = $app;
$this->setDefault();
$this->bindWorkerEvents();
$this->bindHttpEvent();
}
protected function setDefault() {
$this->Swoole->set([
'daemonize' => false,
'dispatch_mode' => 1,
'max_request' => 8000,
'open_tcp_nodelay' => true,
'reload_async' => true,
'max_wait_time' => 60,
'enable_reuse_port' => true,
'enable_coroutine' => true,
'http_compression' => false,
'enable_static_handler' => true,
'document_root' => __DIR__,
'static_handler_locations' => ['/css', '/js', '/images', '/.well-known'],
'buffer_output_size' => 4 * 1024 * 1024,
'worker_num' => 4,
]);
$app = $this->app;
$app->map('stop', function (?int $code = null) use ($app) {
if ($code !== null) {
$app->response()->status($code);
}
});
}
protected function bindHttpEvent() {
$app = $this->app;
$AsyncBridge = new AsyncBridge($app);
$this->Swoole->on('Start', function(SwooleServer $server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
});
$this->Swoole->on('Request', function (SwooleRequest $request, SwooleResponse $response) use ($AsyncBridge) {
$SwooleAsyncRequest = new SwooleAsyncRequest($request);
$SwooleAsyncResponse = new SwooleAsyncResponse($response);
$AsyncBridge->processRequest($SwooleAsyncRequest, $SwooleAsyncResponse);
$response->end();
gc_collect_cycles();
});
}
protected function bindWorkerEvents() {
$createPools = function() {
// create worker-specific connection pools here
};
$closePools = function() {
// close pools / cleanup here
};
$this->Swoole->on('WorkerStart', $createPools);
$this->Swoole->on('WorkerStop', $closePools);
$this->Swoole->on('WorkerError', $closePools);
}
public function start() {
$this->Swoole->start();
}
}
Menjalankan Server
- Pengembangan (server bawaan PHP / PHP-FPM):
- php -S localhost:8000 (atau tambahkan -t public/ jika index Anda berada di public/)
- Produksi (Swoole):
- php swoole_server.php
Tips: Untuk penggunaan produksi, gunakan proxy terbalik (Nginx) di depan Swoole untuk menangani TLS, file statis, dan penyeimbangan beban.
Catatan Konfigurasi
Driver Swoole mengekspos beberapa opsi konfigurasi:
- worker_num: jumlah proses pekerja
- max_request: permintaan per pekerja sebelum restart
- enable_coroutine: gunakan coroutine untuk konkurensi
- buffer_output_size: ukuran buffer output
Sesuaikan ini dengan sumber daya host dan pola lalu lintas Anda.
Penanganan Kesalahan
AsyncBridge menerjemahkan kesalahan Flight menjadi respons HTTP yang tepat. Anda juga dapat menambahkan penanganan kesalahan pada tingkat rute:
$app->route('/*', function() use ($app) {
try {
// logika rute
} catch (Exception $e) {
$app->response()->status(500);
$app->json(['error' => $e->getMessage()]);
}
});
AdapterMan dan Runtime Lainnya
AdapterMan didukung sebagai adapter runtime alternatif. Paket ini dirancang untuk dapat diadaptasi — menambahkan atau menggunakan adapter lain umumnya mengikuti pola yang sama: mengonversi permintaan/respons server menjadi permintaan/respons Flight melalui AsyncBridge dan adapter khusus runtime.
Awesome-plugins/migrations
Migrations
Migrasi untuk proyek Anda menjaga semua perubahan basis data yang terlibat dalam proyek Anda.
byjg/php-migration adalah pustaka inti yang sangat membantu untuk memulai.
Menginstal
Pustaka PHP
Jika Anda ingin menggunakan hanya Pustaka PHP di proyek Anda:
composer require "byjg/migration"
Antarmuka Baris Perintah
Antarmuka baris perintah berdiri sendiri dan tidak memerlukan Anda menginstalnya bersama proyek Anda.
Anda dapat menginstalnya secara global dan membuat tautan simbolis
composer require "byjg/migration-cli"
Silakan kunjungi byjg/migration-cli untuk mendapatkan lebih banyak informasi tentang Migration CLI.
Basis data yang didukung
| Basis Data | Driver | String Koneksi |
|---|---|---|
| Sqlite | pdo_sqlite | sqlite:///path/to/file |
| MySql/MariaDb | pdo_mysql | mysql://username:password@hostname:port/database |
| Postgres | pdo_pgsql | pgsql://username:password@hostname:port/database |
| Sql Server | pdo_dblib, pdo_sysbase Linux | dblib://username:password@hostname:port/database |
| Sql Server | pdo_sqlsrv Windows | sqlsrv://username:password@hostname:port/database |
Bagaimana Ini Bekerja?
Migrasi Basis Data menggunakan SQL MURNI untuk mengelola versi basis data.
Untuk dapat berfungsi, Anda perlu:
- Membuat Skrip SQL
- Mengelola menggunakan Baris Perintah atau API.
Skrip SQL
Skrip dibagi menjadi tiga set skrip:
- Skrip BASIS berisi SEMUA perintah SQL untuk membuat basis data yang baru;
- Skrip UP berisi semua perintah migrasi SQL untuk "naik" versi basis data;
- Skrip DOWN berisi semua perintah migrasi SQL untuk "turun" atau mengembalikan versi basis data;
Direktori skrip adalah:
<root dir>
|
+-- base.sql
|
+-- /migrations
|
+-- /up
|
+-- 00001.sql
+-- 00002.sql
+-- /down
|
+-- 00000.sql
+-- 00001.sql
- "base.sql" adalah skrip dasar
- Folder "up" berisi skrip untuk migrasi naik versi. Sebagai contoh: 00002.sql adalah skrip untuk mengubah basis data dari versi '1' ke '2'.
- Folder "down" berisi skrip untuk migrasi turun versi. Sebagai contoh: 00001.sql adalah skrip untuk mengubah basis data dari versi '2' ke '1'. Folder "down" adalah opsional.
Lingkungan Pengembangan Multi
Jika Anda bekerja dengan beberapa pengembang dan beberapa cabang, sulit untuk menentukan nomor berikutnya.
Dalam kasus itu, Anda mempunyai akhiran "-dev" setelah nomor versi.
Lihat skenarionya:
- Pengembang 1 membuat cabang dan versi terbaru misalnya 42.
- Pengembang 2 membuat cabang pada saat yang sama dan memiliki nomor versi basis data yang sama.
Dalam kedua kasus, para pengembang akan membuat file bernama 43-dev.sql. Kedua pengembang akan bermigrasi NAIK dan TURUN tanpa masalah dan versi lokal Anda akan menjadi 43.
Namun pengembang 1 menggabungkan perubahan Anda dan membuat versi akhir 43.sql (git mv 43-dev.sql 43.sql). Jika pengembang 2 memperbarui cabang lokal Anda, dia akan memiliki file 43.sql (dari dev 1) dan file Anda 43-dev.sql.
Jika dia mencoba untuk bermigrasi NAIK atau TURUN, skrip migrasi akan turun dan memberi tahu bahwa terdapat DUA versi 43. Dalam kasus ini, pengembang 2 harus memperbarui file-nya menjadi 44-dev.sql dan melanjutkan bekerja hingga menggabungkan perubahan Anda dan menghasilkan versi akhir.
Menggunakan API PHP dan Mengintegrasikannya ke dalam Proyek Anda
Penggunaan dasar adalah
- Membuat koneksi objek ConnectionManagement. Untuk informasi lebih lanjut, lihat komponen "byjg/anydataset".
- Membuat objek Migrasi dengan koneksi ini dan folder tempat skrip SQL berada.
- Gunakan perintah yang sesuai untuk "reset", "up", atau "down" skrip migrasi.
Lihat contohnya:
<?php
// Membuat URI Koneksi
// Lihat lebih lanjut: https://github.com/byjg/anydataset#connection-based-on-uri
$connectionUri = new \ByJG\Util\Uri('mysql://migrateuser:migratepwd@localhost/migratedatabase');
// Daftarkan Database atau Basis Data yang dapat menangani URI tersebut:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);
// Membuat instance Migrasi
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');
// Tambahkan fungsi progres callback untuk menerima info dari eksekusi
$migration->addCallbackProgress(function ($action, $currentVersion, $fileInfo) {
echo "$action, $currentVersion, ${fileInfo['description']}\n";
});
// Mengembalikan basis data menggunakan skrip "base.sql"
// dan menjalankan SEMUA skrip yang ada untuk menaikkan versi basis data ke versi terbaru
$migration->reset();
// Jalankan SEMUA skrip yang ada untuk naik atau turun versi basis data
// dari versi sekarang hingga nomor $version;
// Jika nomor versi tidak ditentukan, migrasi hingga versi basis data terakhir
$migration->update($version = null);
Objek Migrasi mengontrol versi basis data.
Membuat kontrol versi di proyek Anda
<?php
// Daftarkan Database atau Basis Data yang dapat menangani URI tersebut:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);
// Membuat instance Migrasi
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');
// Perintah ini akan membuat tabel versi di basis data Anda
$migration->createVersion();
Mendapatkan versi saat ini
<?php
$migration->getCurrentVersion();
Menambahkan Callback untuk mengontrol progres
<?php
$migration->addCallbackProgress(function ($command, $version, $fileInfo) {
echo "Melakukan Perintah: $command di versi $version - ${fileInfo['description']}, ${fileInfo['exists']}, ${fileInfo['file']}, ${fileInfo['checksum']}\n";
});
Mendapatkan instance Driver Db
<?php
$migration->getDbDriver();
Untuk menggunakannya, silakan kunjungi: https://github.com/byjg/anydataset-db
Menghindari Migrasi Parsial (tidak tersedia untuk MySQL)
Migrasi parsial adalah ketika skrip migrasi terhenti di tengah proses karena kesalahan atau penghentian manual.
Tabel migrasi akan memiliki status partial up atau partial down dan perlu diperbaiki secara manual sebelum dapat bermigrasi lagi.
Untuk menghindari situasi ini, Anda dapat menentukan migrasi akan dijalankan dalam konteks transaksional.
Jika skrip migrasi gagal, transaksi akan dibatalkan dan tabel migrasi akan ditandai sebagai complete dan versi akan menjadi versi sebelumnya yang segera sebelum skrip yang menyebabkan kesalahan.
Untuk mengaktifkan fitur ini, Anda perlu memanggil metode withTransactionEnabled dengan melewatkan true sebagai parameter:
<?php
$migration->withTransactionEnabled(true);
CATATAN: Fitur ini tidak tersedia untuk MySQL karena tidak mendukung perintah DDL di dalam transaksi.
Jika Anda menggunakan metode ini dengan MySQL, Migrasi akan mengabaikannya tanpa pemberitahuan.
Info lebih lanjut: https://dev.mysql.com/doc/refman/8.0/en/cannot-roll-back.html
Tips dalam menulis migrasi SQL untuk Postgres
Saat membuat trigger dan fungsi SQL
-- Lakukan
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Periksa bahwa empname dan salary diberikan
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname tidak boleh null'; -- tidak masalah apakah komentar ini kosong atau tidak
END IF; --
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% tidak dapat memiliki salary null', NEW.empname; --
END IF; --
-- Siapa yang bekerja untuk kita ketika mereka harus membayar untuk itu?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% tidak dapat memiliki salary negatif', NEW.empname; --
END IF; --
-- Ingat siapa yang mengubah gaji ketika
NEW.last_date := current_timestamp; --
NEW.last_user := current_user; --
RETURN NEW; --
END; --
$emp_stamp$ LANGUAGE plpgsql;
-- JANGAN
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Periksa bahwa empname dan salary diberikan
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname tidak boleh null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% tidak dapat memiliki salary null', NEW.empname;
END IF;
-- Siapa yang bekerja untuk kita ketika mereka harus membayar untuk itu?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% tidak dapat memiliki salary negatif', NEW.empname;
END IF;
-- Ingat siapa yang mengubah gaji ketika
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
Karena lapisan abstraksi basis data PDO tidak dapat menjalankan kelompok pernyataan SQL,
ketika byjg/migration membaca file migrasi, itu harus memisahkan seluruh isi file SQL pada titik koma, dan menjalankan pernyataan satu per satu. Namun, ada satu jenis pernyataan yang dapat memiliki beberapa titik koma di antara tubuhnya: fungsi.
Agar dapat mem-parsing fungsi dengan benar, byjg/migration 2.1.0 mulai memisahkan file migrasi pada urutan semicolon + EOL bukannya hanya titik koma. Dengan cara ini, jika Anda menambahkan komentar kosong setelah setiap titik koma dalam definisi fungsi, byjg/migration akan dapat mem-parsingnya.
Sayangnya, jika Anda lupa menambahkan salah satu komentar ini, pustaka akan memisahkan pernyataan CREATE FUNCTION menjadi beberapa bagian dan migrasi akan gagal.
Hindari karakter titik dua (:)
-- Lakukan
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY,
booked_at TIMESTAMPTZ NOT NULL CHECK (CAST(booked_at AS DATE) <= check_in),
check_in DATE NOT NULL
);
-- JANGAN
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY,
booked_at TIMESTAMPTZ NOT NULL CHECK (booked_at::DATE <= check_in),
check_in DATE NOT NULL
);
Karena PDO menggunakan karakter titik dua untuk menjelaskan parameter bernama dalam pernyataan yang sudah disiapkan, penggunaannya akan menyebabkan kesalahan dalam konteks lain.
Misalnya, pernyataan PostgreSQL dapat menggunakan :: untuk mengonversi nilai antar tipe. Di sisi lain, PDO akan membaca ini sebagai parameter bernama yang tidak valid dalam konteks yang tidak valid dan gagal ketika mencoba menjalankannya.
Satu-satunya cara untuk memperbaiki ketidakkonsistenan ini adalah dengan menghindari titik dua sama sekali (dalam hal ini, PostgreSQL juga memiliki sintaks alternatif: CAST(value AS type)).
Gunakan editor SQL
Akhirnya, menulis migrasi SQL manual bisa melelahkan, tetapi jauh lebih mudah jika Anda menggunakan editor yang mampu memahami sintaks SQL, menyediakan autocompletion, mengintrospeksi skema basis data Anda saat ini dan/atau memformat kode Anda secara otomatis.
Menangani berbagai migrasi di dalam satu skema
Jika Anda perlu membuat skrip migrasi yang berbeda dan versi di dalam skema yang sama, itu mungkin
tetapi terlalu berisiko dan saya tidak merekomendasikannya sama sekali.
Untuk melakukan ini, Anda perlu membuat "tabel migrasi" yang berbeda dengan mengoper parameter pada konstruktor.
<?php
$migration = new \ByJG\DbMigration\Migration("db:/uri", "/path", true, "NEW_MIGRATION_TABLE_NAME");
Untuk alasan keamanan, fitur ini tidak tersedia di baris perintah, tetapi Anda dapat menggunakan variabel lingkungan
MIGRATION_VERSION untuk menyimpan namanya.
Kami sangat merekomendasikan untuk tidak menggunakan fitur ini. Rekomendasi adalah satu migrasi untuk satu skema.
Menjalankan Uji Unit
Uji unit dasar dapat dijalankan dengan:
vendor/bin/phpunit
Menjalankan uji basis data
Menjalankan uji integrasi memerlukan Anda untuk memiliki basis data yang aktif dan berjalan. Kami menyediakan docker-compose.yml dasar dan Anda
dapat menggunakannya untuk memulai basis data untuk pengujian.
Menjalankan basis data
docker-compose up -d postgres mysql mssql
Menjalankan uji
vendor/bin/phpunit
vendor/bin/phpunit tests/SqliteDatabase*
vendor/bin/phpunit tests/MysqlDatabase*
vendor/bin/phpunit tests/PostgresDatabase*
vendor/bin/phpunit tests/SqlServerDblibDatabase*
vendor/bin/phpunit tests/SqlServerSqlsrvDatabase*
Opsional Anda dapat mengatur host dan kata sandi yang digunakan oleh uji unit
export MYSQL_TEST_HOST=localhost # default ke localhost
export MYSQL_PASSWORD=newpassword # gunakan '.' jika ingin memiliki kata sandi null
export PSQL_TEST_HOST=localhost # default ke localhost
export PSQL_PASSWORD=newpassword # gunakan '.' jika ingin memiliki kata sandi null
export MSSQL_TEST_HOST=localhost # default ke localhost
export MSSQL_PASSWORD=Pa55word
export SQLITE_TEST_HOST=/tmp/test.db # default ke /tmp/test.dbAwesome-plugins/flightmail
FlightMail
Plugin pihak ketiga - dikelola oleh Ryan Stubbs (ryanstubbs/flightmail, dilisensikan MIT). Bukan bagian dari Flight core - silakan laporkan isu di repositori GitHub-nya.
ryanstubbs/flightmail memungkinkan Anda mengirim email dari aplikasi Flight tanpa pusing. Library ini membungkus Symfony Mailer - library mail yang paling teruji di PHP - dan membuatnya terasa seperti bagian dari Flight. Satu baris untuk instal, satu rantai fluent untuk mengirim:
Flight::mail()->compose()
->to('someone@example.com')
->subject('Berhasil!')
->text('Email pertama Anda sedang dalam perjalanan.')
->send();
Fitur
- Penyedia apa pun, masing-masing satu baris. SMTP, Postmark, Sendgrid, Mailgun, Amazon SES, Brevo dan kawan-kawan semuanya bekerja melalui string DSN sederhana.
- Gunakan beberapa penyedia sekaligus. Mail transaksional melalui Postmark, newsletter melalui SMTP Anda sendiri - pilih per pesan.
- Templat jika Anda menginginkannya. Render body dengan Twig atau Latte. Tidak mau templat? Cukup kirim string dan tidak perlu instal apa pun extra.
- Polesan saat kirim. CSS inlining opsional dan bagian teks biasa otomatis yang diturunkan dari HTML Anda, didukung library yang hanya Anda instal jika digunakan.
- Membosankan dengan cara terbaik. Koneksi lazy, error yang jelas alih-alih mail yang ditelan diam-diam, dan semuanya dapat ditukar jika Anda butuh sesuatu yang kustom.
Persyaratan
| Apa | Versi |
|---|---|
| PHP | 8.2 atau lebih baru |
| Flight PHP | core ^3.15 |
| Symfony Mailer | ^7.2 atau ^8.0 (diinstal secara otomatis) |
Instalasi
composer require ryanstubbs/flightmail
Itu saja untuk mengirim email teks biasa dan HTML. Rendering templat bersifat opt-in - tambahkan engine hanya jika Anda akan menggunakannya:
composer require twig/twig # untuk templat .twig
composer require latte/latte # untuk templat .latte
Dua library opsional lagi mendukung peningkatan saat kirim yang dibahas di bawah:
composer require pelago/emogrifier # untuk CSS inlining ("inline_css")
composer require league/html-to-markdown # untuk bagian teks Markdown ("text_from_html")
Semua ini dapat diinstal berdampingan; FlightMail memilih yang tepat berdasarkan apa yang Anda konfigurasi.
Email pertama Anda
Tambahkan ini ke bootstrap Anda (tempat yang sama Anda mendefinisikan rute):
<?php
require 'vendor/autoload.php';
use ryanstubbs\FlightMail\MailPlugin;
// Beritahu FlightMail dari mana dan melalui apa mail dikirim.
MailPlugin::install([
'dsns' => [
'default' => 'smtp://user:pass@localhost:1025',
],
'from' => 'no-reply@example.com',
]);
Flight::route('/signup', function () {
Flight::mail()->compose()
->to('new-user@example.com')
->subject('Selamat bergabung!')
->html('<h1>Selamat datang!</h1><p>Kami senang Anda di sini.</p>')
->send();
});
Flight::start();
Menggunakan Flight PHP skeleton? Daftarkan di app/config/services.php dengan gaya instance:
use ryanstubbs\FlightMail\MailPlugin;
MailPlugin::register($app, [
'dsns' => ['default' => 'smtp://user:pass@localhost:1025'],
'from' => 'no-reply@example.com',
]);
Kedua gaya mengekspos mailer yang sama: Flight::mail() dan $app->mail() dapat dipertukarkan.
Testing secara lokal? Jika proyek Anda berjalan di DDEV, arahkan DSN ke
smtp://127.0.0.1:1025dan baca setiap email yang tertangkap di Mailpit padahttp://<project>.ddev.site:8025. Tidak ada yang keluar dari mesin Anda.
Mengirim email
String biasa (tidak perlu mesin templat)
->text() dan ->html() menerima string mentah dan tidak butuh apa pun lain yang terinstal:
Flight::mail()->compose()
->to('ops@example.com')
->subject('Backup selesai')
->text('Backup malam selesai dalam 42 menit.')
->send();
Flight::mail()->compose()
->to('billing@example.com')
->subject('Faktur #123')
->html('<h1>Faktur #123</h1><p>Total tagihan: $42.00</p>')
->send();
Templat Twig
// welcome.html.twig berisi: Halo {{ name }}, terima kasih sudah mendaftar!
Flight::mail()->compose()
->to('someone@example.com')
->subject('Selamat datang!')
->template('welcome.html.twig', ['name' => 'Ryan'])
->send();
Templat Latte
Ide yang sama, ekstensi .latte:
// welcome.latte berisi: Halo {$name}, terima kasih sudah mendaftar!
Flight::mail()->compose()
->to('someone@example.com')
->subject('Selamat datang!')
->template('welcome.latte', ['name' => 'Ryan'])
->send();
HTML + teks biasa bersama
Best practice untuk deliverability - berikan kedua versi kepada klien mail:
Flight::mail()->compose()
->to('someone@example.com')
->subject('Selamat datang!')
->template('welcome.html.twig', ['name' => 'Ryan']) // versi kaya
->textTemplate('welcome.txt.twig', ['name' => 'Ryan']) // versi fallback
->send();
Beberapa hal yang perlu diketahui tentang templat:
- Mereka di-render secara lazy, pada saat kirim - compose sekarang, render nanti.
- Engine dipilih berdasarkan ekstensi:
.twig→ Twig,.latte→ Latte, yang lain → default yang Anda konfigurasi (opsirenderer). - Body
->html()atau->text()eksplisit selalu menang atas templat, jadi Anda bisa set templat default dan override-nya per pesan.
Styling HTML dan menghasilkan bagian teks
Dua peningkatan opsional saat kirim, keduanya mati secara default dan keduanya didukung library yang hanya Anda instal jika menginginkannya:
| Fitur | Instal | Kunci config |
|---|---|---|
| CSS inlining | pelago/emogrifier |
inline_css |
| Bagian teks dari HTML | league/html-to-markdown |
text_from_html |
Inline CSS ke email HTML Anda
Gmail dan sebagian besar klien webmail membuang blok <style> - atribut style="" inline adalah satu-satunya styling yang mereka hormati secara andal. Menulis itu secara manual sangat menyebalkan; biarkan Emogrifier melakukannya saat kirim:
composer require pelago/emogrifier
MailPlugin::install([
'dsns' => ['default' => 'smtp://user:pass@localhost:1025'],
'inline_css' => true,
]);
Dengan itu aktif, setiap body HTML mendapat CSS-nya di-inline tepat sebelum dikirim - entah berasal dari templat atau ->html(). Pesan seperti <style>p { color: red; }</style><p>Hai</p> keluar sebagai <p style="color: red;">Hai</p>.
Untuk menyuntikkan style bersama ke setiap email (warna merek, reset) tanpa mengulanginya di setiap templat, kirim aturan secara langsung atau tunjuk ke file stylesheet:
'inline_css' => ['css_file' => __DIR__ . '/mail-styles/base.css'],
// atau
'inline_css' => ['css' => '.button { background: #0a84ff; color: #fff; }'],
Kontrol per pesan:
$message->inlineCss(); // paksa inlining untuk pesan ini saja
$message->withoutInlineCss(); // lewati meskipun diaktifkan secara global
Hasilkan bagian teks dari HTML Anda
Best practice adalah mengirim versi HTML dan teks biasa bersama, tetapi menulis keduanya membosankan. FlightMail dapat menurunkan bagian teks dari HTML final secara otomatis - konversi dasar tidak butuh dependensi extra, karena converter sudah ikut dengan Symfony Mime:
MailPlugin::install([
'dsns' => ['default' => 'smtp://user:pass@localhost:1025'],
'text_from_html' => true, // Markdown jika memungkinkan, teks biasa jika tidak
]);
Mode:
trueatau'auto'- output Markdown jikaleague/html-to-markdownterinstal, jika tidak stripping tag sederhana.'markdown'- paksa Markdown (composer require league/html-to-markdown; heading menjadi==, tautan[text](url), tebal**bold**).'plain'- selalu strip tag; bekerja tanpa paket extra sama sekali.
Generasi berjalan setelah rendering dan CSS inlining, dan hanya ketika pesan punya body HTML tetapi tidak punya body teks - ->text() atau ->textTemplate() eksplisit selalu menang. Override per pesan mencerminkan inlining:
$message->textFromHtml('plain'); // paksa tag-stripping untuk yang ini
$message->withoutTextFromHtml(); // email HTML saja
Aktifkan mode yang library-nya belum terinstal dan Anda mendapat error yang jelas yang menyebut composer require persis yang harus dijalankan - tidak pernah degradasi diam-diam.
Memilih penyedia
Penyedia terpasang melalui string DSN. Instal paket bridge, tempel DSN ke dsns, selesai.
| Penyedia | Instal | Contoh DSN |
|---|---|---|
| SMTP | bawaan | smtp://user:pass@host:587 |
| Sendmail | bawaan | sendmail://default |
| Dev/null (buang mail) | bawaan | null://null |
| Postmark | composer require symfony/postmark-mailer |
postmark+api://KEY@api.postmarkapp.com |
| Sendgrid | composer require symfony/sendgrid-mailer |
sendgrid+api://KEY@default |
| Mailgun | composer require symfony/mailgun-mailer |
mailgun+https://KEY:DOMAIN@api.mailgun.net |
| Amazon SES | composer require symfony/amazon-mailer |
ses+https://KEY:SECRET@default |
| Brevo | composer require symfony/brevo-mailer |
brevo+api://KEY@default |
| MailerSend | composer require symfony/mailersend-mailer |
mailersend+api://KEY@default |
Daftar lengkap ada di dokumentasi Symfony Mailer - apa pun yang didokumentasikan di sana bekerja di sini tanpa perubahan.
Beberapa penyedia sekaligus
Beri nama setiap transport, lalu pilih per pesan:
MailPlugin::install([
'dsns' => [
'transactional' => 'postmark+api://KEY@api.postmarkapp.com',
'bulk' => 'smtp://user:pass@bulk.example.com:587',
],
'from' => 'no-reply@example.com',
]);
// Tidak ada pemanggilan ->transport() = kunci pertama di "dsns" ("transactional" di sini).
Flight::mail()->compose()->to('...')->text('struk')->send();
// Pilih rute lain secara eksplisit.
Flight::mail()->compose()->to('...')->text('newsletter')->transport('bulk')->send();
Referensi konfigurasi
Semuanya opsional kecuali dsns.
MailPlugin::install([
// WAJIB - nama transport => Symfony DSN.
// Entri pertama digunakan ketika pesan tidak menyebutkan satu.
'dsns' => [
'default' => 'smtp://user:pass@localhost:1025',
],
// Transport yang digunakan ketika pesan tidak punya ->transport() eksplisit dan
// Anda tidak ingin kunci pertama. Harus ada di "dsns".
'default_transport' => 'default',
// Pengirim global. String, Symfony Address, atau ['email' => 'Name'].
// Diterapkan hanya ketika pesan tidak set ->from() sendiri.
'from' => ['no-reply@example.com' => 'Aplikasi Saya'],
// Mesin templat default: 'twig', 'latte', atau nama kustom.
// Hanya dikonsultasikan untuk templat yang ekstensinya bukan renderer terdaftar.
'renderer' => 'twig',
// Tempat templat berada, dicari berurutan; plus direktori cache opsional.
'templates' => [
'paths' => [__DIR__ . '/mail-templates'],
'cache' => __DIR__ . '/cache/mail',
],
// Opsi extra yang diteruskan langsung ke Twig\Environment.
'twig' => ['options' => ['strict_variables' => true]],
// Sesuaikan mesin Latte saat boot: fn(Latte\Engine $engine): void.
'latte' => ['setup' => static fn (Latte\Engine $e) => $e->addExtension(new MyExtension())],
// Peningkatan body saat kirim (lihat "Styling HTML dan menghasilkan bagian teks").
'inline_css' => true, // atau ['css' => '...', 'css_file' => '...']
'text_from_html' => true, // atau 'plain' / 'markdown'
// Skema DSN kustom, renderer kustom, hook pra-kirim (lihat di bawah).
'transport_factories' => [],
'renderers' => [],
'hooks' => [],
// Plumbing opsional yang diserahkan ke setiap transport.
'event_dispatcher' => $dispatcher, // Symfony MessageEvents
'logger' => $psr3Logger,
]);
Melangkah lebih jauh
Semua di bawah ini opsional. Default sudah mencakup sebagian besar aplikasi.
Menambahkan skema DSN kustom
Implementasikan TransportFactoryInterface milik Symfony dan daftarkan - lalu skema Anda sendiri bekerja persis seperti yang bawaan:
use ryanstubbs\FlightMail\MailPlugin;
use Symfony\Component\Mailer\Transport\Dsn;
use Symfony\Component\Mailer\Transport\TransportFactoryInterface;
use Symfony\Component\Mailer\Transport\TransportInterface;
class MyCarrierFactory implements TransportFactoryInterface
{
public function supports(Dsn $dsn): bool
{
return $dsn->getScheme() === 'mycarrier';
}
public function create(Dsn $dsn): TransportInterface
{
// ... bangun transport yang berbicara ke carrier Anda
}
}
$plugin = MailPlugin::install(['dsns' => ['carrier' => 'mycarrier://key']]);
$plugin->addTransportFactory(new MyCarrierFactory());
Menambahkan renderer templat kustom
Apa pun yang mengubah nama templat plus params menjadi string memenuhi syarat:
use ryanstubbs\FlightMail\MailPlugin;
use ryanstubbs\FlightMail\Render\RendererInterface;
$plugin = MailPlugin::install($config);
$plugin->addRenderer('markdown', fn (array $config): RendererInterface =>
new MarkdownMailRenderer($config['templates']['paths'] ?? [])
);
// Templat yang berakhiran .markdown sekarang menggunakannya secara otomatis:
Flight::mail()->compose()->to('...')->template('welcome.markdown', ['name' => 'Ryan'])->send();
Menjalankan sesuatu tepat sebelum mengirim
Hook menerima pesan yang sudah jadi - setelah rendering, setelah default, tepat sebelum ke kabel:
$plugin->addHook(function (ryanstubbs\FlightMail\Message $message): void {
$message->getHeaders()->addTextHeader('X-Mailer', 'MyApp/1.0');
});
Event dan logging
Serahkan Symfony event dispatcher dan/atau PSR-3 logger dan setiap transport akan menggunakannya:
$plugin->eventDispatcher($dispatcher); // menerima MessageEvent sebelum setiap kirim
$plugin->logger($logger); // log tingkat transport
Contekan API
// Setup
MailPlugin::install($config) // daftarkan pada aplikasi Flight global
MailPlugin::register($app, $config) // daftarkan pada Engine tertentu
$mailer = Flight::mail(); // instance Mailer bersama
// Membangun pesan
$mailer->compose(): Message
$message->to(...)->from(...)->subject(...) // metode Symfony Mime standar
$message->text(string) // body string biasa
$message->html(string) // body string HTML
$message->template($name, $params) // body HTML dari templat
$message->htmlTemplate($name, $params) // alias dari template()
$message->textTemplate($name, $params) // body teks dari templat
$message->inlineCss() / ->withoutInlineCss() // CSS inlining per pesan
$message->textFromHtml($mode) // bagian teks otomatis: true/'auto'/'plain'/'markdown'/false
$message->withoutTextFromHtml() // email HTML saja
$message->transport($name) // rute via DSN bernama
$message->send(): ?SentMessage // render + kirim
// Pada mailer itu sendiri
$mailer->send($message): ?SentMessage // alternatif eksplisit untuk $message->send()
$mailer->render($template, $params): string // render tanpa mengirim
$mailer->addHook(callable): static // fn(Message $message): void
$mailer->transports(): TransportManager // get() / has() / names()
$mailer->renderers(): RendererFactory // create() / has() / add()
Karena Message extends Symfony\Component\Mime\Email, setiap metode Symfony yang sudah Anda kenal - attach(), embed(), priority(), replyTo() - langsung bekerja.
Pemecahan masalah
"No mail DSNs configured"
Anda memanggil Flight::mail() sebelum mendaftarkan plugin, atau array config tidak menyertakan dsns. Error ini disengaja - FlightMail menolak menebak ke mana mail Anda harus pergi alih-alih menjatuhkannya diam-diam.
"Unknown mail template renderer ..."
Anda memakai templat yang engine-nya belum terinstal. Perbaiki dengan composer require twig/twig atau composer require latte/latte, atau daftarkan renderer kustom yang dinamai sesuai ekstensi.
"Unknown mail transport ..."
Sebuah ->transport('name') (atau default_transport) tidak cocok dengan kunci mana pun di dsns. Periksa ejaan - error-nya mencantumkan nama yang dikonfigurasi.
Email tidak sampai
Arahkan dsns ke null://null untuk memastikan sisa kode Anda bekerja, lalu kembali ke DSN yang sebenarnya. Di DDEV, gunakan smtp://127.0.0.1:1025 dan periksa pesan di Mailpit pada port 8025.
Untuk laporan bug, pull request, dan sumber lengkap, kunjungi repositori GitHub.
Awesome-plugins/comment_template
CommentTemplate
CommentTemplate adalah mesin template PHP yang kuat dengan kompilasi aset, pewarisan template, dan pemrosesan variabel. Ini menyediakan cara sederhana namun fleksibel untuk mengelola template dengan minifikasi CSS/JS dan caching bawaan.
Fitur
- Pewarisan Template: Gunakan tata letak dan sertakan template lain
- Kompilasi Aset: Minifikasi dan caching CSS/JS otomatis
- Pemrosesan Variabel: Variabel template dengan filter dan perintah
- Encoding Base64: Aset inline sebagai data URI
- Integrasi Framework Flight: Integrasi opsional dengan framework PHP Flight
Instalasi
Instal dengan composer.
composer require knifelemon/comment-template
Konfigurasi Dasar
Ada beberapa opsi konfigurasi dasar untuk memulai. Anda dapat membaca lebih lanjut tentangnya di Repo CommentTemplate.
Metode 1: Menggunakan Fungsi Callback
<?php
require_once 'vendor/autoload.php';
use KnifeLemon\CommentTemplate\Engine;
$app = Flight::app();
$app->register('view', Engine::class, [], function (Engine $engine) use ($app) {
// Direktori root (tempat index.php berada) - root dokumen aplikasi web Anda
$engine->setPublicPath(__DIR__);
// Direktori file template - mendukung path relatif dan absolut
$engine->setSkinPath('views'); // Relatif terhadap path publik
// Tempat aset yang dikompilasi akan disimpan - mendukung path relatif dan absolut
$engine->setAssetPath('assets'); // Relatif terhadap path publik
// Ekstensi file template
$engine->setFileExtension('.php');
});
$app->map('render', function(string $template, array $data) use ($app): void {
echo $app->view()->render($template, $data);
});
Metode 2: Menggunakan Parameter Konstruktor
<?php
require_once 'vendor/autoload.php';
use KnifeLemon\CommentTemplate\Engine;
$app = Flight::app();
// __construct(string $publicPath = "", string $skinPath = "", string $assetPath = "", string $fileExtension = "")
$app->register('view', Engine::class, [
__DIR__, // publicPath - direktori root (tempat index.php berada)
'views', // skinPath - path template (mendukung relatif/absolut)
'assets', // assetPath - path aset yang dikompilasi (mendukung relatif/absolut)
'.php' // fileExtension - ekstensi file template
]);
$app->map('render', function(string $template, array $data) use ($app): void {
echo $app->view()->render($template, $data);
});
Konfigurasi Path
CommentTemplate menyediakan penanganan path yang cerdas untuk path relatif dan absolut:
Path Publik
Path Publik adalah direktori root aplikasi web Anda, biasanya tempat index.php berada. Ini adalah root dokumen yang disajikan oleh server web.
// Contoh: jika index.php Anda berada di /var/www/html/myapp/index.php
$template->setPublicPath('/var/www/html/myapp'); // Direktori root
// Contoh Windows: jika index.php Anda berada di C:\xampp\htdocs\myapp\index.php
$template->setPublicPath('C:\\xampp\\htdocs\\myapp');
Konfigurasi Path Template
Path template mendukung path relatif dan absolut:
$template = new Engine();
$template->setPublicPath('/var/www/html/myapp'); // Direktori root (tempat index.php berada)
// Path relatif - otomatis digabungkan dengan path publik
$template->setSkinPath('views'); // → /var/www/html/myapp/views/
$template->setSkinPath('templates/pages'); // → /var/www/html/myapp/templates/pages/
// Path absolut - digunakan apa adanya (Unix/Linux)
$template->setSkinPath('/var/www/templates'); // → /var/www/templates/
$template->setSkinPath('/full/path/to/templates'); // → /full/path/to/templates/
// Path absolut Windows
$template->setSkinPath('C:\\www\\templates'); // → C:\www\templates\
$template->setSkinPath('D:/projects/templates'); // → D:/projects/templates/
// Path UNC (share jaringan Windows)
$template->setSkinPath('\\\\server\\share\\templates'); // → \\server\share\templates\
Konfigurasi Path Aset
Path aset juga mendukung path relatif dan absolut:
// Path relatif - otomatis digabungkan dengan path publik
$template->setAssetPath('assets'); // → /var/www/html/myapp/assets/
$template->setAssetPath('static/files'); // → /var/www/html/myapp/static/files/
// Path absolut - digunakan apa adanya (Unix/Linux)
$template->setAssetPath('/var/www/cdn'); // → /var/www/cdn/
$template->setAssetPath('/full/path/to/assets'); // → /full/path/to/assets/
// Path absolut Windows
$template->setAssetPath('C:\\www\\static'); // → C:\www\static\
$template->setAssetPath('D:/projects/assets'); // → D:/projects/assets/
// Path UNC (share jaringan Windows)
$template->setAssetPath('\\\\server\\share\\assets'); // → \\server\share\assets\
Deteksi Path Cerdas:
- Path Relatif: Tidak ada pemisah awal (
/,\) atau huruf drive - Absolut Unix: Dimulai dengan
/(misalnya,/var/www/assets) - Absolut Windows: Dimulai dengan huruf drive (misalnya,
C:\www,D:/assets) - Path UNC: Dimulai dengan
\\(misalnya,\\server\share)
Cara Kerjanya:
- Semua path otomatis diselesaikan berdasarkan tipe (relatif vs absolut)
- Path relatif digabungkan dengan path publik
@cssdan@jsmembuat file yang diminifikasi di:{resolvedAssetPath}/css/atau{resolvedAssetPath}/js/@assetmenyalin file tunggal ke:{resolvedAssetPath}/{relativePath}@assetDirmenyalin direktori ke:{resolvedAssetPath}/{relativePath}- Caching cerdas: file hanya disalin ketika sumber lebih baru daripada tujuan
Integrasi Tracy Debugger
CommentTemplate menyertakan integrasi dengan Tracy Debugger untuk logging dan debugging pengembangan.

Instalasi
composer require tracy/tracy
Penggunaan
<?php
use KnifeLemon\CommentTemplate\Engine;
use Tracy\Debugger;
// Aktifkan Tracy (harus dipanggil sebelum output apa pun)
Debugger::enable(Debugger::DEVELOPMENT);
Flight::set('flight.content_length', false);
// Override template
$app->register('view', Engine::class, [], function (Engine $builder) use ($app) {
$builder->setPublicPath($app->get('flight.views.topPath'));
$builder->setAssetPath($app->get('flight.views.assetPath'));
$builder->setSkinPath($app->get('flight.views.path'));
$builder->setFileExtension($app->get('flight.views.extension'));
});
$app->map('render', function(string $template, array $data) use ($app): void {
echo $app->view()->render($template, $data);
});
$app->start();
Fitur Panel Debug
CommentTemplate menambahkan panel kustom ke debug bar Tracy dengan empat tab:
- Overview: Konfigurasi, metrik kinerja, dan penghitungan
- Assets: Detail kompilasi CSS/JS dengan rasio kompresi
- Variables: Nilai asli dan yang ditransformasi dengan filter yang diterapkan
- Timeline: Tampilan kronologis dari semua operasi template
Apa yang Dicatat
- Rendering template (mulai/selesai, durasi, layout, impor)
- Kompilasi aset (file CSS/JS, ukuran, rasio kompresi)
- Pemrosesan variabel (nilai asli/yang ditransformasi, filter)
- Operasi aset (encoding base64, penyalinan file)
- Metrik kinerja (durasi, penggunaan memori)
Catatan: Tidak ada dampak kinerja ketika Tracy tidak diinstal atau dinonaktifkan.
Lihat contoh lengkap yang berfungsi dengan Flight PHP.
Direktif Template
Pewarisan Tata Letak
Gunakan tata letak untuk membuat struktur umum:
layout/global_layout.php:
<!DOCTYPE html>
<html>
<head>
<title>{$title}</title>
</head>
<body>
<!--@contents-->
</body>
</html>
view/page.php:
<!--@layout(layout/global_layout)-->
<h1>{$title}</h1>
<p>{$content}</p>
Manajemen Aset
File CSS
<!--@css(/css/styles.css)--> <!-- Diminifikasi dan di-cache -->
<!--@cssSingle(/css/critical.css)--> <!-- File tunggal, tidak diminifikasi -->
File JavaScript
CommentTemplate mendukung strategi pemuatan JavaScript yang berbeda:
<!--@js(/js/script.js)--> <!-- Diminifikasi, dimuat di bawah -->
<!--@jsAsync(/js/analytics.js)--> <!-- Diminifikasi, dimuat di bawah dengan async -->
<!--@jsDefer(/js/utils.js)--> <!-- Diminifikasi, dimuat di bawah dengan defer -->
<!--@jsTop(/js/critical.js)--> <!-- Diminifikasi, dimuat di head -->
<!--@jsTopAsync(/js/tracking.js)--> <!-- Diminifikasi, dimuat di head dengan async -->
<!--@jsTopDefer(/js/polyfill.js)--> <!-- Diminifikasi, dimuat di head dengan defer -->
<!--@jsSingle(/js/widget.js)--> <!-- File tunggal, tidak diminifikasi -->
<!--@jsSingleAsync(/js/ads.js)--> <!-- File tunggal, tidak diminifikasi, async -->
<!--@jsSingleDefer(/js/social.js)--> <!-- File tunggal, tidak diminifikasi, defer -->
Direktif Aset dalam File CSS/JS
CommentTemplate juga memproses direktif aset dalam file CSS dan JavaScript selama kompilasi:
Contoh CSS:
/* Dalam file CSS Anda */
@font-face {
font-family: 'CustomFont';
src: url('<!--@asset(fonts/custom.woff2)-->') format('woff2');
}
.background-image {
background: url('<!--@asset(images/bg.jpg)-->');
}
.inline-icon {
background: url('<!--@base64(icons/star.svg)-->');
}
Contoh JavaScript:
/* Dalam file JS Anda */
const fontUrl = '<!--@asset(fonts/custom.woff2)-->';
const imageData = '<!--@base64(images/icon.png)-->';
Encoding Base64
<!--@base64(images/logo.png)--> <!-- Inline sebagai data URI -->
Contoh:
<!-- Inline gambar kecil sebagai data URI untuk pemuatan lebih cepat -->
<img src="<!--@base64(images/logo.png)-->" alt="Logo">
<div style="background-image: url('<!--@base64(icons/star.svg)-->');">
Ikon kecil sebagai latar belakang
</div>
Penyalinan Aset
<!--@asset(images/photo.jpg)--> <!-- Salin aset tunggal ke direktori publik -->
<!--@assetDir(assets)--> <!-- Salin seluruh direktori ke direktori publik -->
Contoh:
<!-- Salin dan rujuk aset statis -->
<img src="<!--@asset(images/hero-banner.jpg)-->" alt="Hero Banner">
<a href="<!--@asset(documents/brochure.pdf)-->" download>Unduh Brosur</a>
<!-- Salin seluruh direktori (font, ikon, dll.) -->
<!--@assetDir(assets/fonts)-->
<!--@assetDir(assets/icons)-->
Penyertaan Template
<!--@import(components/header)--> <!-- Sertakan template lain -->
Contoh:
<!-- Sertakan komponen yang dapat digunakan kembali -->
<!--@import(components/header)-->
<main>
<h1>Selamat datang di situs web kami</h1>
<!--@import(components/sidebar)-->
<div class="content">
<p>Konten utama di sini...</p>
</div>
</main>
<!--@import(components/footer)-->
Pemrosesan Variabel
Variabel Dasar
<h1>{$title}</h1>
<p>{$description}</p>
Filter Variabel
{$title|upper} <!-- Ubah ke huruf besar -->
{$content|lower} <!-- Ubah ke huruf kecil -->
{$html|striptag} <!-- Hapus tag HTML -->
{$text|escape} <!-- Escape HTML -->
{$multiline|nl2br} <!-- Ubah baris baru menjadi <br> -->
{$html|br2nl} <!-- Ubah tag <br> menjadi baris baru -->
{$description|trim} <!-- Potong spasi -->
{$subject|title} <!-- Ubah ke title case -->
Perintah Variabel
{$title|default=Default Title} <!-- Atur nilai default -->
{$name|concat= (Admin)} <!-- Gabungkan teks -->
Perintah Variabel
{$content|striptag|trim|escape} <!-- Rantai beberapa filter -->
Komentar
Komentar template sepenuhnya dihapus dari output dan tidak akan muncul di HTML akhir:
{* Ini adalah komentar template satu baris *}
{*
Ini adalah komentar
template multi-baris
yang meliputi beberapa baris
*}
<h1>{$title}</h1>
{* Komentar debug: memeriksa apakah variabel title berfungsi *}
<p>{$content}</p>
Catatan: Komentar template {* ... *} berbeda dari komentar HTML <!-- ... -->. Komentar template dihapus selama pemrosesan dan tidak pernah mencapai browser.
Struktur Proyek Contoh
project/
├── source/
│ ├── layouts/
│ │ └── default.php
│ ├── components/
│ │ ├── header.php
│ │ └── footer.php
│ ├── css/
│ │ ├── bootstrap.min.css
│ │ └── custom.css
│ ├── js/
│ │ ├── app.js
│ │ └── bootstrap.min.js
│ └── homepage.php
├── public/
│ └── assets/ # Aset yang dihasilkan
│ ├── css/
│ └── js/
└── vendor/Awesome-plugins/easy_query
EasyQuery
knifelemon/easy-query adalah pembuat kueri SQL yang ringan dan fasih yang menghasilkan SQL dan parameter untuk pernyataan yang disiapkan. Bekerja dengan SimplePdo.
Fitur
- 🔗 API Fasih - Rantai metode untuk konstruksi kueri yang mudah dibaca
- 🛡️ Perlindungan Injeksi SQL - Pengikatan parameter otomatis dengan pernyataan yang disiapkan
- 🔧 Dukungan SQL Mentah - Sisipkan ekspresi SQL mentah dengan
raw() - 📝 Jenis Kueri Beragam - SELECT, INSERT, UPDATE, DELETE, COUNT
- 🔀 Dukungan JOIN - INNER, LEFT, RIGHT join dengan alias
- 🎯 Kondisi Lanjutan - LIKE, IN, NOT IN, BETWEEN, operator perbandingan
- 🌐 Agnostik Database - Mengembalikan SQL + params, gunakan dengan koneksi DB apa pun
- 🪶 Ringan - Jejak minimal tanpa ketergantungan
Instalasi
composer require knifelemon/easy-query
Mulai Cepat
use KnifeLemon\EasyQuery\Builder;
$q = Builder::table('users')
->select(['id', 'name', 'email'])
->where(['status' => 'active'])
->orderBy('created_at DESC')
->limit(10)
->build();
// Gunakan dengan SimplePdo Flight
$users = Flight::db()->fetchAll($q['sql'], $q['params']);
Memahami build()
Metode build() mengembalikan array dengan sql dan params. Pemisahan ini menjaga database Anda aman dengan menggunakan pernyataan yang disiapkan.
$q = Builder::table('users')
->where(['email' => 'user@example.com'])
->build();
// Mengembalikan:
// [
// 'sql' => 'SELECT * FROM users WHERE email = ?',
// 'params' => ['user@example.com']
// ]
Jenis Kueri
SELECT
// Pilih semua kolom
$q = Builder::table('users')->build();
// SELECT * FROM users
// Pilih kolom spesifik
$q = Builder::table('users')
->select(['id', 'name', 'email'])
->build();
// SELECT id, name, email FROM users
// Dengan alias tabel
$q = Builder::table('users')
->alias('u')
->select(['u.id', 'u.name'])
->build();
// SELECT u.id, u.name FROM users AS u
INSERT
$q = Builder::table('users')
->insert([
'name' => 'John Doe',
'email' => 'john@example.com',
'status' => 'active'
])
->build();
// INSERT INTO users SET name = ?, email = ?, status = ?
Flight::db()->runQuery($q['sql'], $q['params']);
$userId = Flight::db()->lastInsertId();
UPDATE
$q = Builder::table('users')
->update(['status' => 'inactive', 'updated_at' => date('Y-m-d H:i:s')])
->where(['id' => 123])
->build();
// UPDATE users SET status = ?, updated_at = ? WHERE id = ?
Flight::db()->runQuery($q['sql'], $q['params']);
DELETE
$q = Builder::table('users')
->delete()
->where(['id' => 123])
->build();
// DELETE FROM users WHERE id = ?
Flight::db()->runQuery($q['sql'], $q['params']);
COUNT
$q = Builder::table('users')
->count()
->where(['status' => 'active'])
->build();
// SELECT COUNT(*) AS cnt FROM users WHERE status = ?
$count = Flight::db()->fetchField($q['sql'], $q['params']);
Kondisi WHERE
Kesetaraan Sederhana
$q = Builder::table('users')
->where(['id' => 123, 'status' => 'active'])
->build();
// WHERE id = ? AND status = ?
Operator Perbandingan
$q = Builder::table('users')
->where([
'age' => ['>=', 18],
'score' => ['<', 100],
'name' => ['!=', 'admin']
])
->build();
// WHERE age >= ? AND score < ? AND name != ?
LIKE
$q = Builder::table('users')
->where(['name' => ['LIKE', '%john%']])
->build();
// WHERE name LIKE ?
IN / NOT IN
// IN
$q = Builder::table('users')
->where(['id' => ['IN', [1, 2, 3, 4, 5]]])
->build();
// WHERE id IN (?, ?, ?, ?, ?)
// NOT IN
$q = Builder::table('users')
->where(['status' => ['NOT IN', ['banned', 'deleted']]])
->build();
// WHERE status NOT IN (?, ?)
BETWEEN
$q = Builder::table('products')
->where(['price' => ['BETWEEN', [100, 500]]])
->build();
// WHERE price BETWEEN ? AND ?
Kondisi OR
Gunakan orWhere() untuk menambahkan kondisi OR yang dikelompokkan:
$q = Builder::table('users')
->where(['status' => 'active'])
->orWhere([
'role' => 'admin',
'permissions' => ['LIKE', '%manage%']
])
->build();
// WHERE status = ? AND (role = ? OR permissions LIKE ?)
JOIN
INNER JOIN
$q = Builder::table('users')
->alias('u')
->select(['u.id', 'u.name', 'p.title'])
->innerJoin('posts', 'u.id = p.user_id', 'p')
->build();
// SELECT u.id, u.name, p.title FROM users AS u INNER JOIN posts AS p ON u.id = p.user_id
LEFT JOIN
$q = Builder::table('users')
->alias('u')
->select(['u.name', 'o.total'])
->leftJoin('orders', 'u.id = o.user_id', 'o')
->build();
// ... LEFT JOIN orders AS o ON u.id = o.user_id
Multiple JOINs
$q = Builder::table('orders')
->alias('o')
->select(['o.id', 'u.name AS customer', 'p.title AS product'])
->innerJoin('users', 'o.user_id = u.id', 'u')
->leftJoin('order_items', 'o.id = oi.order_id', 'oi')
->leftJoin('products', 'oi.product_id = p.id', 'p')
->where(['o.status' => 'completed'])
->build();
Pengurutan, Pengelompokan, dan Batasan
ORDER BY
$q = Builder::table('users')
->orderBy('created_at DESC')
->build();
// ORDER BY created_at DESC
GROUP BY
$q = Builder::table('orders')
->select(['user_id', 'COUNT(*) as order_count'])
->groupBy('user_id')
->build();
// SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id
LIMIT dan OFFSET
$q = Builder::table('users')
->limit(10)
->build();
// LIMIT 10
$q = Builder::table('users')
->limit(10, 20) // limit, offset
->build();
// LIMIT 10 OFFSET 20
Ekspresi SQL Mentah
Gunakan raw() ketika Anda membutuhkan fungsi SQL atau ekspresi yang tidak boleh diperlakukan sebagai parameter terikat.
Raw Dasar
$q = Builder::table('users')
->update([
'login_count' => Builder::raw('login_count + 1'),
'updated_at' => Builder::raw('NOW()')
])
->where(['id' => 123])
->build();
// SET login_count = login_count + 1, updated_at = NOW()
Raw dengan Parameter Terikat
$q = Builder::table('orders')
->update([
'total' => Builder::raw('COALESCE(subtotal, ?) + ?', [0, 10])
])
->where(['id' => 1])
->build();
// SET total = COALESCE(subtotal, ?) + ?
// params: [0, 10, 1]
Raw di WHERE (Subquery)
$q = Builder::table('products')
->where([
'price' => ['>', Builder::raw('(SELECT AVG(price) FROM products)')]
])
->build();
// WHERE price > (SELECT AVG(price) FROM products)
Identifier Aman untuk Input Pengguna
Ketika nama kolom berasal dari input pengguna, gunakan safeIdentifier() untuk mencegah injeksi SQL:
$sortColumn = $_GET['sort']; // misalnya, 'created_at'
$safeColumn = Builder::safeIdentifier($sortColumn);
$q = Builder::table('users')
->orderBy($safeColumn . ' DESC')
->build();
// Jika pengguna mencoba: "name; DROP TABLE users--"
// Melempar InvalidArgumentException
rawSafe untuk Nama Kolom dari Pengguna
$userColumn = $_GET['aggregate_column'];
$q = Builder::table('orders')
->select([
Builder::rawSafe('SUM({col})', ['col' => $userColumn])->value . ' AS total'
])
->build();
// Memvalidasi nama kolom, melempar exception jika tidak valid
Peringatan: Jangan pernah menggabungkan input pengguna secara langsung ke dalam
raw(). Selalu gunakan parameter terikat atausafeIdentifier().
Penggunaan Ulang Pembuat Kueri
Metode Clear
Hapus bagian spesifik untuk menggunakan kembali pembuat:
$query = Builder::table('users')
->select(['id', 'name'])
->where(['status' => 'active'])
->orderBy('created_at DESC');
// Kueri pertama
$q1 = $query->limit(10)->build();
// Hapus dan gunakan kembali
$query->clearWhere()->clearLimit();
// Kueri kedua dengan kondisi berbeda
$q2 = $query
->where(['status' => 'pending'])
->limit(5)
->build();
Metode Clear yang Tersedia
| Method | Deskripsi |
|---|---|
clearWhere() |
Hapus kondisi WHERE dan parameter |
clearSelect() |
Reset kolom SELECT ke default '*' |
clearJoin() |
Hapus semua klausa JOIN |
clearGroupBy() |
Hapus klausa GROUP BY |
clearOrderBy() |
Hapus klausa ORDER BY |
clearLimit() |
Hapus LIMIT dan OFFSET |
clearAll() |
Reset pembuat ke keadaan awal |
Contoh Pagination
$baseQuery = Builder::table('users')
->select(['id', 'name', 'email'])
->where(['status' => 'active'])
->orderBy('created_at DESC');
// Dapatkan total hitungan
$countQuery = clone $baseQuery;
$countResult = $countQuery->clearSelect()->count()->build();
$total = Flight::db()->fetchField($countResult['sql'], $countResult['params']);
// Dapatkan hasil paginasi
$page = 1;
$perPage = 20;
$listResult = $baseQuery->limit($perPage, ($page - 1) * $perPage)->build();
$users = Flight::db()->fetchAll($listResult['sql'], $listResult['params']);
Pembuatan Kueri Dinamis
$query = Builder::table('products')->alias('p');
if (!empty($categoryId)) {
$query->where(['p.category_id' => $categoryId]);
}
if (!empty($minPrice)) {
$query->where(['p.price' => ['>=', $minPrice]]);
}
if (!empty($maxPrice)) {
$query->where(['p.price' => ['<=', $maxPrice]]);
}
if (!empty($searchTerm)) {
$query->where(['p.name' => ['LIKE', "%{$searchTerm}%"]]);
}
$result = $query->orderBy('p.created_at DESC')->limit(20)->build();
$products = Flight::db()->fetchAll($result['sql'], $result['params']);
Contoh FlightPHP Lengkap
use KnifeLemon\EasyQuery\Builder;
// Daftar pengguna dengan paginasi
Flight::route('GET /users', function() {
$page = (int) (Flight::request()->query['page'] ?? 1);
$perPage = 20;
$q = Builder::table('users')
->select(['id', 'name', 'email', 'created_at'])
->where(['status' => 'active'])
->orderBy('created_at DESC')
->limit($perPage, ($page - 1) * $perPage)
->build();
$users = Flight::db()->fetchAll($q['sql'], $q['params']);
Flight::json(['users' => $users, 'page' => $page]);
});
// Buat pengguna
Flight::route('POST /users', function() {
$data = Flight::request()->data;
$q = Builder::table('users')
->insert([
'name' => $data->name,
'email' => $data->email,
'created_at' => Builder::raw('NOW()')
])
->build();
Flight::db()->runQuery($q['sql'], $q['params']);
Flight::json(['id' => Flight::db()->lastInsertId()]);
});
// Perbarui pengguna
Flight::route('PUT /users/@id', function($id) {
$data = Flight::request()->data;
$q = Builder::table('users')
->update([
'name' => $data->name,
'email' => $data->email,
'updated_at' => Builder::raw('NOW()')
])
->where(['id' => $id])
->build();
Flight::db()->runQuery($q['sql'], $q['params']);
Flight::json(['success' => true]);
});
// Hapus pengguna
Flight::route('DELETE /users/@id', function($id) {
$q = Builder::table('users')
->delete()
->where(['id' => $id])
->build();
Flight::db()->runQuery($q['sql'], $q['params']);
Flight::json(['success' => true]);
});
Referensi API
Metode Statis
| Method | Deskripsi |
|---|---|
Builder::table(string $table) |
Buat instance pembuat baru untuk tabel |
Builder::raw(string $sql, array $bindings = []) |
Buat ekspresi SQL mentah |
Builder::rawSafe(string $expr, array $identifiers, array $bindings = []) |
Ekspresi mentah dengan substitusi identifier aman |
Builder::safeIdentifier(string $identifier) |
Validasi dan kembalikan nama kolom/tabel yang aman |
Metode Instance
| Method | Deskripsi |
|---|---|
alias(string $alias) |
Set alias tabel |
select(string\|array $columns) |
Set kolom untuk dipilih (default: '*') |
where(array $conditions) |
Tambahkan kondisi WHERE (AND) |
orWhere(array $conditions) |
Tambahkan kondisi OR WHERE |
join(string $table, string $condition, string $alias, string $type) |
Tambahkan klausa JOIN |
innerJoin(string $table, string $condition, string $alias) |
Tambahkan INNER JOIN |
leftJoin(string $table, string $condition, string $alias) |
Tambahkan LEFT JOIN |
groupBy(string $groupBy) |
Tambahkan klausa GROUP BY |
orderBy(string $orderBy) |
Tambahkan klausa ORDER BY |
limit(int $limit, int $offset = 0) |
Tambahkan LIMIT dan OFFSET |
count(string $column = '*') |
Set kueri ke COUNT |
insert(array $data) |
Set kueri ke INSERT |
update(array $data) |
Set kueri ke UPDATE |
delete() |
Set kueri ke DELETE |
build() |
Bangun dan kembalikan ['sql' => ..., 'params' => ...] |
get() |
Alias untuk build() |
Integrasi Tracy Debugger
EasyQuery secara otomatis terintegrasi dengan Tracy Debugger jika terinstal. Tidak perlu pengaturan!
composer require tracy/tracy
use Tracy\Debugger;
Debugger::enable();
// Semua kueri secara otomatis dicatat ke panel Tracy
$q = Builder::table('users')->where(['status' => 'active'])->build();
Panel Tracy menampilkan:
- Total kueri dan rincian berdasarkan jenis
- SQL yang dihasilkan (dengan penyorotan sintaks)
- Array parameter
- Detail kueri (tabel, where, join, dll.)
Untuk dokumentasi lengkap, kunjungi repositori GitHub.
Awesome-plugins/twig
Twig
Twig adalah mesin templat PHP yang fleksibel, cepat, dan aman. Ini adalah bahasa templating yang digunakan oleh Symfony dan banyak proyek lainnya, yang berarti alat coding AI dan sebagian besar pengembang PHP sudah sangat mengenal sintaksnya. Twig mengompilasi templat menjadi PHP yang dioptimalkan, meng-escape output secara otomatis secara default (bagus untuk perlindungan XSS), dan mudah diperluas dengan filter, fungsi, dan ekstensi.
Instalasi
Instal dengan composer.
composer require twig/twig
Konfigurasi Dasar
Ada beberapa opsi konfigurasi dasar untuk memulai. Anda dapat membaca lebih lanjut tentang opsi-opsi tersebut di Dokumentasi Twig.
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function(string $template, array $data): void {
$loader = new \Twig\Loader\FilesystemLoader(Flight::get('flight.views.path'));
$twig = new \Twig\Environment($loader, [
// Tempat Twig menyimpan templat yang telah dikompilasi
'cache' => __DIR__ . '/../cache/twig',
// Kompilasi ulang templat ketika sumber berubah (berguna saat pengembangan)
'auto_reload' => true,
]);
echo $twig->render($template, $data);
});
Mendaftarkan Twig sebagai Kelas View
Jika Anda lebih suka menggunakan ulang satu Twig environment (direkomendasikan untuk produksi), daftarkan dan arahkan render ke sana:
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('view', \Twig\Environment::class, [
new \Twig\Loader\FilesystemLoader($app->get('flight.views.path')),
[
'cache' => __DIR__ . '/../cache/twig',
'auto_reload' => true,
],
]);
$app->map('render', function(string $template, array $data): void {
echo Flight::view()->render($template, $data);
});
Contoh Layout Sederhana
Berikut adalah contoh sederhana dari file layout. Ini adalah file yang akan digunakan untuk membungkus semua view Anda yang lain.
{# app/views/layout.twig #}
<!doctype html>
<html lang="en">
<head>
<title>{% if title %}{{ title }} - {% endif %}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<nav>
{# elemen nav Anda di sini #}
</nav>
</header>
<div id="content">
{# Ini adalah keajaibannya #}
{% block content %}{% endblock %}
</div>
<div id="footer">
© Copyright
</div>
</body>
</html>
Dan sekarang kita memiliki file Anda yang akan dirender di dalam blok konten tersebut:
{# app/views/home.twig #}
{# Ini memberitahu Twig bahwa file ini "di dalam" file layout.twig #}
{% extends 'layout.twig' %}
{# Ini adalah konten yang akan dirender di dalam layout di dalam blok konten #}
{% block content %}
<h1>Home Page</h1>
<p>Welcome to my app!</p>
{% endblock %}
Kemudian ketika Anda pergi untuk merender ini di dalam fungsi atau controller Anda, Anda akan melakukan sesuatu seperti ini:
// rute sederhana
Flight::route('/', function () {
Flight::render('home.twig', [
'title' => 'Home Page'
]);
});
// atau jika Anda menggunakan controller
Flight::route('/', [HomeController::class, 'index']);
// HomeController.php
class HomeController
{
public function index()
{
Flight::render('home.twig', [
'title' => 'Home Page'
]);
}
}
Lihat Dokumentasi Twig untuk informasi lebih lanjut tentang cara menggunakan Twig secara maksimal!
Debugging
Twig dilengkapi dengan Ekstensi Debug yang menambahkan fungsi dump() yang dapat Anda gunakan di dalam templat. Aktifkan hanya saat pengembangan:
$app->register('view', \Twig\Environment::class, [
new \Twig\Loader\FilesystemLoader($app->get('flight.views.path')),
[
'cache' => __DIR__ . '/../cache/twig',
'debug' => true, // diperlukan untuk fungsi dump()
'auto_reload' => true,
],
], function (\Twig\Environment $twig): void {
$twig->addExtension(new \Twig\Extension\DebugExtension());
});
Kemudian dalam sebuah templat:
{{ dump(user) }}
Anda juga dapat menggabungkan Twig dengan Tracy untuk debugging tingkat PHP. Untuk metrik tingkat templat (waktu render, memori, templat/blok mana yang berjalan), gunakan panel Twig opsional di flightphp/tracy-extensions: lewati Twig\Profiler\Profile sebagai twig_profile ke TracyExtensionLoader. TwigTracyExtension opsional mengekspos {{ dump() }} / {{ bdump() }} / {{ dumpe() }} di templat ketika Tracy aktif.
Catatan Keamanan
Twig secara otomatis meng-escape output secara default, yang membantu melindungi dari serangan XSS. Lebih suka {{ variable }} untuk teks. Hanya gunakan filter |raw ketika Anda sengaja mempercayai konten HTML (misalnya, markdown yang telah disanitasi yang telah Anda proses di sisi server).
Awesome-plugins/session
FlightPHP Sesi - Penanganan Sesi Berbasis File Ringan
Ini adalah plugin penanganan sesi berbasis file yang ringan untuk Flight PHP Framework. Ini menyediakan solusi sederhana namun kuat untuk mengelola sesi, dengan fitur seperti pembacaan sesi non-blocking, enkripsi opsional, fungsi auto-commit, dan mode uji untuk pengembangan. Data sesi disimpan dalam file, menjadikannya ideal untuk aplikasi yang tidak memerlukan basis data.
Jika Anda ingin menggunakan basis data, periksa plugin ghostff/session yang memiliki banyak fitur serupa tetapi dengan backend basis data.
Kunjungi repositori Github untuk kode sumber lengkap dan detail.
Instalasi
Instal plugin melalui Composer:
composer require flightphp/session
Penggunaan Dasar
Berikut adalah contoh sederhana cara menggunakan plugin flightphp/session dalam aplikasi Flight Anda:
require 'vendor/autoload.php';
use flight\Session;
$app = Flight::app();
// Daftarkan layanan sesi
$app->register('session', Session::class);
// Contoh rute dengan penggunaan sesi
Flight::route('/login', function() {
$session = Flight::session();
$session->set('user_id', 123);
$session->set('username', 'johndoe');
$session->set('is_admin', false);
echo $session->get('username'); // Keluaran: johndoe
echo $session->get('preferences', 'default_theme'); // Keluaran: default_theme
if ($session->get('user_id')) {
Flight::json(['message' => 'Pengguna telah masuk!', 'user_id' => $session->get('user_id')]);
}
});
Flight::route('/logout', function() {
$session = Flight::session();
$session->clear(); // Hapus semua data sesi
Flight::json(['message' => 'Berhasil keluar']);
});
Flight::start();
Poin Kunci
- Non-Blocking: Menggunakan
read_and_closesecara default untuk memulai sesi, mencegah masalah penguncian sesi. - Auto-Commit: Diaktifkan secara default, sehingga perubahan disimpan secara otomatis saat shutdown kecuali dinonaktifkan.
- File Storage: Sesi disimpan di direktori temp sistem di bawah
/flight_sessionssecara default.
Konfigurasi
Anda dapat menyesuaikan penanganan sesi dengan meneruskan array opsi saat mendaftarkan:
// Ya, ini array ganda :)
$app->register('session', Session::class, [ [
'save_path' => '/custom/path/to/sessions', // Direktori untuk file sesi
'prefix' => 'myapp_', // Awalan untuk file sesi
'encryption_key' => 'a-secure-32-byte-key-here', // Aktifkan enkripsi (32 byte direkomendasikan untuk AES-256-CBC)
'auto_commit' => false, // Nonaktifkan auto-commit untuk kontrol manual
'start_session' => true, // Mulai sesi secara otomatis (default: true)
'test_mode' => false, // Aktifkan mode uji untuk pengembangan
'serialization' => 'json', // Metode serialisasi: 'json' (default) atau 'php' (legacy)
] ]);
Opsi Konfigurasi
| Option | Description | Default Value |
|---|---|---|
save_path |
Direktori tempat file sesi disimpan | sys_get_temp_dir() . '/flight_sessions' |
prefix |
Awalan untuk file sesi yang disimpan | sess_ |
encryption_key |
Kunci untuk enkripsi AES-256-CBC (opsional) | null (tanpa enkripsi) |
auto_commit |
Auto-simpan data sesi saat shutdown | true |
start_session |
Mulai sesi secara otomatis | true |
test_mode |
Jalankan dalam mode uji tanpa memengaruhi sesi PHP | false |
test_session_id |
ID sesi khusus untuk mode uji (opsional) | Dibuat acak jika tidak disetel |
serialization |
Metode serialisasi: 'json' (default, aman) atau 'php' (legacy, mengizinkan objek) | 'json' |
Mode Serialisasi
Secara default, pustaka ini menggunakan serialisasi JSON untuk data sesi, yang aman dan mencegah kerentanan injeksi objek PHP. Jika Anda perlu menyimpan objek PHP dalam sesi (tidak direkomendasikan untuk sebagian besar aplikasi), Anda dapat memilih serialisasi PHP legacy:
'serialization' => 'json'(default):- Hanya array dan primitif yang diizinkan dalam data sesi.
- Lebih aman: kebal terhadap injeksi objek PHP.
- File diawali dengan
J(JSON biasa) atauF(JSON terenkripsi).
'serialization' => 'php':- Mengizinkan penyimpanan objek PHP (gunakan dengan hati-hati).
- File diawali dengan
P(serialisasi PHP biasa) atauE(serialisasi PHP terenkripsi).
Catatan: Jika Anda menggunakan serialisasi JSON, upaya untuk menyimpan objek akan melemparkan pengecualian.
Penggunaan Lanjutan
Commit Manual
Jika Anda menonaktifkan auto-commit, Anda harus secara manual melakukan commit perubahan:
$app->register('session', Session::class, ['auto_commit' => false]);
Flight::route('/update', function() {
$session = Flight::session();
$session->set('key', 'value');
$session->commit(); // Simpan perubahan secara eksplisit
});
Keamanan Sesi dengan Enkripsi
Aktifkan enkripsi untuk data sensitif:
$app->register('session', Session::class, [
'encryption_key' => 'your-32-byte-secret-key-here'
]);
Flight::route('/secure', function() {
$session = Flight::session();
$session->set('credit_card', '4111-1111-1111-1111'); // Dienkripsi secara otomatis
echo $session->get('credit_card'); // Didekripsi saat pengambilan
});
Regenerasi Sesi
Regenerasikan ID sesi untuk keamanan (misalnya, setelah masuk):
Flight::route('/post-login', function() {
$session = Flight::session();
$session->regenerate(); // ID baru, simpan data
// ATAU
$session->regenerate(true); // ID baru, hapus data lama
});
Contoh Middleware
Lindungi rute dengan otentikasi berbasis sesi:
Flight::route('/admin', function() {
Flight::json(['message' => 'Selamat datang di panel admin']);
})->addMiddleware(function() {
$session = Flight::session();
if (!$session->get('is_admin')) {
Flight::halt(403, 'Akses ditolak');
}
});
Ini hanya contoh sederhana cara menggunakannya dalam middleware. Untuk contoh yang lebih mendalam, lihat dokumentasi middleware.
Metode
Kelas Session menyediakan metode-metode berikut:
set(string $key, $value): Menyimpan nilai dalam sesi.get(string $key, $default = null): Mengambil nilai, dengan default opsional jika kunci tidak ada.delete(string $key): Menghapus kunci tertentu dari sesi.clear(): Menghapus semua data sesi, tetapi mempertahankan nama file sesi yang sama.commit(): Menyimpan data sesi saat ini ke sistem file.id(): Mengembalikan ID sesi saat ini.regenerate(bool $deleteOldFile = false): Meregenerasikan ID sesi termasuk membuat file sesi baru, mempertahankan semua data lama dan file lama tetap ada. Jika$deleteOldFileadalahtrue, file sesi lama dihapus.destroy(string $id): Menghancurkan sesi berdasarkan ID dan menghapus file sesi dari sistem. Ini bagian dariSessionHandlerInterfacedan$iddiperlukan. Penggunaan khas adalah$session->destroy($session->id()).getAll(): Mengembalikan semua data dari sesi saat ini.
Semua metode kecuali get() dan id() mengembalikan instance Session untuk chaining.
Mengapa Menggunakan Plugin Ini?
- Ringan: Tidak ada ketergantungan eksternal—hanya file.
- Non-Blocking: Menghindari penguncian sesi dengan
read_and_closesecara default. - Aman: Mendukung enkripsi AES-256-CBC untuk data sensitif.
- Fleksibel: Opsi auto-commit, mode uji, dan kontrol manual.
- Flight-Native: Dibuat khusus untuk kerangka Flight.
Detail Teknis
- Format Penyimpanan: File sesi diawali dengan
sess_dan disimpan disave_pathyang dikonfigurasi. Awalan konten file:J: JSON biasa (default, tanpa enkripsi)F: JSON terenkripsi (default dengan enkripsi)P: Serialisasi PHP biasa (legacy, tanpa enkripsi)E: Serialisasi PHP terenkripsi (legacy dengan enkripsi)
- Enkripsi: Menggunakan AES-256-CBC dengan IV acak per tulis sesi saat
encryption_keydisediakan. Enkripsi berfungsi untuk kedua mode serialisasi JSON dan PHP. - Serialisasi: JSON adalah metode default dan paling aman. Serialisasi PHP tersedia untuk penggunaan legacy/tingkat lanjut, tetapi kurang aman.
- Garbage Collection: Mengimplementasikan
SessionHandlerInterface::gc()untuk membersihkan sesi yang kedaluwarsa.
Berkontribusi
Kontribusi diterima! Fork repositori, buat perubahan Anda, dan kirimkan pull request. Laporkan bug atau sarankan fitur melalui pelacak isu Github.
Lisensi
Plugin ini dilisensikan di bawah Lisensi MIT. Lihat repositori Github untuk detail.
Awesome-plugins/runway
Runway
Runway adalah aplikasi CLI yang membantu Anda mengelola aplikasi Flight. Runway dapat menghasilkan controller, menampilkan semua rute, menjalankan pembantu pengaturan AI, migrasi (dalam skeleton), dan lainnya. Runway didasarkan pada pustaka yang sangat baik adhocore/php-cli.
Klik di sini untuk melihat kode.
Perintah scaffolding sengaja disesuaikan dengan skeleton resmi sehingga alat pengkodean AI dan manusia mendapatkan jalur, namespace, dan gaya constructor-injection yang sama setiap saat.
Instalasi
Instal dengan composer.
composer require flightphp/runway
Skeleton sudah bergantung pada Runway; gunakan php runway dari root proyek.
Konfigurasi Dasar
Saat pertama kali menjalankan Runway, Runway akan mencoba menemukan konfigurasi runway di app/config/config.php melalui kunci 'runway'.
<?php
// app/config/config.php
return [
'runway' => [
'app_root' => 'app/',
'public_root' => 'public/',
// opsional; skeleton juga menggunakan index_root untuk entri publik
'index_root' => 'public/index.php',
],
];
CATATAN - Mulai dari v1.2.0,
.runway-config.jsonsudah tidak digunakan lagi dan digantikan olehapp/config/config.php. Migrasikan denganphp runway config:migratesaat mengupgrade proyek lama. Skeleton mungkin masih menulis.runway-config.jsonkecil saat create-project untuk kompatibilitas; preferensikan kuncirunwaydiconfig.phpke depannya.
Deteksi Root Proyek
Runway cukup pintar untuk mendeteksi root proyek Anda, bahkan jika Anda menjalankannya dari subdirektori. Runway mencari indikator seperti composer.json, .git, atau app/config/config.php untuk menentukan di mana root proyek berada. Ini berarti Anda dapat menjalankan perintah Runway dari mana saja dalam proyek Anda!
Penggunaan
Runway memiliki sejumlah perintah yang dapat Anda gunakan untuk mengelola aplikasi Flight Anda. Ada dua cara mudah untuk menggunakan Runway.
- Jika Anda menggunakan proyek skeleton, Anda dapat menjalankan
php runway [perintah]dari root proyek Anda. - Jika Anda menggunakan Runway sebagai paket yang diinstal melalui composer, Anda dapat menjalankan
vendor/bin/runway [perintah]dari root proyek Anda.
Daftar Perintah
Anda dapat melihat daftar semua perintah yang tersedia dengan menjalankan perintah php runway.
php runway
Hanya bergantung pada perintah yang benar-benar muncul dalam daftar tersebut untuk instalasi Anda (perintah Runway inti vs perintah khusus proyek seperti migrate milik skeleton).
Bantuan Perintah
Untuk perintah apa pun, Anda dapat memberikan flag --help untuk mendapatkan informasi lebih lanjut tentang cara menggunakan perintah tersebut.
php runway routes --help
php runway make:controller --help
Berikut beberapa contohnya:
Membuat Controller
make:controller membuat scaffold controller yang sesuai dengan tata letak skeleton resmi:
| Jalur | app/Controller/{Nama}.php |
| Namespace | App\Controller |
| Gaya | Constructor injection dari flight\Engine (tidak ada Flight:: dalam body kelas) |
php runway make:controller MyController
# → app/Controller/MyController.php
# namespace App\Controller;
Contoh bentuk yang seharusnya Anda harapkan (disederhanakan):
<?php
declare(strict_types=1);
namespace App\Controller;
use flight\Engine;
class MyController
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function index(): void
{
// misalnya $this->app->render('…', […]);
}
}
Daftarkan dengan callable kelas sehingga Dice dapat membangun controller:
// app/config/routes.php
use App\Controller\MyController;
$router->get('/mine', [MyController::class, 'index']);
Mengapa tata letak ini? Folder huruf besar harus sesuai dengan namespace (Controller bukan controllers) untuk Composer PSR-4 di Linux—lihat Autoloading. Jalur yang sama adalah yang diberitahu file AGENTS.md root dan scoped kepada alat AI untuk digunakan, sehingga controller yang dihasilkan dan ditulis tangan tetap identik.
Dokumentasi lama dan proyek komunitas kadang-kadang menggunakan
app/controllers/danapp\controllers. Itu tetap valid jika struktur Anda masih menggunakan folder huruf kecil. Proyek skeleton baru dan outputmake:controllersaat ini menggunakanapp/Controller/+App\Controller.
Membuat Model Active Record
Pastikan dulu Anda telah menginstal plugin Active Record.
php runway make:record users
Dalam skeleton resmi, model berada di bawah app/Model/ dengan namespace App\Model, dan koneksi DB adalah SimplePdo (inject atau lewatkan ke dalam constructor ActiveRecord). Nama file/namespace yang dihasilkan mengikuti default Runway saat ini dan konfigurasi runway Anda—preferensikan menyelaraskan model baru dengan App\Model agar sesuai dengan autoloading dan AGENTS.md.
Contoh model yang konsisten dengan demo posts skeleton:
<?php
declare(strict_types=1);
namespace App\Model;
use flight\ActiveRecord;
/**
* @property int $id
* @property string $title
* // …
*/
class Post extends ActiveRecord
{
protected array $relations = [];
public function __construct($databaseConnection)
{
parent::__construct($databaseConnection, 'posts');
}
}
Jika generator lama masih menghasilkan app/records / app\records, Anda dapat mempertahankan konvensi tersebut dalam aplikasi lama atau memindahkan file ke app/Model/ dan memperbarui namespace agar sesuai dengan huruf folder.
Migrasi (skeleton)
Skeleton resmi menyediakan perintah proyek (ditemukan dari app/commands/) seperti:
php runway migrate
Migrasi adalah file SQL di bawah migrations/ (misalnya YYYYMMDDHHMMSS_description.sql untuk SQLite dan …_description.mysql.sql untuk MySQL), dipilih dari konfigurasi driver database / env Anda. Flag dan perilaku yang tepat didefinisikan oleh perintah proyek tersebut—jalankan php runway migrate --help di aplikasi Anda.
Pembantu AI
Runway menyediakan perintah berorientasi AI yang digunakan dengan AI & pengalaman pengembang:
php runway ai:init
php runway ai:generate-instructions
Perintah ini menyimpan kredensial LLM dan menghasilkan instruksi proyek (terutama AGENTS.md). Pada skeleton, perlakukan AGENTS.md (dan salinan scoped di bawah app/) plus SECURITY.md sebagai sumber kebenaran untuk agent.
Menampilkan Semua Rute
Ini akan menampilkan semua rute yang saat ini terdaftar dengan Flight.
php runway routes
Jika Anda ingin hanya melihat rute tertentu, Anda dapat memberikan flag untuk memfilter rute.
# Menampilkan hanya rute GET
php runway routes --get
# Menampilkan hanya rute POST
php runway routes --post
# dll.
Menambahkan Perintah Kustom ke Runway
Jika Anda membuat paket untuk Flight, atau ingin menambahkan perintah kustom Anda sendiri ke proyek Anda, Anda dapat melakukannya dengan membuat direktori src/commands/, flight/commands/, app/commands/, atau commands/ untuk proyek/paket Anda. Jika Anda memerlukan kustomisasi lebih lanjut, lihat bagian Konfigurasi di bawah.
Dalam skeleton, perintah proyek berada di app/commands/ dengan namespace App\Command. Runway menemukannya berdasarkan jalur; pertahankan folder tersebut agar selaras dengan classmap/PSR-4 Composer seperti yang sudah dilakukan proyek Anda.
Untuk membuat perintah, Anda cukup memperluas kelas AbstractBaseCommand, dan mengimplementasikan setidaknya metode __construct dan metode execute.
<?php
declare(strict_types=1);
namespace App\Command;
use flight\commands\AbstractBaseCommand;
class ExampleCommand extends AbstractBaseCommand
{
/**
* Construct
*
* @param array<string,mixed> $config Config dari app/config/config.php
*/
public function __construct(array $config)
{
parent::__construct('make:example', 'Buat contoh untuk dokumentasi', $config);
$this->argument('<funny-gif>', 'Nama gif lucu');
}
/**
* Menjalankan fungsi
*
* @return void
*/
public function execute()
{
$io = $this->app()->io();
$io->info('Membuat contoh...');
// Lakukan sesuatu di sini
$io->ok('Contoh dibuat!');
}
}
Lihat Dokumentasi adhocore/php-cli untuk informasi lebih lanjut tentang cara membangun perintah kustom Anda sendiri ke dalam aplikasi Flight Anda!
Manajemen Konfigurasi
Karena konfigurasi telah dipindahkan ke app/config/config.php mulai dari v1.2.0, ada beberapa perintah pembantu untuk mengelola konfigurasi.
Tip Skeleton: Pertahankan
config.phpsebagai nilai PHP literal. Rahasia milik.env. Hindari ekspresi$_ENV[...]di dalamconfig.php—config:setmenulis ulang file tersebut sebagai data statis dan dapat membakar rahasia ke dalam file. Lihat Konfigurasi.
Migrasi Konfigurasi Lama
Jika Anda memiliki file .runway-config.json lama, Anda dapat dengan mudah memigrasikannya ke app/config/config.php dengan perintah berikut:
php runway config:migrate
Mengatur Nilai Konfigurasi
Anda dapat mengatur nilai konfigurasi menggunakan perintah config:set. Ini berguna jika Anda ingin memperbarui nilai konfigurasi tanpa membuka file.
php runway config:set app_root "app/"
Mendapatkan Nilai Konfigurasi
Anda dapat mendapatkan nilai konfigurasi menggunakan perintah config:get.
php runway config:get app_root
Semua Konfigurasi Runway
Jika Anda perlu menyesuaikan konfigurasi untuk Runway, Anda dapat mengatur nilai-nilai ini di app/config/config.php. Berikut beberapa konfigurasi tambahan yang dapat Anda atur:
<?php
// app/config/config.php
return [
// ... nilai konfigurasi lainnya ...
'runway' => [
// Ini adalah lokasi direktori aplikasi Anda
'app_root' => 'app/',
// Ini adalah direktori tempat file index root Anda berada
'index_root' => 'public/',
// Ini adalah jalur ke root proyek lain
'root_paths' => [
'/home/user/different-project',
'/var/www/another-project'
],
// Jalur dasar kemungkinan besar tidak perlu dikonfigurasi, tetapi tersedia jika Anda menginginkannya
'base_paths' => [
'/includes/libs/vendor', // jika Anda memiliki jalur yang sangat unik untuk direktori vendor Anda atau semacamnya
],
// Jalur akhir adalah lokasi dalam proyek untuk mencari file perintah
'final_paths' => [
'src/diff-path/commands',
'app/module/admin/commands',
],
// Jika Anda ingin menambahkan jalur lengkap, silakan saja (absolut atau relatif terhadap root proyek)
'paths' => [
'/home/user/different-project/src/diff-path/commands',
'/var/www/another-project/app/module/admin/commands',
'app/my-unique-commands'
]
]
];
Mengakses Konfigurasi
Jika Anda perlu mengakses nilai konfigurasi secara efektif, Anda dapat mengaksesnya melalui metode __construct atau metode app(). Penting juga untuk dicatat bahwa jika Anda memiliki file app/config/services.php, layanan tersebut juga akan tersedia untuk perintah Anda.
public function execute()
{
$io = $this->app()->io();
// Mengakses konfigurasi
$app_root = $this->config['runway']['app_root'];
// Mengakses layanan seperti mungkin koneksi database
$database = $this->config['database']
// ...
}
Pembungkus Pembantu AI
Runway memiliki beberapa pembungkus pembantu yang membuatnya lebih mudah bagi AI untuk menghasilkan perintah. Anda dapat menggunakan addOption dan addArgument dengan cara yang terasa mirip dengan Symfony Console. Ini membantu jika Anda menggunakan alat AI untuk menghasilkan perintah Anda.
public function __construct(array $config)
{
parent::__construct('make:example', 'Buat contoh untuk dokumentasi', $config);
// Argumen mode dapat bernilai null dan default-nya sepenuhnya opsional
$this->addOption('name', 'Nama contoh', null);
}
Lihat Juga
- Instalasi - Pohon skeleton dan default create-project
- Autoloading -
App\dan huruf folder - Dependency Injection - Dice + Engine injection untuk controller yang dihasilkan
- AI & Pengalaman Pengembang -
ai:init,ai:generate-instructions,AGENTS.md - Active Record - Model yang digunakan dengan
make:record/ skeletonApp\Model - SimplePdo - Koneksi DB yang digunakan oleh migrasi dan model skeleton
Awesome-plugins/tracy_extensions
Ekstensi Panel Tracy Flight
Ini adalah sekumpulan ekstensi untuk membuat bekerja dengan Flight sedikit lebih kaya.
- Flight - Menganalisis semua variabel Flight.
- Database - Menganalisis semua query yang telah dijalankan pada halaman (jika Anda menginisiasi koneksi database dengan benar)
- Request - Menganalisis semua variabel
$_SERVERdan memeriksa semua payload global ($_GET,$_POST,$_FILES) - Session - Menganalisis semua variabel
$_SESSIONjika sesi aktif. - Twig (opsional) - Menganalisis waktu render template Twig, memori, dan template/blok/makro mana yang dijalankan (memerlukan
twig/twigdan konfigurasitwig_profile)
Ini sangat berguna dengan kerangka resmi, yang secara default menggunakan Twig: tata letak yang sama alat AI ikuti juga ditampilkan dengan jelas pada bar Tracy.
Ini adalah Panel

Dan setiap panel menampilkan informasi yang sangat membantu tentang aplikasi Anda!

Klik di sini untuk melihat kode.
Instalasi
Jalankan composer require flightphp/tracy-extensions --dev dan Anda siap melangkah!
Twig bukan dependensi keras dari paket ini. Instal twig/twig hanya jika Anda ingin panel Twig (kerangka sudah melakukannya untuk tampilan).
Konfigurasi
Ada sangat sedikit konfigurasi yang perlu Anda lakukan untuk memulai ini. Anda perlu menginisiasi debugger Tracy sebelum menggunakan ini https://tracy.nette.org/en/guide:
<?php
use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;
// kode bootstrap
require __DIR__ . '/vendor/autoload.php';
Debugger::enable();
// Anda mungkin perlu menentukan lingkungan Anda dengan Debugger::enable(Debugger::DEVELOPMENT)
// jika Anda menggunakan koneksi database dalam aplikasi, ada
// wrapper PDO yang diperlukan untuk digunakan HANYA DALAM PENGEMBANGAN (bukan produksi mohon!)
// Ini memiliki parameter yang sama dengan koneksi PDO biasa
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// atau jika Anda melampirkannya ke kerangka Flight
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// sekarang setiap kali Anda membuat query, itu akan menangkap waktu, query, dan parameter
// Ini menghubungkan titik-titik
if(Debugger::$showBar === true) {
// Ini perlu false atau Tracy tidak bisa benar-benar render :(
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app());
}
// kode lainnya
Flight::start();
Konfigurasi Tambahan
Data Sesi
Jika Anda memiliki handler sesi khusus (seperti ghostff/session), Anda dapat meneruskan array data sesi apa pun ke Tracy dan itu akan secara otomatis menampilkannya untuk Anda. Anda meneruskannya dengan kunci session_data dalam parameter kedua konstruktor TracyExtensionLoader.
use Ghostff\Session\Session;
// atau gunakan flight\Session;
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('session', Session::class);
if(Debugger::$showBar === true) {
// Ini perlu false atau Tracy tidak bisa benar-benar render :(
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}
// rute dan hal-hal lainnya...
Flight::start();
Panel Twig (opsional)
Jika aplikasi Anda menggunakan Twig (termasuk kerangka resmi), Anda dapat menampilkan metrik template pada bar Tracy. Buat Profile Twig, lampirkan ProfilerExtension ke lingkungan Anda, lalu lewatkan profil tersebut ke loader di bawah kunci twig_profile. Lampirkan profiling hanya dalam pengembangan.
<?php
use flight\debug\tracy\TracyExtensionLoader;
use flight\debug\tracy\TwigTracyExtension;
use Tracy\Debugger;
use Twig\Environment;
use Twig\Extension\ProfilerExtension;
use Twig\Loader\FilesystemLoader;
use Twig\Profiler\Profile;
$loader = new FilesystemLoader(__DIR__ . '/views');
$twig = new Environment($loader, [
'debug' => true,
'cache' => false,
]);
// Opsional: expose helper dump Tracy dalam template
// {{ dump(var) }}, {{ bdump(var) }}, {{ dumpe(var) }}
$twig->addExtension(new TwigTracyExtension());
$tracyConfig = [];
if (Debugger::$showBar === true) {
$profile = new Profile();
$twig->addExtension(new ProfilerExtension($profile));
$tracyConfig['twig_profile'] = $profile;
}
if (Debugger::$showBar === true) {
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app(), $tracyConfig);
}
// Petakan Flight::render() ke Twig (contoh)
Flight::map('render', function (string $template, array $data = []) use ($twig) {
if (substr($template, -5) !== '.twig') {
$template .= '.twig';
}
echo $twig->render($template, $data);
});
Apa yang ditampilkan panel
- Total waktu render Twig dan memori
- Jumlah panggilan template / blok / makro
- Setiap template yang dirender, dengan waktu dan memorinya sendiri
Tab Twig tersembunyi ketika tidak ada template yang dirender untuk permintaan, atau ketika Anda menghilangkan twig_profile (atau tidak memiliki Twig terinstal)—panel Flight lainnya tetap berfungsi.
Dalam services.php gaya kerangka, bangun $profile / ProfilerExtension yang sama ketika debug aktif, lewatkan twig_profile ke TracyExtensionLoader, dan terus gunakan lingkungan Twig bersama Anda untuk $app->render().
Latte
PHP 8.1+ diperlukan untuk bagian ini.
Jika Anda memiliki Latte terinstal dalam proyek Anda, Tracy memiliki integrasi asli dengan Latte untuk menganalisis template Anda. Anda cukup mendaftarkan ekstensi dengan instance Latte Anda (ini adalah bridge Tracy Latte sendiri, bukan panel Twig di atas).
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function($template, $data, $block = null) {
$latte = new Latte\Engine;
// konfigurasi lainnya...
// hanya tambahkan ekstensi jika Tracy Debug Bar diaktifkan
if(Debugger::$showBar === true) {
// ini adalah tempat Anda menambahkan Latte Panel ke Tracy
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
}
$latte->render($template, $data, $block);
});
Lihat Juga
- Tracy - Setup Tracy dasar untuk Flight
- Twig - Templating yang digunakan oleh kerangka dan panel Twig
- Templates - Bagaimana Flight memetakan
renderke Twig/Latte - Installation - Kerangka menyertakan tracy-extensions dalam dev
Awesome-plugins/apm
Dokumentasi FlightPHP APM
Selamat datang di FlightPHP APM—pelatih performa pribadi untuk aplikasi Anda! Panduan ini adalah peta jalan untuk menyiapkan, menggunakan, dan menguasai Application Performance Monitoring (APM) dengan FlightPHP. Baik Anda sedang mencari permintaan lambat atau hanya ingin mengeksplorasi grafik latensi, kami siap membantu. Mari buat aplikasi Anda lebih cepat, pengguna lebih bahagia, dan sesi debugging lebih mudah!
Lihat demo dari dashboard untuk situs Flight Docs.

Mengapa APM Penting
Bayangkan ini: aplikasi Anda adalah restoran yang sibuk. Tanpa cara untuk melacak berapa lama pesanan diproses atau di mana dapur mengalami hambatan, Anda hanya bisa menebak mengapa pelanggan pergi dengan tidak senang. APM adalah koki kedua Anda—memantau setiap langkah, dari permintaan masuk hingga kueri database, dan menandai apa pun yang memperlambat Anda. Halaman yang lambat kehilangan pengguna (studi menunjukkan 53% bounce jika situs membutuhkan waktu lebih dari 3 detik untuk memuat!), dan APM membantu Anda menangkap masalah tersebut sebelum berdampak. Ini adalah ketenangan pikiran yang proaktif—lebih sedikit momen "kenapa ini rusak?" dan lebih banyak kemenangan "lihat betapa lancarnya ini berjalan!"
Instalasi
Mulai dengan Composer:
composer require flightphp/apm
Anda akan memerlukan:
- PHP 7.4+: Menjaga kami kompatibel dengan distro Linux LTS sambil mendukung PHP modern.
- FlightPHP Core v3.15+: Framework ringan yang kami tingkatkan.
Database yang Didukung
FlightPHP APM saat ini mendukung database berikut untuk menyimpan metrik:
- SQLite3: Sederhana, berbasis file, dan bagus untuk pengembangan lokal atau aplikasi kecil. Opsi default dalam sebagian besar pengaturan.
- MySQL/MariaDB: Ideal untuk proyek yang lebih besar atau lingkungan produksi yang memerlukan penyimpanan yang kuat dan dapat diskalakan.
Anda dapat memilih jenis database selama langkah konfigurasi (lihat di bawah). Pastikan lingkungan PHP Anda memiliki ekstensi yang diperlukan (misalnya, pdo_sqlite atau pdo_mysql).
Memulai
Berikut langkah demi langkah untuk kehebatan APM:
1. Daftarkan APM
Letakkan ini ke dalam file index.php atau services.php Anda untuk mulai melacak:
use flight\apm\logger\LoggerFactory;
use flight\database\SimplePdo;
use flight\Apm;
$ApmLogger = LoggerFactory::create(__DIR__ . '/../../.runway-config.json');
$Apm = new Apm($ApmLogger);
$Apm->bindEventsToFlightInstance($app);
// If you're adding a database connection
// Prefer SimplePdo (or PdoQueryCapture from Tracy Extensions in dev).
// Enable APM query tracking via the options array (5th argument).
$pdo = new SimplePdo('mysql:host=localhost;dbname=example', 'user', 'pass', null, [
'trackApmQueries' => true, // required to capture queries for the APM
]);
$Apm->addPdoConnection($pdo);
Apa yang terjadi di sini?
LoggerFactory::create()mengambil konfigurasi Anda (lebih lanjut segera) dan menyiapkan logger—SQLite secara default.Apmadalah bintangnya—mendengarkan event Flight (permintaan, rute, error, dll.) dan mengumpulkan metrik.bindEventsToFlightInstance($app)menghubungkan semuanya ke aplikasi Flight Anda.
Tips Pro: Sampling Jika aplikasi Anda sibuk, mencatat setiap permintaan mungkin membebani sistem. Gunakan tingkat sampel (0.0 hingga 1.0):
$Apm = new Apm($ApmLogger, 0.1); // Logs 10% of requests
Ini menjaga performa tetap cepat sambil tetap memberikan data yang solid.
2. Konfigurasikan
Jalankan ini untuk membuat .runway-config.json Anda:
php vendor/bin/runway apm:init
Apa yang dilakukan ini?
- Meluncurkan wizard yang menanyakan dari mana metrik mentah berasal (sumber) dan ke mana data yang diproses pergi (tujuan).
- Default adalah SQLite—misalnya,
sqlite:/tmp/apm_metrics.sqliteuntuk sumber, yang lain untuk tujuan. - Anda akan berakhir dengan konfigurasi seperti:
{ "apm": { "source_type": "sqlite", "source_db_dsn": "sqlite:/tmp/apm_metrics.sqlite", "storage_type": "sqlite", "dest_db_dsn": "sqlite:/tmp/apm_metrics_processed.sqlite" } }
Proses ini juga akan menanyakan apakah Anda ingin menjalankan migrasi untuk pengaturan ini. Jika Anda mengatur ini untuk pertama kalinya, jawabannya adalah ya.
Mengapa dua lokasi? Metrik mentah menumpuk cepat (pikirkan log yang tidak difilter). Worker memprosesnya menjadi tujuan terstruktur untuk dashboard. Menjaga segalanya tetap rapi!
3. Proses Metrik dengan Worker
Worker mengubah metrik mentah menjadi data siap dashboard. Jalankan sekali:
php vendor/bin/runway apm:worker
Apa yang dilakukannya?
- Membaca dari sumber Anda (misalnya,
apm_metrics.sqlite). - Memproses hingga 100 metrik (ukuran batch default) ke tujuan Anda.
- Berhenti ketika selesai atau jika tidak ada metrik yang tersisa.
Jaga Tetap Berjalan Untuk aplikasi langsung, Anda akan menginginkan pemrosesan berkelanjutan. Berikut opsi Anda:
-
Mode Daemon:
php vendor/bin/runway apm:worker --daemonBerjalan selamanya, memproses metrik saat datang. Bagus untuk dev atau pengaturan kecil.
-
Crontab: Tambahkan ini ke crontab Anda (
crontab -e):* * * * * php /path/to/project/vendor/bin/runway apm:workerMenembak setiap menit—sempurna untuk produksi.
-
Tmux/Screen: Mulai sesi yang dapat dilepas:
tmux new -s apm-worker php vendor/bin/runway apm:worker --daemon # Ctrl+B, then D to detach; `tmux attach -t apm-worker` to reconnectMenjaganya tetap berjalan bahkan jika Anda logout.
-
Penyesuaian Kustom:
php vendor/bin/runway apm:worker --batch_size 50 --max_messages 1000 --timeout 300--batch_size 50: Proses 50 metrik sekaligus.--max_messages 1000: Berhenti setelah 1000 metrik.--timeout 300: Keluar setelah 5 menit.
Mengapa repot? Tanpa worker, dashboard Anda kosong. Ini adalah jembatan antara log mentah dan wawasan yang dapat ditindaklanjuti.
4. Luncurkan Dashboard
Lihat vital aplikasi Anda:
php vendor/bin/runway apm:dashboard
Apa ini?
- Memulai server PHP di
http://localhost:8001/apm/dashboard. - Menampilkan log permintaan, rute lambat, tingkat error, dan lainnya.
Kustomisasi:
php vendor/bin/runway apm:dashboard --host 0.0.0.0 --port 8080 --php-path=/usr/local/bin/php
--host 0.0.0.0: Dapat diakses dari IP mana pun (berguna untuk melihat dari jarak jauh).--port 8080: Gunakan port berbeda jika 8001 sudah digunakan.--php-path: Tunjuk ke PHP jika tidak ada di PATH Anda.
Buka URL di browser Anda dan jelajahi!
Mode Produksi
Untuk produksi, Anda mungkin harus mencoba beberapa teknik untuk menjalankan dashboard karena mungkin ada firewall dan langkah keamanan lainnya. Berikut beberapa opsi:
- Gunakan Reverse Proxy: Siapkan Nginx atau Apache untuk meneruskan permintaan ke dashboard.
- SSH Tunnel: Jika Anda dapat SSH ke server, gunakan
ssh -L 8080:localhost:8001 youruser@yourserveruntuk men-tunnel dashboard ke mesin lokal Anda. - VPN: Jika server Anda berada di belakang VPN, hubungkan ke sana dan akses dashboard secara langsung.
- Konfigurasi Firewall: Buka port 8001 untuk IP Anda atau jaringan server. (atau port apa pun yang Anda atur).
- Konfigurasi Apache/Nginx: Jika Anda memiliki web server di depan aplikasi Anda, Anda dapat mengonfigurasinya ke domain atau subdomain. Jika Anda melakukan ini, Anda akan mengatur document root ke
/path/to/your/project/vendor/flightphp/apm/dashboard
Ingin dashboard yang berbeda?
Anda dapat membangun dashboard sendiri jika mau! Lihat direktori vendor/flightphp/apm/src/apm/presenter untuk ide tentang cara menyajikan data untuk dashboard Anda sendiri!
Fitur Dashboard
Dashboard adalah markas besar APM Anda—berikut yang akan Anda lihat:
- Request Log: Setiap permintaan dengan timestamp, URL, kode respons, dan total waktu. Klik "Details" untuk middleware, query, dan error.
- Slowest Requests: 5 permintaan teratas yang menghabiskan waktu (misalnya, "/api/heavy" dalam 2.5s).
- Slowest Routes: 5 rute teratas berdasarkan waktu rata-rata—bagus untuk menemukan pola.
- Error Rate: Persentase permintaan yang gagal (misalnya, 2.3% 500s).
- Latency Percentiles: Waktu respons ke-95 (p95) dan ke-99 (p99)—ketahui skenario terburuk Anda.
- Response Code Chart: Visualisasikan 200s, 404s, 500s dari waktu ke waktu.
- Long Queries/Middleware: 5 panggilan database lambat dan lapisan middleware teratas.
- Cache Hit/Miss: Seberapa sering cache Anda menyelamatkan hari.
Ekstra:
- Filter berdasarkan "Last Hour," "Last Day," atau "Last Week."
- Toggle dark mode untuk sesi larut malam.
Contoh:
Permintaan ke /users mungkin menunjukkan:
- Total Time: 150ms
- Middleware:
AuthMiddleware->handle(50ms) - Query:
SELECT * FROM users(80ms) - Cache: Hit pada
user_list(5ms)
Menambahkan Event Kustom
Lacak apa saja—seperti panggilan API atau proses pembayaran:
use flight\apm\CustomEvent;
$app->eventDispatcher()->trigger('apm.custom', new CustomEvent('api_call', [
'endpoint' => 'https://api.example.com/users',
'response_time' => 0.25,
'status' => 200
]));
Di mana itu muncul? Dalam detail permintaan dashboard di bawah "Custom Events"—dapat diperluas dengan pemformatan JSON yang cantik.
Use Case:
$start = microtime(true);
$apiResponse = file_get_contents('https://api.example.com/data');
$app->eventDispatcher()->trigger('apm.custom', new CustomEvent('external_api', [
'url' => 'https://api.example.com/data',
'time' => microtime(true) - $start,
'success' => $apiResponse !== false
]));
Sekarang Anda akan melihat apakah API tersebut memperlambat aplikasi Anda!
Monitoring Database
Lacak query PDO seperti ini:
use flight\database\SimplePdo;
$pdo = new SimplePdo('sqlite:/path/to/db.sqlite', null, null, null, [
'trackApmQueries' => true, // required to capture queries for the APM
]);
$Apm->addPdoConnection($pdo);
Apa yang Anda Dapatkan:
- Teks query (misalnya,
SELECT * FROM users WHERE id = ?) - Waktu eksekusi (misalnya, 0.015s)
- Jumlah baris (misalnya, 42)
Perhatian:
- Opsional: Lewati ini jika Anda tidak memerlukan pelacakan DB.
- SimplePdo (preferred): Gunakan
SimplePdodengantrackApmQueries => true.PdoWrapperyang sudah deprecated masih berfungsi (argumen konstruktor ke-5true). PDO core mentah belum di-hook—tetap pantau! - Performance Warning: Mencatat setiap query pada situs yang berat DB dapat memperlambat hal-hal. Gunakan sampling (
$Apm = new Apm($ApmLogger, 0.1)) untuk meringankan beban.
Contoh Output:
- Query:
SELECT name FROM products WHERE price > 100 - Time: 0.023s
- Rows: 15
Opsi Worker
Sesuaikan worker sesuai keinginan Anda:
--timeout 300: Berhenti setelah 5 menit—bagus untuk pengujian.--max_messages 500: Batas pada 500 metrik—menjaganya tetap terbatas.--batch_size 200: Memproses 200 sekaligus—menyeimbangkan kecepatan dan memori.--daemon: Berjalan tanpa henti—ideal untuk monitoring langsung.
Contoh:
php vendor/bin/runway apm:worker --daemon --batch_size 100 --timeout 3600
Berjalan selama satu jam, memproses 100 metrik sekaligus.
Request ID dalam Aplikasi
Setiap permintaan memiliki request ID unik untuk pelacakan. Anda dapat menggunakan ID ini dalam aplikasi Anda untuk mengkorelasikan log dan metrik. Misalnya, Anda dapat menambahkan request ID ke halaman error:
Flight::map('error', function($message) {
// Get the request ID from the response header X-Flight-Request-Id
$requestId = Flight::response()->getHeader('X-Flight-Request-Id');
// Additionally you could fetch it from the Flight variable
// This method won't work well in swoole or other async platforms.
// $requestId = Flight::get('apm.request_id');
echo "Error: $message (Request ID: $requestId)";
});
Upgrade
Jika Anda melakukan upgrade ke versi APM yang lebih baru, ada kemungkinan ada migrasi database yang perlu dijalankan. Anda dapat melakukannya dengan menjalankan perintah berikut:
php vendor/bin/runway apm:migrate
Ini akan menjalankan migrasi apa pun yang diperlukan untuk memperbarui skema database ke versi terbaru.
Catatan: Jika database APM Anda berukuran besar, migrasi ini mungkin memakan waktu. Anda mungkin ingin menjalankan perintah ini di luar jam sibuk.
Upgrade dari 0.4.3 -> 0.5.0
Jika Anda melakukan upgrade dari 0.4.3 ke 0.5.0, Anda perlu menjalankan perintah berikut:
php vendor/bin/runway apm:config-migrate
Ini akan memigrasikan konfigurasi Anda dari format lama menggunakan file .runway-config.json ke format baru yang menyimpan key/values dalam file config.php.
Menghapus Data Lama
Untuk menjaga database Anda tetap rapi, Anda dapat menghapus data lama. Ini sangat berguna jika Anda menjalankan aplikasi yang sibuk dan ingin menjaga ukuran database tetap manageable. Anda dapat melakukannya dengan menjalankan perintah berikut:
php vendor/bin/runway apm:purge
Ini akan menghapus semua data yang lebih lama dari 30 hari dari database. Anda dapat menyesuaikan jumlah hari dengan memberikan nilai berbeda ke opsi --days:
php vendor/bin/runway apm:purge --days 7
Ini akan menghapus semua data yang lebih lama dari 7 hari dari database.
Troubleshooting
Buntu? Coba ini:
-
Tidak Ada Data Dashboard?
- Apakah worker sedang berjalan? Periksa
ps aux | grep apm:worker. - Apakah path konfigurasi cocok? Verifikasi DSN
.runway-config.jsonmenunjuk ke file yang sebenarnya. - Jalankan
php vendor/bin/runway apm:workersecara manual untuk memproses metrik yang tertunda.
- Apakah worker sedang berjalan? Periksa
-
Worker Errors?
- Lihat file SQLite Anda (misalnya,
sqlite3 /tmp/apm_metrics.sqlite "SELECT * FROM apm_metrics_log LIMIT 5"). - Periksa log PHP untuk stack trace.
- Lihat file SQLite Anda (misalnya,
-
Dashboard Tidak Mulai?
- Port 8001 sedang digunakan? Gunakan
--port 8080. - PHP tidak ditemukan? Gunakan
--php-path /usr/bin/php. - Firewall memblokir? Buka port atau gunakan
--host localhost.
- Port 8001 sedang digunakan? Gunakan
-
Terlalu Lambat?
- Turunkan tingkat sampel:
$Apm = new Apm($ApmLogger, 0.05)(5%). - Kurangi ukuran batch:
--batch_size 20.
- Turunkan tingkat sampel:
-
Tidak Melacak Exception/Error?
- Jika Anda memiliki Tracy diaktifkan untuk proyek Anda, itu akan menggantikan penanganan error Flight. Anda perlu menonaktifkan Tracy dan kemudian pastikan bahwa
Flight::set('flight.handle_errors', true);sudah diatur.
- Jika Anda memiliki Tracy diaktifkan untuk proyek Anda, itu akan menggantikan penanganan error Flight. Anda perlu menonaktifkan Tracy dan kemudian pastikan bahwa
-
Tidak Melacak Query Database?
- Lebih suka
SimplePdodengan['trackApmQueries' => true]sebagai argumen konstruktor ke-5 (array opsi). - Jika Anda masih menggunakan
PdoWrapperyang sudah deprecated, lewatkantruesebagai argumen ke-5. - Panggil
$Apm->addPdoConnection($pdo)setelah membuat koneksi.
- Lebih suka
Awesome-plugins/tracy
Tracy
Tracy adalah penanganan kesalahan yang luar biasa yang dapat digunakan dengan Flight. Ia memiliki sejumlah panel yang dapat membantu Anda men-debug aplikasi Anda. Ia juga sangat mudah untuk diperluas dan menambahkan panel Anda sendiri. Tim Flight telah membuat beberapa panel khusus untuk proyek Flight dengan plugin flightphp/tracy-extensions (Flight vars, kueri DB, permintaan, sesi, dan panel Twig opsional ketika Anda melewatkan profil profiler—lihat Ekstensi Tracy).
Instalasi
Instal dengan composer. Dan Anda sebenarnya ingin menginstal ini tanpa versi dev karena Tracy dilengkapi dengan komponen penanganan kesalahan produksi.
composer require tracy/tracy
Konfigurasi Dasar
Ada beberapa opsi konfigurasi dasar untuk memulai. Anda dapat membaca lebih lanjut tentang mereka di Dokumentasi Tracy.
require 'vendor/autoload.php';
use Tracy\Debugger;
// Aktifkan Tracy
Debugger::enable();
// Debugger::enable(Debugger::DEVELOPMENT) // kadang-kadang Anda harus eksplisit (juga Debugger::PRODUCTION)
// Debugger::enable('23.75.345.200'); // Anda juga dapat menyediakan array alamat IP
// Di sinilah kesalahan dan pengecualian akan dicatat. Pastikan direktori ini ada dan dapat ditulis.
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // tampilkan semua kesalahan
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // semua kesalahan kecuali pemberitahuan yang sudah tidak digunakan
if (Debugger::$showBar) {
$app->set('flight.content_length', false); // jika bar Debugger terlihat, maka content-length tidak dapat diatur oleh Flight
// Ini khusus untuk Ekstensi Tracy untuk Flight jika Anda telah menyertakannya
// jika tidak, komentari ini.
new TracyExtensionLoader($app);
}
Tips Berguna
Saat Anda men-debug kode Anda, ada beberapa fungsi yang sangat membantu untuk menampilkan data untuk Anda.
bdump($var)- Ini akan membuang variabel ke Bar Tracy dalam panel terpisah.dumpe($var)- Ini akan membuang variabel dan kemudian mati segera.
Awesome-plugins/active_record
Flight Active Record
Active record adalah pemetaan entitas basis data ke objek PHP. Secara sederhana, jika Anda memiliki tabel users di basis data Anda, Anda dapat "menerjemahkan" baris dalam tabel tersebut ke kelas User dan objek $user di kode Anda. Lihat contoh dasar.
Klik di sini untuk repositori di GitHub.
Contoh Dasar
Misalkan Anda memiliki tabel berikut:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
password TEXT
);
Sekarang Anda dapat membuat kelas baru untuk merepresentasikan tabel ini:
/**
* Kelas ActiveRecord biasanya bersifat tunggal
*
* Sangat disarankan untuk menambahkan properti tabel sebagai komentar di sini
*
* @property int $id
* @property string $name
* @property string $password
*/
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
// Anda dapat mengaturnya dengan cara ini
parent::__construct($database_connection, 'users');
// atau dengan cara ini
parent::__construct($database_connection, null, [ 'table' => 'users']);
}
}
Sekarang saksikan keajaiban itu terjadi!
// untuk sqlite
$database_connection = new PDO('sqlite:test.db'); // ini hanya contoh, Anda mungkin menggunakan koneksi basis data nyata
// untuk mysql
$database_connection = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'username', 'password');
// atau mysqli
$database_connection = new mysqli('localhost', 'username', 'password', 'test_db');
// atau mysqli dengan pembuatan berbasis non-objek
$database_connection = mysqli_connect('localhost', 'username', 'password', 'test_db');
$user = new User($database_connection);
$user->name = 'Bobby Tables';
$user->password = password_hash('some cool password');
$user->insert();
// atau $user->save();
echo $user->id; // 1
$user->name = 'Joseph Mamma';
$user->password = password_hash('some cool password again!!!');
$user->insert();
// tidak bisa menggunakan $user->save() di sini atau itu akan mengira itu pembaruan!
echo $user->id; // 2
Dan itu semudah itu untuk menambahkan pengguna baru! Sekarang ada baris pengguna di basis data, bagaimana Anda mengambilnya?
$user->find(1); // cari id = 1 di basis data dan kembalikan itu.
echo $user->name; // 'Bobby Tables'
Dan bagaimana jika Anda ingin mencari semua pengguna?
$users = $user->findAll();
Bagaimana dengan kondisi tertentu?
$users = $user->like('name', '%mamma%')->findAll();
Lihat betapa menyenangkannya ini? Mari instal dan mulai!
Instalasi
Cukup instal dengan Composer
composer require flightphp/active-record
Penggunaan
Ini dapat digunakan sebagai pustaka mandiri atau dengan Flight PHP Framework. Sepenuhnya terserah Anda.
Mandiri
Pastikan Anda mengirimkan koneksi PDO ke konstruktor.
$pdo_connection = new PDO('sqlite:test.db'); // ini hanya contoh, Anda mungkin menggunakan koneksi basis data nyata
$User = new User($pdo_connection);
Tidak ingin selalu mengatur koneksi basis data di konstruktor? Lihat Manajemen Koneksi Basis Data untuk ide-ide lain!
Daftarkan sebagai metode di Flight
Jika Anda menggunakan Flight PHP Framework, Anda dapat mendaftarkan kelas ActiveRecord sebagai layanan, tapi sebenarnya tidak harus.
Flight::register('user', 'User', [ $pdo_connection ]);
// kemudian Anda dapat menggunakannya seperti ini di controller, fungsi, dll.
Flight::user()->find(1);
Metode runway
runway adalah alat CLI untuk Flight yang memiliki perintah khusus untuk pustaka ini.
# Penggunaan
php runway make:record database_table_name [class_name]
# Contoh
php runway make:record users
Ini akan membuat kelas baru di direktori app/records/ sebagai UserRecord.php dengan konten berikut:
<?php
declare(strict_types=1);
namespace app\records;
/**
* Kelas ActiveRecord untuk tabel users.
* @link https://docs.flightphp.com/awesome-plugins/active-record
*
* @property int $id
* @property string $username
* @property string $email
* @property string $password_hash
* @property string $created_dt
*/
class UserRecord extends \flight\ActiveRecord
{
/**
* @var array $relations Atur hubungan untuk model
* https://docs.flightphp.com/awesome-plugins/active-record#relationships
*/
protected array $relations = [
// 'relation_name' => [ self::HAS_MANY, 'RelatedClass', 'foreign_key' ],
];
/**
* Konstruktor
* @param mixed $databaseConnection Koneksi ke basis data
*/
public function __construct($databaseConnection)
{
parent::__construct($databaseConnection, 'users');
}
}
Fungsi CRUD
find($id = null) : boolean|ActiveRecord
Cari satu rekaman dan tetapkan ke objek saat ini. Jika Anda mengirimkan $id tertentu, itu akan melakukan pencarian pada kunci utama dengan nilai tersebut. Jika tidak ada yang dikirimkan, itu hanya akan mencari rekaman pertama di tabel.
Selain itu, Anda dapat mengirimkan metode pembantu lain untuk memquery tabel Anda.
// cari rekaman dengan beberapa kondisi sebelumnya
$user->notNull('password')->orderBy('id DESC')->find();
// cari rekaman berdasarkan id tertentu
$id = 123;
$user->find($id);
findAll(): array<int,ActiveRecord>
Mencari semua rekaman di tabel yang Anda tentukan.
$user->findAll();
isHydrated(): boolean (v0.4.0)
Mengembalikan true jika rekaman saat ini telah dihidrasi (diambil dari basis data).
$user->find(1);
// jika rekaman ditemukan dengan data...
$user->isHydrated(); // true
insert(): boolean|ActiveRecord
Memasukkan rekaman saat ini ke basis data.
$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();
Kunci Utama Berbasis Teks
Jika Anda memiliki kunci utama berbasis teks (seperti UUID), Anda dapat mengatur nilai kunci utama sebelum memasukkan dengan salah satu dari dua cara.
$user = new User($pdo_connection, [ 'primaryKey' => 'uuid' ]);
$user->uuid = 'some-uuid';
$user->name = 'demo';
$user->password = md5('demo');
$user->insert(); // atau $user->save();
atau Anda dapat memiliki kunci utama yang dihasilkan secara otomatis untuk Anda melalui event.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
// Anda juga dapat mengatur primaryKey dengan cara ini daripada array di atas.
$this->primaryKey = 'uuid';
}
protected function beforeInsert(self $self) {
$self->uuid = uniqid(); // atau bagaimana pun Anda perlu menghasilkan id unik Anda
}
}
Jika Anda tidak mengatur kunci utama sebelum memasukkan, itu akan diatur ke rowid dan
basis data akan menghasilkannya untuk Anda, tapi itu tidak akan bertahan karena field tersebut mungkin tidak ada
di tabel Anda. Inilah mengapa disarankan untuk menggunakan event untuk menangani ini secara otomatis
untuk Anda.
update(): boolean|ActiveRecord
Memperbarui rekaman saat ini ke basis data.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();
save(): boolean|ActiveRecord
Memasukkan atau memperbarui rekaman saat ini ke basis data. Jika rekaman memiliki id, itu akan memperbarui, jika tidak itu akan memasukkan.
$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();
Catatan: Jika Anda memiliki hubungan yang didefinisikan di kelas, itu akan secara rekursif menyimpan hubungan tersebut juga jika mereka telah didefinisikan, diinstansiasi dan memiliki data kotor untuk diperbarui. (v0.4.0 dan di atas)
delete(): boolean
Menghapus rekaman saat ini dari basis data.
$user->gt('id', 0)->orderBy('id desc')->find();
$user->delete();
Anda juga dapat menghapus beberapa rekaman dengan menjalankan pencarian sebelumnya.
$user->like('name', 'Bob%')->delete();
dirty(array $dirty = []): ActiveRecord
Data kotor merujuk pada data yang telah diubah dalam rekaman.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
// tidak ada yang "kotor" pada titik ini.
$user->email = 'test@example.com'; // sekarang email dianggap "kotor" karena telah diubah.
$user->update();
// sekarang tidak ada data yang kotor karena telah diperbarui dan disimpan di basis data
$user->password = password_hash()'newpassword'); // sekarang ini kotor
$user->dirty(); // mengirimkan tidak ada akan membersihkan semua entri kotor.
$user->update(); // tidak ada yang akan diperbarui karena tidak ada yang ditangkap sebagai kotor.
$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // baik name maupun password diperbarui.
copyFrom(array $data): ActiveRecord (v0.4.0)
Ini adalah alias untuk metode dirty(). Ini sedikit lebih jelas apa yang Anda lakukan.
$user->copyFrom([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // baik name maupun password diperbarui.
isDirty(): boolean (v0.4.0)
Mengembalikan true jika rekaman saat ini telah diubah.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@email.com';
$user->isDirty(); // true
reset(bool $include_query_data = true): ActiveRecord
Mengatur ulang rekaman saat ini ke keadaan awalnya. Ini sangat bagus untuk digunakan dalam perilaku tipe loop.
Jika Anda mengirimkan true itu juga akan mengatur ulang data query yang digunakan untuk menemukan objek saat ini (perilaku default).
$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);
foreach($users as $user) {
$user_company->reset(); // mulai dengan slate bersih
$user_company->user_id = $user->id;
$user_company->company_id = $some_company_id;
$user_company->insert();
}
getBuiltSql(): string (v0.4.1)
Setelah Anda menjalankan metode find(), findAll(), insert(), update(), atau save() Anda dapat memperoleh SQL yang dibangun dan menggunakannya untuk tujuan debugging.
Metode Query SQL
select(string $field1 [, string $field2 ... ])
Anda dapat memilih hanya beberapa kolom di tabel jika Anda suka (ini lebih performant pada tabel lebar yang sangat banyak kolomnya)
$user->select('id', 'name')->find();
from(string $table)
Anda secara teknis dapat memilih tabel lain juga! Mengapa tidak?!
$user->select('id', 'name')->from('user')->find();
join(string $table_name, string $join_condition)
Anda bahkan dapat bergabung ke tabel lain di basis data.
$user->join('contacts', 'contacts.user_id = users.id')->find();
where(string $where_conditions)
Anda dapat mengatur beberapa argumen where kustom (Anda tidak dapat mengatur params di pernyataan where ini)
$user->where('id=1 AND name="demo"')->find();
Catatan Keamanan - Anda mungkin tergoda untuk melakukan sesuatu seperti $user->where("id = '{$id}' AND name = '{$name}'")->find();. Tolong JANGAN LAKUKAN INI!!! Ini rentan terhadap apa yang dikenal sebagai serangan SQL Injection. Ada banyak artikel online, silakan Google "sql injection attacks php" dan Anda akan menemukan banyak artikel tentang subjek ini. Cara yang tepat untuk menangani ini dengan pustaka ini adalah daripada metode where() ini, Anda akan melakukan sesuatu seperti $user->eq('id', $id)->eq('name', $name)->find(); Jika Anda benar-benar harus melakukan ini, pustaka PDO memiliki $pdo->quote($var) untuk melarikan diri untuk Anda. Hanya setelah Anda menggunakan quote() Anda dapat menggunakannya dalam pernyataan where().
group(string $group_by_statement)/groupBy(string $group_by_statement)
Kelompokkan hasil Anda berdasarkan kondisi tertentu.
$user->select('COUNT(*) as count')->groupBy('name')->findAll();
order(string $order_by_statement)/orderBy(string $order_by_statement)
Urutkan query yang dikembalikan dengan cara tertentu.
$user->orderBy('name DESC')->find();
limit(string $limit)/limit(int $offset, int $limit)
Batasi jumlah rekaman yang dikembalikan. Jika int kedua diberikan, itu akan menjadi offset, limit seperti di SQL.
$user->orderby('name DESC')->limit(0, 10)->findAll();
Kondisi WHERE
equal(string $field, mixed $value) / eq(string $field, mixed $value)
Where field = $value
$user->eq('id', 1)->find();
notEqual(string $field, mixed $value) / ne(string $field, mixed $value)
Where field <> $value
$user->ne('id', 1)->find();
isNull(string $field)
Where field IS NULL
$user->isNull('id')->find();
isNotNull(string $field) / notNull(string $field)
Where field IS NOT NULL
$user->isNotNull('id')->find();
greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)
Where field > $value
$user->gt('id', 1)->find();
lessThan(string $field, mixed $value) / lt(string $field, mixed $value)
Where field < $value
$user->lt('id', 1)->find();
greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)
Where field >= $value
$user->ge('id', 1)->find();
lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)
Where field <= $value
$user->le('id', 1)->find();
like(string $field, mixed $value) / notLike(string $field, mixed $value)
Where field LIKE $value atau field NOT LIKE $value
$user->like('name', 'de')->find();
in(string $field, array $values) / notIn(string $field, array $values)
Where field IN($value) atau field NOT IN($value)
$user->in('id', [1, 2])->find();
between(string $field, array $values)
Where field BETWEEN $value AND $value1
$user->between('id', [1, 2])->find();
Kondisi OR
Mungkin untuk membungkus kondisi Anda dalam pernyataan OR. Ini dilakukan dengan metode startWrap() dan endWrap() atau dengan mengisi parameter ke-3 dari kondisi setelah field dan value.
// Metode 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// Ini akan dievaluasi ke `id = 1 AND (name = 'demo' OR name = 'test')`
// Metode 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// Ini akan dievaluasi ke `id = 1 OR name = 'demo'`
Hubungan
Anda dapat mengatur beberapa jenis hubungan menggunakan pustaka ini. Anda dapat mengatur hubungan one->many dan one->one antara tabel. Ini memerlukan sedikit pengaturan tambahan di kelas sebelumnya.
Mengatur array $relations tidak sulit, tapi menebak sintaks yang benar bisa membingungkan.
protected array $relations = [
// Anda dapat menamai kunci apa saja yang Anda suka. Nama ActiveRecord mungkin bagus. Contoh: user, contact, client
'user' => [
// diperlukan
// self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
self::HAS_ONE, // ini adalah jenis hubungan
// diperlukan
'Some_Class', // ini adalah kelas ActiveRecord "lain" yang akan dirujuk
// diperlukan
// tergantung pada jenis hubungan
// self::HAS_ONE = kunci asing yang merujuk ke join
// self::HAS_MANY = kunci asing yang merujuk ke join
// self::BELONGS_TO = kunci lokal yang merujuk ke join
'local_or_foreign_key',
// hanya FYI, ini juga hanya bergabung ke kunci utama dari model "lain"
// opsional
[ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // kondisi tambahan yang Anda inginkan saat bergabung hubungan
// $record->eq('client_id', 5)->select('COUNT(*) as count')->limit(5))
// opsional
'back_reference_name' // ini jika Anda ingin merujuk balik hubungan ini kembali ke dirinya sendiri Contoh: $user->contact->user;
];
]
class User extends ActiveRecord{
protected array $relations = [
'contacts' => [ self::HAS_MANY, Contact::class, 'user_id' ],
'contact' => [ self::HAS_ONE, Contact::class, 'user_id' ],
];
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
}
class Contact extends ActiveRecord{
protected array $relations = [
'user' => [ self::BELONGS_TO, User::class, 'user_id' ],
'user_with_backref' => [ self::BELONGS_TO, User::class, 'user_id', [], 'contact' ],
];
public function __construct($database_connection)
{
parent::__construct($database_connection, 'contacts');
}
}
Sekarang kita telah mengatur referensi sehingga kita dapat menggunakannya dengan sangat mudah!
$user = new User($pdo_connection);
// cari pengguna terbaru.
$user->notNull('id')->orderBy('id desc')->find();
// dapatkan kontak dengan menggunakan hubungan:
foreach($user->contacts as $contact) {
echo $contact->id;
}
// atau kita bisa pergi ke arah lain.
$contact = new Contact();
// cari satu kontak
$contact->find();
// dapatkan pengguna dengan menggunakan hubungan:
echo $contact->user->name; // ini adalah nama pengguna
Cukup keren ya?
Eager Loading
Gambaran Umum
Eager loading menyelesaikan masalah query N+1 dengan memuat hubungan di muka. Daripada menjalankan query terpisah untuk hubungan setiap rekaman, eager loading mengambil semua data terkait hanya dalam satu query tambahan per hubungan.
Catatan: Eager loading hanya tersedia untuk v0.7.0 dan di atas.
Penggunaan Dasar
Gunakan metode with() untuk menentukan hubungan mana yang akan dimuat secara eager:
// Muat pengguna dengan kontak mereka dalam 2 query daripada N+1
$users = $user->with('contacts')->findAll();
foreach ($users as $u) {
foreach ($u->contacts as $contact) {
echo $contact->email; // Tidak ada query tambahan!
}
}
Multiple Relations
Muat beberapa hubungan sekaligus:
$users = $user->with(['contacts', 'profile', 'settings'])->findAll();
Jenis Hubungan
HAS_MANY
// Eager load semua kontak untuk setiap pengguna
$users = $user->with('contacts')->findAll();
foreach ($users as $u) {
// $u->contacts sudah dimuat sebagai array
foreach ($u->contacts as $contact) {
echo $contact->email;
}
}
HAS_ONE
// Eager load satu kontak untuk setiap pengguna
$users = $user->with('contact')->findAll();
foreach ($users as $u) {
// $u->contact sudah dimuat sebagai objek
echo $u->contact->email;
}
BELONGS_TO
// Eager load pengguna induk untuk semua kontak
$contacts = $contact->with('user')->findAll();
foreach ($contacts as $c) {
// $c->user sudah dimuat
echo $c->user->name;
}
Dengan find()
Eager loading bekerja dengan baik findAll() dan find() :
$user = $user->with('contacts')->find(1);
// Pengguna dan semua kontak mereka dimuat dalam 2 query
Manfaat Performa
Tanpa eager loading (masalah N+1):
$users = $user->findAll(); // 1 query
foreach ($users as $u) {
$contacts = $u->contacts; // N query (satu per pengguna!)
}
// Total: 1 + N query
Dengan eager loading:
$users = $user->with('contacts')->findAll(); // 2 query total
foreach ($users as $u) {
$contacts = $u->contacts; // 0 query tambahan!
}
// Total: 2 query (1 untuk pengguna + 1 untuk semua kontak)
Untuk 10 pengguna, ini mengurangi query dari 11 menjadi 2 - pengurangan 82%!
Catatan Penting
- Eager loading sepenuhnya opsional - lazy loading masih bekerja seperti sebelumnya
- Hubungan yang sudah dimuat secara otomatis dilewati
- Back references bekerja dengan eager loading
- Callback hubungan dihormati selama eager loading
Keterbatasan
- Eager loading bersarang (mis., with(['contacts.addresses']) ) saat ini tidak didukung
- Batasan eager load melalui closure tidak didukung dalam versi ini
Mengatur Data Kustom
Kadang-kadang Anda mungkin perlu melampirkan sesuatu yang unik ke ActiveRecord Anda seperti perhitungan kustom yang mungkin lebih mudah untuk hanya melampirkannya ke objek yang kemudian diteruskan ke template.
setCustomData(string $field, mixed $value)
Anda melampirkan data kustom dengan metode setCustomData().
$user->setCustomData('page_view_count', $page_view_count);
Dan kemudian Anda cukup merujuknya seperti properti objek normal.
echo $user->page_view_count;
Event
Satu fitur super hebat lagi tentang pustaka ini adalah tentang event. Event dipicu pada waktu tertentu berdasarkan metode tertentu yang Anda panggil. Mereka sangat sangat membantu dalam mengatur data untuk Anda secara otomatis.
onConstruct(ActiveRecord $ActiveRecord, array &config)
Ini sangat membantu jika Anda perlu mengatur koneksi default atau sesuatu seperti itu.
// index.php atau bootstrap.php
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);
//
//
//
// User.php
class User extends flight\ActiveRecord {
protected function onConstruct(self $self, array &$config) { // jangan lupa referensi &
// Anda bisa melakukan ini untuk secara otomatis mengatur koneksi
$config['connection'] = Flight::db();
// atau ini
$self->transformAndPersistConnection(Flight::db());
// Anda juga dapat mengatur nama tabel dengan cara ini.
$config['table'] = 'users';
}
}
beforeFind(ActiveRecord $ActiveRecord)
Ini mungkin hanya berguna jika Anda perlu manipulasi query setiap kali.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeFind(self $self) {
// selalu jalankan id >= 0 jika itu gaya Anda
$self->gte('id', 0);
}
}
afterFind(ActiveRecord $ActiveRecord)
Yang ini mungkin lebih berguna jika Anda selalu perlu menjalankan beberapa logika setiap kali rekaman ini diambil. Apakah Anda perlu mendekripsi sesuatu? Apakah Anda perlu menjalankan query hitung kustom setiap kali (tidak performant tapi terserah)?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterFind(self $self) {
// mendekripsi sesuatu
$self->secret = yourDecryptFunction($self->secret, $some_key);
// mungkin menyimpan sesuatu kustom seperti query???
$self->setCustomData('view_count', $self->select('COUNT(*) count')->from('user_views')->eq('user_id', $self->id)['count'];
}
}
beforeFindAll(ActiveRecord $ActiveRecord)
Ini mungkin hanya berguna jika Anda perlu manipulasi query setiap kali.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeFindAll(self $self) {
// selalu jalankan id >= 0 jika itu gaya Anda
$self->gte('id', 0);
}
}
afterFindAll(array<int,ActiveRecord> $results)
Mirip dengan afterFind() tapi Anda bisa melakukannya untuk semua rekaman!
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterFindAll(array $results) {
foreach($results as $self) {
// lakukan sesuatu yang keren seperti afterFind()
}
}
}
beforeInsert(ActiveRecord $ActiveRecord)
Sangat membantu jika Anda perlu beberapa nilai default yang diatur setiap kali.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeInsert(self $self) {
// atur beberapa default yang masuk akal
if(!$self->created_date) {
$self->created_date = gmdate('Y-m-d');
}
if(!$self->password) {
$self->password = password_hash((string) microtime(true));
}
}
}
afterInsert(ActiveRecord $ActiveRecord)
Mungkin Anda memiliki kasus pengguna untuk mengubah data setelah dimasukkan?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterInsert(self $self) {
// lakukan apa yang Anda mau
Flight::cache()->set('most_recent_insert_id', $self->id);
// atau apa pun....
}
}
beforeUpdate(ActiveRecord $ActiveRecord)
Sangat membantu jika Anda perlu beberapa nilai default yang diatur setiap kali pada pembaruan.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeInsert(self $self) {
// atur beberapa default yang masuk akal
if(!$self->updated_date) {
$self->updated_date = gmdate('Y-m-d');
}
}
}
afterUpdate(ActiveRecord $ActiveRecord)
Mungkin Anda memiliki kasus pengguna untuk mengubah data setelah diperbarui?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterInsert(self $self) {
// lakukan apa yang Anda mau
Flight::cache()->set('most_recently_updated_user_id', $self->id);
// atau apa pun....
}
}
beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)
Ini berguna jika Anda ingin event terjadi baik saat insert atau update. Saya akan menghemat penjelasan panjang, tapi saya yakin Anda bisa menebak apa itu.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeSave(self $self) {
$self->last_updated = gmdate('Y-m-d H:i:s');
}
}
beforeDelete(ActiveRecord $ActiveRecord)/afterDelete(ActiveRecord $ActiveRecord)
Tidak yakin apa yang ingin Anda lakukan di sini, tapi tidak ada penilaian di sini! Silakan!
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeDelete(self $self) {
echo 'He was a brave soldier... :cry-face:';
}
}
Manajemen Koneksi Basis Data
Saat Anda menggunakan pustaka ini, Anda dapat mengatur koneksi basis data dengan beberapa cara berbeda. Anda dapat mengatur koneksi di konstruktor, Anda dapat mengaturnya melalui variabel config $config['connection'] atau Anda dapat mengaturnya melalui setDatabaseConnection() (v0.4.1).
$pdo_connection = new PDO('sqlite:test.db'); // misalnya
$user = new User($pdo_connection);
// atau
$user = new User(null, [ 'connection' => $pdo_connection ]);
// atau
$user = new User();
$user->setDatabaseConnection($pdo_connection);
Jika Anda ingin menghindari selalu mengatur $database_connection setiap kali Anda memanggil active record, ada cara untuk itu!
// index.php atau bootstrap.php
// Atur ini sebagai kelas terdaftar di Flight
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);
// User.php
class User extends flight\ActiveRecord {
public function __construct(array $config = [])
{
$database_connection = $config['connection'] ?? Flight::db();
parent::__construct($database_connection, 'users', $config);
}
}
// Dan sekarang, tidak ada argumen yang diperlukan!
$user = new User();
Catatan: Jika Anda berencana untuk unit testing, melakukannya dengan cara ini dapat menambah beberapa tantangan untuk unit testing, tapi secara keseluruhan karena Anda dapat menyuntikkan koneksi Anda dengan
setDatabaseConnection()atau$config['connection']itu tidak terlalu buruk.
Jika Anda perlu menyegarkan koneksi basis data, misalnya jika Anda menjalankan skrip CLI yang panjang dan perlu menyegarkan koneksi setiap beberapa saat, Anda dapat mengatur ulang koneksi dengan $your_record->setDatabaseConnection($pdo_connection).
Berkontribusi
Silakan lakukan. :D
Pengaturan
Saat Anda berkontribusi, pastikan Anda menjalankan composer test-coverage untuk mempertahankan cakupan tes 100% (ini bukan cakupan unit test yang sebenarnya, lebih seperti pengujian integrasi).
Juga pastikan Anda menjalankan composer beautify dan composer phpcs untuk memperbaiki kesalahan linting apa pun.
Lisensi
MIT
Awesome-plugins/latte
Latte
Latte adalah mesin templating lengkap yang sangat mudah digunakan dan terasa lebih dekat dengan sintaks PHP daripada Twig atau Smarty. Ini juga sangat mudah untuk diperluas dan menambahkan filter serta fungsi Anda sendiri.
Instalasi
Instal dengan composer.
composer require latte/latte
Konfigurasi Dasar
Ada beberapa opsi konfigurasi dasar untuk memulai. Anda dapat membaca lebih lanjut tentangnya di Dokumentasi Latte.
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Tempat di mana latte secara khusus menyimpan cache-nya
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
$latte->render($finalPath, $data, $block);
});
Contoh Layout Sederhana
Berikut adalah contoh sederhana dari file layout. Ini adalah file yang akan digunakan untuk membungkus semua tampilan Anda yang lain.
<!-- app/views/layout.latte -->
<!doctype html>
<html lang="en">
<head>
<title>{$title ? $title . ' - '}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<nav>
<!-- elemen nav Anda di sini -->
</nav>
</header>
<div id="content">
<!-- Ini adalah keajaiban di sini -->
{block content}{/block}
</div>
<div id="footer">
© Copyright
</div>
</body>
</html>
Dan sekarang kita punya file Anda yang akan dirender di dalam blok konten tersebut:
<!-- app/views/home.latte -->
<!-- Ini memberi tahu Latte bahwa file ini "di dalam" file layout.latte -->
{extends layout.latte}
<!-- Ini adalah konten yang akan dirender di dalam layout di dalam blok konten -->
{block content}
<h1>Halaman Beranda</h1>
<p>Selamat datang di aplikasi saya!</p>
{/block}
Kemudian ketika Anda pergi untuk merender ini di dalam fungsi atau controller Anda, Anda akan melakukan sesuatu seperti ini:
// rute sederhana
Flight::route('/', function () {
Flight::render('home.latte', [
'title' => 'Halaman Beranda'
]);
});
// atau jika Anda menggunakan controller
Flight::route('/', [HomeController::class, 'index']);
// HomeController.php
class HomeController
{
public function index()
{
Flight::render('home.latte', [
'title' => 'Halaman Beranda'
]);
}
}
Lihat Dokumentasi Latte untuk informasi lebih lanjut tentang cara menggunakan Latte secara maksimal!
Debugging dengan Tracy
PHP 8.1+ diperlukan untuk bagian ini.
Anda juga dapat menggunakan Tracy untuk membantu debugging file template Latte Anda langsung dari kotak! Jika Anda sudah menginstal Tracy, Anda perlu menambahkan ekstensi Latte ke Tracy.
// services.php
use Tracy\Debugger;
$app->map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Tempat di mana latte secara khusus menyimpan cache-nya
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
// Ini hanya akan menambahkan ekstensi jika Bilah Debug Tracy diaktifkan
if (Debugger::$showBar === true) {
// ini adalah tempat Anda menambahkan Panel Latte ke Tracy
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
}
$latte->render($finalPath, $data, $block);
});Awesome-plugins/awesome_plugins
Plugin Luar Biasa
Flight sangat dapat diperluas. Ada sejumlah plugin yang dapat digunakan untuk menambahkan fungsionalitas ke aplikasi Flight Anda. Beberapa didukung secara resmi oleh Tim Flight dan yang lainnya adalah library mikro/lite untuk membantu Anda memulai.
Alat AI
Flight dapat menjadi lebih keren dengan plugin berbasis AI.
- Flight MCP - Plugin untuk mengintegrasikan MCP (Model Control Protocol) dengan Flight, memungkinkan fungsionalitas berbasis AI yang mulus. Sebagian besar berfokus pada halaman dokumentasi, membantu menjaga biaya token tetap rendah dengan menyediakan informasi terkini tentang proyek Flight Anda.
Dokumentasi API
Dokumentasi API sangat penting untuk API apa pun. Ini membantu pengembang memahami cara berinteraksi dengan API Anda dan apa yang diharapkan sebagai balasan. Ada beberapa alat yang tersedia untuk membantu Anda menghasilkan dokumentasi API untuk Proyek Flight Anda.
- FlightPHP OpenAPI Generator - Tulisan blog oleh Daniel Schreiber tentang cara menggunakan OpenAPI Spec dengan FlightPHP untuk membangun API Anda menggunakan pendekatan API first.
- SwaggerUI - Swagger UI adalah alat yang bagus untuk membantu Anda menghasilkan dokumentasi API untuk proyek Flight Anda. Sangat mudah digunakan dan dapat disesuaikan untuk memenuhi kebutuhan Anda. Ini adalah library PHP untuk membantu Anda menghasilkan dokumentasi Swagger.
Pemantauan Performa Aplikasi (APM)
Pemantauan Performa Aplikasi (APM) sangat penting untuk aplikasi apa pun. Ini membantu Anda memahami bagaimana aplikasi Anda berkinerja dan di mana bottleneck-nya. Ada sejumlah alat APM yang dapat digunakan dengan Flight.
- official flightphp/apm - Flight APM adalah library APM sederhana yang dapat digunakan untuk memantau aplikasi Flight Anda. Dapat digunakan untuk memantau performa aplikasi Anda dan membantu mengidentifikasi bottleneck.
Async
Flight sudah merupakan framework yang cepat tetapi menambahkan mesin turbo membuat segalanya lebih menyenangkan (dan menantang)!
- flightphp/async - Library Flight Async resmi. Library ini adalah cara sederhana untuk menambahkan pemrosesan asinkron ke aplikasi Anda. Menggunakan Swoole/Openswoole di balik layar untuk menyediakan cara sederhana dan efektif menjalankan tugas secara asinkron.
Otorisasi/Permission
Otorisasi dan Permission sangat penting untuk aplikasi apa pun yang memerlukan kontrol untuk siapa yang dapat mengakses apa.
- official flightphp/permissions - Library Flight Permissions resmi. Library ini adalah cara sederhana untuk menambahkan permission tingkat user dan aplikasi ke aplikasi Anda.
Autentikasi
Autentikasi penting untuk aplikasi yang perlu memverifikasi identitas user dan mengamankan endpoint API.
- firebase/php-jwt - Library JSON Web Token (JWT) untuk PHP. Cara sederhana dan aman untuk mengimplementasikan autentikasi berbasis token dalam aplikasi Flight Anda. Sempurna untuk autentikasi API stateless, melindungi route dengan middleware, dan mengimplementasikan alur otorisasi gaya OAuth.
Caching
Caching adalah cara yang bagus untuk mempercepat aplikasi Anda. Ada sejumlah library caching yang dapat digunakan dengan Flight.
- official flightphp/cache - Class caching dalam file PHP yang ringan, sederhana dan mandiri
CLI
Aplikasi CLI adalah cara yang bagus untuk berinteraksi dengan aplikasi Anda. Anda dapat menggunakannya untuk menghasilkan controller, menampilkan semua route, dan lainnya.
- official flightphp/runway - Runway adalah aplikasi CLI yang membantu Anda mengelola aplikasi Flight Anda.
Cookie
Cookie adalah cara yang bagus untuk menyimpan potongan data kecil di sisi client. Mereka dapat digunakan untuk menyimpan preferensi user, pengaturan aplikasi, dan lainnya.
- overclokk/cookie - PHP Cookie adalah library PHP yang menyediakan cara sederhana dan efektif untuk mengelola cookie.
Debugging
Debugging sangat penting ketika Anda mengembangkan di lingkungan lokal Anda. Ada beberapa plugin yang dapat meningkatkan pengalaman debugging Anda.
- tracy/tracy - Ini adalah error handler yang lengkap yang dapat digunakan dengan Flight. Memiliki sejumlah panel yang dapat membantu Anda debug aplikasi Anda. Juga sangat mudah untuk diperluas dan menambahkan panel Anda sendiri.
- official flightphp/tracy-extensions - Digunakan dengan error handler Tracy, plugin ini menambahkan beberapa panel tambahan untuk membantu debugging khusus untuk proyek Flight.
Database
Database adalah inti dari sebagian besar aplikasi. Ini adalah cara Anda menyimpan dan mengambil data. Beberapa library database hanyalah wrapper untuk menulis query dan beberapa adalah ORM yang lengkap.
- official flightphp/core SimplePdo - Helper PDO Flight resmi yang merupakan bagian dari core. Ini adalah wrapper modern dengan metode helper yang nyaman seperti
insert(),update(),delete(), dantransaction()untuk menyederhanakan operasi database. Semua hasil dikembalikan sebagai Collection untuk akses array/object yang fleksibel. Bukan ORM, hanya cara yang lebih baik untuk bekerja dengan PDO. - deprecated flightphp/core PdoWrapper - Wrapper PDO Flight resmi yang merupakan bagian dari core (deprecated sejak v3.18.0). Gunakan SimplePdo sebagai gantinya.
- official flightphp/active-record - Flight ActiveRecord ORM/Mapper resmi. Library kecil yang bagus untuk mengambil dan menyimpan data dengan mudah di database Anda.
- byjg/php-migration - Plugin untuk melacak semua perubahan database untuk proyek Anda.
- knifelemon/easy-query - Query builder SQL yang ringan dan fluent yang menghasilkan SQL dan parameter untuk prepared statement. Bekerja dengan baik dengan SimplePdo.
Enkripsi
Enkripsi sangat penting untuk aplikasi apa pun yang menyimpan data sensitif. Mengenkripsi dan mendekripsi data tidak terlalu sulit, tetapi menyimpan kunci enkripsi dengan benar bisa menjadi sulit. Hal yang paling penting adalah untuk tidak pernah menyimpan kunci enkripsi Anda di direktori publik atau meng-commitnya ke repositori kode Anda.
- defuse/php-encryption - Ini adalah library yang dapat digunakan untuk mengenkripsi dan mendekripsi data. Memulai dan menjalankan cukup sederhana untuk mulai mengenkripsi dan mendekripsi data.
Mengirim email adalah kebutuhan inti untuk sebagian besar aplikasi web - pesan selamat datang, reset password, notifikasi. Library ini membuatnya mudah sambil menjaga deliverability tetap solid.
- ryanstubbs/flightmail - FlightMail membungkus Symfony Mailer dengan API yang fluent dan ramah Flight. Kirim melalui SMTP atau penyedia utama mana pun via string DSN sederhana, arahkan penyedia berbeda per pesan, dan render body dengan templat Twig atau Latte. Ini adalah plugin tidak resmi untuk Flight dan tidak dikelola oleh tim Flight.
Job Queue
Job queue sangat membantu untuk memproses tugas secara asinkron. Ini bisa berupa mengirim email, memproses gambar, atau apa pun yang tidak perlu dilakukan secara real-time.
- n0nag0n/simple-job-queue - Simple Job Queue adalah library yang dapat digunakan untuk memproses job secara asinkron. Dapat digunakan dengan beanstalkd、MySQL/MariaDB、SQLite、dan PostgreSQL.
Session
Session tidak terlalu berguna untuk API tetapi untuk membangun aplikasi web, session bisa sangat penting untuk mempertahankan state dan informasi login.
- official flightphp/session - Library Flight Session resmi. Ini adalah library session sederhana yang dapat digunakan untuk menyimpan dan mengambil data session. Menggunakan penanganan session bawaan PHP.
- Ghostff/Session - PHP Session Manager (non-blocking, flash, segment, session encryption). Menggunakan PHP open_ssl untuk enkripsi/dekripsi data session opsional.
Templating
Templating adalah inti dari aplikasi web apa pun dengan UI. Ada sejumlah mesin templating yang dapat digunakan dengan Flight.
- deprecated flightphp/core View - Ini adalah mesin templating yang sangat dasar yang merupakan bagian dari core. Tidak disarankan untuk digunakan jika Anda memiliki lebih dari beberapa halaman dalam proyek Anda.
- latte/latte - Latte adalah mesin templating yang lengkap yang sangat mudah digunakan dan terasa lebih dekat dengan sintaks PHP daripada Twig atau Smarty. Juga sangat mudah untuk diperluas dan menambahkan filter dan fungsi Anda sendiri.
- twig/twig - Twig adalah mesin template yang fleksibel, cepat, dan aman (yang sama digunakan oleh Symfony). Alat AI dan banyak pengembang PHP mengenalnya dengan baik, secara otomatis escape output secara default, dan memiliki ekosistem ekstensi yang besar.
- knifelemon/comment-template - CommentTemplate adalah mesin template PHP yang kuat dengan kompilasi asset, pewarisan template, dan pemrosesan variabel. Fitur minifikasi CSS/JS otomatis, caching, encoding Base64, dan integrasi opsional framework Flight PHP.
Integrasi WordPress
Ingin menggunakan Flight dalam proyek WordPress Anda? Ada plugin yang berguna untuk itu!
- n0nag0n/wordpress-integration-for-flight-framework - Plugin WordPress ini memungkinkan Anda menjalankan Flight bersama dengan WordPress. Sempurna untuk menambahkan API khusus, microservice, atau bahkan aplikasi lengkap ke situs WordPress Anda menggunakan framework Flight. Sangat berguna jika Anda ingin yang terbaik dari kedua dunia!
Berkontribusi
Punya plugin yang ingin Anda bagikan? Kirim pull request untuk menambahkannya ke daftar!
Media
Media
Kami telah berusaha melacak apa yang kami bisa tentang berbagai jenis media di internet seputar Flight. Lihat di bawah untuk sumber daya berbeda yang dapat Anda gunakan untuk mempelajari lebih lanjut tentang Flight.
Articles and Write-ups
- Unit Testing and SOLID Principles by Brian Fenton (2015?)
- PHP Web Framework Flight by ojambo (2025)
- Define, Generate, and Implement: An API-First Approach with OpenAPI Generator and FlightPHP by Daniel Schreiber (2025)
- Best PHP Micro Frameworks for 2024 by n0nag0n (2024)
- Creating a RESTful API with Flight Framework by n0nag0n (2024)
- Building a Simple Blog with Flight Part 2 by n0nag0n (2024)
- Building a Simple Blog with Flight Part 1 by n0nag0n (2024)
- 🚀 Build a Simple CRUD API in PHP with the Flight Framework by soheil-khaledabadi (2024)
- Building a PHP Web Application with the Flight Micro-framework by Arthur C. Codex (2023)
- Best PHP Frameworks for Web Development in 2024 by Ravikiran A S (2023)
- Top 12 PHP Frameworks: A Comprehensive Guide for 2023 by marketing kbk (2023)
- 5 PHP Frameworks You've (Probably) Never Heard of by n0nag0n (2022)
- 12 top PHP frameworks for web developers to consider in 2023 by Anna Monus (2022)
- The Best PHP Microframeworks on a Cloud Server by Shahzeb Ahmed (2021)
- PHP framework: Top 15 powerful ones for your web development by AHT Tech (2020)
- Easy PHP Routing with FlightPHP by Lucas Conceição (2019)
- Trying Out New PHP Framework (Flight) by Leon (2017)
- Setting up FlightPHP to work with Backbonejs by Timothy Tocci (2015)
Videos and Tutorials
- Build a Flight PHP App with MVC & MariaDB in 10 Minutes! (Beginner Friendly) by ojamboshop (2025)
- Create a REST API for IoT Devices Using PHP & FlightPHP - ESP32 API by IoT Craft Hub (2024)
- PHP Flight Framework Simple Introductory Video by n0nag0n (2024)
- Set header HTTP code in Flightphp (3 Solutions!!) by Roel Van de Paar (2024)
- PHP Flight Framework Tutorial. Super easy API Project! by n0nag0n (2022)
- Aplicación web CRUD con php y mysql y bootstrap usando flight by Devlopteca - Oscar Uh (2021)
- DevOps & SysAdmins: Lighttpd rewrite rule for Flight PHP microframework by Roel Van de Paar (2021)
- Tutorial REST API Flight PHP #PART2 INSERT TABLE Info #Code (Tagalog) by Info Singkat Official (2020)
- Tutorial REST API Flight PHP #PART1 Info #Code (Tagalog) by Info Singkat Official (2020)
- How To Create JSON REST API IN PHP - Part 2 by Codewife (2018)
- How To Create JSON REST API IN PHP - Part 1 by Codewife (2018)
- Teste Micro Frameworks PHP - Flight PHP, Lumen, Slim 3 e Laravel by Codemarket (2016)
- Tutorial 1 Flight PHP - Instalación by absagg (2014)
- Tutorial 2 Flight PHP - Route parte 1 by absagg (2014)
Missing Anything?
Apakah kami melewatkan sesuatu yang Anda tulis atau rekam? Beri tahu kami dengan issue atau pull request!
Examples
Butuh mulai cepat?
Anda memiliki dua opsi untuk memulai proyek Flight baru:
- Full Skeleton Boilerplate: Contoh yang lebih lengkap dengan controller dan view.
- Single File Skeleton Boilerplate: Satu file tunggal yang mencakup semua yang Anda butuhkan untuk menjalankan aplikasi Anda dalam satu file sederhana.
Contoh yang dikontribusikan oleh komunitas:
- flightravel: FlightPHP dengan direktori Laravel, dengan alat PHP + GH Actions
- fleact - Kit starter FlightPHP dengan integrasi ReactJS.
- flastro - Kit starter FlightPHP dengan integrasi Astro.
- velt - Velt adalah template starter Svelte yang cepat dan mudah dengan backend FlightPHP.
- vite-flightphp - FlightPHP dan frontend modern (Vite + Tailwind CSS) dengan hot reload.
Butuh Inspirasi?
Meskipun ini tidak secara resmi disponsori oleh Tim Flight, ini bisa memberikan Anda ide tentang cara menyusun proyek Anda sendiri yang dibangun dengan Flight!
- ASC REST API Spell Checker - API REST ringan untuk pemeriksaan ejaan Arab yang dibangun dengan FlightPHP dan pustaka ArPHP. API ini menyediakan kemampuan pemeriksaan ejaan teks Arab termasuk deteksi kata salah eja dan saran koreksi.
- Eventify - Eventify adalah aplikasi single-page yang menghubungkan penyelenggara acara dengan peserta. Dibangun dengan PHP (FlightPHP), JavaScript, dan MySQL, ini menampilkan autentikasi JWT, manajemen acara, dan dokumentasi API RESTful menggunakan OpenAPI.
- Ivox Car Rental - Ivox Car Rental adalah aplikasi web penyewaan mobil single-page yang ramah seluler yang dibangun dengan PHP (FlightPHP), JavaScript, dan MySQL. Ini mendukung pendaftaran pengguna, penelusuran, dan pemesanan mobil, sementara admin dapat mengelola mobil, pengguna, dan pemesanan. Aplikasi ini menampilkan API REST, autentikasi JWT, dan desain responsif untuk pengalaman penyewaan modern.
- Decay - Flight v3 dengan HTMX dan SleekDB semuanya tentang zombie! (Demo)
- Flight Example Blog - Flight v3 dengan Middleware, Controllers, Active Record, dan Latte.
- Flight CRUD RESTful API - Proyek API CRUD sederhana menggunakan framework Flight, yang menyediakan struktur dasar bagi pengguna baru untuk dengan cepat menyiapkan aplikasi PHP dengan operasi CRUD dan konektivitas database. Proyek ini mendemonstrasikan cara menggunakan Flight untuk pengembangan API RESTful, menjadikannya alat pembelajaran ideal bagi pemula dan kit starter yang berguna bagi pengembang yang lebih berpengalaman.
- Flight School Management System - Flight v3
- Paste Bin with Comments - Flight v3
- Basic Skeleton App
- Example Wiki
- The IT-Innovator PHP Framework Application
- LittleEducationalCMS (Spanish)
- Italian Yellow Pages API
- Generic Content Management System (with....very little documentation)
- A tiny php framework based on Flight and medoo.
- Example MVC Application
- Production ready Flight Boilerplate - Framework autentikasi siap produksi yang menghemat minggu pengembangan Anda. Fitur keamanan tingkat enterprise: 2FA/TOTP, integrasi LDAP, Azure SSO, pembatasan laju cerdas, fingerprinting sesi, perlindungan brute-force, dashboard analitik keamanan, logging audit komprehensif, dan kontrol akses berbasis peran granular.
Ingin Berbagi Contoh Anda Sendiri?
Jika Anda memiliki proyek yang ingin dibagikan, silakan kirimkan pull request untuk menambahkannya ke daftar ini!
Install/install
Instruksi Instalasi
Ada beberapa prasyarat dasar sebelum Anda dapat menginstal Flight. Yaitu Anda perlu:
- Instal PHP di sistem Anda
- Instal Composer untuk pengalaman pengembang terbaik.
Instalasi Dasar
Jika Anda menggunakan Composer, Anda dapat menjalankan perintah berikut:
composer require flightphp/core
Ini hanya akan menempatkan file inti Flight di sistem Anda. Anda perlu menentukan struktur proyek, tata letak, dependensi, konfigurasi, autoloading, dll. Metode ini memastikan tidak ada dependensi lain selain Flight yang diinstal.
Anda juga dapat mengunduh file secara langsung dan mengekstraknya ke direktori web Anda.
Instalasi dasar sangat cocok untuk belajar, API mikro, dan eksperimen salin-tempel. Untuk tata letak aplikasi lengkap yang dapat diikuti oleh manusia dan alat coding AI dengan cara yang sama, gunakan skeleton yang direkomendasikan di bawah ini.
Instalasi yang Disarankan
Sangat disarankan untuk memulai dengan aplikasi flightphp/skeleton untuk proyek baru apa pun. Instalasinya sangat mudah.
composer create-project flightphp/skeleton my-project/
cd my-project/
composer start
# opsional DB contoh + demo postingan
php runway migrate
Langkah tersebut mengatur struktur proyek, autoloading Composer PSR-4, konfigurasi, dan alat seperti Tracy, Ekstensi Tracy, dan Runway. Ini juga menyertakan AGENTS.md di root (dan salinan scoped di bawah app/) sehingga asisten AI berbagi satu tata letak dengan Anda—lihat Pengalaman AI & pengembang.
Apa yang diberikan skeleton kepada Anda
project-root/
├── AGENTS.md # Sumber kebenaran AI / agen
├── SECURITY.md # Ekspektasi keamanan
├── .env.example # Rahasia / overlay deploy (disalin ke .env)
├── public/index.php # Hanya entri web
├── app/
│ ├── config/ # bootstrap, rute, layanan, config_sample.php
│ ├── Controller/ # App\Controller\* (folder PascalCase!)
│ ├── Middleware/ # App\Middleware\*
│ ├── Model/ # App\Model\* (ActiveRecord)
│ ├── Utils/ # Config, Env, DatabaseFactory
│ ├── commands/ # Perintah CLI Runway
│ ├── views/ # Template Twig (*.twig)
│ ├── cache/
│ └── log/
├── migrations/ # Migrasi SQL (.sql / .mysql.sql)
└── tests/ # PHPUnit
Namespace mengikuti huruf besar/kecil folder. Composer memetakan "App\\": "app/", jadi:
| Path di disk | Namespace |
|---|---|
app/Controller/HomeController.php |
App\Controller\HomeController |
app/Middleware/… |
App\Middleware\… |
app/Model/… |
App\Model\… |
app/Utils/… |
App\Utils\… |
Di Linux, app/controller/ tidak sama dengan app/Controller/. Autoloading peka huruf besar/kecil—cocokkan folder PascalCase milik skeleton. Detail: Autoloading.
Default stack (proyek baru): View Twig, SimplePdo + ActiveRecord, Dice dengan injeksi Engine (lebih baik tanpa Flight:: di dalam kelas aplikasi), SQLite opsional setelah php runway migrate.
create-project biasanya menyalin app/config/config_sample.php → config.php dan .env.example → .env jika ada. Rute berada di app/config/routes.php; layanan dan DI berada di app/config/services.php.
Dokumen ↔ skeleton: Dokumen ini mengajarkan API Flight (sering dengan contoh
Flight::singkat). Skeleton menetapkan bentuk aplikasi. Saat menambahkan kode di bawahapp/, ikuti struktur skeleton; gunakan dokumen untuk nama metode, opsi, dan plugin.
Konfigurasi Server Web Anda
Server Pengembangan PHP Bawaan
Ini adalah cara paling sederhana untuk memulai. Anda dapat menggunakan server bawaan untuk menjalankan aplikasi Anda dan bahkan menggunakan SQLite sebagai basis data (selama sqlite3 terinstal di sistem Anda) dan tidak memerlukan banyak hal! Cukup jalankan perintah berikut setelah PHP terinstal:
php -S localhost:8000
# atau dengan aplikasi skeleton
composer start
Kemudian buka browser Anda dan pergi ke http://localhost:8000.
Jika Anda ingin menjadikan direktori root dokumen proyek Anda sebagai direktori yang berbeda (Contoh: proyek Anda berada di ~/myproject, tetapi root dokumen Anda adalah ~/myproject/public/), Anda dapat menjalankan perintah berikut setelah berada di direktori ~/myproject:
php -S localhost:8000 -t public/
# dengan aplikasi skeleton, ini sudah dikonfigurasi
composer start
Kemudian buka browser Anda dan pergi ke http://localhost:8000.
Apache
Pastikan Apache sudah terinstal di sistem Anda. Jika belum, cari di Google cara menginstal Apache di sistem Anda.
Untuk Apache, edit file .htaccess Anda dengan berikut:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
Catatan: Jika Anda perlu menggunakan Flight di subdirektori, tambahkan baris
RewriteBase /subdir/tepat setelahRewriteEngine On.
Catatan: Jika Anda ingin melindungi semua file server, seperti file db atau env. Letakkan ini di file
.htaccessAnda:
RewriteEngine On
RewriteRule ^(.*)$ index.php
Nginx
Pastikan Nginx sudah terinstal di sistem Anda. Jika belum, cari di Google cara menginstal Nginx di sistem Anda.
Untuk Nginx, tambahkan berikut ke deklarasi server Anda:
server {
location / {
try_files $uri $uri/ /index.php;
}
}
Membuat File index.php Anda
Jika Anda melakukan instalasi dasar, Anda perlu memiliki beberapa kode untuk memulai.
<?php
// Jika Anda menggunakan Composer, require autoloader.
require 'vendor/autoload.php';
// jika Anda tidak menggunakan Composer, muat framework secara langsung
// require 'flight/Flight.php';
// Kemudian tentukan rute dan tetapkan fungsi untuk menangani permintaan.
Flight::route('/', function () {
echo 'hello world!';
});
// Terakhir, jalankan framework.
Flight::start();
Dengan aplikasi skeleton, entri publik hanya mem-boot aplikasi. Rute didaftarkan di app/config/routes.php (biasanya [App\Controller\…::class, 'method'] sehingga Dice dapat menyuntikkan dependensi). Layanan, Twig, SimplePdo, dan kontainer dihubungkan di app/config/services.php. Struktur tersebut disengaja agar alat AI dan manusia mengedit tempat yang sama setiap saat.
Menginstal PHP
Jika Anda sudah memiliki php yang terinstal di sistem Anda, silakan lewati instruksi ini dan lanjutkan ke bagian unduhan
macOS
Menginstal PHP menggunakan Homebrew
-
Instal Homebrew (jika belum terinstal):
- Buka Terminal dan jalankan:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- Buka Terminal dan jalankan:
-
Instal PHP:
- Instal versi terbaru:
brew install php - Untuk menginstal versi tertentu, misalnya PHP 8.1:
brew tap shivammathur/php brew install shivammathur/php/php@8.1
- Instal versi terbaru:
-
Berpindah antar versi PHP:
- Lepas tautan versi saat ini dan tautkan versi yang diinginkan:
brew unlink php brew link --overwrite --force php@8.1 - Verifikasi versi yang terinstal:
php -v
- Lepas tautan versi saat ini dan tautkan versi yang diinginkan:
Windows 10/11
Menginstal PHP secara manual
-
Unduh PHP:
- Kunjungi PHP untuk Windows dan unduh versi terbaru atau versi tertentu (mis., 7.4, 8.0) sebagai file zip non-thread-safe.
-
Ekstrak PHP:
- Ekstrak file zip yang diunduh ke
C:\php.
- Ekstrak file zip yang diunduh ke
-
Tambahkan PHP ke PATH sistem:
- Buka System Properties > Environment Variables.
- Di bawah System variables, temukan Path dan klik Edit.
- Tambahkan path
C:\php(atau di mana pun Anda mengekstrak PHP). - Klik OK untuk menutup semua jendela.
-
Konfigurasi PHP:
- Salin
php.ini-developmentkephp.ini. - Edit
php.iniuntuk mengonfigurasi PHP sesuai kebutuhan (mis., mengaturextension_dir, mengaktifkan ekstensi).
- Salin
-
Verifikasi instalasi PHP:
- Buka Command Prompt dan jalankan:
php -v
- Buka Command Prompt dan jalankan:
Menginstal Beberapa Versi PHP
-
Ulangi langkah-langkah di atas untuk setiap versi, letakkan masing-masing di direktori terpisah (mis.,
C:\php7,C:\php8). -
Berpindah antar versi dengan menyesuaikan variabel PATH sistem agar menunjuk ke direktori versi yang diinginkan.
Ubuntu (20.04, 22.04, dll.)
Menginstal PHP menggunakan apt
-
Perbarui daftar paket:
- Buka Terminal dan jalankan:
sudo apt update
- Buka Terminal dan jalankan:
-
Instal PHP:
- Instal versi PHP terbaru:
sudo apt install php - Untuk menginstal versi tertentu, misalnya PHP 8.1:
sudo apt install php8.1
- Instal versi PHP terbaru:
-
Instal modul tambahan (opsional):
- Misalnya, untuk menginstal dukungan MySQL:
sudo apt install php8.1-mysql
- Misalnya, untuk menginstal dukungan MySQL:
-
Berpindah antar versi PHP:
- Gunakan
update-alternatives:sudo update-alternatives --set php /usr/bin/php8.1
- Gunakan
-
Verifikasi versi yang terinstal:
- Jalankan:
php -v
- Jalankan:
Rocky Linux
Menginstal PHP menggunakan yum/dnf
-
Aktifkan repositori EPEL:
- Buka Terminal dan jalankan:
sudo dnf install epel-release
- Buka Terminal dan jalankan:
-
Instal repositori Remi:
- Jalankan:
sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm sudo dnf module reset php
- Jalankan:
-
Instal PHP:
- Untuk menginstal versi default:
sudo dnf install php - Untuk menginstal versi tertentu, misalnya PHP 7.4:
sudo dnf module install php:remi-7.4
- Untuk menginstal versi default:
-
Berpindah antar versi PHP:
- Gunakan perintah modul
dnf:sudo dnf module reset php sudo dnf module enable php:remi-8.0 sudo dnf install php
- Gunakan perintah modul
-
Verifikasi versi yang terinstal:
- Jalankan:
php -v
- Jalankan:
Catatan Umum
- Untuk lingkungan pengembangan, penting untuk mengonfigurasi pengaturan PHP sesuai kebutuhan proyek Anda.
- Saat berpindah versi PHP, pastikan semua ekstensi PHP yang relevan terinstal untuk versi tertentu yang ingin Anda gunakan.
- Restart server web Anda (Apache, Nginx, dll.) setelah berpindah versi PHP atau memperbarui konfigurasi agar perubahan diterapkan.
Guides
Panduan
Flight PHP dirancang untuk sederhana namun powerful, dan panduan kami akan membantu Anda membangun aplikasi dunia nyata langkah demi langkah. Tutorial praktis ini membawa Anda melalui proyek lengkap untuk menunjukkan bagaimana Flight dapat digunakan secara efektif.
Panduan Resmi
Membangun Sebuah Blog
Pelajari cara membuat aplikasi blog fungsional dengan Flight PHP. Panduan ini membawa Anda melalui:
- Menyiapkan struktur proyek
- Bekerja dengan templat menggunakan Latte
- Mengimplementasikan rute untuk postingan
- Menyimpan dan mengambil data
- Menangani pengiriman formulir
- Penanganan kesalahan dasar
Tutorial ini sempurna untuk pemula yang ingin melihat bagaimana semua bagian saling terhubung dalam aplikasi nyata.
Pengujian Unit dan Prinsip SOLID
Panduan ini mencakup dasar-dasar pengujian unit dalam aplikasi Flight PHP. Ini mencakup:
- Menyiapkan PHPUnit
- Menulis kode yang dapat diuji menggunakan prinsip SOLID
- Mocking dependensi
- Jebakan umum yang harus dihindari
- Menskala tes Anda seiring pertumbuhan aplikasi Tutorial ini ideal untuk pengembang yang ingin meningkatkan kualitas kode dan maintainability.
Panduan Tidak Resmi
Meskipun panduan ini tidak secara resmi dikelola oleh tim Flight, mereka adalah sumber daya berharga yang dibuat oleh komunitas. Mereka mencakup berbagai topik dan kasus penggunaan, memberikan wawasan tambahan tentang penggunaan Flight PHP.
Membuat API RESTful dengan Flight Framework
Panduan ini membawa Anda melalui pembuatan API RESTful menggunakan framework Flight PHP. Ini mencakup dasar-dasar pengaturan API, mendefinisikan rute, dan mengembalikan respons JSON.
Membangun Blog Sederhana
Panduan ini membawa Anda melalui pembuatan blog dasar menggunakan framework Flight PHP. Sebenarnya ada 2 bagian: satu untuk mencakup dasar-dasar dan yang lain untuk mencakup topik lebih lanjut serta perbaikan untuk blog yang siap produksi.
- Membangun Blog Sederhana dengan Flight - Bagian 1 - Memulai dengan blog sederhana.
- Membangun Blog Sederhana dengan Flight - Bagian 2 - Memperbaiki blog untuk produksi.
Membangun API Pokémon di PHP: Panduan untuk Pemula
Panduan menyenangkan ini membawa Anda melalui pembuatan API Pokémon sederhana menggunakan Flight PHP. Ini mencakup dasar-dasar pengaturan API, mendefinisikan rute, dan mengembalikan respons JSON.
Berkontribusi
Punya ide untuk panduan? Menemukan kesalahan? Kami menyambut kontribusi! Panduan kami dikelola di repositori dokumentasi FlightPHP.
Jika Anda telah membangun sesuatu yang menarik dengan Flight dan ingin membagikannya sebagai panduan, silakan kirimkan pull request. Berbagi pengetahuan membantu komunitas Flight berkembang.
Mencari Dokumentasi API?
Jika Anda mencari informasi spesifik tentang fitur dan metode inti Flight, periksa bagian Learn dari dokumentasi kami.