PhalconPHP: O soluție pentru API-uri RESTful cu încărcare mare

Publicat: 2022-03-11

Să presupunem că trebuie să creați un proiect cu încărcare mare bazat pe un cadru PHP MVC. Probabil ați folosi memorarea în cache ori de câte ori este posibil. Poate ați construi proiectul într-un singur fișier sau poate chiar ați scrie propriul cadru MVC cu funcționalitate minimă sau ați rescrie unele părți ale altui cadru. În timp ce, da, funcționează, este puțin cam complicat, nu-i așa? Din fericire, mai există o soluție care face ca majoritatea acestor manipulări să fie inutile (cu excepția cache-ului, poate), iar această soluție se numește framework PhalconPHP.

Ce este PhalconPHP?

PhalconPHP este un cadru MVC pentru PHP scris în C și furnizat ca extensie PHP compilată. Acesta este ceea ce îl face unul dintre cele mai rapide cadre disponibile (ca să fiu complet sincer, cel mai rapid este Yaf, dar este un micro cadru și are o funcționalitate mult, mult mai limitată decât Phalcon). PhalconPHP nu are nevoie de operațiuni lungi cu fișiere PHP și nu trebuie să fie interpretat la fiecare solicitare - este încărcat în RAM o dată când serverul dvs. web este pornit și consumă foarte puține resurse.

Cadrele MVC au fost considerate de multă vreme cea mai bună practică în dezvoltarea web - până acum este un fel de standard profesional, așa că majoritatea dezvoltatorilor web sunt familiarizați cu cel puțin un cadru MVC pentru PHP: Symfony, Yii, Laravel, CodeIgniter, Zend. Cadrul, etc. Fiecare are propriile avantaje și dezavantaje, dar ce au toate în comun? Toate sunt scrise în PHP și constau din multe fișiere PHP incluse cu o cantitate uriașă de logică care trebuie să fie rulată de interpret la fiecare solicitare, de fiecare dată când rulează codul. În timp ce acest lucru asigură o mare transparență, plătim cu performanță. Cantități mari de cod și multe fișiere incluse costă multă memorie și timp, mai ales în PHP (deoarece este interpretat, nu compilat). Da, situația a devenit mult mai bună în PHP 7, dar mai sunt multe de îmbunătățit, iar PhalconPHP aduce acele îmbunătățiri pe masă.

Să aruncăm o privire la câteva repere.

Puncte de referință PhalconPHP

Valorile de referință oficiale au cinci ani – prea vechi pentru a fi valabile acum, dar chiar și atunci puteți vedea în mod dramatic ce distinge PhalconPHP. Să ne uităm la ceva mai nou. Într-o comparație din 2016, Phalcon se plasează pe primele cinci - un lider evident printre cadrele profesionale și ceda doar PHP brut și unele micro cadre.

Puncte de referință PhalconPHP

Deci, Falcon este rapid. PHP brut este, de asemenea, rapid, dar avem nevoie de toate facilitățile pe care le oferă un cadru MVC, iar Phalcon răspunde provocării, inclusiv componente precum:

  • ORM
  • Motor șablon Volt
  • Container de injecție de dependență (DI).
  • Memorarea în cache
  • Logare
  • Sisteme de rutare
  • Bloc de securitate
  • Încărcător automat
  • Modul de formulare

Asta pentru a numi câteva. Pe scurt, PhalconPHP are tot ce aveți nevoie pentru a construi o aplicație de întreprindere mare, cum ar fi un API RESTful pentru un sistem cu încărcare mare.

Încă un lucru drăguț despre Phalcon este stilul său mic - comparați doar Phalcon ORM și enormul Doctrine 2.

Să aruncăm o privire la crearea unui proiect PhalconPHP.

Două tipuri de proiecte Falcon: Full-stack și Micro

În general, există două tipuri de cadre MVC: cadre full-stack (cum ar fi Symfony, Yii) și cadre micro (cum ar fi Lumen, Slim, Silex).

Frame-urile full-stack sunt o alegere bună pentru un proiect mare, deoarece oferă mai multe funcționalități, dar au nevoie de puțin mai multă calificare și timp pentru a rula.

