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

Kekurangan dibandingkan Flight

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 diatur secara default, tetapi Anda dapat menimpa pengaturan tersebut sesuai kebutuhan. Anda juga dapat mengatur variabel sendiri untuk digunakan di seluruh aplikasi Anda.

Pemahaman

Anda dapat menyesuaikan perilaku tertentu dari Flight dengan mengatur nilai konfigurasi melalui metode set.

Flight::set('flight.log_errors', true);

Dalam file app/config/config.php, Anda dapat melihat semua variabel konfigurasi default yang tersedia untuk Anda.

Penggunaan Dasar

Opsi Konfigurasi Flight

Berikut adalah daftar semua pengaturan konfigurasi yang tersedia:

Konfigurasi Loader

Ada juga pengaturan konfigurasi lain untuk loader. Ini akan memungkinkan Anda untuk memuat kelas secara otomatis dengan _ dalam nama kelas.

// Aktifkan pemuatan kelas dengan underscore
// Defaulted to true
Loader::$v2ClassLoading = false;

Variabel

Flight memungkinkan Anda menyimpan variabel agar dapat digunakan di mana saja dalam aplikasi Anda.

// Simpan variabel Anda
Flight::set('id', 123);

// Di tempat lain dalam aplikasi Anda
$id = Flight::get('id');

Untuk melihat apakah variabel telah diatur, Anda dapat melakukan:

if (Flight::has('id')) {
  // Lakukan sesuatu
}

Anda dapat menghapus variabel dengan melakukan:

// Hapus variabel id
Flight::clear('id');

// Hapus semua variabel
Flight::clear();

Catatan: Hanya karena Anda dapat mengatur variabel tidak berarti Anda harus melakukannya. Gunakan fitur ini secara hemat. Alasan mengapa adalah bahwa apa pun yang disimpan di sini menjadi variabel global. Variabel global buruk karena dapat diubah dari mana saja dalam aplikasi Anda, membuat sulit untuk melacak bug. Selain itu, ini dapat mempersulit hal-hal seperti unit testing.

Kesalahan dan Pengecualian

Semua kesalahan dan pengecualian ditangkap oleh Flight dan diteruskan ke metode error. jika flight.handle_errors diatur ke true.

Perilaku default adalah mengirim respons HTTP 500 Internal Server Error umum dengan beberapa informasi kesalahan.

Anda dapat menimpa perilaku ini untuk kebutuhan Anda sendiri:

Flight::map('error', function (Throwable $error) {
  // Tangani kesalahan
  echo $error->getTraceAsString();
});

Secara default, kesalahan tidak dicatat ke server web. Anda dapat mengaktifkan ini dengan mengubah konfigurasi:

Flight::set('flight.log_errors', true);

404 Tidak Ditemukan

Ketika URL tidak dapat ditemukan, Flight memanggil metode notFound. Perilaku default adalah mengirim respons HTTP 404 Not Found dengan pesan sederhana.

Anda dapat menimpa perilaku ini untuk kebutuhan Anda sendiri:

Flight::map('notFound', function () {
  // Tangani tidak ditemukan
});

Lihat Juga

Pemecahan Masalah

Changelog

Learn/ai

AI & Pengalaman Pengembang dengan Flight

Gambaran Umum

Flight memudahkan Anda untuk meningkatkan proyek PHP Anda dengan alat berbasis AI dan alur kerja pengembang modern. Dengan perintah bawaan untuk menghubungkan ke penyedia LLM (Large Language Model) dan menghasilkan instruksi pengkodean AI khusus proyek, Flight membantu Anda dan tim Anda mendapatkan manfaat maksimal dari asisten AI seperti GitHub Copilot, Cursor, dan Windsurf.

Pemahaman

Asisten pengkodean AI paling membantu ketika mereka memahami konteks proyek Anda, konvensi, dan tujuan. Pembantu AI Flight memungkinkan Anda:

Fitur-fitur ini dibangun ke dalam CLI inti Flight dan proyek starter resmi flightphp/skeleton.

Penggunaan Dasar

Menyiapkan Kredensial LLM

Perintah ai:init memandu Anda melalui proses menghubungkan proyek Anda ke penyedia LLM.

php runway ai:init

Anda akan diminta untuk:

Ini membuat file .runway-creds.json di root proyek Anda (dan memastikan itu ada di .gitignore Anda).

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 membantu Anda membuat atau memperbarui instruksi untuk asisten pengkodean AI, disesuaikan dengan proyek Anda.

php runway ai:generate-instructions

Anda akan menjawab beberapa pertanyaan tentang proyek Anda (deskripsi, database, templating, keamanan, ukuran tim, dll.). Flight menggunakan penyedia LLM Anda untuk menghasilkan instruksi, kemudian menulisnya ke:

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)? latte
Is security an important element of this project? (y/n) y
...
AI instructions updated successfully.

Sekarang, alat AI Anda akan memberikan saran yang lebih cerdas dan relevan berdasarkan kebutuhan nyata proyek Anda.

Penggunaan Lanjutan

Lihat Juga

Pemecahan Masalah

Changelog

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:

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:

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

Gambaran Umum

Keamanan adalah hal besar ketika berbicara tentang aplikasi web. Anda ingin memastikan bahwa aplikasi Anda aman dan data pengguna Anda aman. Flight menyediakan sejumlah fitur untuk membantu Anda mengamankan aplikasi web Anda.

Pemahaman

Ada sejumlah ancaman keamanan umum yang harus Anda sadari saat membangun aplikasi web. Beberapa ancaman paling umum termasuk:

Templates membantu dengan XSS dengan meng-escape output secara default sehingga Anda tidak perlu mengingat untuk melakukannya. Sessions dapat membantu dengan CSRF dengan menyimpan token CSRF di sesi pengguna seperti yang diuraikan di bawah ini. Menggunakan prepared statements dengan PDO dapat membantu mencegah serangan SQL injection (atau menggunakan metode yang berguna di kelas PdoWrapper). CORS dapat ditangani dengan hook sederhana sebelum Flight::start() dipanggil.

Semua metode ini bekerja sama untuk membantu menjaga aplikasi web Anda aman. Ini harus selalu menjadi yang terdepan di pikiran Anda untuk belajar dan memahami praktik terbaik keamanan.

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 yang dapat Anda gunakan untuk menambahkan header ini ke aplikasi Anda.

Dua situs web hebat untuk memeriksa keamanan header Anda adalah securityheaders.com dan observatory.mozilla.org. Setelah Anda menyiapkan kode di bawah ini, Anda dapat dengan mudah memverifikasi bahwa header Anda berfungsi dengan dua situs web tersebut.

Tambahkan Secara Manual

Anda dapat menambahkan header ini secara manual dengan menggunakan metode header pada objek Flight\Response.

// Set header X-Frame-Options untuk mencegah clickjacking
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');

// Set header Content-Security-Policy untuk mencegah XSS
// Catatan: header ini bisa sangat kompleks, jadi Anda akan ingin
//  berkonsultasi dengan contoh di internet untuk aplikasi Anda
Flight::response()->header("Content-Security-Policy", "default-src 'self'");

// Set header X-XSS-Protection untuk mencegah XSS
Flight::response()->header('X-XSS-Protection', '1; mode=block');

// Set header X-Content-Type-Options untuk mencegah MIME sniffing
Flight::response()->header('X-Content-Type-Options', 'nosniff');

// Set header Referrer-Policy untuk mengontrol seberapa banyak informasi referrer yang dikirim
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');

// Set header Strict-Transport-Security untuk memaksa HTTPS
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');

// Set header Permissions-Policy untuk mengontrol fitur dan API apa 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 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 kelas middleware yang memberikan fleksibilitas terbesar untuk rute mana yang akan diterapkan ini. Secara umum, header ini harus diterapkan pada semua respons HTML dan API.

// app/middlewares/SecurityHeadersMiddleware.php

namespace app\middlewares;

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();
        $response->header('X-Frame-Options', 'SAMEORIGIN');
        $response->header("Content-Security-Policy", "default-src 'self'");
        $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=()');
    }
}

// index.php atau di mana pun Anda memiliki rute
// FYI, grup string kosong ini bertindak sebagai middleware global untuk
// semua rute. Tentu saja Anda bisa melakukan hal yang sama dan hanya menambahkan
// ini ke rute tertentu.
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // lebih banyak rute
}, [ SecurityHeadersMiddleware::class ]);

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 mengimplementasikan sendiri dengan menggunakan middleware.

Penyiapan

Pertama, Anda perlu menghasilkan token CSRF dan menyimpannya di sesi pengguna. Kemudian Anda dapat menggunakan token ini di formulir Anda dan memeriksanya ketika formulir dikirim. Kami 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 berbagai 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 Flight PHP Default
<!-- Gunakan token CSRF di formulir Anda -->
<form method="post">
    <input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
    <!-- field formulir lainnya -->
</form>
Menggunakan Latte

Anda juga dapat mengatur fungsi kustom untuk mengeluarkan token CSRF di template Latte Anda.


Flight::map('render', function(string $template, array $data, ?string $block): void {
    $latte = new Latte\Engine;

    // konfigurasi lainnya...

    // Set fungsi kustom untuk mengeluarkan 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, Anda dapat menggunakan fungsi csrf() untuk mengeluarkan token CSRF.

<form method="post">
    {csrf()}
    <!-- field formulir lainnya -->
</form>

Periksa Token CSRF

Anda dapat memeriksa token CSRF menggunakan beberapa metode.

Middleware
// app/middlewares/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;
            if($token !== $this->app->session()->get('csrf_token')) {
                $this->app->halt(403, 'Invalid CSRF token');
            }
        }
    }
}

// index.php atau di mana pun Anda memiliki rute
use app\middlewares\CsrfMiddleware;

Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // lebih banyak rute
}, [ CsrfMiddleware::class ]);
Filter Event
// Middleware ini memeriksa apakah permintaan adalah permintaan POST dan jika ya, memeriksa apakah token CSRF valid
Flight::before('start', function() {
    if(Flight::request()->method == 'POST') {

        // tangkap token csrf dari nilai formulir
        $token = Flight::request()->data->csrf_token;
        if($token !== Flight::session()->get('csrf_token')) {
            Flight::halt(403, 'Invalid CSRF token');
            // atau untuk respons JSON
            Flight::jsonHalt(['error' => 'Invalid CSRF token'], 403);
        }
    }
});

Cross Site Scripting (XSS)

Cross Site Scripting (XSS) adalah jenis serangan di mana input formulir berbahaya dapat menyuntikkan kode ke situs web Anda. Sebagian besar peluang ini berasal dari nilai formulir yang akan diisi oleh pengguna akhir Anda. Anda tidak pernah boleh mempercayai output dari pengguna Anda! Selalu asumsikan semua mereka adalah hacker 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 kelas view Flight atau engine templating lain seperti Latte, Anda dapat dengan mudah meng-escape output untuk mencegah serangan XSS.

// Mari kita asumsikan pengguna pintar dan mencoba menggunakan ini sebagai nama mereka
$name = '<script>alert("XSS")</script>';

// Ini akan meng-escape output
Flight::view()->set('name', $name);
// Ini akan mengeluarkan: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// Jika Anda menggunakan sesuatu seperti Latte yang terdaftar sebagai kelas view Anda, itu juga akan auto escape ini.
Flight::view()->render('template', ['name' => $name]);

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 di database Anda. Sekali lagi Anda tidak pernah boleh mempercayai input dari pengguna Anda! Selalu asumsikan mereka mengincar darah. Anda dapat menggunakan prepared statements di objek PDO Anda akan mencegah SQL injection.

// Dengan asumsi Anda memiliki Flight::db() yang terdaftar sebagai objek PDO Anda
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();

// Jika Anda menggunakan kelas PdoWrapper, ini dapat dengan mudah dilakukan dalam satu baris
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);

// Anda dapat melakukan hal yang sama dengan objek PDO dengan placeholder ?
$statement = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);

Contoh Tidak Aman

Di bawah ini adalah alasan mengapa kami menggunakan pernyataan prepared SQL untuk melindungi dari contoh tidak bersalah seperti di bawah ini:

// pengguna akhir mengisi formulir web.
// untuk nilai formulir, hacker memasukkan sesuatu seperti ini:
$username = "' OR 1=1; -- ";

$sql = "SELECT * FROM users WHERE username = '$username' LIMIT 5";
$users = Flight::db()->fetchAll($sql);
// Setelah query dibangun, itu terlihat seperti ini
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5

// Terlihat aneh, tapi itu query yang valid yang akan berfungsi. Bahkan,
// itu adalah serangan SQL injection yang sangat umum yang akan mengembalikan semua pengguna.

var_dump($users); // ini akan membuang semua pengguna di database, bukan hanya satu nama pengguna tunggal

CORS

Cross-Origin Resource Sharing (CORS) adalah mekanisme yang memungkinkan banyak sumber daya (misalnya, font, JavaScript, dll.) pada halaman web untuk diminta dari domain lain di luar domain tempat sumber daya berasal. Flight tidak memiliki fungsionalitas bawaan, tetapi ini dapat dengan mudah ditangani dengan hook untuk dijalankan sebelum metode Flight::start() dipanggil.

// app/utils/CorsUtil.php

namespace app\utils;

class CorsUtil
{
    public function set(array $params): void
    {
        $request = Flight::request();
        $response = Flight::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 = Flight::request();

        if (in_array($request->getVar('HTTP_ORIGIN'), $allowed, true) === true) {
            $response = Flight::response();
            $response->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN'));
        }
    }
}

// index.php atau di mana pun Anda memiliki rute
$CorsUtil = new CorsUtil();

// Ini perlu dijalankan sebelum start berjalan.
Flight::before('start', [ $CorsUtil, 'setupCors' ]);

Penanganan Error

Sembunyikan detail error sensitif di produksi untuk menghindari kebocoran info ke penyerang. Di produksi, log error daripada menampilkannya dengan display_errors disetel 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);     // Log error sebagai gantinya
    ini_set('error_log', '/path/to/error.log');
}

// Di rute atau controller Anda
// Gunakan Flight::halt() untuk respons error yang terkendali
Flight::halt(403, 'Access denied');

Sanitasi Input

Jangan pernah percaya input pengguna. Sanitasi itu menggunakan filter_var sebelum diproses untuk mencegah data berbahaya menyusup masuk.


// Mari kita asumsikan 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);

Penyimpanan Hash Kata Sandi

Simpan kata sandi dengan aman dan verifikasi dengan aman menggunakan fungsi bawaan PHP seperti password_hash dan password_verify. Kata sandi tidak boleh disimpan dalam teks biasa, juga tidak boleh dienkripsi dengan metode yang dapat dibalik. Hashing memastikan bahwa bahkan jika database Anda dikompromikan, kata sandi sebenarnya tetap terlindungi.

$password = Flight::request()->data->password;
// Hash kata sandi saat menyimpan (misalnya, selama pendaftaran)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);

// Verifikasi kata sandi (misalnya, selama login)
if (password_verify($password, $stored_hash)) {
    // Kata sandi cocok
}

Pembatasan Laju

Lindungi dari serangan brute force atau serangan denial-of-service dengan membatasi laju permintaan menggunakan cache.

// Dengan asumsi Anda memiliki flightphp/cache yang diinstal 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, 'Too many requests');
    }

    $cache->set($key, $attempts + 1, 60); // Reset setelah 60 detik
});

Lihat Juga

Pemecahan Masalah

Changelog

Learn/routing

Routing

Gambaran Umum

Routing di Flight PHP memetakan pola URL ke fungsi callback atau metode kelas, memungkinkan penanganan permintaan yang cepat dan sederhana. Ini dirancang untuk overhead minimal, penggunaan yang ramah pemula, dan kemampuan ekstensi tanpa ketergantungan 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 spesifik, baik melalui fungsi, metode kelas, atau aksi pengontrol. Sistem routing Flight fleksibel, mendukung pola dasar, parameter bernama, ekspresi reguler, dan fitur lanjutan seperti injeksi dependensi dan routing sumber daya. Pendekatan ini menjaga kode Anda tetap terorganisir dan mudah dipelihara, sambil tetap cepat dan sederhana untuk pemula serta dapat diekstensikan untuk pengguna lanjutan.

