PhalconPHP: una soluzione per API RESTful ad alto carico

Pubblicato: 2022-03-11

Supponiamo di dover creare un progetto ad alto carico basato su un framework PHP MVC. Probabilmente utilizzeresti la memorizzazione nella cache ove possibile. Forse costruiresti il ​​progetto in un unico file, o forse anche scriveresti il ​​tuo framework MVC con funzionalità minime, o riscriveresti alcune parti di un altro framework. Anche se sì, funziona, è un po' complicato, vero? Fortunatamente, esiste un'altra soluzione che rende superflua la maggior parte di queste manipolazioni (ad eccezione della cache, forse) e questa soluzione è chiamata framework PhalconPHP.

Cos'è PhalconPHP?

PhalconPHP è un framework MVC per PHP scritto in C e fornito come estensione PHP compilata. Questo è ciò che lo rende uno dei framework più veloci disponibili (a dire il vero il più veloce è Yaf, ma è un micro framework e ha funzionalità molto, molto più limitate di Phalcon). PhalconPHP non richiede lunghe operazioni con i file PHP e non ha bisogno di essere interpretato ad ogni richiesta: viene caricato nella RAM una volta all'avvio del server Web e consuma pochissime risorse.

I framework MVC sono stati considerati le migliori pratiche nello sviluppo web per molto tempo, ormai è una sorta di standard professionale, quindi la maggior parte degli sviluppatori web ha familiarità con almeno un framework MVC per PHP: Symfony, Yii, Laravel, CodeIgniter, Zend Struttura, ecc. Ognuno di loro ha i propri vantaggi e svantaggi, ma cosa hanno tutti in comune? Tutti sono scritti in PHP e sono costituiti da molti file PHP inclusi con un'enorme quantità di logica che deve essere eseguita dall'interprete su ogni richiesta, ogni volta che viene eseguito il codice. Sebbene ciò garantisca una grande trasparenza, paghiamo con le prestazioni. Grandi quantità di codice e molti file inclusi costano una grande quantità di memoria e tempo, specialmente in PHP (poiché è interpretato, non compilato). Sì, la situazione è migliorata molto in PHP 7, ma c'è ancora molto da migliorare e PhalconPHP porta questi miglioramenti sul tavolo.

Diamo un'occhiata ad alcuni benchmark.

Benchmark PhalconPHP

I benchmark ufficiali hanno cinque anni, troppo vecchi per essere validi ora, ma anche allora puoi vedere in modo drammatico cosa distingue PhalconPHP. Diamo un'occhiata a qualcosa di più nuovo. In un confronto del 2016, Phalcon si colloca tra i primi cinque, un evidente leader tra i framework professionali e concede solo a PHP grezzo e alcuni micro framework.

benchmark PhalconPHP

Quindi, Phalcon è veloce. Anche il PHP grezzo è veloce, ma abbiamo bisogno di tutte le strutture che un framework MVC ha da offrire e Phalcon è all'altezza della sfida, inclusi componenti come:

  • ORM
  • Motore modello Volt
  • Contenitore di iniezione di dipendenza (DI).
  • Memorizzazione nella cache
  • Registrazione
  • Sistemi di instradamento
  • Blocco di sicurezza
  • Caricatore automatico
  • Modulo Moduli

Questo è solo per citarne alcuni. Per farla breve, PhalconPHP ha tutto il necessario per creare una grande applicazione aziendale come un'API RESTful per un sistema ad alto carico.

Un'altra cosa bella di Phalcon è il suo stile minuscolo: basta confrontare Phalcon ORM e l'enorme Doctrine 2.

Diamo un'occhiata alla creazione di un progetto PhalconPHP.

Due tipi di progetti Phalcon: Full-stack e Micro

In generale, ci sono due tipi di framework MVC: framework full-stack (come Symfony, Yii) e micro framework (come Lumen, Slim, Silex).

I framework full-stack sono una buona scelta per un grande progetto poiché forniscono più funzionalità, ma richiedono un po' più di qualificazione e tempo per essere eseguiti.