Micro cadrele vă permit să creați prototipuri ușoare foarte rapid, dar le lipsește funcționalitatea, așa că este, în general, mai bine să evitați să le folosiți pentru proiecte mari. Un avantaj al micro cadrelor este însă performanța lor. Ele sunt de obicei mult mai rapide decât cele full-stack (de exemplu, framework-ul Yaf este inferior ca performanță doar PHP brut).

PhalconPHP acceptă ambele: puteți crea fie o aplicație full-stack, fie o micro-aplicație. Și mai bine, atunci când vă dezvoltați proiectul în PhalconPHP ca o microaplicație, aveți în continuare acces la majoritatea funcțiilor puternice ale Phalcon, iar performanța sa rămâne mai rapidă decât ca o aplicație full-stack.

La o slujbă anterioară, echipa mea trebuia să construiască un sistem RESTful cu încărcare mare. Unul dintre lucrurile pe care le-am făcut a fost să comparăm performanța prototipului între o aplicație full-stack în Phalcon și o aplicație micro Phalcon. Am descoperit că micro-aplicațiile PhalconPHP tind să fie mult mai rapide. Nu vă pot arăta niciun benchmark din motive NDA, dar după părerea mea, dacă doriți să profitați la maximum de performanța Phalcon, folosiți micro aplicații. Deși este mai puțin convenabil să codificați o microaplicație decât una full-stack, microaplicațiile PhalconPHP au tot ceea ce ați putea avea nevoie pentru proiectul dvs. și performanță mai bună. Pentru a ilustra, să scriem o microaplicație RESTful foarte simplă în Phalcon.

Crearea unui API RESTful

Aproape toate permutările unei aplicații RESTful au un lucru în comun: o entitate User . Deci, pentru proiectul nostru exemplu, vom crea o aplicație REST minusculă pentru a crea, citi, actualiza și șterge utilizatori (cunoscută și sub numele de CRUD).

Puteți vedea acest proiect complet finalizat în depozitul meu GitLab. Există două ramuri acolo, deoarece am decis să împart acest proiect în două părți: prima ramură, master , conține doar funcționalitate de bază fără caracteristici specifice PhalconPHP, în timp ce a doua, logging-and-cache , conține funcționalitatea de logare și cache a lui Phalcon. Puteți să le comparați și să vedeți cât de ușor este să implementați astfel de funcții în Phalcon.

Instalare

Nu voi trece peste instalare: puteți utiliza orice bază de date, orice sistem de operare și orice server web doriți. Este descris bine în documentația oficială de instalare, așa că urmați instrucțiunile în funcție de sistemul dvs. de operare.

Notele de instalare a serverului web sunt, de asemenea, disponibile în documentația oficială Phalcon.

Rețineți că versiunea dvs. PHP nu trebuie să fie mai mică de 5.6.

Folosesc Ubuntu 16.10, PostgreSQL 9.5.6, Nginx 1.10.0, PHP 7 și Phalcon 3.0. Am inclus un eșantion de configurare Nginx și un fișier dump PostgreSQL în proiect, așa că nu ezitați să le utilizați. Dacă preferați o altă configurație, nu va fi dificil să o schimbați.

Structura și configurația proiectului

În primul rând, creați structura inițială a proiectului.

În timp ce Phalcon vă permite să utilizați orice structură doriți, structura pe care am ales-o pentru acest exercițiu implementează parțial un model MVC. Nu avem vizualizări pentru că este un proiect RESTful, dar avem controlere și modele, fiecare cu propriul folder și servicii. Serviciile sunt clasele care implementează logica de afaceri a proiectului, împărțind partea „model” a MVC în două părți: modele de date (care comunică cu baza de date) și modele de logica de afaceri.

index.php , situat în folderul public , este un fișier bootstrap care încarcă toate părțile și configurația necesare. Rețineți că toate fișierele noastre de configurare sunt plasate în folderul de config . Le-am putea pune în fișierul bootstrap (și așa se arată în documentația oficială), dar, în opinia mea, acest lucru devine ilizibil în proiectele mari, așa că prefer separarea folderelor de la bun început.

Se creează index.php