Catatan: Ingin memahami lebih lanjut tentang routing? Lihat halaman "why a 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 yang didefinisikan. Rute pertama yang cocok dengan permintaan akan dipanggil.

Menggunakan Fungsi sebagai Callback

Callback bisa berupa objek apa pun yang dapat dipanggil. Jadi Anda bisa menggunakan fungsi biasa:

function hello() {
    echo 'hello world!';
}

Flight::route('/', 'hello');

Menggunakan Kelas dan Metode sebagai Pengontrol

Anda juga bisa menggunakan metode (statis atau tidak) dari kelas:

class GreetingController {
    public function hello() {
        echo 'hello world!';
    }
}

Flight::route('/', [ 'GreetingController','hello' ]);
// or
Flight::route('/', [ GreetingController::class, 'hello' ]); // preferred method
// or
Flight::route('/', [ 'GreetingController::hello' ]);
// or 
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 pengontrol dipanggil dalam kerangka kerja, kelas flight\Engine selalu diinjeksi kecuali Anda menentukannya melalui container injeksi dependensi

Routing Spesifik Metode

Secara default, pola rute dicocokkan dengan semua metode permintaan. Anda bisa merespons metode spesifik dengan menempatkan pengenal sebelum URL.

Flight::route('GET /', function () {
  echo 'I received a GET request.';
});

Flight::route('POST /', function () {
  echo 'I received a POST request.';
});

// You cannot use Flight::get() for routes as that is a method 
//    to get variables, not create a route.
Flight::post('/', function() { /* code */ });
Flight::patch('/', function() { /* code */ });
Flight::put('/', function() { /* code */ });
Flight::delete('/', function() { /* code */ });

Anda juga bisa memetakan beberapa metode ke satu callback dengan menggunakan pemisah |:

Flight::route('GET|POST /', function () {
  echo 'I received either a GET or a POST request.';
});

Penanganan Khusus untuk Permintaan HEAD dan OPTIONS

Flight menyediakan penanganan bawaan untuk permintaan HTTP HEAD dan OPTIONS:

Permintaan HEAD

Flight::route('GET /info', function() {
    echo 'This is some info!';
});
// A HEAD request to /info will return the same headers, but no body.

Permintaan OPTIONS

Permintaan OPTIONS ditangani secara otomatis oleh Flight untuk rute apa pun yang didefinisikan.

// For a route defined as:
Flight::route('GET|POST /users', function() { /* ... */ });

// An OPTIONS request to /users will respond with:
//
// Status: 204 No Content
// Allow: GET, POST, HEAD, OPTIONS

Menggunakan Objek Router

Selain itu, Anda bisa mengambil objek Router yang memiliki beberapa metode pembantu untuk digunakan:


$router = Flight::router();

// maps all methods just like Flight::route()
$router->map('/', function() {
    echo 'hello world!';
});

// GET request
$router->get('/users', function() {
    echo 'users';
});
$router->post('/users',             function() { /* code */});
$router->put('/users/update/@id',   function() { /* code */});
$router->delete('/users/@id',       function() { /* code */});
$router->patch('/users/@id',        function() { /* code */});

Ekspresi Regulasi (Regex)

Anda bisa menggunakan ekspresi reguler di rute Anda:

Flight::route('/user/[0-9]+', function () {
  // This will match /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 bisa menentukan parameter bernama di rute Anda yang akan diteruskan ke fungsi callback Anda. Ini lebih untuk keterbacaan rute daripada hal lain. Lihat bagian di bawah tentang peringatan penting.

Flight::route('/@name/@id', function (string $name, string $id) {
  echo "hello, $name ($id)!";
});

Anda juga bisa menyertakan ekspresi reguler dengan parameter bernama Anda dengan menggunakan pemisah ::

Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
  // This will match /bob/123
  // But will not match /bob/12345
});

Catatan: Mencocokkan grup regex () dengan parameter posisional tidak didukung. Contoh: :'\(

Peringatan Penting

Meskipun dalam contoh di atas, tampaknya @name langsung terkait dengan variabel $name, sebenarnya bukan. Urutan parameter dalam fungsi callback yang menentukan apa yang diteruskan kepadanya. Jika Anda menukar urutan parameter dalam fungsi callback, variabel juga akan bertukar. Berikut contohnya:

Flight::route('/@name/@id', function (string $id, string $name) {
  echo "hello, $name ($id)!";
});

Dan jika Anda mengunjungi URL berikut: /bob/123, outputnya akan menjadi hello, 123 (bob)!. Please be careful ketika Anda menyiapkan rute dan fungsi callback Anda!

Parameter Opsional

Anda bisa menentukan parameter bernama yang opsional untuk pencocokan dengan membungkus segmen dalam tanda kurung.

Flight::route(
  '/blog(/@year(/@month(/@day)))',
  function(?string $year, ?string $month, ?string $day) {
    // This will match the following URLS:
    // /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 individu. Jika Anda ingin mencocokkan beberapa segmen, Anda bisa menggunakan wildcard *.

Flight::route('/blog/*', function () {
  // This will match /blog/2000/02/01
});

Untuk merutekan semua permintaan ke satu callback, Anda bisa lakukan:

Flight::route('*', function () {
  // Do something
});

Penanganan 404 Tidak Ditemukan

Secara default, jika URL tidak ditemukan, Flight akan mengirim respons HTTP 404 Not Found yang sangat sederhana dan polos. Jika Anda ingin respons 404 yang lebih disesuaikan, Anda bisa memetakan metode notFound sendiri:

Flight::map('notFound', function() {
    $url = Flight::request()->url;

    // You could also use Flight::render() with a custom template.
    $output = <<<HTML
        <h1>My Custom 404 Not Found</h1>
        <h3>The page you have requested {$url} could not be found.</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 mengirim respons HTTP 405 Method Not Allowed yang sangat sederhana dan polos (Contoh: Method Not Allowed. Allowed Methods are: GET, POST). Ini juga akan menyertakan header Allow dengan metode yang diizinkan untuk URL tersebut.

Jika Anda ingin respons 405 yang lebih disesuaikan, Anda bisa memetakan metode methodNotFound sendiri:

use flight\net\Route;

Flight::map('methodNotFound', function(Route $route) {
    $url = Flight::request()->url;
    $methods = implode(', ', $route->methods);

    // You could also use Flight::render() with a custom template.
    $output = <<<HTML
        <h1>My Custom 405 Method Not Allowed</h1>
        <h3>The method you have requested for {$url} is not allowed.</h3>
        <p>Allowed Methods are: {$methods}</p>
        HTML;

    $this->response()
        ->clearBody()
        ->status(405)
        ->setHeader('Allow', $methods)
        ->write($output)
        ->send();
});

Penggunaan Lanjutan

Injeksi Dependensi di Rute

Jika Anda ingin menggunakan injeksi dependensi melalui container (PSR-11, PHP-DI, Dice, dll), satu-satunya jenis rute di mana itu tersedia adalah dengan membuat objek secara langsung sendiri dan menggunakan container untuk membuat objek Anda atau Anda bisa menggunakan string untuk mendefinisikan kelas dan metode yang akan dipanggil. Anda bisa pergi ke halaman Dependency Injection untuk informasi lebih lanjut.

Berikut contoh cepat:


use flight\database\PdoWrapper;

// Greeting.php
class Greeting
{
    protected PdoWrapper $pdoWrapper;
    public function __construct(PdoWrapper $pdoWrapper) {
        $this->pdoWrapper = $pdoWrapper;
    }

    public function hello(int $id) {
        // do something with $this->pdoWrapper
        $name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
        echo "Hello, world! My name is {$name}!";
    }
}

// index.php

// Setup the container with whatever params you need
// See the Dependency Injection page for more information on PSR-11
$dice = new \Dice\Dice();

// Don't forget to reassign the variable with '$dice = '!!!!!
$dice = $dice->addRule('flight\database\PdoWrapper', [
    'shared' => true,
    'constructParams' => [ 
        'mysql:host=localhost;dbname=test', 
        'root',
        'password'
    ]
]);

// Register the container handler
Flight::registerContainerHandler(function($class, $params) use ($dice) {
    return $dice->create($class, $params);
});

// Routes like normal
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// or
Flight::route('/hello/@id', 'Greeting->hello');
// or
Flight::route('/hello/@id', 'Greeting::hello');

Flight::start();

Mengalihkan Eksekusi ke Rute Berikutnya

Deprecated Anda bisa mengalihkan eksekusi ke rute pencocokan berikutnya dengan mengembalikan true dari fungsi callback Anda.

Flight::route('/user/@name', function (string $name) {
  // Check some condition
  if ($name !== "Bob") {
    // Continue to next route
    return true;
  }
});

Flight::route('/user/*', function () {
  // This will get called
});

Sekarang disarankan untuk menggunakan middleware untuk menangani kasus penggunaan kompleks seperti ini.

Alias Rute

Dengan menetapkan alias ke rute, Anda bisa memanggil alias tersebut di aplikasi Anda secara dinamis untuk dihasilkan nanti di kode Anda (contoh: tautan di template HTML, atau menghasilkan URL redirect).

Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// or 
Flight::route('/users/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');

// later in code somewhere
class UserController {
    public function update() {

        // code to save user...
        $id = $user['id']; // 5 for example

        $redirectUrl = Flight::getUrl('user_view', [ 'id' => $id ]); // will return '/users/5'
        Flight::redirect($redirectUrl);
    }
}

Ini sangat membantu jika URL Anda berubah. Dalam contoh di atas, katakanlah bahwa users dipindahkan ke /admin/users/@id sebagai gantinya. Dengan aliasing di tempat untuk rute, Anda tidak lagi perlu mencari semua URL lama di kode Anda dan mengubahnya karena alias sekarang akan mengembalikan /admin/users/5 seperti dalam contoh di atas.

Alias rute masih berfungsi dalam grup juga:

Flight::group('/users', function() {
    Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
    // or
    Flight::route('/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
});

Memeriksa Informasi Rute

Jika Anda ingin memeriksa informasi rute pencocokan, ada 2 cara Anda bisa lakukan ini:

  1. Anda bisa menggunakan properti executedRoute pada objek Flight::router().
  2. Anda bisa meminta objek rute diteruskan ke callback Anda dengan meneruskan true sebagai 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;
  // Do something with $route
  // Array of HTTP methods matched against
  $route->methods;

  // Array of named parameters
  $route->params;

  // Matching regular expression
  $route->regex;

  // Contains the contents of any '*' used in the URL pattern
  $route->splat;

  // Shows the url path....if you really need it
  $route->pattern;

  // Shows what middleware is assigned to this
  $route->middleware;

  // Shows the alias assigned to this route
  $route->alias;
});

Catatan: Properti executedRoute hanya akan ditetapkan setelah rute dieksekusi. Jika Anda mencoba mengaksesnya sebelum rute dieksekusi, itu akan menjadi NULL. Anda juga bisa menggunakan executedRoute di middleware juga!

Meneruskan true ke definisi rute

Flight::route('/', function(\flight\net\Route $route) {
  // Array of HTTP methods matched against
  $route->methods;

  // Array of named parameters
  $route->params;

  // Matching regular expression
  $route->regex;

  // Contains the contents of any '*' used in the URL pattern
  $route->splat;

  // Shows the url path....if you really need it
  $route->pattern;

  // Shows what middleware is assigned to this
  $route->middleware;

  // Shows the alias assigned to this route
  $route->alias;
}, true);// <-- This true parameter is what makes that happen

Pengelompokan Rute dan Middleware

Mungkin ada saatnya Anda ingin mengelompokkan rute terkait bersama (seperti /api/v1). Anda bisa lakukan ini dengan menggunakan metode group:

Flight::group('/api/v1', function () {
  Flight::route('/users', function () {
    // Matches /api/v1/users
  });

  Flight::route('/posts', function () {
    // Matches /api/v1/posts
  });
});

Anda bahkan bisa menumpuk grup dari grup:

Flight::group('/api', function () {
  Flight::group('/v1', function () {
    // Flight::get() gets variables, it doesn't set a route! See object context below
    Flight::route('GET /users', function () {
      // Matches GET /api/v1/users
    });

    Flight::post('/posts', function () {
      // Matches POST /api/v1/posts
    });

    Flight::put('/posts/1', function () {
      // Matches PUT /api/v1/posts
    });
  });
  Flight::group('/v2', function () {

    // Flight::get() gets variables, it doesn't set a route! See object context below
    Flight::route('GET /users', function () {
      // Matches GET /api/v2/users
    });
  });
});

Pengelompokan dengan Konteks Objek

Anda masih bisa menggunakan pengelompokan rute dengan objek Engine dengan cara berikut:

$app = Flight::app();

$app->group('/api/v1', function (Router $router) {

  // user the $router variable
  $router->get('/users', function () {
    // Matches GET /api/v1/users
  });

  $router->post('/posts', function () {
    // Matches POST /api/v1/posts
  });
});

Catatan: Ini adalah metode yang disukai untuk mendefinisikan rute dan grup dengan objek $router.

Pengelompokan dengan Middleware

Anda juga bisa menetapkan middleware ke grup rute:

Flight::group('/api/v1', function () {
  Flight::route('/users', function () {
    // Matches /api/v1/users
  });
}, [ MyAuthMiddleware::class ]); // or [ new MyAuthMiddleware() ] if you want to use an instance

Lihat detail lebih lanjut di halaman group middleware.

Routing Sumber Daya

Anda bisa membuat set rute untuk sumber daya menggunakan metode resource. Ini akan membuat set rute untuk sumber daya yang mengikuti konvensi RESTful.

Untuk membuat sumber daya, lakukan hal berikut:

Flight::resource('/users', UsersController::class);

Dan yang akan terjadi di latar belakang adalah itu akan membuat 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 pengontrol Anda akan menggunakan 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 bisa melihat rute yang baru ditambahkan dengan runway dengan menjalankan php runway routes.

Menyesuaikan Rute Sumber Daya

Ada beberapa opsi untuk mengonfigurasi rute sumber daya.

Alias Dasar

Anda bisa mengonfigurasi aliasBase. Secara default alias adalah bagian terakhir dari URL yang ditentukan. Misalnya /users/ akan menghasilkan aliasBase dari users. Ketika rute ini dibuat, aliasnya adalah users.index, users.create, dll. Jika Anda ingin mengubah alias, tetapkan aliasBase ke nilai yang Anda inginkan.

Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);
Only dan Except

Anda juga bisa menentukan rute mana yang ingin Anda buat dengan menggunakan opsi only dan except.

// Whitelist only these methods and blacklist the rest
Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
// Blacklist only these methods and whitelist the rest
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);

Ini pada dasarnya opsi whitelisting dan blacklisting sehingga Anda bisa menentukan rute mana yang ingin Anda buat.

Middleware

Anda juga bisa 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 bisa melakukan streaming respons ke klien menggunakan stream() atau streamWithHeaders(). Ini berguna untuk mengirim file besar, proses jangka panjang, atau menghasilkan respons besar. Streaming rute ditangani sedikit berbeda daripada rute biasa.

Catatan: Respons streaming hanya tersedia jika Anda memiliki flight.v2.output_buffering yang diatur ke false.

Stream dengan Header Manual

Anda bisa melakukan streaming respons ke klien dengan menggunakan metode stream() pada rute. Jika Anda melakukan ini, Anda harus menetapkan semua header secara manual sebelum Anda mengoutput apa pun ke klien. Ini dilakukan dengan fungsi php header() atau metode Flight::response()->setRealHeader().

Flight::route('/@filename', function($filename) {

    $response = Flight::response();

    // obviously you would sanitize the path and whatnot.
    $fileNameSafe = basename($filename);

    // If you have additional headers to set here after the route has executed
    // you must define them before anything is echoed out.
    // They must all be a raw call to the header() function or 
    // a call to Flight::response()->setRealHeader()
    header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
    // or
    $response->setRealHeader('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');

    $filePath = '/some/path/to/files/'.$fileNameSafe;

    if (!is_readable($filePath)) {
        Flight::halt(404, 'File not found');
    }

    // manually set the content length if you'd like
    header('Content-Length: '.filesize($filePath));
    // or
    $response->setRealHeader('Content-Length: '.filesize($filePath));

    // Stream the file to the client as it's read
    readfile($filePath);

// This is the magic line here
})->stream();

Stream dengan Header

Anda juga bisa menggunakan metode streamWithHeaders() untuk menetapkan header sebelum Anda mulai streaming.

Flight::route('/stream-users', function() {

    // you can add any additional headers you want here
    // you just must use header() or Flight::response()->setRealHeader()

    // however you pull your data, just as an example...
    $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 ',';
        }

        // This is required to send the data to the client
        ob_flush();
    }
    echo '}';

// This is how you'll set the headers before you start streaming.
})->streamWithHeaders([
    'Content-Type' => 'application/json',
    'Content-Disposition' => 'attachment; filename="users.json"',
    // optional status code, defaults to 200
    'status' => 200
]);