I micro framework consentono di creare prototipi leggeri molto rapidamente, ma mancano di funzionalità, quindi è generalmente meglio evitare di utilizzarli per progetti di grandi dimensioni. Un vantaggio dei micro framework, tuttavia, sono le loro prestazioni. Di solito sono molto più veloci di quelli full-stack (ad esempio, il framework Yaf ha prestazioni inferiori solo al PHP grezzo).

PhalconPHP supporta entrambi: puoi creare uno stack completo o una microapplicazione. Ancora meglio, quando sviluppi il tuo progetto in PhalconPHP come microapplicazione, hai comunque accesso alla maggior parte delle potenti funzionalità di Phalcon e le sue prestazioni rimangono ancora più veloci rispetto a un'applicazione full-stack.

In un lavoro passato, il mio team aveva bisogno di creare un sistema RESTful ad alto carico. Una delle cose che abbiamo fatto è stata confrontare le prestazioni del prototipo tra un'applicazione full-stack in Phalcon e una microapplicazione Phalcon. Abbiamo scoperto che le micro applicazioni di PhalconPHP tendevano ad essere molto più veloci. Non posso mostrarti alcun benchmark per motivi NDA, ma secondo me, se vuoi sfruttare al massimo le prestazioni di Phalcon, usa le micro applicazioni. Sebbene sia meno conveniente codificare una micro applicazione rispetto a una full-stack, le micro applicazioni di PhalconPHP hanno comunque tutto ciò di cui potresti aver bisogno per il tuo progetto e prestazioni migliori. Per illustrare, scriviamo una microapplicazione RESTful molto semplice in Phalcon.

Creazione di un'API RESTful

Quasi tutte le permutazioni di un'applicazione RESTful hanno una cosa in comune: un'entità User . Quindi, per il nostro progetto di esempio, creeremo una piccola applicazione REST per creare, leggere, aggiornare ed eliminare utenti (nota anche come CRUD).

Puoi vedere questo progetto completamente completato nel mio repository GitLab. Ci sono due rami lì perché ho deciso di dividere questo progetto in due parti: il primo ramo, master , contiene solo funzionalità di base senza alcuna caratteristica specifica di PhalconPHP, mentre il secondo, logging-and-cache , contiene la funzionalità di registrazione e memorizzazione nella cache di Phalcon. Puoi confrontarli e vedere com'è facile implementare tali funzioni in Phalcon.

Installazione

Non esaminerò l'installazione: puoi utilizzare qualsiasi database, qualsiasi sistema operativo e qualsiasi server Web che desideri. È descritto bene nella documentazione di installazione ufficiale, quindi segui semplicemente le istruzioni a seconda del tuo sistema operativo.

Le note sull'installazione del server Web sono disponibili anche nella documentazione ufficiale di Phalcon.

Nota che la tua versione di PHP non dovrebbe essere inferiore a 5.6.

Uso Ubuntu 16.10, PostgreSQL 9.5.6, Nginx 1.10.0, PHP 7 e Phalcon 3.0. Ho incluso un esempio di configurazione Nginx e un file di dump PostgreSQL nel progetto, quindi sentiti libero di usarli. Se preferisci un'altra configurazione, non sarà difficile cambiarla.

Struttura e configurazione del progetto

Per prima cosa, crea la struttura del progetto iniziale.

Mentre Phalcon ti consente di utilizzare qualsiasi struttura che ti piace, la struttura che ho scelto per questo esercizio implementa in parte un modello MVC. Non abbiamo visualizzazioni perché è un progetto RESTful, ma abbiamo controller e modelli, ognuno con la propria cartella e servizi. I servizi sono le classi che implementano la logica di business del progetto, dividendo la parte “modello” di MVC in due parti: modelli di dati (che comunicano con il database) e modelli di business logic.

index.php , che si trova nella cartella public , è un file bootstrap che carica tutte le parti e la configurazione necessarie. Nota che tutti i nostri file di configurazione sono inseriti nella cartella config . Potremmo metterli nel file bootstrap (e questo è il modo mostrato nella documentazione ufficiale), ma, secondo me, questo diventa illeggibile nei grandi progetti, quindi preferisco la separazione delle cartelle sin dall'inizio.