Prima noastră trecere la index.php va încărca clasele de configurare și încărcare automată, apoi va inițializa rutele, un container de injectare a dependenței și microaplicația PhalconPHP. Apoi va transfera controlul asupra acelui nucleu al micro-aplicației, care va gestiona cererile în funcție de rute, va rula logica de afaceri și va returna rezultate.

Să aruncăm o privire la cod:

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

Configurarea unui \Phalcon\Config

Există mai multe moduri de a stoca fișierele de configurare în Phalcon:

  • Un fișier YAML
  • Un fișier JSON
  • Un fișier INI
  • O matrice PHP

Stocarea configurației într-o matrice PHP este cea mai rapidă opțiune și, deoarece scriem o aplicație cu încărcare mare și nu trebuie să ne încetinim performanța, aceasta este ceea ce vom face. Mai exact, vom folosi un \Phalcon\Config pentru a încărca opțiunile noastre de configurare în proiect. Vom avea un obiect de configurare foarte scurt:

 <?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' => "/", ], ] );

Acest fișier conține două configurații de bază, una pentru baza de date și una pentru aplicație. Evident, configurația bazei de date este folosită pentru conectarea la baza de date, iar în ceea ce privește matricea application , vom avea nevoie de ea mai târziu, deoarece este folosită de instrumentele de sistem ale Phalcon. Despre configurațiile Phalcon puteți citi mai detaliat în documentația oficială.

Se configurează loader.php

Să ne uităm la următorul nostru fișier de configurare, loader.php . Fișierul loader.php înregistrează spații de nume cu directoarele corespunzătoare prin obiectul \Phalcon\Loader . Este si mai simplu:

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

Acum toate clasele din aceste spații de nume vor fi încărcate și disponibile automat. Dacă doriți să adăugați un nou spațiu de nume și director, trebuie doar să adăugați o linie în acest fișier. De asemenea, puteți evita utilizarea spațiilor de nume înregistrând anumite directoare sau anumite fișiere. Toate aceste posibilități sunt descrise în documentația de încărcare PhalconPHP.

Configurarea containerului de injectare a dependenței

La fel ca multe alte cadre contemporane, Phalcon implementează un model de injecție de dependență (DI). Obiectele vor fi inițializate în containerul DI și vor fi accesibile din acesta. De asemenea, containerul DI este conectat la obiectul aplicației și va fi accesibil din toate clasele care moștenesc din \Phalcon\DI\Injectable , cum ar fi controlerele și serviciile noastre.

Modelul DI al lui Phalcon este foarte puternic. Consider că această componentă este una dintre cele mai importante în acest cadru și vă recomand cu tărie să citiți întreaga ei documentație pentru a înțelege cum funcționează. Acesta oferă cheia multor funcții ale lui Phalcon.

Să aruncăm o privire la câteva dintre ele. Fișierul nostru di.php va arăta astfel:

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

După cum puteți vedea, fișierul nostru de injectare a dependenței (DI) este puțin mai complicat și există câteva detalii de care ar trebui să știți. În primul rând, luați în considerare șirul de inițializare: $di = new \Phalcon\DI\FactoryDefault(); . Creăm un obiect FactoryDefault care moștenește \Phalcon\Di (Phalcon vă permite să creați orice fabrică DI dorită). Conform documentației, FactoryDefault „înregistrează automat toate serviciile oferite de framework. Datorită acestui fapt, dezvoltatorul nu trebuie să înregistreze fiecare serviciu individual, oferind un cadru de stivă complet.” Aceasta înseamnă că serviciile comune, cum ar fi Request și Response , vor fi accesibile în cadrul claselor cadru. Puteți vedea lista completă a acestor servicii în documentația de service Phalcon.

Următorul lucru important este procesul de setare: Există mai multe moduri de a înregistra ceva în containerul DI și toate sunt complet descrise în documentația de înregistrare PhalconPHP. În proiectul nostru, totuși, folosim trei moduri: o funcție anonimă, o variabilă și un șir.

Funcția anonimă ne permite să facem multe lucruri în timp ce inițializam clasa. În special, în acest proiect, înlocuim mai întâi un obiect Response pentru a seta content-type ca JSON pentru toate răspunsurile proiectului și apoi inițializam un adaptor de bază de date, folosind obiectul nostru de configurare.

