PhalconPHP: Eine Lösung für hochbelastete RESTful-APIs

Veröffentlicht: 2022-03-11

Angenommen, Sie müssen ein Hochlastprojekt basierend auf einem PHP-MVC-Framework erstellen. Sie würden wahrscheinlich Caching verwenden, wo immer dies möglich ist. Vielleicht würden Sie das Projekt in einer einzigen Datei erstellen oder vielleicht sogar Ihr eigenes MVC-Framework mit minimaler Funktionalität schreiben oder einige Teile eines anderen Frameworks neu schreiben. Während, ja, das funktioniert, ist es ein bisschen knifflig, nicht wahr? Glücklicherweise gibt es eine weitere Lösung, die die meisten dieser Manipulationen überflüssig macht (außer vielleicht für den Cache), und diese Lösung heißt PhalconPHP-Framework.

Was ist PhalconPHP?

PhalconPHP ist ein MVC-Framework für PHP, das in C geschrieben und als kompilierte PHP-Erweiterung bereitgestellt wird. Das macht es zu einem der schnellsten verfügbaren Frameworks (um ganz ehrlich zu sein, das schnellste ist Yaf, aber es ist ein Mikro-Framework und hat eine viel, viel eingeschränktere Funktionalität als Phalcon). PhalconPHP benötigt keine langen Operationen mit PHP-Dateien und muss nicht bei jeder Anfrage interpretiert werden – es wird einmal beim Start Ihres Webservers in den Arbeitsspeicher geladen und verbraucht nur sehr wenige Ressourcen.

MVC-Frameworks gelten seit langem als Best Practice in der Webentwicklung – mittlerweile ist es eine Art professioneller Standard, sodass die meisten Webentwickler mit mindestens einem MVC-Framework für PHP vertraut sind: Symfony, Yii, Laravel, CodeIgniter, Zend Framework usw. Sie haben jeweils ihre eigenen Vor- und Nachteile, aber was haben sie alle gemeinsam? Alle sind in PHP geschrieben und bestehen aus vielen enthaltenen PHP-Dateien mit einer riesigen Menge an Logik, die vom Interpreter bei jeder Anfrage ausgeführt werden muss, jedes Mal, wenn Ihr Code ausgeführt wird. Während dies für große Transparenz sorgt, zahlen wir mit Leistung. Große Mengen an Code und viele eingebundene Dateien kosten viel Speicher und Zeit, insbesondere in PHP (da es interpretiert, nicht kompiliert wird). Ja, die Situation ist in PHP 7 viel besser geworden, aber es gibt noch viel zu verbessern, und PhalconPHP bringt diese Verbesserungen auf den Tisch.

Werfen wir einen Blick auf einige Benchmarks.

PhalconPHP-Benchmarks

Die offiziellen Benchmarks sind fünf Jahre alt – zu alt, um noch gültig zu sein, aber selbst dann sieht man dramatisch, was PhalconPHP auszeichnet. Schauen wir uns etwas neueres an. In einem Vergleich aus dem Jahr 2016 platziert sich Phalcon unter den Top 5 – ein klarer Spitzenreiter unter den professionellen Frameworks, der nur gegen rohes PHP und einige Mikro-Frameworks kassiert.

PhalconPHP-Benchmarks

Phalcon ist also schnell. Raw PHP ist auch schnell, aber wir brauchen alle Möglichkeiten, die ein MVC-Framework zu bieten hat, und Phalcon stellt sich der Herausforderung, einschließlich Komponenten wie:

  • ORM
  • Volt-Vorlage Engine
  • Container für Abhängigkeitsinjektion (DI).
  • Caching
  • Protokollierung
  • Routing-Systeme
  • Sicherheitsblock
  • Autoloader
  • Formularmodul

Das ist nur um einige zu nennen. Um es kurz zu machen, PhalconPHP hat alles, was Sie brauchen, um eine große Unternehmensanwendung zu erstellen, wie z. B. eine RESTful-API für ein Hochlastsystem.

Eine weitere nette Sache an Phalcon ist sein winziger Stil – vergleichen Sie einfach Phalcon ORM und Huge Doctrine 2.