Creazione di index.php

Il nostro primo passaggio su index.php caricherà la configurazione e caricherà automaticamente le classi, quindi inizializzerà i percorsi, un contenitore di iniezione delle dipendenze e la micro applicazione PhalconPHP. Passerà quindi il controllo a quel core della microapplicazione, che gestirà le richieste in base ai percorsi, eseguirà la logica aziendale e restituirà i risultati.

Diamo un'occhiata al codice:

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

Configurazione di un oggetto \Phalcon\Config

Esistono diversi modi per archiviare i file di configurazione in Phalcon:

  • Un file YAML
  • Un file JSON
  • Un file INI
  • Un array PHP

La memorizzazione della configurazione in un array PHP è l'opzione più veloce e poiché stiamo scrivendo un'applicazione ad alto carico e non è necessario rallentare le nostre prestazioni, questo è ciò che faremo. In particolare, utilizzeremo un oggetto \Phalcon\Config per caricare le nostre opzioni di configurazione nel progetto. Avremo un oggetto di configurazione molto breve:

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

Questo file contiene due configurazioni di base, una per il database e una per l'applicazione. Ovviamente, la configurazione del database viene utilizzata per la connessione al database e, per quanto riguarda l'array application , ne avremo bisogno in seguito poiché è utilizzato dagli strumenti di sistema di Phalcon. Puoi leggere le configurazioni di Phalcon in modo più dettagliato nella documentazione ufficiale.

Configurazione loader.php

Diamo un'occhiata al nostro prossimo file di configurazione, loader.php . Il file loader.php registra gli spazi dei nomi con le directory corrispondenti tramite l'oggetto \Phalcon\Loader . È ancora più semplice:

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

Ora tutte le classi di questi spazi dei nomi verranno automaticamente caricate e disponibili. Se vuoi aggiungere un nuovo spazio dei nomi e una nuova directory, aggiungi semplicemente una riga in questo file. Puoi anche evitare di usare gli spazi dei nomi registrando directory o file specifici. Tutte queste possibilità sono descritte nella documentazione del caricatore PhalconPHP.

Configurazione del contenitore di iniezione delle dipendenze

Come molti altri framework contemporanei, Phalcon implementa un modello di dependency injection (DI). Gli oggetti verranno inizializzati nel contenitore DI e saranno accessibili da esso. Allo stesso modo, il contenitore DI è connesso all'oggetto dell'applicazione e sarà accessibile da tutte le classi che ereditano dalla classe \Phalcon\DI\Injectable , come i nostri controller e servizi.

Il pattern DI di Phalcon è molto potente. Considero questo componente uno dei più importanti in questo framework e ti consiglio vivamente di leggere tutta la sua documentazione per capire come funziona. Fornisce la chiave per molte delle funzioni di Phalcon.

Diamo un'occhiata ad alcuni di loro. Il nostro file di.php sarà simile a questo:

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

Come puoi vedere, il nostro file di iniezione delle dipendenze (DI) è leggermente più complicato e ci sono alcuni dettagli di cui dovresti essere a conoscenza. Prima di tutto, considera la stringa di inizializzazione: $di = new \Phalcon\DI\FactoryDefault(); . Creiamo un oggetto FactoryDefault che eredita \Phalcon\Di (Phalcon ti consente di creare qualsiasi fabbrica DI che desideri). Secondo la documentazione, FactoryDefault “registra automaticamente tutti i servizi forniti dal framework. Grazie a ciò, lo sviluppatore non ha bisogno di registrare ogni servizio individualmente fornendo un framework completo di stack. Ciò significa che i servizi comuni come Request e Response saranno accessibili all'interno delle classi framework. È possibile visualizzare l'elenco completo di tali servizi nella documentazione del servizio Phalcon.

La prossima cosa importante è il processo di impostazione: ci sono diversi modi per registrare qualcosa nel contenitore DI e tutti sono completamente descritti nella documentazione di registrazione di PhalconPHP. Nel nostro progetto, però, utilizziamo tre modi: una funzione anonima, una variabile e una stringa.

