PhalconPHP: Solusi untuk RESTful API dengan beban tinggi

Diterbitkan: 2022-03-11

Misalkan Anda perlu membuat proyek beban tinggi berdasarkan kerangka kerja PHP MVC. Anda mungkin akan menggunakan caching jika memungkinkan. Mungkin Anda akan membangun proyek dalam satu file, atau bahkan mungkin menulis kerangka kerja MVC Anda sendiri dengan fungsionalitas minimal, atau menulis ulang beberapa bagian kerangka kerja lain. Sementara, ya, ini berhasil, itu sedikit rumit, bukan? Untungnya, ada satu solusi lagi yang membuat sebagian besar manipulasi ini tidak perlu (kecuali untuk cache, mungkin), dan solusi ini disebut kerangka kerja PhalconPHP.

Apa itu PhalconPHP?

PhalconPHP adalah kerangka kerja MVC untuk PHP yang ditulis dalam C dan disediakan sebagai ekstensi PHP yang dikompilasi. Inilah yang menjadikannya salah satu kerangka kerja tercepat yang tersedia (sejujurnya yang tercepat adalah Yaf, tetapi ini adalah kerangka kerja mikro dan memiliki fungsionalitas yang jauh lebih terbatas daripada Phalcon). PhalconPHP tidak memerlukan operasi yang lama dengan file PHP dan tidak perlu diinterpretasikan pada setiap permintaan—itu dimuat ke dalam RAM sekali ketika server web Anda dimulai dan mengkonsumsi sumber daya yang sangat sedikit.

Kerangka kerja MVC telah dianggap sebagai praktik terbaik dalam pengembangan web untuk waktu yang lama—sekarang ini adalah semacam standar profesional, sehingga sebagian besar pengembang web sudah familiar dengan setidaknya satu kerangka kerja MVC untuk PHP: Symfony, Yii, Laravel, CodeIgniter, Zend Kerangka kerja, dll. Mereka masing-masing memiliki kelebihan dan kekurangan, tetapi apa kesamaan mereka? Semuanya ditulis dalam PHP dan terdiri dari banyak file PHP yang disertakan dengan sejumlah besar logika yang harus dijalankan oleh juru bahasa pada setiap permintaan, setiap kali kode Anda berjalan. Meskipun ini menghasilkan transparansi yang luar biasa, kami membayar dengan kinerja. Sejumlah besar kode dan banyak file yang disertakan menghabiskan banyak memori dan waktu, terutama di PHP (karena itu ditafsirkan, tidak dikompilasi). Ya, situasinya telah menjadi jauh lebih baik di PHP 7, tetapi masih banyak yang harus diperbaiki, dan PhalconPHP membawa perbaikan tersebut ke meja.

Mari kita lihat beberapa benchmark.

Tolok Ukur PhalconPHP

Tolok ukur resmi sudah berumur lima tahun—terlalu tua untuk valid sekarang, tetapi meskipun demikian Anda dapat melihat secara dramatis apa yang membedakan PhalconPHP. Mari kita lihat sesuatu yang lebih baru. Dalam perbandingan 2016, Phalcon menempatkan lima besar — ​​pemimpin yang jelas di antara kerangka kerja profesional, dan hanya mengakui PHP mentah dan beberapa kerangka kerja mikro.

Tolok ukur PhalconPHP

Jadi, Phalcon cepat. PHP mentah juga cepat, tetapi kami membutuhkan semua fasilitas yang ditawarkan kerangka kerja MVC, dan Phalcon menjawab tantangan tersebut, termasuk komponen seperti:

  • ORM
  • Mesin templat volt
  • Kontainer Injeksi Ketergantungan (DI)
  • Cache
  • Pencatatan
  • Sistem perutean
  • Blok keamanan
  • Pemuat otomatis
  • Modul formulir

Itu hanya untuk beberapa nama. Singkatnya, PhalconPHP memiliki semua yang Anda butuhkan untuk membangun aplikasi perusahaan besar seperti RESTful API untuk sistem beban tinggi.

Satu hal lagi yang menyenangkan tentang Phalcon adalah gayanya yang kecil—bandingkan saja Phalcon ORM dan Doctrine 2 yang sangat besar.

Mari kita lihat pembuatan proyek PhalconPHP.

Dua Jenis Proyek Phalcon: Tumpukan Penuh dan Mikro

Secara umum, ada dua jenis kerangka kerja MVC: kerangka kerja tumpukan penuh (seperti Symfony, Yii) dan kerangka kerja mikro (seperti Lumen, Slim, Silex).

Kerangka kerja tumpukan penuh adalah pilihan yang baik untuk proyek besar karena menyediakan lebih banyak fungsionalitas, tetapi membutuhkan sedikit lebih banyak kualifikasi dan waktu untuk dijalankan.

Kerangka kerja mikro memungkinkan Anda membuat prototipe ringan dengan sangat cepat, tetapi tidak memiliki fungsionalitas, sehingga umumnya lebih baik untuk menghindari menggunakannya untuk proyek besar. Namun, satu keuntungan dari kerangka kerja mikro adalah kinerjanya. Mereka biasanya jauh lebih cepat daripada yang full-stack (misalnya, kerangka kerja Yaf lebih rendah kinerjanya hanya untuk PHP mentah).