Werfen wir einen Blick auf die Erstellung eines PhalconPHP-Projekts.

Zwei Arten von Phalcon-Projekten: Full-Stack und Micro

Im Allgemeinen gibt es zwei Arten von MVC-Frameworks: Full-Stack-Frameworks (wie Symfony, Yii) und Mikro-Frameworks (wie Lumen, Slim, Silex).

Full-Stack-Frameworks sind eine gute Wahl für ein großes Projekt, da sie mehr Funktionalität bieten, aber etwas mehr Qualifikation und Zeit zum Ausführen benötigen.

Mit Mikro-Frameworks können Sie sehr schnell leichte Prototypen erstellen, aber ihnen fehlt die Funktionalität, und daher ist es im Allgemeinen besser, sie nicht für große Projekte zu verwenden. Ein Vorteil von Micro Frameworks ist jedoch ihre Leistungsfähigkeit. Sie sind normalerweise viel schneller als Full-Stack-Frameworks (zum Beispiel ist das Yaf-Framework in der Leistung nur dem reinen PHP unterlegen).

PhalconPHP unterstützt beides: Sie können entweder eine Full-Stack- oder eine Mikroanwendung erstellen. Noch besser, wenn Sie Ihr Projekt in PhalconPHP als Mikroanwendung entwickeln, haben Sie immer noch Zugriff auf die meisten leistungsstarken Funktionen von Phalcon und seine Leistung bleibt immer noch schneller als bei einer Full-Stack-Anwendung.

Bei einem früheren Job musste mein Team ein Hochlast-RESTful-System erstellen. Wir haben unter anderem die Prototypleistung zwischen einer Full-Stack-Anwendung in Phalcon und einer Phalcon-Mikroanwendung verglichen. Wir haben festgestellt, dass die Mikroanwendungen von PhalconPHP tendenziell viel schneller sind. Ich kann Ihnen aus NDA-Gründen keine Benchmarks zeigen, aber meiner Meinung nach sollten Sie Mikroanwendungen verwenden, wenn Sie die Leistung von Phalcon optimal nutzen möchten. Es ist zwar weniger bequem, eine Mikroanwendung zu programmieren als eine Full-Stack-Anwendung, aber die Mikroanwendungen von PhalconPHP haben immer noch alles, was Sie für Ihr Projekt benötigen, und eine bessere Leistung. Lassen Sie uns zur Veranschaulichung eine sehr einfache RESTful-Mikroanwendung in Phalcon schreiben.

Erstellen einer RESTful-API

Fast alle Permutationen einer RESTful-Anwendung haben eines gemeinsam: eine User . Für unser Beispielprojekt erstellen wir also eine kleine REST-Anwendung zum Erstellen, Lesen, Aktualisieren und Löschen von Benutzern (auch bekannt als CRUD).

Sie können dieses vollständig abgeschlossene Projekt in meinem GitLab-Repository sehen. Es gibt dort zwei Zweige, weil ich mich entschieden habe, dieses Projekt in zwei Teile zu unterteilen: Der erste Zweig, master , enthält nur grundlegende Funktionen ohne spezifische PhalconPHP-Funktionen, während der zweite, logging-and-cache , die Protokollierungs- und Caching-Funktionalität von Phalcon enthält. Sie können sie vergleichen und sehen, wie einfach es ist, solche Funktionen in Phalcon zu implementieren.

Installation

Ich werde nicht auf die Installation eingehen: Sie können jede beliebige Datenbank, jedes Betriebssystem und jeden beliebigen Webserver verwenden. Es ist in der offiziellen Installationsdokumentation gut beschrieben, also befolgen Sie einfach die Anweisungen für Ihr Betriebssystem.

Hinweise zur Installation des Webservers sind auch in der offiziellen Phalcon-Dokumentation verfügbar.

Beachten Sie, dass Ihre PHP-Version nicht kleiner als 5.6 sein sollte.

Ich verwende Ubuntu 16.10, PostgreSQL 9.5.6, Nginx 1.10.0, PHP 7 und Phalcon 3.0. Ich habe ein Nginx-Konfigurationsbeispiel und eine PostgreSQL-Dump-Datei in das Projekt aufgenommen, Sie können sie also gerne verwenden. Wenn Sie eine andere Konfiguration bevorzugen, wird es nicht schwierig sein, sie zu ändern.