Lihat Juga

Pemecahan Masalah

404 Tidak Ditemukan atau Perilaku Rute Tak Terduga

Jika Anda melihat kesalahan 404 Tidak Ditemukan (tapi Anda bersumpah dengan hidup Anda bahwa itu benar-benar ada dan bukan kesalahan ketik) ini sebenarnya bisa menjadi masalah dengan Anda mengembalikan nilai di endpoint rute Anda daripada hanya mencetaknya. Alasan untuk ini disengaja tapi bisa menyelinap pada beberapa pengembang.

Flight::route('/hello', function(){
    // This might cause a 404 Not Found error
    return 'Hello World';
});

// What you probably want
Flight::route('/hello', function(){
    echo 'Hello World';
});

Alasan untuk ini adalah karena mekanisme khusus yang dibangun ke dalam router yang menangani output return sebagai sinyal untuk "pergi ke rute berikutnya". Anda bisa melihat perilaku yang didokumentasikan di bagian Routing.

Changelog

Learn/learn

Pelajari Tentang Flight

Flight adalah framework PHP yang cepat, sederhana, dan dapat diperluas. Ini sangat serbaguna dan dapat digunakan untuk membangun berbagai jenis aplikasi web. Ini dibangun dengan prinsip kesederhanaan dan ditulis dengan cara yang mudah dipahami dan digunakan.

Catatan: Anda akan melihat contoh yang menggunakan Flight:: sebagai variabel statis dan beberapa yang menggunakan objek Engine $app->. Keduanya dapat digunakan secara bergantian. $app dan $this->app di controller/middleware adalah pendekatan yang direkomendasikan oleh tim Flight.

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 di aplikasi Anda.

Autoloading

Pelajari cara memuat otomatis kelas Anda sendiri di aplikasi Anda.

Requests

Pelajari cara menangani permintaan dan respons di aplikasi Anda.

Responses

Pelajari cara mengirim respons ke pengguna Anda.

HTML Templates

Pelajari cara menggunakan engine tampilan bawaan untuk merender template HTML Anda.

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 hook event ke metode Anda dan metode framework internal.

Dependency Injection Container (DIC)

Pelajari cara menggunakan wadah injeksi dependensi (DIC) untuk mengelola dependensi aplikasi Anda.

Kelas Utilitas

Collections

Koleksi digunakan untuk menyimpan data dan dapat diakses sebagai array atau objek untuk kemudahan penggunaan.

JSON Wrapper

Ini memiliki beberapa fungsi sederhana untuk membuat pengkodean dan dekode JSON Anda konsisten.

PDO Wrapper

PDO terkadang bisa menimbulkan lebih banyak masalah daripada yang diperlukan. Kelas wrapper sederhana ini dapat membuat interaksi dengan database Anda jauh lebih mudah.

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 harus menggunakan framework. Ini ide bagus untuk memahami manfaat menggunakan framework sebelum Anda mulai menggunakannya.

Selain itu, tutorial yang sangat baik telah dibuat oleh @lubiana. Meskipun tidak membahas secara mendalam tentang Flight secara khusus, panduan ini akan membantu Anda memahami beberapa konsep utama seputar framework dan mengapa mereka bermanfaat untuk digunakan. Anda dapat menemukan tutorial di sini.

Flight Compared to Other Frameworks

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 kode Flight Anda agar menjadi kokoh.

AI & Developer Experience

Pelajari bagaimana Flight bekerja dengan alat AI dan alur kerja pengembang modern untuk membantu Anda mengkode lebih cepat dan lebih cerdas.

Migrating v2 -> v3

Kompatibilitas mundur sebagian besar telah dipertahankan, tetapi ada beberapa perubahan yang harus Anda ketahui saat bermigrasi dari v2 ke v3.

Learn/unit_testing

Pengujian Unit

Gambaran Umum

Pengujian unit di Flight membantu Anda memastikan aplikasi Anda berperilaku sesuai harapan, menangkap bug lebih awal, dan membuat kode dasar Anda lebih mudah dipelihara. Flight dirancang untuk bekerja dengan lancar dengan PHPUnit, framework pengujian PHP paling populer.

Pemahaman

Pengujian unit memeriksa perilaku potongan kecil aplikasi Anda (seperti controller atau service) secara terisolasi. Di Flight, ini berarti menguji bagaimana rute, controller, dan logika Anda merespons input yang berbeda—tanpa bergantung pada status global atau layanan eksternal nyata.

Prinsip kunci:

Penggunaan Dasar

Menyiapkan PHPUnit

  1. Instal PHPUnit dengan Composer:
    composer require --dev phpunit/phpunit
  2. Buat direktori tests di root proyek Anda.
  3. Tambahkan skrip pengujian ke composer.json Anda:
    "scripts": {
       "test": "phpunit --configuration phpunit.xml"
    }
  4. 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 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 controller 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:

Menggunakan Injeksi Dependensi untuk Controller yang Dapat Diuji

Injeksi dependensi (seperti database atau mailer) ke dalam controller Anda untuk membuatnya mudah dimock di pengujian:

use flight\database\PdoWrapper;

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\PdoWrapper::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

Lihat Juga

Pemecahan Masalah

Changelog

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

Kekurangan dibandingkan Flight

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

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 sedikit 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 dapat menggunakan notasi array atau objek.

Anda dapat 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 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 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 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;

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 dapat 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 // param terakhir mengaktifkan APM
]);

Setelah menjalankan query, Anda dapat mencatatnya secara manual tetapi APM akan mencatatnya secara otomatis jika diaktifkan:

Flight::db()->logQueries();

Ini akan memicu sebuah 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 tunggal
    $user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);

    // Dapatkan satu nilai tunggal
    $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

Pemecahan Masalah

Changelog

Learn/dependency_injection_container

Wadah Injeksi Ketergantungan

Gambaran Umum

Wadah Injeksi Ketergantungan (DIC) adalah peningkatan kuat yang memungkinkan Anda untuk mengelola ketergantungan aplikasi Anda.

Pemahaman

Injeksi Ketergantungan (DI) adalah konsep kunci dalam framework PHP modern dan digunakan untuk mengelola instansiasi dan konfigurasi objek. Beberapa contoh pustaka DIC adalah: flightphp/container, Dice, Pimple, PHP-DI, dan league/container.

Sebuah DIC adalah cara mewah untuk memungkinkan Anda membuat dan mengelola kelas Anda di lokasi terpusat. Ini berguna ketika Anda perlu meneruskan objek yang sama ke beberapa kelas (seperti controller atau middleware Anda misalnya).

Penggunaan Dasar

Cara lama melakukan hal-hal mungkin terlihat seperti ini:


require 'vendor/autoload.php';

// class to manage users from the 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());
    }
}

// in your routes.php file

$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');

$UserController = new UserController($db);
Flight::route('/user/@id', [ $UserController, 'view' ]);
// other UserController routes...

Flight::start();

Anda dapat melihat dari kode di atas bahwa kami membuat objek PDO baru dan meneruskannya ke kelas UserController kami. Ini baik untuk aplikasi kecil, tetapi saat aplikasi Anda berkembang, Anda akan menemukan bahwa Anda membuat atau meneruskan objek PDO yang sama di beberapa tempat. Inilah di mana DIC sangat berguna.

Berikut adalah contoh yang sama menggunakan DIC (menggunakan Dice):


require 'vendor/autoload.php';

// same class as above. Nothing changed
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());
    }
}

// create a new container
$container = new \Dice\Dice;

// add a rule to tell the container how to create a PDO object
// don't forget to reassign it to itself like below!
$container = $container->addRule('PDO', [
    // shared means that the same object will be returned each time
    'shared' => true,
    'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);

// This registers the container handler so Flight knows to use it.
Flight::registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

// now we can use the container to create our UserController
Flight::route('/user/@id', [ UserController::class, 'view' ]);

Flight::start();

Saya yakin Anda mungkin berpikir bahwa ada banyak kode tambahan yang ditambahkan ke contoh ini. Sihirnya muncul ketika Anda memiliki controller lain yang membutuhkan objek PDO.


// If all your controllers have a constructor that needs a PDO object
// each of the routes below will automatically have it injected!!!
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 memanfaatkan DIC adalah bahwa pengujian unit menjadi jauh lebih mudah. Anda bisa membuat objek mock dan meneruskannya ke kelas Anda. Ini adalah manfaat besar ketika Anda menulis tes untuk aplikasi Anda!

Membuat Penangan DIC Terpusat

Anda dapat membuat penangan DIC terpusat di file layanan Anda dengan memperluas aplikasi Anda. Berikut adalah contoh:

// services.php

// create a new container
$container = new \Dice\Dice;
// don't forget to reassign it to itself like below!
$container = $container->addRule('PDO', [
    // shared means that the same object will be returned each time
    'shared' => true,
    'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);

// now we can create a mappable method to create any object. 
Flight::map('make', function($class, $params = []) use ($container) {
    return $container->create($class, $params);
});

// This registers the container handler so Flight knows to use it for controllers/middleware
Flight::registerContainerHandler(function($class, $params) {
    Flight::make($class, $params);
});

// lets say we have the following sample class that takes a PDO object in the constructor
class EmailCron {
    protected PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function send() {
        // code that sends an email
    }
}

// And finally you can create objects using dependency injection
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();

flightphp/container

Flight memiliki plugin yang menyediakan wadah sederhana yang sesuai dengan PSR-11 yang dapat Anda gunakan untuk menangani injeksi ketergantungan Anda. Berikut adalah contoh cepat tentang cara menggunakannya:


// index.php for example
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);
    // will output this correctly!
  }
}

Flight::route('GET /', [TestController::class, 'index']);

Flight::start();

Penggunaan Lanjutan flightphp/container

Anda juga dapat menyelesaikan ketergantungan secara rekursif. Berikut adalah contoh:

<?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 {
    // Implementation ...
    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 wadah kustom yang ingin Anda gunakan yang bukan PSR-11 (Dice). Lihat bagian penggunaan dasar untuk cara melakukannya.

Selain itu, ada beberapa default yang membantu yang akan memudahkan hidup Anda saat menggunakan Flight.

Instance Engine

Jika Anda menggunakan instance Engine di controller/middleware Anda, berikut cara Anda mengonfigurasinya:


// Somewhere in your bootstrap file
$engine = Flight::app();

$container = new \Dice\Dice;
$container = $container->addRule('*', [
    'substitutions' => [
        // This is where you pass in the instance
        Engine::class => $engine
    ]
]);

$engine->registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

// Now you can use the Engine instance in your controllers/middleware

class MyController {
    public function __construct(Engine $app) {
        $this->app = $app;
    }

    public function index() {
        $this->app->render('index');
    }
}

Menambahkan Kelas Lain

Jika Anda memiliki kelas lain yang ingin Anda tambahkan ke wadah, dengan Dice ini mudah karena mereka akan diselesaikan secara otomatis oleh wadah. Berikut adalah contoh:


$container = new \Dice\Dice;
// If you don't need to inject any dependencies into your classes
// you don't need to define anything!
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 wadah apa pun yang sesuai dengan PSR-11. Ini berarti bahwa Anda dapat menggunakan wadah apa pun yang mengimplementasikan antarmuka PSR-11. Berikut adalah contoh menggunakan wadah PSR-11 League:


require 'vendor/autoload.php';

// same UserController class as above

$container = new \League\Container\Container();
$container->add(UserController::class)->addArgument(PdoWrapper::class);
$container->add(PdoWrapper::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 verbose daripada contoh Dice sebelumnya, itu masih mendapatkan pekerjaan selesai dengan manfaat yang sama!

Lihat Juga

Pemecahan Masalah

Changelog

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 perilaku after() 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\Engine ke 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:

  1. Anda dapat mengembalikan false dari fungsi middleware dan Flight akan secara otomatis mengembalikan kesalahan 403 Forbidden, tapi tidak ada kustomisasi.
  2. Anda dapat mengarahkan pengguna ke halaman login menggunakan Flight::redirect().
  3. 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

Pemecahan Masalah

Changelog

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 map dan register tidak dapat disaring karena mereka dipanggil secara langsung dan tidak dipanggil secara dinamis. Lihat Memperluas Flight untuk informasi lebih lanjut.

Lihat Juga

Pemecahan Masalah

Log Perubahan

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:

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 mengembalikan null. Jika tidak ada header Accept yang didefinisikan, metode akan mengembalikan jenis pertama dalam array $availableTypes.

Lihat Juga

Pemecahan Masalah

Changelog

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:

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:

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:

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/json dengan respons. Ini juga akan menggunakan flag JSON_THROW_ON_ERROR dan JSON_UNESCAPED_SLASHES saat 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 menggunakan exit atau return setelah memanggil Flight::stop() untuk mencegah eksekusi lebih lanjut, tetapi umumnya disarankan untuk menggunakan Flight::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

Pemecahan Masalah

Changelog

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:

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:

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

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

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!.

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?

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();

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();

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!";
});

Praktik Terbaik untuk Flight

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

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

Lihat Juga

Pemecahan Masalah

Changelog

Learn/templates

Tampilan HTML dan Template

Gambaran Umum

Flight menyediakan fungsionalitas templating HTML dasar secara default. Templating adalah cara yang sangat efektif bagi Anda untuk memisahkan logika aplikasi dari lapisan presentasi Anda.

Pemahaman

Ketika Anda membangun aplikasi, kemungkinan besar Anda akan memiliki HTML yang ingin dikirimkan kembali ke pengguna akhir. PHP dengan sendirinya 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 jauh lebih mudah untuk memisahkan dan menguji unit kode Anda. Anda akan berterima kasih kepada kami jika Anda menggunakan template!

Penggunaan Dasar

Flight memungkinkan Anda untuk menukar engine tampilan default hanya dengan mendaftarkan kelas tampilan Anda sendiri. Gulir ke bawah untuk melihat contoh cara menggunakan Smarty, Latte, Blade, dan lainnya!

Latte

direkomendasikan

Berikut adalah cara Anda menggunakan engine template Latte untuk tampilan Anda.

Instalasi

composer require latte/latte

Konfigurasi Dasar

Ide utamanya adalah Anda menimpa metode render untuk menggunakan Latte alih-alih renderer PHP default.

// overwrite the render method to use latte instead of the default PHP renderer
Flight::map('render', function(string $template, array $data, ?string $block): void {
    $latte = new Latte\Engine;

    // Where latte specifically stores its cache
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    $finalPath = Flight::get('flight.views.path') . $template;

    $latte->render($finalPath, $data, $block);
});

Menggunakan Latte di Flight

Sekarang setelah 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
    ]);
});

Ketika Anda mengunjungi /Bob di browser Anda, 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 tata letak ditunjukkan di bagian awesome plugins dari dokumentasi ini.

Anda dapat mempelajari lebih lanjut tentang kemampuan penuh Latte termasuk terjemahan dan kemampuan bahasa dengan membaca dokumentasi resmi.

Engine Tampilan Built-in

deprecated

Catatan: Meskipun ini masih 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 seluruh tampilan Anda. Jadi Anda dapat dengan mudah melakukan:

Flight::render('hello');

Perhatikan bahwa ketika menentukan nama template di metode render, Anda dapat meninggalkan 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');

Tata Letak

Umum bagi situs web untuk memiliki satu file template tata letak dengan konten yang saling berganti. Untuk merender konten yang akan digunakan dalam tata letak, 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 yang disimpan bernama headerContent dan bodyContent. Anda kemudian dapat merender tata letak 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 adalah cara Anda menggunakan engine template Smarty untuk tampilan Anda:

// Load Smarty library
require './Smarty/libs/Smarty.class.php';

// Register Smarty as the view class
// Also pass a callback function to configure Smarty on load
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
  $smarty->setTemplateDir('./templates/');
  $smarty->setCompileDir('./templates_c/');
  $smarty->setConfigDir('./config/');
  $smarty->setCacheDir('./cache/');
});

// Assign template data
Flight::view()->assign('name', 'Bob');

// Display the 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 adalah cara Anda menggunakan engine 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
// Load BladeOne library
use eftec\bladeone\BladeOne;

// Register BladeOne as the view class
// Also pass a callback function to configure BladeOne on load
Flight::register('view', BladeOne::class, [], function (BladeOne $blade) {
  $views = __DIR__ . '/../views';
  $cache = __DIR__ . '/../cache';

  $blade->setPath($views);
  $blade->setCompiledPath($cache);
});

// Assign template data
Flight::view()->share('name', 'Bob');

// Display the 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

Pemecahan Masalah

Changelog

Learn/collections

Koleksi

Gambaran Umum

Kelas Collection di Flight adalah utilitas yang berguna untuk mengelola kumpulan data. Ini memungkinkan Anda mengakses dan memanipulasi data menggunakan notasi array maupun objek, membuat kode Anda lebih bersih dan fleksibel.

Pemahaman

Collection pada dasarnya adalah pembungkus sekitar array, tetapi dengan beberapa kemampuan tambahan. Anda dapat menggunakannya seperti array, mengulanginya, menghitung item-nya, dan bahkan mengakses item seolah-olah itu adalah properti objek. Ini sangat berguna ketika Anda ingin meneruskan data terstruktur di aplikasi Anda, atau ketika Anda ingin membuat kode Anda sedikit lebih mudah dibaca.

Koleksi mengimplementasikan beberapa antarmuka PHP:

Penggunaan Dasar

Membuat Koleksi

Anda dapat membuat koleksi dengan hanya meneruskan 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']; // Output: Flight

// Notasi objek
echo $collection->version; // Output: 3

Jika Anda mencoba mengakses kunci yang tidak ada, Anda akan mendapatkan null alih-alih kesalahan.

Mengatur Item

Anda juga dapat mengatur item menggunakan notasi yang sama:

// Notasi array
$collection['author'] = 'Mike Cao';

// Notasi objek
$collection->license = 'MIT';

Memeriksa dan Menghapus Item

Periksa apakah item ada:

if (isset($collection['name'])) {
  // Lakukan sesuatu
}

if (isset($collection->version)) {
  // Lakukan sesuatu
}

Hapus item:

unset($collection['author']);
unset($collection->license);

Mengulang Koleksi

Koleksi dapat diulang, sehingga Anda dapat menggunakannya dalam loop foreach:

foreach ($collection as $key => $value) {
  echo "$key: $value\n";
}

Menghitung Item

Anda dapat menghitung jumlah item dalam koleksi:

echo count($collection); // Output: 4

Mendapatkan Semua Kunci atau Data

Dapatkan semua kunci:

$keys = $collection->keys(); // ['name', 'version', 'features', 'license']

Dapatkan semua data sebagai array:

$data = $collection->getData();

Membersihkan Koleksi

Hapus semua item:

$collection->clear();

Serialisasi JSON

Koleksi dapat dengan mudah dikonversi ke JSON:

echo json_encode($collection);
// Output: {"name":"Flight","version":3,"features":["routing","views","extending"],"license":"MIT"}

Penggunaan Lanjutan

Anda dapat mengganti array data internal sepenuhnya jika diperlukan:

$collection->setData(['foo' => 'bar']);

Koleksi 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

Pemecahan Masalah

Changelog

Learn/flight_vs_fat_free

Flight vs Fat-Free

Apa itu Fat-Free?

Fat-Free (dikenal dengan sayang sebagai F3) adalah micro-framework PHP yang kuat namun mudah digunakan yang dirancang untuk membantu Anda membangun aplikasi web dinamis dan kokoh - dengan cepat!

Flight dibandingkan dengan Fat-Free dalam banyak hal dan mungkin sepupu terdekat dalam hal fitur dan kesederhanaan. Fat-Free memiliki banyak fitur yang tidak dimiliki Flight, tetapi ia juga memiliki banyak fitur yang dimiliki Flight. Fat-Free mulai menunjukkan usianya dan tidak sepopuler dulu.

Pembaruan menjadi kurang sering dan komunitas tidak seaktif dulu. Kode sederhana, tetapi terkadang kurangnya disiplin sintaks dapat membuatnya sulit dibaca dan dipahami. Ia bekerja untuk PHP 8.3, tetapi kode itu sendiri masih terlihat seperti hidup di PHP 5.3.

Kelebihan dibandingkan Flight

Kekurangan dibandingkan Flight

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:

  1. 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.
  2. 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

Pemecahan Masalah

Changelog

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:

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

Pemecahan Masalah

Changelog

Learn/flight_vs_slim

Flight vs Slim

Apa itu Slim?

Slim adalah kerangka kerja mikro PHP yang membantu Anda menulis aplikasi web dan API sederhana namun kuat dengan cepat.

Banyak inspirasi untuk beberapa fitur v3 dari Flight sebenarnya berasal dari Slim. Pengelompokan rute, dan eksekusi middleware dalam urutan tertentu adalah dua fitur yang terinspirasi dari Slim. Slim v3 dirilis dengan fokus pada kesederhanaan, tetapi ada ulasan campuran mengenai v4.

Kelebihan dibandingkan Flight

Kekurangan dibandingkan Flight

Learn/autoloading

Autoloading

Gambaran Umum

Autoloading adalah konsep di PHP di mana Anda menentukan direktori atau direktori untuk memuat kelas dari. Ini jauh lebih bermanfaat daripada menggunakan require atau include untuk memuat kelas. Ini juga merupakan persyaratan untuk menggunakan paket Composer.

Pemahaman

Secara default, kelas Flight apa pun dimuat secara otomatis untuk Anda berkat composer. Namun, jika Anda ingin memuat kelas Anda sendiri secara otomatis, Anda dapat menggunakan metode Flight::path() untuk menentukan direktori untuk memuat kelas dari.

Menggunakan autoloader dapat membantu menyederhanakan kode Anda secara signifikan. Alih-alih memiliki file yang dimulai dengan berbagai pernyataan include atau require di bagian atas untuk menangkap semua kelas yang digunakan dalam file tersebut, Anda dapat secara dinamis memanggil kelas Anda dan mereka akan disertakan secara otomatis.

Penggunaan Dasar

Misalkan 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 (semua huruf kapital dengan sengaja untuk contoh nanti)
│   └── views
└── public
    └── css
    └── js
    └── index.php

Anda mungkin telah memperhatikan bahwa ini adalah struktur file yang sama dengan situs dokumentasi 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 diperlukan namespacing

// Semua kelas yang dimuat secara otomatis disarankan menggunakan Pascal Case (setiap kata dikapitalisasi, tanpa spasi)
class MyController {

    public function index() {
        // lakukan sesuatu
    }
}

Namespaces

Jika Anda memiliki namespace, sebenarnya sangat mudah untuk mengimplementasikannya. 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 tampilan controller Anda. Lihat contoh di bawah ini, tetapi perhatikan komentar untuk informasi penting.

/**
 * app/controllers/MyController.php
 */

// namespace diperlukan
// namespace sama dengan struktur direktori
// namespace harus mengikuti case yang sama dengan struktur direktori
// namespace dan direktori tidak boleh memiliki underscore (kecuali Loader::setV2ClassLoading(false) diatur)
namespace app\controllers;

// Semua kelas yang dimuat secara otomatis disarankan menggunakan Pascal Case (setiap kata dikapitalisasi, tanpa spasi)
// Mulai dari 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 memuat kelas di direktori utils Anda secara otomatis, Anda akan melakukan hal yang sama secara dasar:


/**
 * app/UTILS/ArrayHelperUtil.php
 */

// namespace harus cocok dengan struktur direktori dan case (perhatikan direktori UTILS semua huruf kapital
//     seperti di pohon file di atas)
namespace app\UTILS;

class ArrayHelperUtil {

    public function changeArrayCase(array $array) {
        // lakukan sesuatu
    }
}

Underscores di Nama Kelas

Mulai dari 3.7.2, Anda dapat menggunakan Pascal_Snake_Case untuk nama kelas Anda dengan menjalankan Loader::setV2ClassLoading(false);. Ini akan memungkinkan Anda menggunakan underscore di nama kelas Anda. Ini tidak disarankan, tetapi tersedia untuk 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 diperlukan namespacing

class My_Controller {

    public function index() {
        // lakukan sesuatu
    }
}

Lihat Juga

Pemecahan Masalah

Kelas Tidak Ditemukan (autoloading tidak berfungsi)

Ada beberapa alasan mengapa ini bisa terjadi. Di bawah ini beberapa contoh tetapi pastikan Anda juga memeriksa bagian autoloading.

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 Salah

Jika Anda menggunakan namespace, maka namespace harus cocok dengan struktur direktori.

// ...code...

// jika MyController Anda berada di direktori app/controllers dan bernamespace
// ini tidak akan berfungsi.
Flight::route('/hello', 'MyController->hello');

// Anda perlu memilih salah satu opsi ini
Flight::route('/hello', 'app\controllers\MyController->hello');
// atau jika Anda memiliki pernyataan use di atas

use app\controllers\MyController;

Flight::route('/hello', [ MyController::class, 'hello' ]);
// juga bisa ditulis
Flight::route('/hello', MyController::class.'->hello');
// juga...
Flight::route('/hello', [ 'app\controllers\MyController', 'hello' ]);

path() tidak didefinisikan

Di aplikasi skeleton, ini didefinisikan di dalam file config.php, tetapi agar kelas Anda ditemukan, Anda perlu memastikan bahwa metode path() didefinisikan (mungkin ke root direktori Anda) sebelum Anda mencoba menggunakannya.

// Tambahkan path ke autoloader
Flight::path(__DIR__.'/../');

Changelog

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:

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

Pemecahan Masalah

Changelog

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 penting dan bagaimana menerapkannya secara praktis. Kami akan fokus pada pengujian perilaku—memastikan aplikasi Anda melakukan apa yang diharapkan, seperti mengirim email atau menyimpan catatan—bukan perhitungan sepele. Kami akan mulai dengan route handler sederhana dan maju ke controller yang lebih kompleks, menggabungkan dependency injection (DI) dan mocking layanan pihak ketiga.

Mengapa Pengujian Unit?

Pengujian unit memastikan kode Anda berperilaku seperti yang diharapkan, menangkap bug sebelum mencapai produksi. Ini sangat berharga di Flight, di mana routing ringan dan fleksibilitas dapat menyebabkan interaksi kompleks. Bagi pengembang solo atau tim, pengujian unit berfungsi sebagai jaring pengaman, mendokumentasikan perilaku yang diharapkan dan mencegah regresi saat Anda mengunjungi ulang kode nanti. Mereka juga meningkatkan desain: kode yang sulit diuji sering menandakan kelas yang terlalu kompleks atau terikat erat.

Tidak seperti 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 diakses dan bermakna.

Prinsip Panduan Umum

  1. Uji Perilaku, Bukan Implementasi: Fokus pada hasil (misalnya, "email dikirim" atau "catatan disimpan") daripada detail internal. Ini membuat pengujian tahan terhadap refactoring.
  2. Berhenti menggunakan Flight::: Metode statis Flight sangat nyaman, tapi membuat pengujian sulit. Anda harus terbiasa menggunakan variabel $app dari $app = Flight::app();. $app memiliki semua metode yang sama dengan Flight::. Anda masih bisa menggunakan $app->route() atau $this->app->json() di controller Anda dll. Anda juga harus menggunakan router Flight yang sebenarnya dengan $router = $app->router() dan kemudian Anda bisa menggunakan $router->get(), $router->post(), $router->group() dll. Lihat Routing.
  3. Jaga Pengujian Cepat: Pengujian cepat mendorong eksekusi yang sering. Hindari operasi lambat seperti panggilan database di pengujian unit. Jika Anda memiliki pengujian lambat, itu tanda Anda sedang menulis pengujian integrasi, bukan pengujian unit. Pengujian integrasi adalah ketika Anda benar-benar melibatkan database nyata, panggilan HTTP nyata, pengiriman email nyata dll. Mereka memiliki tempatnya, tapi mereka lambat dan bisa tidak stabil, artinya kadang gagal karena alasan yang tidak diketahui.
  4. Gunakan Nama Deskriptif: Nama pengujian harus dengan jelas menggambarkan perilaku yang diuji. Ini meningkatkan keterbacaan dan pemeliharaan.
  5. Hindari Globals Seperti Wabah: Minimalkan penggunaan $app->set() dan $app->get(), karena mereka bertindak seperti state global, memerlukan mock di setiap pengujian. Lebih suka DI atau container 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 bisa mock objek sesi di pengujian Anda. Jangan panggil $_SESSION secara langsung di kode Anda karena itu menyuntikkan variabel global ke kode Anda, membuatnya sulit diuji.
  6. Gunakan Dependency Injection: Suntik dependensi (misalnya, PDO, mailer) ke controller untuk mengisolasi logika dan menyederhanakan mocking. Jika Anda memiliki kelas dengan terlalu banyak dependensi, pertimbangkan untuk merestrukturisasinya menjadi kelas yang lebih kecil yang masing-masing memiliki tanggung jawab tunggal mengikuti prinsip SOLID.
  7. Mock Layanan Pihak Ketiga: Mock database, klien HTTP (cURL), atau layanan email untuk menghindari panggilan eksternal. Uji satu atau dua lapis dalam, tapi biarkan logika inti berjalan. Misalnya, jika aplikasi Anda mengirim pesan teks, Anda TIDAK ingin benar-benar mengirim pesan teks setiap kali menjalankan pengujian karena biaya itu akan bertambah (dan akan lebih lambat). Sebaliknya, mock layanan pesan teks dan hanya verifikasi bahwa kode Anda memanggil layanan pesan teks dengan parameter yang benar.
  8. Bidik Cakupan Tinggi, Bukan Kesempurnaan: Cakupan baris 100% bagus, tapi itu tidak benar-benar berarti bahwa semuanya di kode Anda diuji seperti yang seharusnya (silakan teliti branch/path coverage di PHPUnit). Prioritaskan perilaku kritis (misalnya, pendaftaran pengguna, respons API dan menangkap respons gagal).
  9. Gunakan Controller untuk Route: Di definisi route Anda, gunakan controller bukan closures. flight\Engine $app disuntikkan ke setiap controller melalui constructor secara default. Di pengujian, gunakan $app = new Flight\Engine() untuk menginisialisasi Flight dalam pengujian, suntikkan ke controller Anda, dan panggil metode secara langsung (misalnya, $controller->register()). Lihat Extending Flight dan Routing.
  10. Pilih gaya mocking dan patuhi itu: PHPUnit mendukung beberapa gaya mocking (misalnya, prophecy, mock built-in), atau Anda bisa menggunakan kelas anonim yang memiliki manfaat sendiri seperti code completion, rusak jika Anda mengubah definisi metode, dll. Hanya konsisten di seluruh pengujian Anda. Lihat PHPUnit Mock Objects.
  11. Gunakan visibilitas protected untuk metode/properti yang ingin Anda uji di subclass: Ini memungkinkan Anda untuk menimpa mereka 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 PHPUnit Getting Started untuk detail lebih lanjut.

  1. Di direktori proyek Anda, jalankan:

    composer require --dev phpunit/phpunit

    Ini menginstal PHPUnit terbaru sebagai dependensi pengembangan.

  2. Buat direktori tests di root proyek Anda untuk file pengujian.

  3. Tambahkan skrip pengujian ke composer.json untuk kenyamanan:

    // other composer.json content
    "scripts": {
       "test": "phpunit --configuration phpunit.xml"
    }
  4. Buat file phpunit.xml di root:

    <?xml version="1.0" encoding="UTF-8"?>
    <phpunit bootstrap="vendor/autoload.php">
       <testsuites>
           <testsuite name="Flight Tests">
               <directory>tests</directory>
           </testsuite>
       </testsuites>
    </phpunit>