PhalconPHP mendukung keduanya: Anda dapat membuat aplikasi full-stack atau mikro. Lebih baik lagi, ketika Anda mengembangkan proyek Anda di PhalconPHP sebagai aplikasi mikro, Anda masih memiliki akses ke sebagian besar fitur canggih Phalcon dan kinerjanya tetap lebih cepat daripada sebagai aplikasi full-stack.

Di pekerjaan sebelumnya, tim saya perlu membangun sistem RESTful dengan beban tinggi. Salah satu hal yang kami lakukan adalah membandingkan kinerja prototipe antara aplikasi full-stack di Phalcon dan aplikasi mikro Phalcon. Kami menemukan bahwa aplikasi mikro PhalconPHP cenderung jauh lebih cepat. Saya tidak dapat menunjukkan tolok ukur apa pun karena alasan NDA, tetapi menurut saya, jika Anda ingin memaksimalkan kinerja Phalcon, gunakan aplikasi mikro. Meskipun kurang nyaman untuk membuat kode aplikasi mikro daripada yang full-stack, aplikasi mikro PhalconPHP masih memiliki semua yang mungkin Anda perlukan untuk proyek Anda dan kinerja yang lebih baik. Sebagai ilustrasi, mari kita tulis aplikasi mikro RESTful yang sangat sederhana di Phalcon.

Membangun RESTful API

Hampir semua permutasi dari aplikasi RESTful memiliki satu kesamaan: entitas User . Jadi, untuk contoh proyek kami, kami akan membuat aplikasi REST kecil untuk membuat, membaca, memperbarui, dan menghapus pengguna, (juga dikenal sebagai CRUD).

Anda dapat melihat proyek ini selesai sepenuhnya di repositori GitLab saya. Ada dua cabang di sana karena saya memutuskan untuk membagi proyek ini menjadi dua bagian: cabang pertama, master , hanya berisi fungsionalitas dasar tanpa fitur PhalconPHP tertentu, sedangkan cabang kedua, logging-and-cache , berisi fungsi logging dan caching Phalcon. Anda dapat membandingkannya dan melihat betapa mudahnya mengimplementasikan fungsi tersebut di Phalcon.

Instalasi

Saya tidak akan membahas instalasi: Anda dapat menggunakan basis data apa pun, sistem operasi apa pun, dan server web apa pun yang Anda inginkan. Ini dijelaskan dengan baik dalam dokumentasi pemasangan resmi, jadi ikuti saja petunjuknya tergantung pada sistem operasi Anda.

Catatan instalasi server web juga tersedia di dokumentasi resmi Phalcon.

Perhatikan bahwa versi PHP Anda tidak boleh kurang dari 5.6.

Saya menggunakan Ubuntu 16.10, PostgreSQL 9.5.6, Nginx 1.10.0, PHP 7, dan Phalcon 3.0. Saya telah menyertakan contoh konfigurasi Nginx dan file dump PostgreSQL dalam proyek, jadi jangan ragu untuk menggunakannya. Jika Anda lebih suka konfigurasi lain, tidak akan sulit untuk mengubahnya.

Struktur dan Konfigurasi Proyek

Pertama-tama, buat struktur proyek awal.

Sementara Phalcon memungkinkan Anda menggunakan struktur apa pun yang Anda suka, struktur yang saya pilih untuk latihan ini sebagian mengimplementasikan pola-MVC. Kami tidak memiliki tampilan karena ini adalah proyek RESTful, tetapi kami memiliki pengontrol dan model, masing-masing dengan folder dan layanannya sendiri. Layanan adalah kelas yang mengimplementasikan logika bisnis proyek, membagi bagian "model" dari MVC menjadi dua bagian: model data (yang berkomunikasi dengan database) dan model logika bisnis.

index.php , terletak di folder public , adalah file bootstrap yang memuat semua bagian dan konfigurasi yang diperlukan. Perhatikan bahwa semua file konfigurasi kami ditempatkan di folder config . Kita bisa meletakkan ini di file bootstrap (dan ini adalah cara yang ditunjukkan dalam dokumentasi resmi), tetapi, menurut saya, ini tidak dapat dibaca di proyek-proyek besar, jadi saya lebih suka pemisahan folder dari awal.

Membuat index.php

Pass pertama kita di index.php akan memuat kelas konfigurasi dan autoload, kemudian menginisialisasi rute, wadah injeksi dependensi, dan aplikasi mikro PhalconPHP. Ini kemudian akan memberikan kontrol ke inti aplikasi mikro itu, yang akan menangani permintaan sesuai dengan rute, menjalankan logika bisnis, dan mengembalikan hasil.