Projektstruktur und Konfiguration

Erstellen Sie zunächst die initiale Projektstruktur.

Während Sie mit Phalcon jede beliebige Struktur verwenden können, implementiert die Struktur, die ich für diese Übung gewählt habe, teilweise ein MVC-Muster. Wir haben keine Ansichten, weil es sich um ein RESTful-Projekt handelt, aber wir haben Controller und Modelle mit jeweils eigenen Ordnern und Diensten. Dienste sind die Klassen, die die Geschäftslogik des Projekts implementieren und den „Modell“-Teil von MVC in zwei Teile unterteilen: Datenmodelle (die mit der Datenbank kommunizieren) und Geschäftslogikmodelle.

index.php , die sich im public Ordner befindet, ist eine Bootstrap-Datei, die alle erforderlichen Teile und Konfigurationen lädt. Beachten Sie, dass alle unsere Konfigurationsdateien im config abgelegt werden. Wir könnten diese in die Bootstrap-Datei packen (und so wird es auch in der offiziellen Dokumentation gezeigt), aber das wird meiner Meinung nach in großen Projekten unlesbar, daher bevorzuge ich die Ordnertrennung von Anfang an.

index.php

Unser erster Durchgang bei index.php lädt die Konfiguration und Autoload-Klassen, initialisiert dann Routen, einen Abhängigkeitsinjektionscontainer und die PhalconPHP-Mikroanwendung. Sie übergibt dann die Kontrolle an diesen Mikroanwendungskern, der Anfragen gemäß den Routen verarbeitet, Geschäftslogik ausführt und Ergebnisse zurückgibt.

Schauen wir uns den Code an:

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

Konfigurieren eines \Phalcon\Config -Objekts

Es gibt mehrere Möglichkeiten, Konfigurationsdateien in Phalcon zu speichern:

  • Eine YAML-Datei
  • Eine JSON-Datei
  • Eine INI-Datei
  • Ein PHP-Array

Das Speichern Ihrer Konfiguration in einem PHP-Array ist die schnellste Option, und da wir eine Anwendung mit hoher Auslastung schreiben und unsere Leistung nicht verlangsamen müssen, werden wir dies tun. Insbesondere verwenden wir ein \Phalcon\Config -Objekt, um unsere Konfigurationsoptionen in das Projekt zu laden. Wir haben ein sehr kurzes Konfigurationsobjekt:

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

Diese Datei enthält zwei grundlegende Konfigurationen, eine für die Datenbank und eine für die Anwendung. Offensichtlich wird die Datenbankkonfiguration für die Verbindung zur Datenbank verwendet, und was das application betrifft, werden wir es später benötigen, da es von den Systemtools von Phalcon verwendet wird. Weitere Informationen zu Phalcon-Konfigurationen finden Sie in der offiziellen Dokumentation.

loader.php konfigurieren

Schauen wir uns unsere nächste Konfigurationsdatei an, loader.php . Die Datei loader.php registriert Namespaces mit entsprechenden Verzeichnissen über das \Phalcon\Loader -Objekt. Es geht noch einfacher:

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

Jetzt werden alle Klassen aus diesen Namespaces automatisch geladen und sind verfügbar. Wenn Sie einen neuen Namensraum und ein neues Verzeichnis hinzufügen möchten, fügen Sie einfach eine Zeile in diese Datei ein. Sie können die Verwendung von Namespaces auch vermeiden, indem Sie bestimmte Verzeichnisse oder bestimmte Dateien registrieren. Alle diese Möglichkeiten sind in der PhalconPHP-Loader-Dokumentation beschrieben.

Konfigurieren des Abhängigkeitsinjektionscontainers

Wie viele andere zeitgenössische Frameworks implementiert Phalcon ein Abhängigkeitsinjektionsmuster (DI). Objekte werden im DI-Container initialisiert und sind von dort aus zugänglich. Ebenso ist der DI-Container mit dem Anwendungsobjekt verbunden und von allen Klassen aus zugänglich, die von der \Phalcon\DI\Injectable -Klasse erben, wie z. B. unsere Controller und Dienste.