La funzione anonima ci consente di fare molte cose durante l'inizializzazione della classe. In questo progetto in particolare, sovrascriviamo prima un oggetto Response per impostare il content-type come JSON per tutte le risposte del progetto e quindi inizializziamo un adattatore di database, utilizzando il nostro oggetto di configurazione.

Come accennato in precedenza, questo progetto utilizza PostgreSQL. Se si decide di utilizzare un altro motore di database, è sufficiente modificare l'adattatore del database nella funzione db set. Puoi leggere di più sugli adattatori di database disponibili e sul livello di database nella documentazione del database di PhalconPHP.

La terza cosa da notare è che registro una variabile $config che implementa il servizio \Phalcon\Config . Sebbene non sia effettivamente utilizzato nel nostro progetto di esempio, ho deciso di includerlo qui perché è uno dei servizi più comunemente usati; altri progetti potrebbero richiedere l'accesso alla configurazione quasi ovunque.

L'ultima cosa interessante qui è l'effettivo metodo setShared stesso. Chiamarlo rende un servizio "condiviso", il che significa che inizia a comportarsi come un singleton. Secondo la documentazione: "Una volta che il servizio è stato risolto per la prima volta, la stessa istanza viene restituita ogni volta che un consumatore recupera il servizio dal contenitore".

Configurazione routes.php … o no

L'ultimo file incluso è routes.php . Lasciamolo vuoto per ora: lo riempiremo insieme ai nostri controller.

Implementazione di un core RESTful

Cosa rende RESTful un progetto web? Secondo Wikipedia, ci sono tre parti principali di un'applicazione RESTful: - Un URL di base - Un tipo di media Internet che definisce gli elementi dei dati di transizione dello stato - Metodi HTTP standard ( GET , POST , PUT , DELETE ) e codici di risposta HTTP standard (200, 403, 400, 500, ecc.).

Nel nostro progetto, gli URL di base verranno inseriti nel file routes.php e altri punti menzionati verranno ora descritti.

Riceveremo i dati della richiesta come application/x-www-form-urlencoded e invieremo i dati di risposta come application/json . Anche se non credo sia una buona idea usare x-www-form-urlencoded in un'applicazione reale (dal momento che farai fatica a inviare strutture di dati complesse e array associativi con x-www-form-urlencoded ), ho deciso di implementare questo standard per motivi di semplicità.

Se ricordi, abbiamo già impostato la nostra intestazione JSON di risposta nel nostro file DI:

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

Ora dobbiamo impostare i codici di risposta e un formato di risposta. Il tutorial ufficiale suggerisce di formare risposte JSON in ogni singolo metodo, ma non credo sia una buona idea. È molto più universale restituire i risultati del metodo del controller come array e quindi convertirli in risposte JSON standard. È anche più saggio formare codici di risposta HTTP in un unico punto all'interno del progetto; lo faremo nel nostro file index.php .

Per fare ciò, utilizzeremo la capacità di Phalcon di eseguire codice prima e dopo la gestione delle richieste con i metodi $app->before() e $app->after() . Metteremo un callback nel metodo $app->after() per il nostro scopo:

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

Qui otteniamo il valore restituito e trasformiamo l'array in JSON. Se tutto andava bene, ma il valore restituito era vuoto (ad esempio, se avessimo aggiunto correttamente un nuovo utente), forniremmo un codice HTTP 204 e non invieremo alcun contenuto. In tutti gli altri casi, generiamo un'eccezione.

Gestione delle eccezioni

Uno degli aspetti più importanti di un'applicazione RESTful sono le risposte corrette e informative. Le applicazioni ad alto carico sono generalmente di grandi dimensioni e errori di vario tipo possono verificarsi ovunque: errori di convalida, errori di accesso, errori di connessione, errori imprevisti, ecc. Vogliamo trasformare tutti questi errori in codici di risposta HTTP unificati. Può essere fatto facilmente con l'aiuto delle eccezioni.

Nel mio progetto, ho deciso di utilizzare due diversi tipi di eccezioni: ci sono eccezioni "locali": classi speciali ereditate dalla classe \RuntimeException , separate da servizi, modelli, adattatori e così via (tale divisione aiuta a trattare ogni livello del modello MVC come separato)- quindi ci sono HttpExceptions , ereditato dalla classe AbstractHttpException . Queste eccezioni sono coerenti con i codici di risposta HTTP, quindi i loro nomi sono Http400Exception , Http500Exception e così via.