Sekarang ketika pengujian Anda dibangun, Anda bisa menjalankan composer test untuk mengeksekusi pengujian.

Menguji Route Handler Sederhana

Mari mulai dengan route dasar yang memvalidasi input email pengguna. Kami akan menguji perilakunya: mengembalikan pesan sukses untuk email valid dan kesalahan untuk yang tidak valid. Untuk validasi email, kami 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 and SOLID Principles untuk lebih lanjut tentang struktur 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'; // Simulate POST data
        $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'; // Simulate POST data
        $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 Kunci:

Jalankan composer test untuk memverifikasi route berperilaku seperti 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 globals Flight (misalnya, Flight::set(), Flight::map(), Flight::register()) karena mereka bertindak seperti state global, memerlukan mock untuk setiap pengujian. Sebaliknya, gunakan container DI Flight, DICE, PHP-DI atau DI manual.

Mari gunakan flight\database\PdoWrapper daripada PDO mentah. Wrapper ini jauh lebih mudah untuk dimock dan diuji unit!

Berikut adalah controller yang menyimpan pengguna ke database dan mengirim email selamat datang:

use flight\database\PdoWrapper;

class UserController {
    protected $app;
    protected $db;
    protected $mailer;

    public function __construct(Engine $app, PdoWrapper $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)) {
            // adding the return here helps unit testing to stop execution
            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 Kunci:

Menguji Controller dengan Mocks

Sekarang, mari uji perilaku UserController: memvalidasi email, menyimpan ke database, dan mengirim email. Kami akan mock database dan mailer untuk mengisolasi controller.

// tests/UserControllerDICTest.php
use PHPUnit\Framework\TestCase;

class UserControllerDICTest extends TestCase {
    public function testValidEmailSavesAndSendsEmail() {

        // Sometimes mixing mocking styles is necessary
        // Here we use PHPUnit's built-in mock for PDOStatement
        $statementMock = $this->createMock(PDOStatement::class);
        $statementMock->method('execute')->willReturn(true);
        // Using an anonymous class to mock PdoWrapper
        $mockDb = new class($statementMock) extends PdoWrapper {
            protected $statementMock;
            public function __construct($statementMock) {
                $this->statementMock = $statementMock;
            }

            // When we mock it this way, we are not really making a database call.
            // We can further setup this to alter the PDOStatement mock to simulate failures, etc.
            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 PdoWrapper {
            // An empty constructor bypasses the parent constructor
            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';

        // Need to map jsonHalt to avoid exiting
        $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 Kunci:

Mocking Terlalu Banyak

Hati-hati jangan mock terlalu banyak kode Anda. Biarkan saya beri contoh di bawah tentang mengapa ini mungkin hal buruk menggunakan UserController kami. Kami akan ubah pemeriksaan itu menjadi metode yang disebut isEmailValid (menggunakan filter_var) dan penambahan baru lainnya menjadi metode terpisah yang disebut registerUser.

use flight\database\PdoWrapper;
use flight\Engine;

// UserControllerDICV2.php
class UserControllerDICV2 {
    protected $app;
    protected $db;
    protected $mailer;

    public function __construct(Engine $app, PdoWrapper $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)) {
            // adding the return here helps unit testing to stop execution
            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 dimock yang sebenarnya tidak menguji apa-apa:

use PHPUnit\Framework\TestCase;

class UserControllerTest extends TestCase {
    public function testValidEmailSavesAndSendsEmail() {
        $app = new Engine();
        $app->request()->data->email = 'test@example.com';
        // we are skipping the extra dependency injection here cause it's "easy"
        $controller = new class($app) extends UserControllerDICV2 {
            protected $app;
            // Bypass the deps in the construct
            public function __construct($app) {
                $this->app = $app;
            }

            // We'll just force this to be valid.
            protected function isEmailValid($email) {
                return true; // Always return true, bypassing real validation
            }

            // Bypass the actual DB and mailer calls
            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 kami memiliki pengujian unit dan mereka lulus! Tapi tunggu, bagaimana jika saya benar-benar mengubah cara kerja internal isEmailValid atau registerUser? Pengujian saya masih akan lulus karena saya telah mock semua fungsionalitas. Biarkan saya tunjukkan apa yang saya maksud.

// UserControllerDICV2.php
class UserControllerDICV2 {

    // ... other methods ...

    protected function isEmailValid($email) {
        // Changed logic
        $validEmail = filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
        // Now it should only have a specific domain
        $validDomain = strpos($email, '@example.com') !== false; 
        return $validEmail && $validDomain;
    }
}

Jika saya jalankan pengujian unit di atas, mereka masih lulus! Tapi karena saya tidak menguji perilaku (benar-benar membiarkan sebagian kode berjalan), saya berpotensi mengkode bug yang menunggu untuk terjadi di produksi. Pengujian harus dimodifikasi untuk memperhitungkan perilaku baru, dan juga kebalikan ketika perilaku bukan seperti yang kami harapkan.

Contoh Lengkap

Anda bisa menemukan contoh lengkap proyek Flight PHP dengan pengujian unit di GitHub: n0nag0n/flight-unit-tests-guide. Untuk pemahaman lebih dalam, lihat Unit Testing and SOLID Principles.

Kesalahan Umum

Skala dengan Pengujian Unit

Pengujian unit bersinar di proyek yang lebih besar atau saat mengunjungi ulang kode setelah berbulan-bulan. Mereka mendokumentasikan perilaku dan menangkap regresi, menghemat Anda dari belajar ulang aplikasi Anda. Bagi pengembang solo, uji jalur kritis (misalnya, pendaftaran pengguna, pemrosesan pembayaran). Bagi tim, pengujian memastikan perilaku konsisten di seluruh kontribusi. Lihat Why Frameworks? untuk lebih lanjut tentang manfaat menggunakan framework dan pengujian.

Sumbangkan 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 melalui pembuatan blog dasar menggunakan framework Flight PHP. Anda akan mengatur proyek, mendefinisikan rute, mengelola pos dengan JSON, dan merendernya dengan mesin templating Latte—semuanya menunjukkan kesederhanaan dan fleksibilitas Flight. Pada akhir panduan, Anda akan memiliki blog fungsional dengan halaman utama, halaman pos individu, dan formulir pembuatan.

Prasyarat

Langkah 1: Siapkan Proyek Anda

Mulailah dengan membuat direktori proyek baru dan menginstal Flight melalui Composer.

  1. Buat Direktori:

    mkdir flight-blog
    cd flight-blog
  2. Instal Flight:

    composer require flightphp/core
  3. Buat Direktori Publik: Flight menggunakan titik masuk tunggal (index.php). Buat folder public/ untuk itu:

    mkdir public
  4. index.php Dasar: Buat public/index.php dengan rute "hello world" yang sederhana:

    <?php
    require '../vendor/autoload.php';
    
    Flight::route('/', function () {
       echo 'Halo, Flight!';
    });
    
    Flight::start();
  5. Jalankan Server Bawaan: Uji pengaturan Anda dengan server pengembangan PHP:

    php -S localhost:8000 -t public/

    Kunjungi http://localhost:8000 untuk melihat "Halo, Flight!".

Langkah 2: Atur Struktur Proyek Anda

Untuk pengaturan yang bersih, struktur proyek Anda seperti ini:

flight-blog/
├── app/
│   ├── config/
│   └── views/
├── data/
├── public/
│   └── index.php
├── vendor/
└── composer.json

Langkah 3: Instal dan Konfigurasi Latte

Latte adalah mesin templating ringan yang terintegrasi dengan baik dengan Flight.

  1. Instal Latte:

    composer require latte/latte
  2. Konfigurasikan Latte di Flight: Perbarui public/index.php untuk 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' => 'Blog Saya']);
    });
    
    Flight::start();
  3. Buat Template Layout: Di app/views/layout.latte:

    <!DOCTYPE html>
    <html>
    <head>
    <title>{$title}</title>
    </head>
    <body>
    <header>
        <h1>Blog Saya</h1>
        <nav>
            <a href="/">Beranda</a> | 
            <a href="/create">Buat Pos</a>
        </nav>
    </header>
    <main>
        {block content}{/block}
    </main>
    <footer>
        <p>&copy; {date('Y')} Blog Flight</p>
    </footer>
    </body>
    </html>
  4. Buat Template 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:8000 untuk melihat halaman yang dirender.

  5. Buat File Data:

    Gunakan file JSON untuk mensimulasikan database untuk kesederhanaan.

    Di data/posts.json:

    [
       {
           "slug": "first-post",
           "title": "Pos Pertama Saya",
           "content": "Ini adalah pos blog pertama saya dengan Flight PHP!"
       }
    ]

Langkah 4: Definisikan Rute

Pisahkan rute Anda ke dalam file konfigurasi untuk organisasi yang lebih baik.

  1. Buat routes.php: Di app/config/routes.php:

    <?php
    Flight::route('/', function () {
       Flight::view()->render('home.latte', ['title' => 'Blog Saya']);
    });
    
    Flight::route('/post/@slug', function ($slug) {
       Flight::view()->render('post.latte', ['title' => 'Pos: ' . $slug, 'slug' => $slug]);
    });
    
    Flight::route('GET /create', function () {
       Flight::view()->render('create.latte', ['title' => 'Buat Pos']);
    });
  2. Perbarui index.php: Termasuk 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 Pos Blog

Tambahkan metode untuk memuat dan menyimpan pos.

  1. Tambahkan Metode Pos: Di index.php, tambahkan metode untuk memuat pos:

    Flight::map('posts', function () {
       $file = __DIR__ . '/../data/posts.json';
       return json_decode(file_get_contents($file), true);
    });
  2. Perbarui Rute: Modifikasi app/config/routes.php untuk menggunakan pos:

    <?php
    Flight::route('/', function () {
       $posts = Flight::posts();
       Flight::view()->render('home.latte', [
           'title' => 'Blog Saya',
           '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 Pos']);
    });

Langkah 6: Buat Template

Perbarui template Anda untuk menampilkan pos.

  1. Halaman Pos (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 Pos

Tangani pengiriman formulir untuk menambahkan pos baru.

  1. Formulir Buat (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 Pos</button>
        </form>
    {/block}
  2. 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('/');
    });
  3. Uji Coba:

    • Kunjungi http://localhost:8000/create.
    • Kirim pos baru (misalnya, “Pos Kedua” dengan beberapa konten).
    • Periksa halaman utama untuk melihatnya terdaftar.

Langkah 8: Perbaiki dengan Penanganan Kesalahan

Timpakan 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 tersebut tidak ada!</p>
{/block}

Langkah Selanjutnya

Kesimpulan

Anda telah membangun blog sederhana dengan Flight PHP! Panduan ini menunjukkan fitur inti seperti routing, templating dengan Latte, dan menangani pengiriman formulir—semuanya tetap ringan. Jelajahi dokumentasi Flight untuk fitur-fitur lebih lanjut untuk 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

Kerangka Kerja PHP Flight

Flight adalah kerangka kerja yang cepat, sederhana, dan dapat diperluas untuk PHP—dibuat untuk pengembang yang ingin menyelesaikan pekerjaan dengan cepat, tanpa keributan. Baik Anda membangun aplikasi web klasik, API yang sangat cepat, atau bereksperimen dengan alat-alat terkini yang didukung AI, jejak rendah dan desain langsung Flight membuatnya cocok sempurna. Flight dimaksudkan untuk ringan, tetapi juga dapat menangani kebutuhan arsitektur perusahaan.

Mengapa Memilih Flight?

Ikhtisar Video

Cukup sederhana, kan?
Pelajari lebih lanjut tentang Flight dalam dokumentasi!

Mulai Cepat

Untuk instalasi dasar 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. Anda sekarang dapat menjalankan file ini dengan php -S localhost:8000 dan kunjungi http://localhost:8000 di browser Anda untuk melihat output.

Aplikasi Skeleton/Boilerplate

Ada contoh aplikasi untuk membantu Anda memulai proyek dengan Flight. Ini memiliki tata letak terstruktur, konfigurasi dasar yang sudah disetel, dan menangani skrip composer langsung dari awal! Periksa flightphp/skeleton untuk proyek yang siap pakai, atau kunjungi halaman examples untuk inspirasi. Ingin melihat bagaimana AI cocok? Jelajahi contoh yang didukung AI.

Menginstal Aplikasi Skeleton

Cukup mudah!

# Buat proyek baru
composer create-project flightphp/skeleton my-project/
# Masuk ke direktori proyek baru Anda
cd my-project/
# Buka server pengembangan lokal untuk memulai segera!
composer start

Ini akan membuat struktur proyek, menyiapkan file yang Anda butuhkan, dan Anda siap berangkat!

Kinerja Tinggi

Flight adalah salah satu kerangka kerja PHP tercepat di luar sana. Inti ringannya berarti overhead lebih sedikit dan kecepatan lebih—sempurna untuk aplikasi tradisional dan proyek modern yang didukung AI. Anda dapat melihat semua benchmark di TechEmpower

Lihat benchmark di bawah dengan beberapa kerangka kerja 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 menanganinya AI? Temukan bagaimana Flight membuat bekerja dengan LLM pengkodean favorit Anda menjadi mudah!

Komunitas

Kami ada di Matrix Chat

Matrix

Dan Discord

Berkontribusi

Ada dua cara Anda dapat berkontribusi ke Flight:

  1. Berkontribusi ke kerangka kerja inti dengan mengunjungi core repository.
  2. Bantu membuat dokumen lebih baik! Situs web dokumentasi ini dihosting di Github. Jika Anda menemukan kesalahan atau ingin meningkatkan sesuatu, silakan submit pull request. Kami menyukai pembaruan dan ide baru—terutama seputar AI dan teknologi baru!

Persyaratan

Flight memerlukan PHP 7.4 atau lebih baru.

Catatan: PHP 7.4 didukung karena pada saat penulisan (2024) PHP 7.4 adalah versi default untuk beberapa distribusi Linux LTS. Memaksa perpindahan ke PHP >8 akan menimbulkan masalah bagi pengguna tersebut. Kerangka kerja 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 penangkapan file PHP mandiri yang ringan, sederhana dan standalone, difork dari Wruczek/PHP-File-Cache

Keunggulan

Situs dokumentasi ini menggunakan pustaka ini untuk menangkap setiap halaman!

Klik di sini untuk melihat kode.

Instalasi

Instal melalui composer:

composer require flightphp/cache

Penggunaan

Penggunaan cukup sederhana. 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 disetel 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 cache. Jika Anda ingin metode kemudahan yang akan menyegarkan cache jika sudah kedaluwarsa, Anda bisa menggunakan refreshIfExpired().


// Dapatkan instance cache
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
    return date("H:i:s"); // return data to be cached
}, 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 di cache.

Flight::cache()->set('simple-cache-test', 'my cached data', 10); // 10 detik

Menghapus Nilai Cache

Anda menggunakan metode delete() untuk menghapus nilai di cache.

Flight::cache()->delete('simple-cache-test');

Memeriksa Apakah Nilai Cache Ada

Anda menggunakan metode exists() untuk memeriksa apakah nilai ada di cache.

if(Flight::cache()->exists('simple-cache-test')) {
    // lakukan sesuatu
}

Membersihkan Cache

Anda menggunakan metode flush() untuk membersihkan seluruh cache.

Flight::cache()->flush();

Mengambil metadata dengan cache

Jika Anda ingin mengambil timestamp dan metadata 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"); // return data to be cached
}, 10, true); // true = return with metadata
// atau
$data = $cache->get("simple-cache-meta-test", true); // true = return with metadata

/*
Contoh item cache yang diambil dengan metadata:
{
    "time":1511667506, <-- save unix timestamp
    "expire":10,       <-- expire time in seconds
    "data":"04:38:26", <-- unserialized data
    "permanent":false
}

Menggunakan metadata, kita bisa, misalnya, menghitung kapan item disimpan atau kapan kedaluwarsa
Kita juga bisa mengakses data itu sendiri dengan kunci "data"
*/

$expiresin = ($data["time"] + $data["expire"]) - time(); // get unix timestamp when data expires and subtract current timestamp from it
$cacheddate = $data["data"]; // we access the data itself with the "data" key

echo "Latest cache save: $cacheddate, expires in $expiresin seconds";

Dokumentasi

Kunjungi https://github.com/flightphp/cache untuk melihat kode. Pastikan Anda melihat folder examples untuk cara tambahan menggunakan cache.

Awesome-plugins/permissions

FlightPHP/Permissions

Ini adalah modul izin yang dapat digunakan dalam proyek Anda jika Anda memiliki beberapa peran di aplikasi Anda dan setiap peran memiliki sedikit fungsi yang 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 sudah siap!

Penggunaan

Pertama, Anda perlu mengatur izin Anda, lalu Anda memberi tahu 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 anggap Anda memiliki fitur dalam aplikasi Anda yang memeriksa apakah seorang pengguna sudah masuk. Anda dapat membuat objek izin seperti ini:

// index.php
require 'vendor/autoload.php';

// beberapa kode

// lalu Anda mungkin memiliki sesuatu yang memberi tahu Anda siapa peran saat ini dari orang tersebut
// kemungkinan Anda memiliki sesuatu di mana Anda mengambil peran saat ini
// dari variabel sesi yang mendefinisikan ini
// setelah seseorang masuk, jika tidak, mereka akan memiliki peran 'tamu' atau 'publik'.
$current_role = 'admin';

// mengatur izin
$permission = new \flight\Permission($current_role);
$permission->defineRule('loggedIn', function($current_role) {
    return $current_role !== 'guest';
});

// Anda mungkin ingin menyimpan objek ini di Flight di suatu tempat
Flight::set('permission', $permission);

Kemudian di dalam controller di suatu tempat, Anda mungkin memiliki sesuatu seperti ini.

<?php

// beberapa controller
class SomeController {
    public function someAction() {
        $permission = Flight::get('permission');
        if ($permission->has('loggedIn')) {
            // lakukan sesuatu
        } else {
            // lakukan sesuatu yang lain
        }
    }
}

Anda juga dapat menggunakan ini untuk melacak apakah mereka memiliki izin untuk melakukan sesuatu dalam aplikasi Anda. Sebagai contoh, 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';

// mengatur izin
$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 dalam controller di suatu tempat...

class PostController {
    public function create() {
        $permission = Flight::get('permission');
        if ($permission->can('post.create')) {
            // lakukan sesuatu
        } else {
            // lakukan sesuatu yang lain
        }
    }
}

Menginjeksi ketergantungan

Anda dapat menginjeksi ketergantungan ke dalam closure yang mendefinisikan izin. Ini berguna jika Anda memiliki semacam toggle, id, atau titik data lain yang ingin Anda periksa. Hal ini juga berlaku untuk panggilan jenis Class->Method, kecuali Anda mendefinisikan argumen dalam metode tersebut.

Closure

$Permission->defineRule('order', function(string $current_role, MyDependency $MyDependency = null) {
    // ... kode
});

// di file controller Anda
public function createOrder() {
    $MyDependency = Flight::myDependency();
    $permission = Flight::get('permission');
    if ($permission->can('order.create', $MyDependency)) {
        // lakukan sesuatu
    } else {
        // lakukan sesuatu yang lain
    }
}

Kelas

namespace MyApp;

class Permissions {

    public function order(string $current_role, MyDependency $MyDependency = null) {
        // ... kode
    }
}

Pintasan untuk mengatur izin dengan kelas

Anda juga dapat menggunakan kelas untuk mendefinisikan izin Anda. Ini berguna jika Anda memiliki banyak izin dan Anda ingin menjaga kode Anda tetap bersih. Anda bisa melakukan sesuatu seperti ini:

<?php

// kode bootstrap
$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) {
        // Mengasumsikan Anda telah mengatur ini sebelumnya
        /** @var \flight\database\PdoWrapper $db */
        $db = Flight::db();
        $allowed_permissions = [ 'read' ]; // semua orang dapat melihat sebuah pesanan
        if($current_role === 'manager') {
            $allowed_permissions[] = 'create'; // manajer dapat membuat pesanan
        }
        $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'; // jika pengguna memiliki toggle khusus, mereka dapat memperbarui pesanan
        }
        if($current_role === 'admin') {
            $allowed_permissions[] = 'delete'; // admin dapat menghapus pesanan
        }
        return $allowed_permissions;
    }
}

Bagian yang keren adalah bahwa ada juga pintasan yang dapat Anda gunakan (yang juga dapat dicache!!!) di mana Anda cukup memberi tahu kelas izin untuk memetakan semua metode dalam sebuah kelas ke dalam izin. Jadi jika Anda memiliki metode bernama order() dan metode bernama company(), ini akan secara otomatis dipetakan sehingga Anda cukup menjalankan $Permissions->has('order.read') atau $Permissions->has('company.read') dan itu akan berhasil. Mendefinisikan ini sangat sulit, jadi ikutlah bersama 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 {
        // kode untuk menentukan izin
        return $permissions_array;
    }

    public function company(string $current_role, int $company_id): array {
        // kode untuk menentukan izin
        return $permissions_array;
    }
}