Das DI-Muster von Phalcon ist sehr leistungsfähig. Ich halte diese Komponente für eine der wichtigsten in diesem Framework und empfehle Ihnen dringend, die gesamte Dokumentation zu lesen, um zu verstehen, wie sie funktioniert. Es ist der Schlüssel zu vielen Funktionen von Phalcon.

Werfen wir einen Blick auf einige davon. Unsere di.php -Datei sieht folgendermaßen aus:

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

Wie Sie sehen können, ist unsere Dependency Injection (DI)-Datei etwas komplizierter und es gibt einige Besonderheiten, die Sie beachten sollten. Betrachten Sie zunächst den Initialisierungsstring: $di = new \Phalcon\DI\FactoryDefault(); . Wir erstellen ein FactoryDefault Objekt, das \Phalcon\Di erbt (mit Phalcon können Sie jede gewünschte DI-Factory erstellen). Laut Dokumentation registriert FactoryDefault „automatisch alle vom Framework bereitgestellten Dienste. Dadurch muss der Entwickler nicht jeden Dienst einzeln registrieren, um ein vollständiges Stack-Framework bereitzustellen.“ Dies bedeutet, dass gemeinsame Dienste wie Request und Response innerhalb der Framework-Klassen zugänglich sein werden. Die vollständige Liste dieser Dienste finden Sie in der Dienstdokumentation von Phalcon.

Der nächste wichtige Punkt ist der Einstellungsprozess: Es gibt mehrere Möglichkeiten, etwas im DI-Container zu registrieren, und alle sind in der PhalconPHP-Registrierungsdokumentation vollständig beschrieben. In unserem Projekt verwenden wir jedoch drei Möglichkeiten: eine anonyme Funktion, eine Variable und einen String.

Mit der anonymen Funktion können wir viele Dinge tun, während wir die Klasse initialisieren. Speziell in diesem Projekt überschreiben wir zuerst ein Response content-type für alle Antworten des Projekts auf JSON festzulegen, und initialisieren dann einen Datenbankadapter mithilfe unseres Konfigurationsobjekts.

Wie ich bereits erwähnt habe, verwendet dieses Projekt PostgreSQL. Wenn Sie sich entscheiden, eine andere Datenbank-Engine zu verwenden, ändern Sie einfach den Datenbankadapter in der Funktion db set. Weitere Informationen zu verfügbaren Datenbankadaptern und zur Datenbankschicht finden Sie in der Datenbankdokumentation von PhalconPHP.

Die dritte bemerkenswerte Sache ist, dass ich eine $config Variable registriere, die den \Phalcon\Config -Dienst implementiert. Obwohl es in unserem Beispielprojekt nicht wirklich verwendet wird, habe ich mich entschieden, es hier aufzunehmen, weil es einer der am häufigsten verwendeten Dienste ist. andere Projekte benötigen möglicherweise fast überall Zugriff auf die Konfiguration.

Das letzte Interessante hier ist die eigentliche setShared Methode selbst. Wenn Sie dies aufrufen, wird ein Dienst „geteilt“, was bedeutet, dass er beginnt, sich wie ein Singleton zu verhalten. Gemäß der Dokumentation: „Sobald der Dienst zum ersten Mal aufgelöst wurde, wird jedes Mal dieselbe Instanz davon zurückgegeben, wenn ein Verbraucher den Dienst aus dem Container abruft.“

Konfiguration routes.php … oder nicht

Die letzte enthaltene Datei ist routes.php . Lassen wir es vorerst leer – wir füllen es zusammen mit unseren Controllern.

Implementieren eines RESTful-Kerns

Was macht ein Webprojekt RESTful? Laut Wikipedia besteht eine RESTful-Anwendung aus drei Hauptteilen: - Eine Basis-URL - Ein Internet-Medientyp, der Zustandsübergangsdatenelemente definiert - Standard-HTTP-Methoden ( GET , POST , PUT , DELETE ) und Standard-HTTP-Antwortcodes (200, 403, 400, 500 usw.).

In unserem Projekt werden Basis-URLs in die Datei routes.php und andere erwähnte Punkte werden nun beschrieben.