La classe AbstractHttpException ha tre proprietà: httpCode , httpMessage e appError . Le prime due proprietà vengono sovrascritte nei rispettivi eredi e contengono informazioni di risposta di base come httpCode: 400 e httpMessage: Bad request . La proprietà appError è una matrice di informazioni dettagliate sull'errore, inclusa la descrizione dell'errore.

La nostra versione finale di index.php rileverà tre tipi di eccezioni: AbstractHttpExceptions , come descritto sopra; Phalcon Request Eccezioni, che possono verificarsi durante l'analisi di una richiesta; e tutte le altre eccezioni impreviste. Tutti vengono convertiti in un bel formato JSON e inviati al client tramite la classe Phalcon Response standard:

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

Creazione di modelli con Phalcon Dev Tools

Se usi un IDE contemporaneo, probabilmente sei abituato all'evidenziazione e al completamento del codice. Allo stesso modo, in un tipico framework PHP, puoi includere una cartella con un framework per andare a una dichiarazione di funzione con un solo clic. Dato che Phalcon è un'estensione, non otteniamo questa opzione automaticamente. Fortunatamente, c'è uno strumento che colma questa lacuna chiamato "Phalcon Dev Tools", che può essere installato tramite Composer (se ancora non sai di cosa si tratta, è giunto il momento di conoscere questo fantastico gestore di pacchetti). Phalcon Dev Tools consistono in stub di codice per tutte le classi e funzioni in Phalcon e forniscono alcuni generatori di codice con entrambe le versioni console e GUI, documentati sul sito Web PhalconPHP. Questi strumenti possono aiutare a creare tutte le parti del pattern MVC, ma tratteremo solo la generazione del modello.

OK, installiamo Phalcon Dev Tools tramite Composer. Il nostro file composer.json sarà simile a questo:

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

Come puoi vedere, richiediamo PHP 5.6, Phalcon 3 e l'estensione pgsql (che puoi modificare nell'estensione del tuo database o escludere del tutto).

Assicurati di avere le versioni di estensione PHP, Phalcon e DB corrette ed esegui il compositore:

 $ composer install

Il prossimo passo è creare il nostro database. È molto semplice e consiste solo in una tabella, users . Anche se ho incluso un file pg_dump nel progetto, ecco l'SQL nel dialetto 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 );

Ora che il database è stato creato, possiamo procedere al processo di generazione del modello. Phalcon Dev Tools utilizza una cartella .phalcon vuota per rilevare se un'applicazione è un progetto Phalcon, quindi dovrai creare questa cartella vuota nella radice del tuo progetto. Utilizza anche alcune impostazioni dal file di configurazione che abbiamo creato, tutte le variabili memorizzate nella sezione application e l' adapter dalla sezione del database . Per generare il nostro modello, dobbiamo eseguire il seguente comando dalla cartella principale del progetto:

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

Se tutti i passaggi precedenti sono stati eseguiti correttamente, otterrai un file di modello funzionante, Users.php , nella cartella dei models , già posizionato in uno spazio dei nomi con getter e setter come indicato dalla riga di comando. Il prossimo è il controller.

Controller e Routing

Poiché la nostra applicazione utilizza solo CRUD (crea, legge, aggiorna ed elimina) gli utenti, creeremo un solo controller, il controller Users con le seguenti operazioni:

  • Aggiungi utente
  • Mostra l'elenco degli utenti
  • Aggiorna utente
  • Elimina utente

Sebbene i controller possano essere creati con l'aiuto di Phalcon Dev Tools, lo faremo manualmente e implementeremo AbstractController e il suo figlio, UsersController .

La creazione di AbstractController è una buona decisione per Phalcon perché possiamo inserire tutte le classi necessarie che otterremo dall'iniezione di dipendenza nel blocco PHPDoc. Questo aiuterà con la funzione di completamento automatico dell'IDE. Possiamo anche programmare alcune costanti di errore che sono comuni a tutti i potenziali controllori.