Kemudian buat izin tersebut dapat ditemukan menggunakan pustaka ini.

$Permissions = new \flight\Permission($current_role);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class);
Flight::set('permissions', $Permissions);

Akhirnya, panggil izin di 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('Anda tidak bisa membuat sebuah pesanan. Maaf!');
        }
    }
}

Cache

Untuk mengaktifkan caching, lihat pustaka sederhana wruczak/phpfilecache. Contoh untuk mengaktifkannya ada di bawah ini.


// $app ini bisa menjadi bagian dari kode Anda, atau
// Anda bisa langsung meneruskan null dan itu akan
// mengambil dari Flight::app() di dalam konstruktor
$app = Flight::app();

// Untuk saat ini, ini menerima sebagai cache file. Lainnya dapat dengan mudah
// ditambahkan di masa depan. 
$Cache = new Wruczek\PhpFileCache\PhpFileCache;

$Permissions = new \flight\Permission($current_role, $app, $Cache);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class, 3600); // 3600 adalah berapa detik untuk menyimpan cache ini. Biarkan ini kosong untuk tidak menggunakan caching

Dan Anda sudah 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:

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/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?

Instalasi

  1. Unggah folder flight-integration ke direktori /wp-content/plugins/ Anda.
  2. Aktifkan plugin di admin WordPress (menu Plugins).
  3. Buka Pengaturan > Flight Framework untuk mengonfigurasi plugin.
  4. Atur jalur vendor ke instalasi Flight Anda (atau gunakan Composer untuk menginstal Flight).
  5. Konfigurasi jalur folder aplikasi Anda dan buat struktur folder (plugin dapat membantu dengan ini!).
  6. 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/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

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

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

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:

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:

Skrip SQL

Skrip dibagi menjadi tiga set skrip:

Direktori skrip adalah:

 <root dir>
     |
     +-- base.sql
     |
     +-- /migrations
              |
              +-- /up
                   |
                   +-- 00001.sql
                   +-- 00002.sql
              +-- /down
                   |
                   +-- 00000.sql
                   +-- 00001.sql

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:

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

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.db

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

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:

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:

Semua metode kecuali get() dan id() mengembalikan instance Session untuk chaining.

Mengapa Menggunakan Plugin Ini?

Detail Teknis

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

Jalur

Jalur adalah aplikasi CLI yang membantu Anda mengelola aplikasi Flight Anda. Ini dapat menghasilkan pengontrol, menampilkan semua rute, dan banyak lagi. Ini didasarkan pada pustaka adhocore/php-cli yang sangat baik.

Klik di sini untuk melihat kodenya.

Instalasi

Instal dengan composer.

composer require flightphp/runway

Konfigurasi Dasar

Kali pertama Anda menjalankan Jalur, itu akan memandu Anda melalui proses pengaturan dan membuat file konfigurasi .runway.json di akar proyek Anda. File ini akan berisi beberapa konfigurasi yang diperlukan agar Jalur berfungsi dengan baik.

Penggunaan

Jalur memiliki sejumlah perintah yang dapat Anda gunakan untuk mengelola aplikasi Flight Anda. Ada dua cara mudah untuk menggunakan Jalur.

  1. Jika Anda menggunakan proyek tulang, Anda dapat menjalankan php runway [command] dari akar proyek Anda.
  2. Jika Anda menggunakan Jalur sebagai paket yang diinstal melalui composer, Anda dapat menjalankan vendor/bin/runway [command] dari akar proyek Anda.

Untuk setiap perintah, Anda dapat melewatkan bendera --help untuk mendapatkan informasi lebih lanjut tentang cara menggunakan perintah tersebut.

php runway routes --help

Berikut adalah beberapa contoh:

Menghasilkan Pengontrol

Berdasarkan konfigurasi dalam file .runway.json Anda, lokasi default akan menghasilkan pengontrol untuk Anda di direktori app/controllers/.

php runway make:controller MyController

Menghasilkan Model Rekaman Aktif

Berdasarkan konfigurasi dalam file .runway.json Anda, lokasi default akan menghasilkan pengontrol untuk Anda di direktori app/records/.

php runway make:record users

Jika misalnya Anda memiliki tabel users dengan skema berikut: id, name, email, created_at, updated_at, sebuah file yang mirip dengan yang berikut akan dibuat di file app/records/UserRecord.php:

<?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 $name
 * @property string $email
 * @property string $created_at
 * @property string $updated_at
 * // Anda juga bisa menambahkan hubungan di sini setelah Anda mendefinisikannya di array $relations
 * @property CompanyRecord $company Contoh hubungan
 */
class UserRecord extends \flight\ActiveRecord
{
    /**
     * @var array $relations Tetapkan hubungan untuk model
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relations = [];

    /**
     * Konstruktor
     * @param mixed $databaseConnection Koneksi ke database
     */
    public function __construct($databaseConnection)
    {
        parent::__construct($databaseConnection, 'users');
    }
}

Tampilkan 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 melewatkan bendera untuk menyaring rute.

# Tampilkan hanya rute GET
php runway routes --get

# Tampilkan hanya rute POST
php runway routes --post

# dll.

Menyesuaikan Jalur

Jika Anda membuat paket untuk Flight, atau ingin menambahkan perintah kustom Anda sendiri ke dalam proyek Anda, Anda dapat melakukannya dengan membuat direktori src/commands/, flight/commands/, app/commands/, atau commands/ untuk proyek/paket Anda. Jika Anda memerlukan penyesuaian lebih lanjut, lihat bagian di bawah tentang Konfigurasi.

Untuk membuat perintah, Anda cukup memperluas kelas AbstractBaseCommand, dan menerapkan setidaknya metode __construct dan metode execute.

<?php

declare(strict_types=1);

namespace flight\commands;

class ExampleCommand extends AbstractBaseCommand
{
    /**
     * Konstruktor
     *
     * @param array<string,mixed> $config Konfigurasi JSON dari .runway-config.json
     */
    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(string $controller)
    {
        $io = $this->app()->io();

        $io->info('Membuat contoh...');

        // Lakukan sesuatu di sini

        $io->ok('Contoh dibuat!');
    }
}

Lihat adhocore/php-cli Documentation untuk informasi lebih lanjut tentang cara membangun perintah kustom Anda sendiri ke dalam aplikasi Flight Anda!

Konfigurasi

Jika Anda perlu menyesuaikan konfigurasi untuk Jalur, Anda dapat membuat file .runway-config.json di akar proyek Anda. Di bawah ini adalah beberapa konfigurasi tambahan yang dapat Anda tetapkan:

{

    // Ini adalah tempat direktori aplikasi Anda berada
    "app_root": "app/",

    // Ini adalah direktori tempat file indeks akar Anda berada
    "index_root": "public/",

    // Ini adalah jalur ke akar proyek lainnya
    "root_paths": [
        "/home/user/different-project",
        "/var/www/another-project"
    ],

    // Jalur dasar kemungkinan besar tidak perlu dikonfigurasi, tapi ada di sini jika Anda menginginkannya
    "base_paths": {
        "/includes/libs/vendor", // jika Anda memiliki jalur yang sangat unik untuk direktori vendor Anda atau sesuatu
    },

    // Jalur akhir adalah lokasi dalam proyek untuk mencari file perintah
    "final_paths": {
        "src/diff-path/commands",
        "app/module/admin/commands",
    },

    // Jika Anda ingin hanya menambahkan jalur lengkap, silakan saja (absolut atau relatif terhadap akar proyek)
    "paths": [
        "/home/user/different-project/src/diff-path/commands",
        "/var/www/another-project/app/module/admin/commands",
        "app/my-unique-commands"
    ]
}

Awesome-plugins/tracy_extensions

Tracy Flight Panel Extensions

Ini adalah serangkaian ekstensi untuk membuat kerja dengan Flight menjadi sedikit lebih kaya.

Ini adalah Panel

Flight Bar

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

Flight Data Flight Database Flight Request

Klik di sini untuk melihat kode.

Installation

Jalankan composer require flightphp/tracy-extensions --dev dan Anda siap melanjutkan!

Configuration

Ada sangat sedikit konfigurasi yang perlu Anda lakukan untuk memulai ini. Anda perlu menginisialisasi debugger Tracy sebelum menggunakan ini https://tracy.nette.org/en/guide:

<?php

use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;

// bootstrap code
require __DIR__ . '/vendor/autoload.php';

Debugger::enable();
// You may need to specify your environment with Debugger::enable(Debugger::DEVELOPMENT)

// if you use database connections in your app, there is a 
// required PDO wrapper to use ONLY IN DEVELOPMENT (not production please!)
// It has the same parameters as a regular PDO connection
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// or if you attach this to the Flight framework
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// now whenever you make a query it will capture the time, query, and parameters