Wir erhalten Anforderungsdaten als application/x-www-form-urlencoded und senden Antwortdaten als application/json . Obwohl ich nicht glaube, dass es eine gute Idee ist, x-www-form-urlencoded in einer echten Anwendung zu verwenden (da Sie Schwierigkeiten haben werden, komplexe Datenstrukturen und assoziative Arrays mit x-www-form-urlencoded zu senden), habe ich es getan entschieden, diesen Standard der Einfachheit halber zu implementieren.

Wenn Sie sich erinnern, haben wir unseren Antwort-JSON-Header bereits in unserer DI-Datei festgelegt:

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

Jetzt müssen wir Antwortcodes und ein Antwortformat einrichten. Das offizielle Tutorial schlägt vor, dass wir JSON-Antworten in jeder einzelnen Methode bilden, aber ich denke nicht, dass es eine gute Idee ist. Es ist viel universeller, Ergebnisse von Controller-Methoden als Arrays zurückzugeben und sie dann in Standard-JSON-Antworten zu konvertieren. Es ist auch klüger, HTTP-Antwortcodes an einer einzigen Stelle innerhalb des Projekts zu bilden; Wir werden dies in unserer index.php -Datei tun.

Dazu nutzen wir die Fähigkeit von Phalcon, Code vor und nach der Anfragebehandlung mit den $app->before() und $app->after() . Für unseren Zweck platzieren wir einen Callback in der Methode $app->after() :

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

Hier erhalten wir den Rückgabewert und transformieren das Array in JSON. Wenn alles in Ordnung war, aber der Rückgabewert leer war (z. B. wenn wir erfolgreich einen neuen Benutzer hinzugefügt hatten), würden wir einen 204-HTTP-Code geben und keinen Inhalt senden. In allen anderen Fällen lösen wir eine Ausnahme aus.

Umgang mit Ausnahmen

Einer der wichtigsten Aspekte einer RESTful-Anwendung sind korrekte und informative Antworten. Anwendungen mit hoher Auslastung sind normalerweise groß, und Fehler verschiedener Art können überall auftreten: Validierungsfehler, Zugriffsfehler, Verbindungsfehler, unerwartete Fehler usw. Wir wollen alle diese Fehler in einheitliche HTTP-Antwortcodes umwandeln. Das geht ganz einfach mit Hilfe der Ausnahmen.

In meinem Projekt habe ich mich entschieden, zwei verschiedene Arten von Ausnahmen zu verwenden: Es gibt „lokale“ Ausnahmen – spezielle Klassen, die von der \RuntimeException -Klasse geerbt werden, getrennt nach Diensten, Modellen, Adaptern usw. (eine solche Unterteilung hilft, jede Ebene zu behandeln des MVC-Modells als separates) - dann gibt es HttpExceptions , geerbt von der Klasse AbstractHttpException . Diese Ausnahmen stimmen mit HTTP-Antwortcodes überein, daher lauten ihre Namen Http400Exception , Http500Exception und so weiter.

Die Klasse AbstractHttpException hat drei Eigenschaften: httpCode , httpMessage und appError . Die ersten beiden Eigenschaften werden in ihren Erben überschrieben und enthalten grundlegende Antwortinformationen wie httpCode: 400 und httpMessage: Bad request . Die Eigenschaft appError ist ein Array mit detaillierten Fehlerinformationen, einschließlich der Fehlerbeschreibung.

Unsere endgültige Version von index.php fängt drei Arten von Ausnahmen ab: AbstractHttpExceptions , wie oben beschrieben; Phalcon Request Exceptions, die beim Analysieren einer Anfrage auftreten können; und alle anderen unvorhergesehenen Ausnahmen. Alle werden in ein hübsches JSON-Format konvertiert und über die Standard-Phalcon-Antwortklasse an den Client gesendet:

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

Erstellen von Modellen mit Phalcon Dev Tools