După cum am menționat anterior, acest proiect folosește PostgreSQL. Dacă decideți să utilizați un alt motor de bază de date, trebuie doar să schimbați adaptorul bazei de date în funcția db set. Puteți citi mai multe despre adaptoarele de baze de date disponibile și despre nivelul bazei de date în documentația bazei de date a PhalconPHP.

Al treilea lucru de remarcat este că înregistrez o variabilă $config care implementează serviciul \Phalcon\Config . Deși nu este de fapt folosit în proiectul nostru exemplu, am decis să îl includ aici, deoarece este unul dintre cele mai frecvent utilizate servicii; alte proiecte pot avea nevoie de acces la configurație aproape peste tot.

Ultimul lucru interesant aici este metoda setShared în sine. Apelarea acestui serviciu face ca un serviciu să fie „partajat”, ceea ce înseamnă că începe să se comporte ca un singleton. Potrivit documentației: „Odată ce serviciul este rezolvat pentru prima dată, aceeași instanță a acestuia este returnată de fiecare dată când un consumator preia serviciul din container.”

Configurarea routes.php …sau Nu

Ultimul fișier inclus este routes.php . Să-l lăsăm gol deocamdată - îl vom completa împreună cu controlerele noastre.

Implementarea unui Core RESTful

Ce face un proiect web RESTful? Potrivit Wikipedia, există trei părți principale ale unei aplicații RESTful: - O adresă URL de bază - Un tip media de internet care definește elementele de date de tranziție a stării - Metode HTTP standard ( GET , POST , PUT , DELETE ) și coduri de răspuns HTTP standard (200, 403, 400, 500 etc).

În proiectul nostru, adresele URL de bază vor fi plasate în fișierul routes.php și alte puncte menționate vor fi descrise acum.

Vom primi datele cererii ca application/x-www-form-urlencoded și vom trimite datele de răspuns ca application/json . Deși nu cred că este o idee bună să utilizați x-www-form-urlencoded într-o aplicație reală (din moment ce vă veți chinui să trimiteți structuri de date complexe și matrice asociative cu x-www-form-urlencoded ), am a decis să implementeze acest standard din motive de simplitate.

Dacă vă amintiți, am setat deja antetul JSON de răspuns în fișierul nostru DI:

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

Acum trebuie să setăm coduri de răspuns și un format de răspuns. Tutorialul oficial sugerează că formăm răspunsuri JSON în fiecare metodă, dar nu cred că este o idee bună. Este mult mai universal să returnați rezultatele metodei controlerului ca matrice și apoi să le convertiți în răspunsuri JSON standard. De asemenea, este mai înțelept să formați coduri de răspuns HTTP într-un singur loc în cadrul proiectului; vom face acest lucru în fișierul nostru index.php .

Pentru a face acest lucru, vom folosi capacitatea lui Phalcon de a executa cod înainte și după gestionarea cererilor cu metodele $app->before() și $app->after() . Vom plasa un callback în metoda $app->after() pentru scopul nostru:

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

Aici obținem valoarea returnată și transformăm tabloul în JSON. Dacă totul era OK, dar valoarea returnată era goală (de exemplu, dacă am adăugat cu succes un utilizator nou), am da un cod HTTP 204 și nu am trimite conținut. În toate celelalte cazuri, aruncăm o excepție.

Gestionarea excepțiilor

Unul dintre cele mai importante aspecte ale unei aplicații RESTful este răspunsurile corecte și informative. Aplicațiile cu încărcare mare sunt de obicei mari, iar erori de diferite tipuri pot apărea peste tot: erori de validare, erori de acces, erori de conectare, erori neașteptate etc. Dorim să transformăm toate aceste erori în coduri de răspuns HTTP unificate. Se poate face cu ușurință cu ajutorul excepțiilor.

În proiectul meu, am decis să folosesc două tipuri diferite de excepții: există excepții „locale” - clase speciale moștenite din clasa \RuntimeException , separate prin servicii, modele, adaptoare și așa mai departe (o astfel de diviziune ajută la tratarea fiecărui nivel). al modelului MVC ca unul separat) - atunci există HttpExceptions , moștenit din clasa AbstractHttpException . Aceste excepții sunt în concordanță cu codurile de răspuns HTTP, așa că numele lor sunt Http400Exception , Http500Exception și așa mai departe.