Per ora, il nostro controller astratto sarà simile a questo:

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

Solo una semplice classe iniettabile Phalcon, come specificato dalla sintassi extends , niente di più. Quindi, creiamo lo scheletro di 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) { } }

Al momento, è solo una classe con azioni vuote che alla fine conterrà le richieste HTTP corrispondenti.

Ora è il momento di compilare il file routes.php . Nelle micro applicazioni Phalcon creiamo raccolte, una per ogni controller, e aggiungiamo tutte le richieste gestite come metodi get , post , put , delete , che prendono un percorso e una funzione di procedimento come argomenti. Si noti che una funzione di procedura dovrebbe essere una funzione anonima o il nome del metodo di un controller. Ecco come appare il nostro file 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; } );

Abbiamo anche impostato un controller di gestione e un prefisso URI. Per il nostro esempio, un URI sarà simile a http://article.dev/user/add e deve essere una richiesta di post . Se vogliamo modificare i dati dell'utente, l'URI deve essere una richiesta put e sarà simile a http://article.dev/user/12 per modificare i dati per l'utente con un ID di 12 . Definiamo anche un gestore URL non trovato, che genera un errore. Per ulteriori informazioni, fare riferimento alla documentazione di PhalconPHP per le route in un'applicazione full stack e per le route in una microapplicazione.

Passiamo al corpo del controller, e nello specifico al metodo addAction (tutti gli altri sono simili, li potete vedere nel codice dell'applicazione). Un metodo controller fa cinque cose:

  1. Ottiene e convalida i parametri della richiesta
  2. Prepara i dati per il metodo di servizio
  3. Chiama il metodo di servizio
  4. Gestisce le eccezioni
  5. Invia la risposta

Esaminiamo ogni passaggio, a partire dalla convalida. Sebbene Phalcon abbia un potente componente di convalida, in questo caso è molto più opportuno convalidare i dati in un modo vecchio stile, quindi il nostro blocco di convalida sarà simile al seguente:

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

Qui controlliamo se il parametro post è una stringa che corrisponde a un'espressione regolare. Tutti i valori vengono inseriti nell'array $data , che viene quindi passato alla classe UsersService . Tutti gli errori vengono inseriti nell'array $errors , che viene quindi aggiunto all'array di dettagli dell'errore all'interno di Http400Exception , dove verrà trasformato nella risposta dettagliata vista in index.php :

Ecco il codice completo del metodo addAction con tutta la sua convalida, che include una chiamata al metodo createUser in UsersService (che non abbiamo ancora creato):

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

Come puoi vedere, gestiamo due eccezioni note in quest'ultima sezione: user already exists e non è unable to create user a causa di qualche problema interno come un errore di connessione al database. Per impostazione predefinita, le eccezioni sconosciute verranno generate come HTTP 500 (errore interno del server). Sebbene non diamo alcun dettaglio all'utente finale, si consiglia vivamente di archiviare tutti i dettagli dell'errore (inclusa la traccia) nel registro.

E, per favore, non dimenticare di use tutte le classi necessarie, prese in prestito dagli altri namespace:

 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 di business

L'ultima parte da creare è la logica aziendale. Proprio come con i controller, creeremo una classe di servizio astratta:

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

L'idea è completamente la stessa del blocco del controller, quindi non la commenterò. Ecco lo scheletro della nostra classe 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) { } }

E il metodo createUser stesso:

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

Questo metodo è il più semplice possibile. Creiamo semplicemente un nuovo oggetto modello, chiamiamo i suoi setter (che restituisce l'oggetto stesso; questo ci permette di fare una catena di chiamate) e lanciamo una ServiceException in caso di errore. Questo è tutto! Possiamo ora procedere al test.

Test

Ora diamo un'occhiata ai risultati usando Postman. Proviamo prima alcuni dati spazzatura:

Postino con dati non validi.

Richiesta:

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

Risposta (400: Richiesta errata):

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

Che controlla. Ora per alcuni dati corretti:

Postino con dati validi.

Richiesta:

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

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

Svantaggi

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.