// This connects the dots
if(Debugger::$showBar === true) {
    // This needs to be false or Tracy can't actually render :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

// more code

Flight::start();

Additional Configuration

Session Data

Jika Anda memiliki handler sesi kustom (seperti ghostff/session), Anda dapat mengirimkan array data sesi apa pun ke Tracy dan itu akan secara otomatis menampilkannya untuk Anda. Anda mengirimkannya dengan kunci session_data di parameter kedua dari konstruktor TracyExtensionLoader.


use Ghostff\Session\Session;
// or use flight\Session;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('session', Session::class);

if(Debugger::$showBar === true) {
    // This needs to be false or Tracy can't actually render :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}

// routes and other things...

Flight::start();

Latte

PHP 8.1+ diperlukan untuk bagian ini.

Jika Anda memiliki Latte yang terinstal di proyek Anda, Tracy memiliki integrasi native dengan Latte untuk menganalisis template Anda. Anda cukup mendaftarkan ekstensi dengan instance Latte Anda.


require 'vendor/autoload.php';

$app = Flight::app();

$app->map('render', function($template, $data, $block = null) {
    $latte = new Latte\Engine;

    // other configurations...

    // only add the extension if Tracy Debug Bar is enabled
    if(Debugger::$showBar === true) {
        // this is where you add the Latte Panel to Tracy
        $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
    }

    $latte->render($template, $data, $block);
});

Awesome-plugins/apm

Dokumentasi FlightPHP APM

Selamat datang di FlightPHP APM—pelatih performa pribadi untuk aplikasi Anda! Panduan ini adalah peta jalan Anda untuk menyiapkan, menggunakan, dan menguasai Application Performance Monitoring (APM) dengan FlightPHP. Baik Anda sedang memburu permintaan lambat atau hanya ingin bersemangat dengan grafik latensi, kami siap membantu. Mari buat aplikasi Anda lebih cepat, pengguna Anda lebih bahagia, dan sesi debugging Anda menjadi mudah!

Lihat demo dari dashboard untuk situs Flight Docs.

FlightPHP APM

Mengapa APM Penting

Bayangkan ini: aplikasi Anda seperti restoran sibuk. Tanpa cara untuk melacak berapa lama pesanan memakan waktu atau di mana dapur tersendat, Anda hanya menebak-nebak mengapa pelanggan pergi dengan kesal. APM adalah sous-chef Anda—ia mengawasi setiap langkah, dari permintaan masuk hingga kueri database, dan menandai apa pun yang memperlambat Anda. Halaman lambat membuat pengguna pergi (studi mengatakan 53% bounce jika situs memakan waktu lebih dari 3 detik untuk dimuat!), dan APM membantu Anda menangkap masalah-masalah itu sebelum mereka menyakitkan. Ini adalah ketenangan pikiran yang proaktif—lebih sedikit momen “mengapa ini rusak?” dan lebih banyak kemenangan “lihat betapa lancarnya ini berjalan!”.

Instalasi

Mulai dengan Composer:

composer require flightphp/apm

Anda memerlukan:

Database yang Didukung

FlightPHP APM saat ini mendukung database berikut untuk menyimpan metrik:

Anda dapat memilih jenis database Anda selama langkah konfigurasi (lihat di bawah). Pastikan lingkungan PHP Anda memiliki ekstensi yang diperlukan terinstal (misalnya, pdo_sqlite atau pdo_mysql).

Memulai

Berikut langkah demi langkah untuk kehebatan APM:

1. Daftarkan APM

Masukkan ini ke dalam file index.php atau services.php Anda untuk mulai melacak:

use flight\apm\logger\LoggerFactory;
use flight\Apm;

$ApmLogger = LoggerFactory::create(__DIR__ . '/../../.runway-config.json');
$Apm = new Apm($ApmLogger);
$Apm->bindEventsToFlightInstance($app);

// Jika Anda menambahkan koneksi database
// Harus berupa PdoWrapper atau PdoQueryCapture dari Tracy Extensions
$pdo = new PdoWrapper('mysql:host=localhost;dbname=example', 'user', 'pass', null, true); // <-- True diperlukan untuk mengaktifkan pelacakan di APM.
$Apm->addPdoConnection($pdo);

Apa yang terjadi di sini?

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); // Mencatat 10% permintaan

Ini menjaga performa tetap cepat sambil tetap memberikan data yang solid.

2. Konfigurasikan Itu

Jalankan ini untuk membuat .runway-config.json Anda:

php vendor/bin/runway apm:init

Apa yang dilakukan ini?

Proses ini juga akan menanyakan apakah Anda ingin menjalankan migrasi untuk pengaturan ini. Jika Anda menyiapkannya untuk pertama kali, jawabannya ya.

Mengapa dua lokasi? Metrik mentah menumpuk dengan cepat (bayangkan log yang tidak difilter). Worker memprosesnya menjadi tujuan terstruktur untuk dashboard. Menjaga semuanya 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?

Jaga Agar Tetap Berjalan Untuk aplikasi langsung, Anda ingin pemrosesan berkelanjutan. Berikut opsi Anda:

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?

Kustomisasi Itu:

php vendor/bin/runway apm:dashboard --host 0.0.0.0 --port 8080 --php-path=/usr/local/bin/php

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:

Ingin dashboard berbeda?

Anda bisa 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 APM Anda—berikut yang akan Anda lihat:

Tambahan:

Contoh: Permintaan ke /users mungkin menunjukkan:

Menambahkan Peristiwa 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 munculnya? Di detail permintaan dashboard di bawah “Peristiwa Kustom”—dapat diperluas dengan format JSON yang bagus.

Kasus Penggunaan:

$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 jika API itu menyeret aplikasi Anda!

Pemantauan Database

Lacak kueri PDO seperti ini:

use flight\database\PdoWrapper;

$pdo = new PdoWrapper('sqlite:/path/to/db.sqlite', null, null, null, true); // <-- True diperlukan untuk mengaktifkan pelacakan di APM.
$Apm->addPdoConnection($pdo);

Apa yang Anda Dapat:

Peringatan:

Contoh Output:

Opsi Worker

Sesuaikan worker sesuai keinginan Anda:

Contoh:

php vendor/bin/runway apm:worker --daemon --batch_size 100 --timeout 3600

Berjalan selama satu jam, memproses 100 metrik sekaligus.

ID Permintaan di Aplikasi

Setiap permintaan memiliki ID permintaan unik untuk pelacakan. Anda dapat menggunakan ID ini di aplikasi Anda untuk mengkorelasikan log dan metrik. Misalnya, Anda dapat menambahkan ID permintaan ke halaman kesalahan:

Flight::map('error', function($message) {
    // Dapatkan ID permintaan dari header respons X-Flight-Request-Id
    $requestId = Flight::response()->getHeader('X-Flight-Request-Id');

    // Selain itu, Anda bisa mengambilnya dari variabel Flight
    // Metode ini tidak akan bekerja dengan baik di swoole atau platform async lainnya.
    // $requestId = Flight::get('apm.request_id');

    echo "Error: $message (Request ID: $requestId)";
});

Upgrade

Jika Anda sedang meng-upgrade ke versi APM yang lebih baru, ada kemungkinan ada migrasi database yang perlu dijalankan. Anda bisa 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 besar ukurannya, migrasi ini mungkin memakan waktu. Anda mungkin ingin menjalankan perintah ini selama jam non-puncak.

Membersihkan Data Lama

Untuk menjaga database Anda rapi, Anda bisa membersihkan data lama. Ini sangat berguna jika Anda menjalankan aplikasi sibuk dan ingin menjaga ukuran database tetap terkendali. Anda bisa melakukannya dengan menjalankan perintah berikut:

php vendor/bin/runway apm:purge

Ini akan menghapus semua data lebih tua dari 30 hari dari database. Anda bisa menyesuaikan jumlah hari dengan memberikan nilai berbeda ke opsi --days:

php vendor/bin/runway apm:purge --days 7

Ini akan menghapus semua data lebih tua dari 7 hari dari database.

Pemecahan Masalah

Tersangkut? Coba ini:

Awesome-plugins/tracy

Tracy

Tracy adalah penangan kesalahan yang luar biasa yang dapat digunakan dengan Flight. Ia memiliki sejumlah panel yang dapat membantu Anda dalam mendebug aplikasi Anda. Ini 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.

Instalasi

Instal dengan composer. Dan Anda akan 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;

// Mengaktifkan 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 ditulisi.
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // tampilkan semua kesalahan
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // semua kesalahan kecuali pemberitahuan kadaluarsa
if (Debugger::$showBar) {
    $app->set('flight.content_length', false); // jika bilah Debugger terlihat, maka panjang konten tidak dapat diatur oleh Flight

    // Ini khusus untuk Ekstensi Tracy untuk Flight jika Anda telah menyertakannya
    // jika tidak, silakan komentari ini.
    new TracyExtensionLoader($app);
}

Tips Berguna

Saat Anda mendebug kode Anda, ada beberapa fungsi yang sangat berguna untuk mengeluarkan data untuk Anda.

Awesome-plugins/active_record

Flight Active Record

Sebuah active record adalah pemetaan entitas basis data ke objek PHP. Sederhananya, jika Anda memiliki tabel pengguna di basis data Anda, Anda dapat "menerjemahkan" sebuah baris di tabel tersebut ke dalam kelas User dan objek $user dalam kode Anda. Lihat contoh dasar.

Klik di sini untuk repositori di GitHub.

Contoh Dasar

Mari kita asumsikan Anda memiliki tabel berikut:

CREATE TABLE users (
    id INTEGER PRIMARY KEY, 
    name TEXT, 
    password TEXT 
);

Sekarang Anda dapat mengatur kelas baru untuk mewakili tabel ini:

/**
 * Sebuah kelas ActiveRecord biasanya 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 mengatur ini dengan cara ini
        parent::__construct($database_connection, 'users');
        // atau dengan cara ini
        parent::__construct($database_connection, null, [ 'table' => 'users']);
    }
}

Sekarang saksikan sihir terjadi!

// untuk sqlite
$database_connection = new PDO('sqlite:test.db'); // ini hanya untuk contoh, Anda mungkin akan menggunakan koneksi basis data yang 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 yang tidak berdasarkan 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 dapat menggunakan $user->save() di sini, atau itu akan mengira ini adalah pembaruan!

echo $user->id; // 2

Dan itu sangat mudah untuk menambahkan pengguna baru! Sekarang setelah ada baris pengguna di basis data, bagaimana cara Anda mengeluarkannya?

$user->find(1); // cari id = 1 dalam basis data dan kembalikan.
echo $user->name; // 'Bobby Tables'

Dan bagaimana jika Anda ingin menemukan semua pengguna?

$users = $user->findAll();

Bagaimana dengan kondisi tertentu?

$users = $user->like('name', '%mamma%')->findAll();

Lihat betapa menyenangkannya ini? Mari kita 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 mengoper koneksi PDO ke konstruktor.

$pdo_connection = new PDO('sqlite:test.db'); // ini hanya untuk contoh, Anda mungkin akan menggunakan koneksi basis data yang nyata

$User = new User($pdo_connection);

Tidak ingin selalu mengatur koneksi basis data Anda di konstruktor? Lihat Manajemen Koneksi Basis Data untuk ide lainnya!

Daftarkan sebagai metode dalam Flight

Jika Anda menggunakan Flight PHP Framework, Anda dapat mendaftarkan kelas ActiveRecord sebagai layanan, tetapi sejujurnya Anda tidak harus melakukannya.

Flight::register('user', 'User', [ $pdo_connection ]);

// kemudian Anda dapat menggunakannya seperti ini di pengontrol, 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 pengguna.
 * @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 Menetapkan 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

Mencari satu catatan dan menetapkannya pada objek saat ini. Jika Anda mengoper $id dari jenis tertentu, itu akan melakukan pencarian pada kunci utama dengan nilai itu. Jika tidak ada yang dipassing, ini hanya akan menemukan catatan pertama di tabel.

Selain itu, Anda dapat mengoper metode pembantu lainnya untuk menanyakan tabel Anda.

// mencari catatan dengan beberapa kondisi terlebih dahulu
$user->notNull('password')->orderBy('id DESC')->find();

// mencari catatan berdasarkan id tertentu
$id = 123;
$user->find($id);

findAll(): array<int,ActiveRecord>

Menemukan semua catatan di tabel yang Anda tentukan.

$user->findAll();

isHydrated(): boolean (v0.4.0)

Mengembalikan true jika catatan saat ini telah terhidrat (diambil dari database).

$user->find(1);
// jika catatan ditemukan dengan data...
$user->isHydrated(); // true

insert(): boolean|ActiveRecord

Menyisipkan catatan saat ini ke dalam 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 menyisipkan dalam 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 membiarkan kunci utama dihasilkan secara otomatis untuk Anda melalui peristiwa.

class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
        // Anda juga dapat mengatur primaryKey ini alih-alih array di atas.
        $this->primaryKey = 'uuid';
    }

    protected function beforeInsert(self $self) {
        $self->uuid = uniqid(); // atau sesuaikan bagaimana Anda perlu menghasilkan id unik Anda
    }
}

Jika Anda tidak mengatur kunci utama sebelum menyisipkan, itu akan diatur ke rowid dan basis data akan menghasilkan untuk Anda, tetapi tidak akan dipertahankan karena bidang itu mungkin tidak ada dalam tabel Anda. Inilah sebabnya mengapa disarankan untuk menggunakan peristiwa untuk menangani ini secara otomatis untuk Anda.

update(): boolean|ActiveRecord

Memperbarui catatan saat ini ke dalam basis data.

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();

save(): boolean|ActiveRecord

Menyisipkan atau memperbarui catatan saat ini ke dalam basis data. Jika catatan memiliki id, itu akan memperbarui, jika tidak, itu akan menyisipkan.

$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();

Catatan: Jika Anda memiliki hubungan yang ditentukan dalam kelas, itu akan menyimpan hubungan tersebut secara rekursif juga jika telah ditentukan, diinstansiasi, dan memiliki data yang perlu diperbarui. (v0.4.0 dan lebih baru)

delete(): boolean

Menghapus catatan saat ini dari basis data.

$user->gt('id', 0)->orderBy('id desc')->find();
$user->delete();

Anda juga dapat menghapus beberapa catatan dengan mengeksekusi pencarian terlebih dahulu.

$user->like('name', 'Bob%')->delete();

dirty(array $dirty = []): ActiveRecord

Data "dirty" merujuk pada data yang telah diubah dalam sebuah catatan.

$user->greaterThan('id', 0)->orderBy('id desc')->find();

// tidak ada yang "dirty" pada titik ini.

$user->email = 'test@example.com'; // sekarang email dianggap "dirty" karena telah diubah.
$user->update();
// sekarang tidak ada data yang dirty karena telah diperbarui dan dipertahankan dalam basis data

$user->password = password_hash('newpassword'); // sekarang ini kotor
$user->dirty(); // melewatkan apa pun akan membersihkan semua entri yang kotor.
$user->update(); // tidak ada yang akan diperbarui karena tidak ada yang ditangkap sebagai kotor.

$user->dirty([ 'name' => 'sesuatu', 'password' => password_hash('password yang berbeda') ]);
$user->update(); // baik nama dan kata sandi diperbarui.

copyFrom(array $data): ActiveRecord (v0.4.0)

Ini adalah alias untuk metode dirty(). Ini sedikit lebih jelas tentang apa yang Anda lakukan.

$user->copyFrom([ 'name' => 'sesuatu', 'password' => password_hash('password yang berbeda') ]);
$user->update(); // baik nama dan kata sandi diperbarui.

isDirty(): boolean (v0.4.0)

Mengembalikan true jika catatan 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

Mereset catatan saat ini ke keadaan awalnya. Ini sangat baik digunakan dalam perilaku tipe loop. Jika Anda mengoper true, itu juga akan mereset data kueri 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 yang 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 Kuery SQL

select(string $field1 [, string $field2 ... ])

Anda dapat memilih hanya beberapa kolom di tabel jika Anda mau (ini lebih efisien pada tabel yang sangat lebar dengan banyak kolom)

$user->select('id', 'name')->find();

from(string $table)

Anda dapat memilih tabel lain juga! Untuk apa tidak?!

$user->select('id', 'name')->from('user')->find();

join(string $table_name, string $join_condition)

Anda bahkan dapat bergabung dengan tabel lain di basis data.

$user->join('contacts', 'contacts.user_id = users.id')->find();

where(string $where_conditions)

Anda dapat menetapkan beberapa argumen where kustom (Anda tidak dapat mengatur parameter dalam pernyataan where ini)

$user->where('id=1 AND name="demo"')->find();

Catatan Keamanan - Anda mungkin terdorong 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 di internet, silakan Google "sql injection attacks php" dan Anda akan menemukan banyak artikel tentang subjek ini. Cara yang tepat untuk menangani ini dengan perpustakaan ini adalah alih-alih metode where() ini, Anda akan melakukan sesuatu yang lebih seperti $user->eq('id', $id)->eq('name', $name)->find(); Jika Anda harus melakukan ini, pustaka PDO memiliki $pdo->quote($var) untuk menghindarinya 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 kueri yang dikembalikan dengan cara tertentu.

$user->orderBy('name DESC')->find();

limit(string $limit)/limit(int $offset, int $limit)

Batasi jumlah rekaman yang dikembalikan. Jika bilangan kedua diberikan, itu akan di-offset, batasi saja seperti di SQL.

$user->orderby('name DESC')->limit(0, 10)->findAll();

Kondisi WHERE

equal(string $field, mixed $value) / eq(string $field, mixed $value)

Di mana field = $value

$user->eq('id', 1)->find();

notEqual(string $field, mixed $value) / ne(string $field, mixed $value)

Di mana field <> $value

$user->ne('id', 1)->find();

isNull(string $field)

Di mana field IS NULL

$user->isNull('id')->find();

isNotNull(string $field) / notNull(string $field)

Di mana field IS NOT NULL

$user->isNotNull('id')->find();

greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)

Di mana field > $value

$user->gt('id', 1)->find();

lessThan(string $field, mixed $value) / lt(string $field, mixed $value)

Di mana field < $value

$user->lt('id', 1)->find();

greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)

Di mana field >= $value

$user->ge('id', 1)->find();

lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)

Di mana field <= $value

$user->le('id', 1)->find();

like(string $field, mixed $value) / notLike(string $field, mixed $value)

Di mana field LIKE $value atau field NOT LIKE $value

$user->like('name', 'de')->find();

in(string $field, array $values) / notIn(string $field, array $values)

Di mana field IN($value) atau field NOT IN($value)

$user->in('id', [1, 2])->find();

between(string $field, array $values)

Di mana field BETWEEN $value AND $value1

$user->between('id', [1, 2])->find();

Kondisi OR

Dimungkinkan untuk membungkus kondisi Anda dalam pernyataan OR. Ini dilakukan dengan metode startWrap() dan endWrap() atau dengan mengisi parameter ke-3 dari kondisi setelah bidang dan nilai.

// Metode 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// Ini akan dievaluasi menjadi `id = 1 AND (name = 'demo' OR name = 'test')`

// Metode 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// Ini akan dievaluasi menjadi `id = 1 OR name = 'demo'`

Hubungan

Anda dapat mengatur beberapa jenis hubungan menggunakan pustaka ini. Anda dapat mengatur hubungan satu->banyak dan satu->satu antara tabel. Ini membutuhkan pengaturan ekstra dalam kelas sebelumnya.

Mengatur array $relations tidaklah sulit, tetapi menebak sintaks yang benar bisa membingungkan.

protected array $relations = [
    // Anda dapat memberi nama kuncinya dengan cara apa pun yang Anda suka. Nama ActiveRecord mungkin bagus. Mis: user, contact, client
    'user' => [
        // wajib
        // self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
        self::HAS_ONE, // ini adalah jenis hubungan

        // wajib
        'Some_Class', // ini adalah kelas ActiveRecord "lain" yang akan direferensikan

        // wajib
        // tergantung pada jenis hubungan
        // self::HAS_ONE = kunci asing yang mereferensikan gabungan
        // self::HAS_MANY = kunci asing yang mereferensikan gabungan
        // self::BELONGS_TO = kunci lokal yang mereferensikan gabungan
        'local_or_foreign_key',
        // hanya FYI, ini juga hanya bergabung dengan kunci utama model "lain"

        // opsional
        [ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // kondisi tambahan yang Anda inginkan ketika menggabungkan hubungan
        // $record->eq('client_id', 5)->select('COUNT(*) as count')->limit(5))

        // opsional
        'nama_referensi_kembali' // ini jika Anda ingin merujuk kembali hubungan ini kembali ke dirinya sendiri Mis: $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();

// ambil kontak dengan menggunakan hubungan:
foreach($user->contacts as $contact) {
    echo $contact->id;
}

// atau kita bisa pergi ke arah yang lain.
$contact = new Contact();

// cari satu kontak
$contact->find();

// dapatkan pengguna dengan menggunakan hubungan:
echo $contact->user->name; // ini adalah nama pengguna

Keren kan?

Mengatur Data Kustom

Terkadang Anda mungkin perlu melampirkan sesuatu yang unik pada ActiveRecord Anda seperti perhitungan khusus yang mungkin lebih mudah untuk dilampirkan pada objek yang kemudian akan 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 biasa.

echo $user->page_view_count;

Peristiwa

Satu fitur luar biasa lainnya tentang pustaka ini adalah tentang peristiwa. Peristiwa dipicu pada saat tertentu berdasarkan metode tertentu yang Anda panggil. Mereka sangat membantu dalam menyiapkan 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 kueri 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 adalah yang Anda inginkan
        $self->gte('id', 0); 
    } 
}

afterFind(ActiveRecord $ActiveRecord)

Yang ini mungkin lebih berguna jika Anda selalu perlu menjalankan beberapa logika setiap kali catatan ini diambil. Apakah Anda perlu mendekripsi sesuatu? Apakah Anda perlu menjalankan kueri hitung kustom setiap kali (tidak efisien tetapi tidak apa-apa)?

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 yang kustom seperti kueri???
        $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 kueri 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 adalah yang Anda inginkan
        $self->gte('id', 0); 
    } 
}

afterFindAll(array<int,ActiveRecord> $results)

Mirip dengan afterFind() tetapi Anda bisa melakukannya ke semua catatan!

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 berguna jika Anda perlu menetapkan beberapa nilai default setiap kali.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // menetapkan beberapa default yang baik
        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 skenario untuk mengubah data setelah disisipkan?

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterInsert(self $self) {
        // Anda melakukan Anda
        Flight::cache()->set('most_recent_insert_id', $self->id);
        // atau apa pun....
    } 
}

beforeUpdate(ActiveRecord $ActiveRecord)

Sangat berguna jika Anda perlu menetapkan beberapa nilai default setiap kali ada pembaruan.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // menetapkan beberapa default yang baik
        if(!$self->updated_date) {
            $self->updated_date = gmdate('Y-m-d');
        }
    } 
}

afterUpdate(ActiveRecord $ActiveRecord)

Mungkin Anda memiliki skenario 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) {
        // Anda melakukan Anda
        Flight::cache()->set('most_recently_updated_user_id', $self->id);
        // atau apa pun....
    } 
}

beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)

Ini berguna jika Anda ingin peristiwa terjadi baik saat sisip atau pembaruan terjadi. Saya akan menghemat penjelasan panjangnya, tetapi 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, tetapi tidak ada penilaian di sini! Ayo lakukan!

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeDelete(self $self) {
        echo 'Dia adalah seorang prajurit yang berani... :cry-face:';
    } 
}

Manajemen Koneksi Basis Data

Ketika Anda menggunakan pustaka ini, Anda dapat mengatur koneksi basis data dengan beberapa cara berbeda. Anda dapat mengatur koneksi di konstruktor, Anda dapat mengatur melalui variabel konfigurasi $config['connection'] atau Anda dapat mengatur melalui setDatabaseConnection() (v0.4.1).

$pdo_connection = new PDO('sqlite:test.db'); // untuk contoh
$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 record aktif, ada cara untuk mengatasinya!

// index.php atau bootstrap.php
// Set 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 melakukan pengujian unit, melakukan ini dapat menambah beberapa tantangan untuk pengujian unit, tetapi secara keseluruhan karena Anda dapat menyuntikkan koneksi Anda dengan setDatabaseConnection() atau $config['connection'], ini tidak terlalu buruk.

Jika Anda perlu menyegarkan koneksi basis data, misalnya jika Anda menjalankan skrip CLI yang berjalan lama dan perlu menyegarkan koneksi setiap saat, Anda dapat mengatur ulang koneksi dengan $your_record->setDatabaseConnection($pdo_connection).

Kontribusi

Silakan lakukan. :D

Pengaturan

Saat Anda berkontribusi, pastikan Anda menjalankan composer test-coverage untuk mempertahankan 100% cakupan pengujian (ini bukan cakupan pengujian unit yang sebenarnya, lebih seperti pengujian integrasi).

Juga pastikan Anda menjalankan composer beautify dan composer phpcs untuk memperbaiki kesalahan linting.

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">
            &copy; 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 Hebat

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 pustaka mikro/lite untuk membantu Anda memulai.

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.

Pemantauan Kinerja Aplikasi (APM)

Pemantauan Kinerja Aplikasi (APM) sangat penting untuk aplikasi apa pun. Ini membantu Anda memahami bagaimana aplikasi Anda berkinerja dan di mana titik penyumbatannya. Ada sejumlah alat APM yang dapat digunakan dengan Flight.

Async

Flight sudah merupakan framework yang cepat, tetapi menambahkan mesin turbo padanya membuat semuanya lebih menyenangkan (dan menantang)!

Otorisasi/Izin

Otorisasi dan Izin sangat penting untuk aplikasi apa pun yang memerlukan kontrol untuk siapa yang dapat mengakses apa.

Penyimpanan Cache

Penyimpanan cache adalah cara hebat untuk mempercepat aplikasi Anda. Ada sejumlah pustaka caching yang dapat digunakan dengan Flight.

CLI

Aplikasi CLI adalah cara hebat untuk berinteraksi dengan aplikasi Anda. Anda dapat menggunakannya untuk menghasilkan controller, menampilkan semua rute, dan banyak lagi.

Cookies

Cookies adalah cara hebat untuk menyimpan bit data kecil di sisi klien. Mereka dapat digunakan untuk menyimpan preferensi pengguna, pengaturan aplikasi, dan banyak lagi.

Debugging

Debugging sangat penting ketika Anda mengembangkan di lingkungan lokal Anda. Ada beberapa plugin yang dapat meningkatkan pengalaman debugging Anda.

Database

Database adalah inti dari sebagian besar aplikasi. Ini adalah cara Anda menyimpan dan mengambil data. Beberapa pustaka database hanyalah wrapper untuk menulis query dan beberapa adalah ORM lengkap.

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 jangan pernah menyimpan kunci enkripsi Anda di direktori publik atau mengommitnya ke repositori kode Anda.

Antrian Pekerjaan

Antrian pekerjaan sangat membantu untuk memproses tugas secara asinkron. Ini bisa mengirim email, memproses gambar, atau apa pun yang tidak perlu dilakukan secara real-time.

Sesi

Sesi tidak terlalu berguna untuk API tetapi untuk membangun aplikasi web, sesi bisa sangat penting untuk mempertahankan status dan informasi login.

Templating

Templating adalah inti dari aplikasi web apa pun dengan UI. Ada sejumlah mesin templating yang dapat digunakan dengan Flight.

Integrasi WordPress

Ingin menggunakan Flight di proyek WordPress Anda? Ada plugin yang berguna untuk itu!

Kontribusi

Punya plugin yang ingin Anda bagikan? Kirimkan 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

Videos and Tutorials

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:

Contoh yang disumbangkan oleh komunitas:

Butuh Inspirasi?

Meskipun ini tidak secara resmi disponsori oleh Tim Flight, ini bisa memberi Anda ide tentang cara menyusun proyek Anda sendiri yang dibangun dengan Flight!

Ingin Berbagi Contoh Anda Sendiri?

Jika Anda memiliki proyek yang ingin dibagikan, silakan kirimkan pull request untuk menambahkannya ke daftar ini!

Install/install

Petunjuk Instalasi

Ada beberapa prasyarat dasar sebelum Anda dapat menginstal Flight. Yaitu, Anda perlu:

  1. Instal PHP di sistem Anda
  2. Instal Composer untuk pengalaman pengembang terbaik.

Instalasi Dasar

Jika Anda menggunakan Composer, Anda dapat menjalankan perintah berikut:

composer require flightphp/core

Ini hanya akan meletakkan file inti Flight di sistem Anda. Anda perlu mendefinisikan struktur proyek, layout, dependencies, configs, autoloading, dll. Metode ini memastikan bahwa tidak ada dependensi lain selain Flight yang diinstal.

Anda juga dapat mengunduh file secara langsung dan mengekstraknya ke direktori web Anda.

Instalasi yang Direkomendasikan

Sangat disarankan untuk memulai dengan aplikasi flightphp/skeleton untuk proyek baru apa pun. Instalasi sangat mudah.

composer create-project flightphp/skeleton my-project/

Ini akan menyiapkan struktur proyek Anda, mengonfigurasi autoloading dengan namespace, menyiapkan konfigurasi, dan menyediakan alat lain seperti Tracy, Tracy Extensions, dan Runway

Konfigurasi Server Web Anda

Server Pengembangan PHP Bawaan

Ini adalah cara termudah untuk memulai dan menjalankan. Anda dapat menggunakan server bawaan untuk menjalankan aplikasi Anda dan bahkan menggunakan SQLite untuk database (selama sqlite3 diinstal di sistem Anda) dan tidak memerlukan banyak hal apa pun! Cukup jalankan perintah berikut setelah PHP diinstal:

php -S localhost:8000
# atau dengan aplikasi skeleton
composer start

Kemudian buka browser Anda dan pergi ke http://localhost:8000.

Jika Anda ingin menjadikan document root proyek Anda direktori yang berbeda (Contoh: proyek Anda adalah ~/myproject, tetapi document root 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 diinstal di sistem Anda. Jika tidak, cari di Google cara menginstal Apache di sistem Anda.

Untuk Apache, edit file .htaccess Anda dengan yang 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 setelah RewriteEngine On.

Catatan: Jika Anda ingin melindungi semua file server, seperti file db atau env. Letakkan ini di file .htaccess Anda:

RewriteEngine On
RewriteRule ^(.*)$ index.php

Nginx

Pastikan Nginx sudah diinstal di sistem Anda. Jika tidak, cari di Google cara menginstal Nginx di sistem Anda.

Untuk Nginx, tambahkan yang berikut ke deklarasi server Anda:

server {
  location / {
    try_files $uri $uri/ /index.php;
  }
}

Buat file index.php Anda

Jika Anda melakukan instalasi dasar, Anda akan membutuhkan beberapa kode untuk memulai.

<?php

// Jika Anda menggunakan Composer, require the autoloader.
require 'vendor/autoload.php';
// jika Anda tidak menggunakan Composer, load the framework directly
// require 'flight/Flight.php';

// Kemudian definisikan rute dan tetapkan fungsi untuk menangani permintaan.
Flight::route('/', function () {
  echo 'hello world!';
});

// Akhirnya, mulai framework.
Flight::start();

Dengan aplikasi skeleton, ini sudah dikonfigurasi dan ditangani di file app/config/routes.php Anda. Layanan dikonfigurasi di app/config/services.php

Menginstal PHP

Jika Anda sudah memiliki php yang diinstal di sistem Anda, lanjutkan dan lewati petunjuk ini dan pindah ke bagian unduhan

macOS

Menginstal PHP menggunakan Homebrew

  1. Instal Homebrew (jika belum diinstal):

    • Buka Terminal dan jalankan:
      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  2. Instal PHP:

    • Instal versi terbaru:
      brew install php
    • Untuk menginstal versi spesifik, misalnya, PHP 8.1:
      brew tap shivammathur/php
      brew install shivammathur/php/php@8.1
  3. Beralih antar versi PHP:

    • Unlink versi saat ini dan link versi yang diinginkan:
      brew unlink php
      brew link --overwrite --force php@8.1
    • Verifikasi versi yang diinstal:
      php -v

Windows 10/11

Menginstal PHP secara manual

  1. Unduh PHP:

    • Kunjungi PHP for Windows dan unduh versi terbaru atau versi spesifik (misalnya, 7.4, 8.0) sebagai file zip non-thread-safe.
  2. Ekstrak PHP:

    • Ekstrak file zip yang diunduh ke C:\php.
  3. Tambahkan PHP ke PATH sistem:

    • Pergi ke 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.
  4. Konfigurasi PHP:

    • Salin php.ini-development ke php.ini.
    • Edit php.ini untuk mengonfigurasi PHP sesuai kebutuhan (misalnya, mengatur extension_dir, mengaktifkan ekstensi).
  5. Verifikasi instalasi PHP:

    • Buka Command Prompt dan jalankan:
      php -v

Menginstal Beberapa Versi PHP

  1. Ulangi langkah di atas untuk setiap versi, letakkan masing-masing di direktori terpisah (misalnya, C:\php7, C:\php8).

  2. Beralih antar versi dengan menyesuaikan variabel PATH sistem untuk menunjuk ke direktori versi yang diinginkan.

Ubuntu (20.04, 22.04, dll.)

Menginstal PHP menggunakan apt

  1. Perbarui daftar paket:

    • Buka Terminal dan jalankan:
      sudo apt update
  2. Instal PHP:

    • Instal versi PHP terbaru:
      sudo apt install php
    • Untuk menginstal versi spesifik, misalnya, PHP 8.1:
      sudo apt install php8.1
  3. Instal modul tambahan (opsional):

    • Misalnya, untuk menginstal dukungan MySQL:
      sudo apt install php8.1-mysql
  4. Beralih antar versi PHP:

    • Gunakan update-alternatives:
      sudo update-alternatives --set php /usr/bin/php8.1
  5. Verifikasi versi yang diinstal:

    • Jalankan:
      php -v

Rocky Linux

Menginstal PHP menggunakan yum/dnf

  1. Aktifkan repositori EPEL:

    • Buka Terminal dan jalankan:
      sudo dnf install epel-release
  2. Instal repositori Remi's:

    • Jalankan:
      sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
      sudo dnf module reset php
  3. Instal PHP:

    • Untuk menginstal versi default:
      sudo dnf install php
    • Untuk menginstal versi spesifik, misalnya, PHP 7.4:
      sudo dnf module install php:remi-7.4
  4. Beralih antar versi PHP:

    • Gunakan perintah modul dnf:
      sudo dnf module reset php
      sudo dnf module enable php:remi-8.0
      sudo dnf install php
  5. Verifikasi versi yang diinstal:

    • Jalankan:
      php -v

Catatan Umum

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:

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:

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 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.