Clasa AbstractHttpException are trei proprietăți: httpCode , httpMessage și appError . Primele două proprietăți sunt suprascrise în moștenitorii lor și conțin informații de răspuns de bază, cum ar fi httpCode: 400 și httpMessage: Bad request . Proprietatea appError este o serie de informații detaliate despre eroare, inclusiv descrierea erorii.

Versiunea noastră finală de index.php va prinde trei tipuri de excepții: AbstractHttpExceptions , așa cum este descris mai sus; Excepții de solicitare Falcon, care pot apărea în timpul analizării unei cereri; și toate celelalte excepții neprevăzute. Toate sunt convertite într-un format JSON frumos și trimise clientului prin clasa standard Phalcon Response:

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

Crearea de modele cu Phalcon Dev Tools

Dacă utilizați un IDE contemporan, probabil că sunteți obișnuit cu evidențierea și completarea codurilor. La fel, într-un cadru PHP tipic, puteți include un folder cu un cadru pentru a accesa o declarație de funcție doar cu un singur clic. Având în vedere că Phalcon este o extensie, nu primim automat această opțiune. Din fericire, există un instrument care umple acest gol numit „Phalcon Dev Tools”, care poate fi instalat prin Composer (dacă încă nu știți ce este, acum este timpul să cunoașteți acest manager de pachete uimitor). Phalcon Dev Tools constau din coduri pentru toate clasele și funcțiile din Phalcon și oferă niște generatoare de cod atât cu versiuni pentru consolă, cât și cu GUI, documentate pe site-ul web PhalconPHP. Aceste instrumente pot ajuta la crearea tuturor părților modelului MVC, dar vom acoperi doar generarea modelului.

OK, să instalăm Phalcon Dev Tools prin Composer. Fișierul nostru composer.json va arăta astfel:

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

După cum puteți vedea, avem nevoie de PHP 5.6, Phalcon 3 și extensia pgsql (pe care o puteți schimba în extensia bazei de date sau o puteți exclude cu totul).

Asigurați-vă că aveți versiunile corecte de extensie PHP, Phalcon și DB și rulați compozitorul:

 $ composer install

Următorul pas este să ne creăm baza de date. Este foarte simplu și este format dintr-un singur tabel, users . Deși am inclus un fișier pg_dump în proiect, iată SQL-ul în dialectul 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 );

Acum că baza de date este creată, putem trece la procesul de generare a modelului. Phalcon Dev Tools folosește un folder .phalcon gol pentru a detecta dacă o aplicație este un proiect Phalcon, așa că va trebui să creați acest folder gol în rădăcina proiectului. De asemenea, folosește unele setări din fișierul de configurare pe care l-am creat - toate variabilele stocate în secțiunea application și adapter din secțiunea bază de database . Pentru a genera modelul nostru, trebuie să executăm următoarea comandă din folderul rădăcină al proiectului:

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

Dacă toți pașii anteriori au fost făcuți corect, veți obține un fișier model de lucru, Users.php , în folderul models dvs., deja plasat într-un spațiu de nume cu gettere și setare așa cum a indicat linia de comandă. Urmează controlerul.

Controlere și rutare

Deoarece aplicația noastră numai utilizatori CRUD (creează, citește, actualizează și șterge) utilizatori, vom crea un singur controler, controlerul Users cu următoarele operațiuni:

  • Adăugați utilizator
  • Afișează lista de utilizatori
  • Actualizați utilizatorul
  • Ștergeți utilizatorul

În timp ce controlerele pot fi create cu ajutorul Phalcon Dev Tools, o vom face manual și vom implementa AbstractController și copilul său, UsersController .

Crearea AbstractController este o decizie bună pentru Phalcon, deoarece putem plasa toate clasele necesare pe care le vom obține din injectarea dependenței în blocul PHPDoc. Acest lucru va ajuta cu funcția de autocompletare IDE. De asemenea, putem programa în unele constante de eroare care sunt comune tuturor controlerelor potențiale.

Deocamdată, controlerul nostru abstract va arăta astfel:

 <?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; }