Wenn Sie eine moderne IDE verwenden, sind Sie wahrscheinlich daran gewöhnt, Hervorhebungen und Vervollständigungen zu codieren. Ebenso können Sie in einem typischen PHP-Framework einen Ordner mit einem Framework einfügen, um mit nur einem Klick zu einer Funktionsdeklaration zu gelangen. Da Phalcon eine Erweiterung ist, erhalten wir diese Option nicht automatisch. Glücklicherweise gibt es ein Tool namens „Phalcon Dev Tools“, das diese Lücke füllt und über Composer installiert werden kann (wenn Sie immer noch nicht wissen, was es ist, ist es jetzt an der Zeit, sich mit diesem erstaunlichen Paketmanager vertraut zu machen). Phalcon Dev Tools bestehen aus Code-Stubs für alle Klassen und Funktionen in Phalcon und stellen einige Code-Generatoren mit sowohl Konsolen- als auch GUI-Versionen bereit, die auf der PhalconPHP-Website dokumentiert sind. Diese Tools können beim Erstellen aller Teile des MVC-Musters helfen, aber wir behandeln nur die Modellgenerierung.

OK, installieren wir Phalcon Dev Tools über Composer. Unsere composer.json -Datei sieht folgendermaßen aus:

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

Wie Sie sehen können, benötigen wir PHP 5.6, Phalcon 3 und die pgsql Erweiterung (die Sie in Ihre Datenbankerweiterung ändern oder ganz ausschließen können).

Stellen Sie sicher, dass Sie die richtigen PHP-, Phalcon- und DB-Erweiterungsversionen haben, und führen Sie Composer aus:

 $ composer install

Der nächste Schritt ist die Erstellung unserer Datenbank. Es ist sehr einfach und besteht nur aus einer Tabelle, users . Obwohl ich eine pg_dump -Datei in das Projekt eingefügt habe, ist hier die SQL im PostgreSQL-Dialekt:

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

Nachdem die Datenbank erstellt wurde, können wir mit dem Modellgenerierungsprozess fortfahren. Phalcon Dev Tools verwendet einen leeren .phalcon Ordner, um zu erkennen, ob eine Anwendung ein Phalcon-Projekt ist, also müssen Sie diesen leeren Ordner in Ihrem Projektstammverzeichnis erstellen. Es verwendet auch einige Einstellungen aus der Konfigurationsdatei, die wir erstellt haben – alle Variablen, die im Abschnitt „ application “ und „ adapter “ im Abschnitt „ database “ gespeichert sind. Um unser Modell zu generieren, müssen wir den folgenden Befehl aus dem Projektstammordner ausführen:

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

Wenn alle vorherigen Schritte korrekt ausgeführt wurden, erhalten Sie eine funktionierende models , Users.php , in Ihrem Modellordner, die bereits in einem Namespace mit Gettern und Settern platziert ist, wie die Befehlszeile anzeigt. Als nächstes ist der Controller.

Controller und Routing

Da unsere Anwendung nur CRUDs (erstellt, liest, aktualisiert und löscht) Benutzer erstellt, erstellen wir nur einen Controller, den Users -Controller mit den folgenden Operationen:

  • Benutzer hinzufügen
  • Liste der Benutzer anzeigen
  • Benutzer aktualisieren
  • Benutzer löschen

Während Controller mit Hilfe von Phalcon Dev Tools erstellt werden können, werden wir dies manuell tun und AbstractController und sein untergeordnetes Element, UsersController , implementieren.

Das Erstellen des AbstractController ist eine gute Entscheidung für Phalcon, da wir alle erforderlichen Klassen, die wir von der Abhängigkeitsinjektion erhalten, in den PHPDoc-Block einfügen können. Dies hilft bei der IDE-Autovervollständigungsfunktion. Wir können auch einige Fehlerkonstanten programmieren, die allen möglichen Controllern gemeinsam sind.

Im Moment sieht unser abstrakter Controller so aus:

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

Nur eine einfache injizierbare Phalcon-Klasse, wie von der extends -Syntax angegeben, nichts weiter. Als Nächstes erstellen wir das UsersController -Skelett:

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

Im Moment ist es nur eine Klasse mit leeren Aktionen, die schließlich entsprechende HTTP-Anforderungen enthalten.