Mari kita lihat kodenya:

 <?php try { // Loading Configs $config = require(__DIR__ . '/../app/config/config.php'); // Autoloading classes require __DIR__ . '/../app/config/loader.php'; // Initializing DI container /** @var \Phalcon\DI\FactoryDefault $di */ $di = require __DIR__ . '/../app/config/di.php'; // Initializing application $app = new \Phalcon\Mvc\Micro(); // Setting DI container $app->setDI($di); // Setting up routing require __DIR__ . '/../app/config/routes.php'; // Making the correct answer after executing $app->after( function () use ($app) { // Returning a successful response } ); // Processing request $app->handle(); } catch (\Exception $e) { // Returning an error response }

Mengonfigurasi Objek \Phalcon\Config

Ada beberapa cara untuk menyimpan file konfigurasi di Phalcon:

  • File YAML
  • File JSON
  • Sebuah file INI
  • Sebuah array PHP

Menyimpan konfigurasi Anda dalam array PHP adalah opsi tercepat, dan karena kami sedang menulis aplikasi beban tinggi dan tidak perlu memperlambat kinerja kami, inilah yang akan kami lakukan. Secara khusus, kami akan menggunakan objek \Phalcon\Config untuk memuat opsi konfigurasi kami ke dalam proyek. Kami akan memiliki objek konfigurasi yang sangat singkat:

 <?php return new \Phalcon\Config( [ 'database' => [ 'adapter' => 'Postgresql', 'host' => 'localhost', 'port' => 5432, 'username' => 'postgres', 'password' => '12345', 'dbname' => 'articledemo', ], 'application' => [ 'controllersDir' => "app/controllers/", 'modelsDir' => "app/models/", 'baseUri' => "/", ], ] );

File ini berisi dua konfigurasi dasar, satu untuk database dan satu untuk aplikasi. Jelas, konfigurasi database digunakan untuk menghubungkan ke database, dan untuk array application , kita akan membutuhkannya nanti karena digunakan oleh alat sistem Phalcon. Anda dapat membaca tentang konfigurasi Phalcon lebih detail di dokumentasi resmi.

Mengonfigurasi loader.php

Mari kita lihat file konfigurasi berikutnya, loader.php . File loader.php mendaftarkan ruang nama dengan direktori yang sesuai melalui objek \Phalcon\Loader . Ini bahkan lebih sederhana:

 <?php $loader = new \Phalcon\Loader(); $loader->registerNamespaces( [ 'App\Services' => realpath(__DIR__ . '/../services/'), 'App\Controllers' => realpath(__DIR__ . '/../controllers/'), 'App\Models' => realpath(__DIR__ . '/../models/'), ] ); $loader->register();

Sekarang semua kelas dari ruang nama ini akan dimuat dan tersedia secara otomatis. Jika Anda ingin menambahkan namespace dan direktori baru, cukup tambahkan satu baris di file ini. Anda juga dapat menghindari penggunaan ruang nama dengan mendaftarkan direktori tertentu atau file tertentu. Semua kemungkinan ini dijelaskan dalam dokumentasi pemuat PhalconPHP.

Mengonfigurasi Wadah Injeksi Ketergantungan

Seperti banyak kerangka kerja kontemporer lainnya, Phalcon mengimplementasikan pola injeksi ketergantungan (DI). Objek akan diinisialisasi dalam wadah DI dan dapat diakses darinya. Demikian pula, wadah DI terhubung ke objek aplikasi, dan itu akan dapat diakses dari semua kelas yang mewarisi dari kelas \Phalcon\DI\Injectable , seperti pengontrol dan layanan kami.

Pola DI Phalcon sangat kuat. Saya menganggap komponen ini sebagai salah satu yang paling penting dalam kerangka kerja ini dan saya sangat menyarankan Anda untuk membaca seluruh dokumentasinya untuk memahami cara kerjanya. Ini menyediakan kunci untuk banyak fungsi Phalcon.

Mari kita lihat beberapa di antaranya. File di.php kita akan terlihat seperti ini:

 <?php use Phalcon\Db\Adapter\Pdo\Postgresql; // Initializing a DI Container $di = new \Phalcon\DI\FactoryDefault(); /** * Overriding Response-object to set the Content-type header globally */ $di->setShared( 'response', function () { $response = new \Phalcon\Http\Response(); $response->setContentType('application/json', 'utf-8'); return $response; } ); /** Common config */ $di->setShared('config', $config); /** Database */ $di->set( "db", function () use ($config) { return new Postgresql( [ "host" => $config->database->host, "username" => $config->database->username, "password" => $config->database->password, "dbname" => $config->database->dbname, ] ); } ); return $di;

Seperti yang Anda lihat, file injeksi ketergantungan (DI) kami sedikit lebih rumit dan ada beberapa hal khusus yang harus Anda ketahui. Pertama, pertimbangkan string inisialisasi: $di = new \Phalcon\DI\FactoryDefault(); . Kami membuat objek FactoryDefault yang mewarisi \Phalcon\Di (Phalcon memungkinkan Anda untuk membuat pabrik DI yang Anda inginkan). Menurut dokumentasi, FactoryDefault “secara otomatis mendaftarkan semua layanan yang disediakan oleh kerangka kerja. Berkat ini, pengembang tidak perlu mendaftarkan setiap layanan satu per satu untuk menyediakan kerangka kerja tumpukan penuh.” Ini berarti bahwa layanan umum seperti Request dan Response akan dapat diakses dalam kelas kerangka kerja. Anda dapat melihat daftar lengkap layanan tersebut di dokumentasi layanan Phalcon.

Hal penting berikutnya adalah proses setting: Ada beberapa cara untuk mendaftarkan sesuatu ke dalam container DI, dan semuanya dijelaskan secara lengkap pada dokumentasi registrasi PhalconPHP. Namun, dalam proyek kami, kami menggunakan tiga cara: fungsi anonim, variabel, dan string.

Fungsi anonim memungkinkan kita untuk melakukan banyak hal saat menginisialisasi kelas. Dalam proyek ini secara khusus, pertama-tama kita menimpa objek Response untuk menetapkan tipe content-type sebagai JSON untuk semua respons proyek dan kemudian menginisialisasi adaptor database, menggunakan objek konfigurasi kita.

Seperti yang saya sebutkan sebelumnya, proyek ini menggunakan PostgreSQL. Jika Anda memutuskan untuk menggunakan mesin database lain, cukup ubah adaptor database di fungsi set db . Anda dapat membaca lebih lanjut tentang adaptor database yang tersedia dan tentang lapisan database di dokumentasi database PhalconPHP.

Hal ketiga yang perlu diperhatikan adalah saya mendaftarkan variabel $config yang mengimplementasikan layanan \Phalcon\Config . Meskipun sebenarnya tidak digunakan dalam proyek contoh kami, saya telah memutuskan untuk memasukkannya di sini karena ini adalah salah satu layanan yang paling umum digunakan; proyek lain mungkin memerlukan akses ke konfigurasi hampir di semua tempat.

Hal terakhir yang menarik di sini adalah metode setShared yang sebenarnya. Menyebut ini membuat layanan "bersama", yang berarti mulai bertindak seperti seorang lajang. Menurut dokumentasi: "Setelah layanan diselesaikan untuk pertama kalinya, instance yang sama dikembalikan setiap kali konsumen mengambil layanan dari wadah."

Mengkonfigurasi routes.php …atau Tidak

File terakhir yang disertakan adalah routes.php . Mari kita biarkan kosong untuk saat ini—kita akan mengisinya bersama controller kita.

Menerapkan Inti RESTful

Apa yang membuat proyek web RESTful? Menurut Wikipedia, ada tiga bagian utama dari aplikasi RESTful: - URL dasar - Jenis media internet yang mendefinisikan elemen data transisi status - Metode HTTP standar ( GET , POST , PUT , DELETE ) dan kode respons HTTP standar (200, 403, 400, 500, dll).

Dalam proyek kami, URL dasar akan ditempatkan ke dalam file routes.php dan poin-poin lain yang disebutkan akan dijelaskan sekarang.

Kami akan menerima data permintaan sebagai application/x-www-form-urlencoded dan mengirim data respons sebagai application/json . Meskipun saya tidak percaya itu adalah ide yang baik untuk menggunakan x-www-form-urlencoded dalam aplikasi nyata (karena Anda akan berjuang untuk mengirim struktur data yang kompleks dan array asosiatif dengan x-www-form-urlencoded ), saya sudah memutuskan untuk menerapkan standar ini demi kesederhanaan.

Jika Anda ingat, kami telah mengatur header JSON respons kami di file DI kami:

 $di->setShared( 'response', function () { $response = new \Phalcon\Http\Response(); $response->setContentType('application/json', 'utf-8'); return $response; } );

Sekarang kita harus menyiapkan kode respons dan format respons. Tutorial resmi menyarankan agar kami membentuk respons JSON di setiap metode, tetapi menurut saya itu bukan ide yang bagus. Jauh lebih universal untuk mengembalikan hasil metode pengontrol sebagai array dan kemudian mengubahnya menjadi respons JSON standar. Juga lebih bijaksana untuk membentuk kode respons HTTP di satu tempat dalam proyek; kita akan melakukan ini di file index.php kita.

Untuk melakukan ini, kita akan menggunakan kemampuan Phalcon untuk mengeksekusi kode sebelum dan sesudah penanganan permintaan dengan metode $app->before() dan $app->after() . Kami akan menempatkan panggilan balik dalam metode $app->after() untuk tujuan kami:

 // Making the correct answer after executing $app->after( function () use ($app) { // Getting the return value of method $return = $app->getReturnedValue(); if (is_array($return)) { // Transforming arrays to JSON $app->response->setContent(json_encode($return)); } elseif (!strlen($return)) { // Successful response without any content $app->response->setStatusCode('204', 'No Content'); } else { // Unexpected response throw new Exception('Bad Response'); } // Sending response to the client $app->response->send(); }

Di sini kita mendapatkan nilai pengembalian dan mengubah array ke JSON. Jika semuanya baik-baik saja, tetapi nilai pengembaliannya kosong (misalnya, jika kami berhasil menambahkan pengguna baru), kami akan memberikan kode HTTP 204 dan tidak mengirim konten. Dalam semua kasus lain, kami melempar pengecualian.

Menangani Pengecualian

Salah satu aspek terpenting dari aplikasi RESTful adalah respons yang benar dan informatif. Aplikasi dengan beban tinggi biasanya berukuran besar, dan berbagai jenis kesalahan dapat terjadi di mana-mana: kesalahan validasi, kesalahan akses, kesalahan koneksi, kesalahan tak terduga, dll. Kami ingin mengubah semua kesalahan ini menjadi kode respons HTTP terpadu. Itu dapat dengan mudah dilakukan dengan bantuan pengecualian.

Dalam proyek saya, saya telah memutuskan untuk menggunakan dua jenis pengecualian yang berbeda: ada pengecualian "lokal" - kelas khusus yang diwarisi dari kelas \RuntimeException , dipisahkan oleh layanan, model, adaptor, dan sebagainya (divisi semacam itu membantu menangani setiap level dari model MVC sebagai yang terpisah)- lalu ada HttpExceptions , yang diwarisi dari kelas AbstractHttpException . Pengecualian ini konsisten dengan kode respons HTTP, jadi namanya adalah Http400Exception , Http500Exception , dan seterusnya.

Kelas AbstractHttpException memiliki tiga properti: httpCode , httpMessage dan appError . Dua properti pertama ditimpa dalam pewarisnya dan berisi informasi respons dasar seperti httpCode: 400 dan httpMessage: Bad request . Properti appError adalah larik informasi kesalahan terperinci, termasuk deskripsi kesalahan.

Versi terakhir dari index.php akan menangkap tiga jenis pengecualian: AbstractHttpExceptions , seperti dijelaskan di atas; Pengecualian Permintaan Phalcon, yang mungkin terjadi saat menguraikan permintaan; dan semua pengecualian tak terduga lainnya. Semuanya dikonversi ke format JSON yang cantik dan dikirim ke klien melalui kelas Respons Phalcon standar:

 <?php use App\Controllers\AbstractHttpException; try { // Loading Configs $config = require(__DIR__ . '/../app/config/config.php'); // Autoloading classes require __DIR__ . '/../app/config/loader.php'; // Initializing DI container /** @var \Phalcon\DI\FactoryDefault $di */ $di = require __DIR__ . '/../app/config/di.php'; // Initializing application $app = new \Phalcon\Mvc\Micro(); // Setting DI container $app->setDI($di); // Setting up routing require __DIR__ . '/../app/config/routes.php'; // Making the correct answer after executing $app->after( // After Code ); // Processing request $app->handle(); } catch (AbstractHttpException $e) { $response = $app->response; $response->setStatusCode($e->getCode(), $e->getMessage()); $response->setJsonContent($e->getAppError()); $response->send(); } catch (\Phalcon\Http\Request\Exception $e) { $app->response->setStatusCode(400, 'Bad request') ->setJsonContent([ AbstractHttpException::KEY_CODE => 400, AbstractHttpException::KEY_MESSAGE => 'Bad request' ]) ->send(); } catch (\Exception $e) { // Standard error format $result = [ AbstractHttpException::KEY_CODE => 500, AbstractHttpException::KEY_MESSAGE => 'Some error occurred on the server.' ]; // Sending error response $app->response->setStatusCode(500, 'Internal Server Error') ->setJsonContent($result) ->send(); }

Membuat Model dengan Phalcon Dev Tools

Jika Anda menggunakan IDE kontemporer, Anda mungkin terbiasa dengan penyorotan dan penyelesaian kode. Demikian juga, dalam kerangka kerja PHP biasa, Anda dapat menyertakan folder dengan kerangka kerja untuk membuka deklarasi fungsi hanya dalam satu klik. Melihat Phalcon adalah ekstensi, kami tidak mendapatkan opsi ini secara otomatis. Untungnya, ada alat yang mengisi celah ini yang disebut "Phalcon Dev Tools," yang dapat diinstal melalui Composer (jika Anda masih tidak tahu apa itu, sekaranglah saatnya untuk mengetahui tentang manajer paket yang luar biasa ini). Phalcon Dev Tools terdiri dari stub kode untuk semua kelas dan fungsi di Phalcon, dan menyediakan beberapa pembuat kode dengan versi konsol dan GUI, didokumentasikan di situs web PhalconPHP. Alat-alat ini dapat membantu membuat semua bagian dari pola MVC, tetapi kami hanya akan membahas pembuatan model.

Oke langsung saja kita install Phalcon Dev Tools melalui Composer. File composer.json kami akan terlihat seperti ini:

 { "require": { "php": ">=5.6.0", "ext-phalcon": ">=3", "ext-pgsql": "*" }, "require-dev": { "phalcon/devtools": "3.*.*@dev" } }

Seperti yang Anda lihat, kami membutuhkan PHP 5.6, Phalcon 3, dan ekstensi pgsql (yang dapat Anda ubah ke ekstensi database Anda atau kecualikan sama sekali).

Pastikan Anda memiliki versi ekstensi PHP, Phalcon, dan DB yang benar, dan jalankan composer:

 $ composer install

Langkah selanjutnya adalah membuat database kita. Ini sangat sederhana dan hanya terdiri dari satu tabel, users . Meskipun saya telah menyertakan file pg_dump dalam proyek, inilah SQL dalam dialek PostgreSQL:

 CREATE DATABASE articledemo; CREATE TABLE public.users ( id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('users_id_seq'::regclass), first_name CHARACTER VARYING(255), last_name CHARACTER VARYING(255), pass CHARACTER VARYING(255), login CHARACTER VARYING(255) NOT NULL );

Setelah database dibuat, kita dapat melanjutkan ke proses pembuatan model. Phalcon Dev Tools menggunakan folder .phalcon kosong untuk mendeteksi apakah suatu aplikasi adalah proyek Phalcon, jadi Anda harus membuat folder kosong ini di root proyek Anda. Ini juga menggunakan beberapa pengaturan dari file konfigurasi yang telah kita buat—semua variabel yang disimpan di bawah bagian application dan adapter dari bagian database . Untuk menghasilkan model kita, kita perlu menjalankan perintah berikut dari folder root proyek:

 $ php vendor/phalcon/devtools/phalcon.php model users --namespace="App\Models" --get-set

Jika semua langkah sebelumnya telah dilakukan dengan benar, Anda akan mendapatkan file model kerja, Users.php , di folder models Anda, sudah ditempatkan di namespace dengan getter dan setter sebagai baris perintah yang ditunjukkan. Selanjutnya adalah pengontrol.

Pengontrol dan Perutean

Karena aplikasi kami hanya CRUD (membuat, membaca, memperbarui, dan menghapus) pengguna, kami hanya akan membuat satu pengontrol, pengontrol Users dengan operasi berikut:

  • Tambahkan pengguna
  • Tampilkan daftar pengguna
  • Perbarui pengguna
  • Hapus pengguna

Meskipun pengontrol dapat dibuat dengan bantuan Phalcon Dev Tools, kita akan melakukannya secara manual dan mengimplementasikan AbstractController dan anaknya, UsersController .

Membuat AbstractController adalah keputusan yang baik untuk Phalcon karena kita dapat menempatkan semua kelas yang diperlukan yang akan kita dapatkan dari injeksi ketergantungan ke dalam blok PHPDoc. Ini akan membantu dengan fungsi pelengkapan otomatis IDE. Kami juga dapat memprogram dalam beberapa konstanta kesalahan yang umum untuk semua pengontrol potensial.

Untuk saat ini, pengontrol abstrak kami akan terlihat seperti ini:

 <?php namespace App\Controllers; /** * Class AbstractController * * @property \Phalcon\Http\Request $request * @property \Phalcon\Http\Response $htmlResponse * @property \Phalcon\Db\Adapter\Pdo\Postgresql $db * @property \Phalcon\Config $config * @property \App\Services\UsersService $usersService * @property \App\Models\Users $user */ abstract class AbstractController extends \Phalcon\DI\Injectable { /** * Route not found. HTTP 404 Error */ const ERROR_NOT_FOUND = 1; /** * Invalid Request. HTTP 400 Error. */ const ERROR_INVALID_REQUEST = 2; }

Hanya kelas injeksi Phalcon sederhana, seperti yang ditentukan oleh extends ekstensi, tidak lebih. Selanjutnya, mari buat kerangka UsersController :

 <?php namespace App\Controllers; /** * Operations with Users: CRUD */ class UsersController extends AbstractController { /** * Adding user */ public function addAction() { } /** * Returns user list * * @return array */ public function getUserListAction() { } /** * Updating existing user * * @param string $userId */ public function updateUserAction($userId) { } /** * Delete an existing user * * @param string $userId */ public function deleteUserAction($userId) { } }

Saat ini, itu hanya kelas dengan tindakan kosong yang pada akhirnya akan menampung permintaan HTTP yang sesuai.

Sekarang saatnya mengisi file routes.php . Dalam aplikasi mikro Phalcon kami membuat koleksi, satu untuk setiap pengontrol, dan menambahkan semua permintaan yang ditangani sebagai metode get , post , put , delete , yang mengambil pola rute dan fungsi lanjutan sebagai argumen. Perhatikan bahwa fungsi lanjutan harus berupa fungsi anonim atau nama metode pengontrol. Berikut tampilan file routes.php kami:

 <?php $usersCollection = new \Phalcon\Mvc\Micro\Collection(); $usersCollection->setHandler('\App\Controllers\UsersController', true); $usersCollection->setPrefix('/user'); $usersCollection->post('/add', 'addAction'); $usersCollection->get('/list', 'getUserListAction'); $usersCollection->put('/{userId:[1-9][0-9]*}', 'updateUserAction'); $usersCollection->delete('/{userId:[1-9][0-9]*}', 'deleteUserAction'); $app->mount($usersCollection); // not found URLs $app->notFound( function () use ($app) { $exception = new \App\Controllers\HttpExceptions\Http404Exception( _('URI not found or error in request.'), \App\Controllers\AbstractController::ERROR_NOT_FOUND, new \Exception('URI not found: ' . $app->request->getMethod() . ' ' . $app->request->getURI()) ); throw $exception; } );

Kami juga mengatur pengontrol penanganan dan awalan URI. Sebagai contoh kita, URI akan terlihat seperti http://article.dev/user/add , dan itu harus berupa permintaan post . Jika kita ingin mengubah data pengguna, URI harus berupa permintaan put dan akan terlihat seperti http://article.dev/user/12 untuk mengubah data untuk pengguna dengan ID 12 . Kami juga mendefinisikan penangan URL yang tidak ditemukan, yang menimbulkan kesalahan. Untuk informasi lebih lanjut, lihat dokumentasi PhalconPHP untuk rute dalam aplikasi tumpukan penuh, dan untuk rute dalam aplikasi mikro.

Mari pindah ke badan pengontrol, dan khususnya metode addAction (semua yang lain serupa; Anda dapat melihatnya di kode aplikasi). Metode pengontrol melakukan lima hal:

  1. Mendapatkan dan memvalidasi parameter permintaan
  2. Menyiapkan data untuk metode layanan
  3. Memanggil metode layanan
  4. Menangani pengecualian
  5. Mengirim tanggapan

Mari kita telusuri setiap langkah, dimulai dengan validasi. Meskipun Phalcon memiliki komponen validasi yang kuat, dalam kasus ini jauh lebih bijaksana untuk memvalidasi data dengan cara gaya lama, jadi, blok validasi kita akan terlihat seperti ini:

 $errors = []; $data = []; $data['login'] = $this->request->getPost('login'); if (!is_string($data['login']) || !preg_match('/^[A-z0-9_-]{3,16}$/', $data['login'])) { $errors['login'] = 'Login must consist of 3-16 latin symbols, numbers or \'-\' and \'_\' symbols'; }

Di sini kita memeriksa apakah parameter post adalah string yang cocok dengan ekspresi reguler. Semua nilai dimasukkan ke dalam array $data , yang kemudian diteruskan ke kelas UsersService . Semua kesalahan ditempatkan ke dalam larik $errors , yang kemudian ditambahkan ke larik detail kesalahan di dalam Http400Exception , di mana ia akan diubah menjadi respons terperinci yang terlihat di index.php :

Berikut adalah kode metode addAction lengkap dengan semua validasinya, yang mencakup panggilan ke metode createUser di UsersService (yang belum kami buat):

 public function addAction() { /** Init Block **/ $errors = []; $data = []; /** End Init Block **/ /** Validation Block **/ $data['login'] = $this->request->getPost('login'); if (!is_string($data['login']) || !preg_match('/^[A-z0-9_-]{3,16}$/', $data['login'])) { $errors['login'] = 'Login must consist of 3-16 latin symbols, numbers or \'-\' and \'_\' symbols'; } $data['password'] = $this->request->getPost('password'); if (!is_string($data['password']) || !preg_match('/^[A-z0-9_-]{6,18}$/', $data['password'])) { $errors['password'] = 'Password must consist of 6-18 latin symbols, numbers or \'-\' and \'_\' symbols'; } $data['first_name'] = $this->request->getPost('first_name'); if ((!empty($data['first_name'])) && (!is_string($data['first_name']))) { $errors['first_name'] = 'String expected'; } $data['last_name'] = $this->request->getPost('last_name'); if ((!empty($data['last_name'])) && (!is_string($data['last_name']))) { $errors['last_name'] = 'String expected'; } if ($errors) { $exception = new Http400Exception(_('Input parameters validation error'), self::ERROR_INVALID_REQUEST); throw $exception->addErrorDetails($errors); } /** End Validation Block **/ /** Passing to business logic and preparing the response **/ try { $this->usersService->createUser($data); } catch (ServiceException $e) { switch ($e->getCode()) { case AbstractService::ERROR_ALREADY_EXISTS: case UsersService::ERROR_UNABLE_CREATE_USER: throw new Http422Exception($e->getMessage(), $e->getCode(), $e); default: throw new Http500Exception(_('Internal Server Error'), $e->getCode(), $e); } } /** End Passing to business logic and preparing the response **/ }

Seperti yang Anda lihat, kami menangani dua pengecualian yang diketahui di bagian terakhir: user already exists dan unable to create user karena beberapa masalah internal seperti kesalahan koneksi database. Secara default, pengecualian yang tidak diketahui akan ditampilkan sebagai HTTP 500 (kesalahan server internal). Meskipun kami tidak memberikan detail apa pun kepada pengguna akhir, sangat disarankan untuk menyimpan semua detail kesalahan (termasuk jejak) di log.

Dan, tolong, jangan lupa untuk use semua kelas yang diperlukan, yang dipinjam dari ruang nama lain:

 use App\Controllers\HttpExceptions\Http400Exception; use App\Controllers\HttpExceptions\Http422Exception; use App\Controllers\HttpExceptions\Http500Exception; use App\Services\AbstractService; use App\Services\ServiceException; use App\Services\UsersService;

Logika bisnis

Bagian terakhir yang harus dibuat adalah logika bisnis. Sama seperti dengan pengontrol, kami akan membuat kelas layanan abstrak:

 <?php namespace App\Services; /** * Class AbstractService * * @property \Phalcon\Db\Adapter\Pdo\Postgresql $db * @property \Phalcon\Config $config */ abstract class AbstractService extends \Phalcon\DI\Injectable { /** * Invalid parameters anywhere */ const ERROR_INVALID_PARAMETERS = 10001; /** * Record already exists */ const ERROR_ALREADY_EXISTS = 10002; }

Idenya benar-benar sama seperti di blok pengontrol, jadi saya tidak akan mengomentarinya. Inilah kerangka kelas UsersService kami:

 <?php namespace App\Services; use App\Models\Users; /** * business logic for users * * Class UsersService */ class UsersService extends AbstractService { /** Unable to create user */ const ERROR_UNABLE_CREATE_USER = 11001; /** * Creating a new user * * @param array $userData */ public function createUser(array $userData) { } }

Dan metode createUser itu sendiri:

 public function createUser(array $userData) { try { $user = new Users(); $result = $user->setLogin($userData['login']) ->setPass(password_hash($userData['password'], PASSWORD_DEFAULT)) ->setFirstName($userData['first_name']) ->setLastName($userData['last_name']) ->create(); if (!$result) { throw new ServiceException('Unable to create user', self::ERROR_UNABLE_CREATE_USER); } } catch (\PDOException $e) { if ($e->getCode() == 23505) { throw new ServiceException('User already exists', self::ERROR_ALREADY_EXISTS, $e); } else { throw new ServiceException($e->getMessage(), $e->getCode(), $e); } } }

Metode ini semudah mungkin. Kami baru saja membuat objek model baru, memanggil setternya (yang mengembalikan objek itu sendiri; ini memungkinkan kami untuk membuat rantai panggilan) dan melemparkan ServiceException jika terjadi kesalahan. Itu dia! Sekarang kita dapat melanjutkan ke pengujian.

Pengujian

Sekarang mari kita lihat hasilnya menggunakan Postman. Mari kita uji beberapa data sampah terlebih dahulu:

Tukang pos dengan data yang tidak valid.

Meminta:

 POST http://article.dev/user/add login:1 password:1 first_name:Name last_name:Sourname

Tanggapan (400: Permintaan Buruk):

 { "error": 2, "error_description": "Input parameters validation error", "details": { "login": "Login must consist of 3-16 latin symbols, numbers or '-' and '_' symbols", "password": "Password must consist of 6-18 latin symbols, numbers or '-' and '_' symbols" } }

Itu memeriksa. Sekarang untuk beberapa data yang benar:

Tukang pos dengan data yang valid.

Meminta:

 POST http://article.dev/user/add login:user4 password:password4 first_name:Name last_name:Sourname

Tanggapan (204):

Tidak ada konten, itulah yang kami harapkan. Now let's make sure it worked and get the full user list (which we didn't describe in the article, but you can see it in the application example):

Request:

 GET http://article.dev/user/list

Response (200 OK):

 [ { "id": 1, "login": "user4", "first_name": "Name", "last_name": "Sourname" } ]

Well, it works!

Logging and Caching

It's hard to imagine a high-load application without logging and caching, and Phalcon provides very seductive classes for it. But I'm writing an article here, not a book; I've added logging and caching to the sample application, but I've placed this code into another branch called logging-and-cache so you can easily look at it and see the difference in the code. Just like the other Phalcon features, these two are well-documented: Logging and Caching.

Kekurangan

As you can see, Phalcon is really cool, but like other frameworks, it has its disadvantages, the first of which is the same as its main advantage—it's a compiled C extension. That's why there is no way for you to change its code easily. Well, if you know C, you can try to understand its code and make some changes, run make and get your own modification of Phalcon, but it is much more complicated than making some tweaks in PHP code. So, generally, if you find a bug inside Phalcon, it won't be so easy to fix.

This is partially solved in Phalcon 2 and Phalcon 3, which let you write extensions to Phalcon in Zephir. Zephir is a programming language designed to ease the creation and maintainability of extensions for PHP with a focus on type and memory safety. Its syntax is very close to PHP and Zephir code is compiled into shared libraries, same as the PHP extension. So, if you want to enhance Phalcon, now you can.

The second disadvantage is the free framework structure. While Symfony makes developers use a firm project structure, Phalcon has very few strict rules; developers can create any structure they like, though there is a structure that is recommended by its authors. This isn't a critical disadvantage, but some people may consider it too raw when you write the paths to all the directories in a bootstrap file manually.

PhalconPHP: Not Just For High-load Apps

I hope you've enjoyed this brief overview of PhalconPHP's killing features and the accompanying simple example of a Phalcon project. Obviously, I didn't cover all the possibilities of this framework since it's impossible to describe all of them in one article, but fortunately Phalcon has brilliantly detailed documentation with seven marvelous tutorials which help you understand almost everything about Phalcon.

You've now got a brand new way to create high load applications easily, and you'll find, if you like Phalcon, it can be a good choice for other types of applications too.