Doar o simplă clasă injectabilă Phalcon, așa cum este specificat de sintaxa extends , nimic mai mult. Apoi, să creăm scheletul 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) { } }

Momentan, este doar o clasă cu acțiuni goale, care în cele din urmă va deține cereri HTTP corespunzătoare.

Acum este timpul să completați fișierul routes.php . În micro-aplicațiile Phalcon creăm colecții, câte una pentru fiecare controler, și adăugăm toate cererile gestionate ca metode get , post , put , delete , care iau ca argumente un model de rută și o funcție de procedare. Rețineți că o funcție de procedură ar trebui să fie fie o funcție anonimă, fie numele unei metode de controler. Iată cum arată fișierul nostru routes.php :

 <?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; } );

Am setat, de asemenea, un controler de manipulare și un prefix URI. Pentru exemplul nostru, un URI va arăta ca http://article.dev/user/add și trebuie să fie o solicitare de post . Dacă dorim să modificăm datele utilizatorului, URI-ul trebuie să fie o cerere put și va arăta ca http://article.dev/user/12 pentru a schimba datele pentru utilizator cu un ID de 12 . De asemenea, definim un handler URL negăsit, care aruncă o eroare. Pentru mai multe informații, consultați documentația PhalconPHP pentru rute într-o aplicație full stack și pentru rute într-o aplicație micro.

Să trecem la corpul controlerului, și în special la metoda addAction (toate celelalte sunt similare; le puteți vedea în codul aplicației). O metodă de controler face cinci lucruri:

  1. Obține și validează parametrii cererii
  2. Pregătește datele pentru metoda de service
  3. Apelează metoda de service
  4. Se ocupă de excepții
  5. Trimite răspunsul

Să parcurgem fiecare pas, începând cu validarea. În timp ce Phalcon are o componentă de validare puternică, este mult mai oportun să validăm datele într-un mod vechi în acest caz, așa că blocul nostru de validare va arăta astfel:

 $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'; }

Aici verificăm dacă parametrul post este un șir care se potrivește cu o expresie regulată. Toate valorile sunt introduse în matricea $data , care este apoi transmisă clasei UsersService . Toate erorile sunt plasate în tabloul $errors , care apoi este adăugat la o matrice de detalii despre erori în interiorul Http400Exception , unde va fi transformat în răspunsul detaliat văzut în index.php :

Iată codul complet al metodei addAction cu toată validarea sa, care include un apel la metoda createUser în UsersService (pe care nu am creat-o încă):

 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 **/ }

După cum puteți vedea, gestionăm două excepții cunoscute în acea ultimă secțiune: user already exists și unable to create user din cauza unei probleme interne, cum ar fi o eroare de conectare la baza de date. În mod implicit, excepțiile necunoscute vor fi trimise ca HTTP 500 (eroare internă de server). Deși nu oferim niciun detaliu utilizatorului final, este recomandat să stocați toate detaliile erorii (inclusiv urmele) în jurnal.

Și, vă rog, nu uitați să use toate clasele necesare, împrumutate din celelalte spații de nume:

 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;

Logica de afaceri

Ultima parte care trebuie creată este logica de afaceri. La fel ca în cazul controlerelor, vom crea o clasă de serviciu abstractă:

 <?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; }

Ideea este complet aceeași ca în blocul controlerului, așa că nu o voi comenta. Iată scheletul clasei noastre UsersService :

 <?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) { } }

Și metoda createUser în sine:

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

Această metodă este cât se poate de ușoară. Doar creăm un nou obiect model, îi apelăm seterii (care returnează obiectul în sine; acest lucru ne permite să facem un lanț de apeluri) și lansăm o ServiceException în cazul unei erori. Asta e! Acum putem trece la testare.

Testare

Acum să ne uităm la rezultate folosind Postman. Să testăm mai întâi câteva date de gunoi:

Poștaș cu date nevalide.

Cerere:

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

Răspuns (400: Solicitare greșită):

 { "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" } }

Asta se verifică. Acum, pentru câteva date corecte:

Poștaș cu date valide.

Cerere:

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

Răspuns (204):

No content, which is what we expected. 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.

Dezavantaje

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.