Jetzt ist es an der Zeit, die Datei routes.php . In Phalcon-Mikroanwendungen erstellen wir Sammlungen, eine für jeden Controller, und fügen alle behandelten Anforderungen als get , post , put und delete -Methoden hinzu, die ein Routenmuster und eine fortschreitende Funktion als Argumente verwenden. Beachten Sie, dass eine fortschreitende Funktion entweder eine anonyme Funktion oder der Methodenname eines Controllers sein sollte. So sieht unsere Datei " routes.php " aus:

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

Wir setzen auch einen Handhabungscontroller und ein URI-Präfix. In unserem Beispiel sieht ein URI wie folgt aus: http://article.dev/user/add , und es muss sich um eine post -Anfrage handeln. Wenn wir Benutzerdaten ändern möchten, muss der URI ein put Request sein und sieht aus wie http://article.dev/user/12 , um Daten für den Benutzer mit der ID 12 zu ändern. Wir definieren auch einen Not-Found-URL-Handler, der einen Fehler auslöst. Weitere Informationen finden Sie in der PhalconPHP-Dokumentation für Routen in einer Full-Stack-Anwendung und für Routen in einer Mikroanwendung.

Kommen wir zum Hauptteil des Controllers und insbesondere zur addAction Methode (alle anderen sind ähnlich; Sie können sie im Anwendungscode sehen). Eine Controller-Methode macht fünf Dinge:

  1. Ruft Anforderungsparameter ab und validiert sie
  2. Bereitet Daten für die Dienstmethode vor
  3. Ruft die Dienstmethode auf
  4. Behandelt Ausnahmen
  5. Sendet die Antwort

Lassen Sie uns jeden Schritt durchgehen, beginnend mit der Validierung. Während Phalcon über eine leistungsstarke Validierungskomponente verfügt, ist es in diesem Fall viel sinnvoller, Daten auf alte Weise zu validieren, sodass unser Validierungsblock wie folgt aussehen wird:

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

Hier prüfen wir, ob der post -Parameter ein String ist, der mit einem regulären Ausdruck übereinstimmt. Alle Werte werden in das $data Array gestellt, das dann an die UsersService -Klasse übergeben wird. Alle Fehler werden in das Array $errors platziert, das dann zu einem Array mit Fehlerdetails in Http400Exception wird, wo es in die detaillierte Antwort umgewandelt wird, die in index.php zu sehen ist:

Hier ist der vollständige addAction -Methodencode mit all seiner Validierung, die einen Aufruf der createUser Methode in UsersService (die wir noch nicht erstellt haben):

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

Wie Sie sehen können, behandeln wir in diesem letzten Abschnitt zwei bekannte Ausnahmen: user already exists und kann aufgrund eines internen Problems, z. B. eines Datenbankverbindungsfehlers, unable to create user . Standardmäßig werden unbekannte Ausnahmen als HTTP 500 (interner Serverfehler) ausgelöst. Obwohl wir keine Details an den Endbenutzer weitergeben, wird dringend empfohlen, alle Fehlerdetails (einschließlich Trace) im Protokoll zu speichern.

Und vergessen Sie bitte nicht, alle notwendigen Klassen zu use , die von den anderen Namespaces entlehnt wurden:

 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;

Geschäftslogik

Der letzte zu erstellende Teil ist die Geschäftslogik. Genau wie bei den Controllern erstellen wir eine abstrakte Serviceklasse:

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

Die Idee ist völlig dieselbe wie im Block des Controllers, daher werde ich sie nicht kommentieren. Hier ist das Skelett unserer UsersService -Klasse:

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

Und die createUser Methode selbst:

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

Diese Methode ist so einfach wie möglich. Wir erstellen einfach ein neues Modellobjekt, rufen seine Setter auf (was das Objekt selbst zurückgibt; dies ermöglicht uns, eine Aufrufkette zu erstellen) und lösen im Fehlerfall eine ServiceException aus. Das ist es! Wir können jetzt mit dem Testen fortfahren.

Testen

Sehen wir uns nun die Ergebnisse mit Postman an. Lassen Sie uns zuerst einige Mülldaten testen:

Postbote mit ungültigen Daten.

Anfrage:

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

Antwort (400: Bad Request):

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

Das checkt aus. Nun zu einigen korrekten Daten:

Postbote mit gültigen Daten.

Anfrage:

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

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

Nachteile

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.