PhalconPHP: rozwiązanie dla wysoko obciążonych interfejsów API RESTful

Opublikowany: 2022-03-11

Załóżmy, że musisz utworzyć projekt o dużym obciążeniu w oparciu o framework PHP MVC. Prawdopodobnie używałbyś buforowania wszędzie tam, gdzie to możliwe. Może zbudujesz projekt w pojedynczym pliku, a może nawet napiszesz własny framework MVC z minimalną funkcjonalnością lub przepiszesz niektóre części innego frameworka. Chociaż tak, to działa, jest to trochę trudne, prawda? Na szczęście jest jeszcze jedno rozwiązanie, które sprawia, że ​​większość tych manipulacji jest niepotrzebna (być może z wyjątkiem pamięci podręcznej), a to rozwiązanie nazywa się frameworkiem PhalconPHP.

Co to jest PhalconPHP?

PhalconPHP to framework MVC dla PHP napisany w C i dostarczany jako skompilowane rozszerzenie PHP. To sprawia, że ​​jest to jeden z najszybszych dostępnych frameworków (szczerze mówiąc, najszybszym z nich jest Yaf, ale jest to mikro framework i ma znacznie, znacznie bardziej ograniczoną funkcjonalność niż Phalcon). PhalconPHP nie wymaga długich operacji na plikach PHP i nie musi być interpretowany przy każdym żądaniu — jest ładowany do pamięci RAM raz po uruchomieniu serwera WWW i zużywa bardzo mało zasobów.

Frameworki MVC przez długi czas były uważane za najlepsze praktyki w tworzeniu stron internetowych — do tej pory jest to rodzaj profesjonalnego standardu, więc większość programistów internetowych zna przynajmniej jeden framework MVC dla PHP: Symfony, Yii, Laravel, CodeIgniter, Zend Framework itp. Każdy z nich ma swoje wady i zalety, ale co je wszystkie łączy? Wszystkie są napisane w PHP i składają się z wielu dołączonych plików PHP z ogromną ilością logiki, która musi być uruchamiana przez interpreter przy każdym żądaniu, przy każdym uruchomieniu kodu. Zapewnia to dużą przejrzystość, ale płacimy wydajnością. Duże ilości kodu i wiele dołączonych plików kosztuje dużo pamięci i czasu, zwłaszcza w PHP (ponieważ jest interpretowany, a nie kompilowany). Tak, sytuacja znacznie się poprawiła w PHP 7, ale wciąż jest wiele do poprawienia, a PhalconPHP wprowadza te ulepszenia do tabeli.

Przyjrzyjmy się kilku benchmarkom.

Benchmarki PhalconPHP

Oficjalne testy porównawcze mają już pięć lat — są za stare, aby mogły być teraz aktualne, ale nawet wtedy można dramatycznie zobaczyć, co wyróżnia PhalconPHP. Spójrzmy na coś nowszego. W porównaniu z 2016 r. Phalcon plasuje się w pierwszej piątce — oczywisty lider wśród profesjonalnych frameworków i ustępujący tylko surowemu PHP i niektórym mikro frameworkom.

Testy PhalconPHP

Więc Phalcon jest szybki. Surowy PHP jest również szybki, ale potrzebujemy wszystkich udogodnień, jakie ma do zaoferowania framework MVC, a Phalcon podejmuje wyzwanie, w tym komponenty takie jak:

  • ORM
  • Szablon Volt Silnik
  • Kontener wstrzykiwania zależności (DI)
  • Buforowanie
  • Logowanie
  • Systemy routingu
  • Blokada bezpieczeństwa
  • Autoloader
  • Moduł formularzy

To tylko kilka z nich. Krótko mówiąc, PhalconPHP ma wszystko, czego potrzebujesz do zbudowania dużej aplikacji korporacyjnej, takiej jak RESTful API dla systemu o dużym obciążeniu.

Jeszcze jedną fajną rzeczą w Phalcon jest jego malutki styl — wystarczy porównać Phalcon ORM i ogromną Doctrine 2.

Przyjrzyjmy się tworzeniu projektu PhalconPHP.

Dwa rodzaje projektów Phalcon: Full-stack i Micro

Generalnie istnieją dwa rodzaje frameworków MVC: frameworki fullstack (takie jak Symfony, Yii) oraz mikroframeworki (takie jak Lumen, Slim, Silex).

Frameworki z pełnym stosem są dobrym wyborem w przypadku dużego projektu, ponieważ zapewniają większą funkcjonalność, ale wymagają nieco więcej kwalifikacji i czasu na uruchomienie.

Mikro frameworki pozwalają na bardzo szybkie tworzenie lekkich prototypów, ale brakuje im funkcjonalności, więc generalnie lepiej jest unikać ich używania w dużych projektach. Jedną z zalet mikro frameworków jest jednak ich wydajność. Zwykle są znacznie szybsze niż te z pełnym stosem (na przykład framework Yaf jest gorszy pod względem wydajności tylko od surowego PHP).

PhalconPHP obsługuje oba: Możesz stworzyć pełny stos lub mikroaplikację. Co więcej, kiedy rozwijasz swój projekt w PhalconPHP jako mikroaplikację, nadal masz dostęp do większości zaawansowanych funkcji Phalcon, a jego wydajność jest nadal szybsza niż w przypadku aplikacji typu full-stack.

W poprzedniej pracy mój zespół musiał zbudować system RESTful o dużym obciążeniu. Jedną z rzeczy, które zrobiliśmy, było porównanie wydajności prototypu między aplikacją z pełnym stosem w Phalcon i mikroaplikacją Phalcon. Odkryliśmy, że mikroaplikacje PhalconPHP były znacznie szybsze. Nie mogę pokazać żadnych benchmarków z powodów NDA, ale moim zdaniem, jeśli chcesz w pełni wykorzystać wydajność Phalcona, użyj mikroaplikacji. Chociaż kodowanie mikroaplikacji jest mniej wygodne niż pełnego stosu, mikroaplikacje PhalconPHP wciąż mają wszystko, czego możesz potrzebować do swojego projektu i lepszą wydajność. Aby to zilustrować, napiszmy bardzo prostą mikroaplikację RESTful w Phalconie.

Budowanie RESTful API

Prawie wszystkie permutacje aplikacji RESTful mają jedną wspólną cechę: encję User . Tak więc w naszym przykładowym projekcie utworzymy małą aplikację REST do tworzenia, odczytywania, aktualizowania i usuwania użytkowników (znaną również jako CRUD).

Możesz zobaczyć ten projekt w pełni ukończony w moim repozytorium GitLab. Są tam dwie gałęzie, ponieważ zdecydowałem się podzielić ten projekt na dwie części: pierwsza gałąź, master , zawiera tylko podstawową funkcjonalność bez żadnych konkretnych funkcji PhalconPHP, a druga, logging-and-cache , zawiera funkcje logowania i buforowania Phalcona. Możesz je porównać i zobaczyć jak łatwo zaimplementować takie funkcje w Phalconie.

Instalacja

Nie będę omawiać instalacji: możesz użyć dowolnej bazy danych, dowolnego systemu operacyjnego i dowolnego serwera WWW. Jest to dobrze opisane w oficjalnej dokumentacji instalacyjnej, więc postępuj zgodnie z instrukcjami w zależności od systemu operacyjnego.

Uwagi dotyczące instalacji serwera WWW są również dostępne w oficjalnej dokumentacji Phalcon.

Pamiętaj, że twoja wersja PHP nie powinna być mniejsza niż 5.6.

Używam Ubuntu 16.10, PostgreSQL 9.5.6, Nginx 1.10.0, PHP 7 i Phalcon 3.0. Do projektu dołączyłem przykład konfiguracji Nginx i plik zrzutu PostgreSQL, więc możesz z nich korzystać. Jeśli wolisz inną konfigurację, jej zmiana nie będzie trudna.

Struktura i konfiguracja projektu

Przede wszystkim stwórz wstępną strukturę projektu.

Podczas gdy Phalcon pozwala na użycie dowolnej struktury, struktura, którą wybrałem do tego ćwiczenia, częściowo implementuje wzorzec MVC. Nie mamy widoków, ponieważ jest to projekt RESTful, ale mamy kontrolery i modele, każdy z własnym folderem i usługami. Usługi to klasy, które implementują logikę biznesową projektu, dzieląc „modelową” część MVC na dwie części: modele danych (które komunikują się z bazą danych) oraz modele logiki biznesowej.

index.php , znajdujący się w folderze public , to plik startowy, który ładuje wszystkie niezbędne części i konfigurację. Zauważ, że wszystkie nasze pliki konfiguracyjne są umieszczone w folderze config . Moglibyśmy umieścić je w pliku bootstrap (i tak pokazuje oficjalna dokumentacja), ale moim zdaniem staje się to nieczytelne w dużych projektach, więc od samego początku wolę rozdzielenie folderów.

Tworzenie index.php

Nasz pierwszy przebieg w index.php załaduje klasy konfiguracyjne i autoload, a następnie zainicjuje trasy, kontener wstrzykiwania zależności i mikroaplikację PhalconPHP. Następnie przekaże kontrolę do tego rdzenia mikroaplikacji, który będzie obsługiwać żądania zgodnie z trasami, uruchamiać logikę biznesową i zwracać wyniki.

Rzućmy okiem na kod:

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

Konfiguracja obiektu \Phalcon\Config

Istnieje kilka sposobów przechowywania plików konfiguracyjnych w Phalcon:

  • Plik YAML
  • Plik JSON
  • Plik INI
  • Tablica PHP

Przechowywanie konfiguracji w tablicy PHP jest najszybszą opcją, a ponieważ piszemy aplikację o dużym obciążeniu i nie musimy spowalniać naszej wydajności, właśnie to zrobimy. W szczególności użyjemy obiektu \Phalcon\Config do załadowania naszych opcji konfiguracyjnych do projektu. Będziemy mieli bardzo krótki obiekt konfiguracyjny:

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

Ten plik zawiera dwie podstawowe konfiguracje, jedną dla bazy danych i jedną dla aplikacji. Oczywiście konfiguracja bazy danych służy do łączenia się z bazą danych, a jeśli chodzi o tablicę application , będziemy jej potrzebować później, ponieważ jest ona wykorzystywana przez narzędzia systemowe Phalcona. Więcej o konfiguracjach Phalcona można przeczytać w oficjalnej dokumentacji.

Konfiguracja loader.php

Przyjrzyjmy się naszemu kolejnemu plikowi konfiguracyjnemu, loader.php . Plik loader.php rejestruje przestrzenie nazw z odpowiednimi katalogami poprzez obiekt \Phalcon\Loader . To jeszcze prostsze:

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

Teraz wszystkie klasy z tych przestrzeni nazw będą automatycznie ładowane i dostępne. Jeśli chcesz dodać nową przestrzeń nazw i katalog, po prostu dodaj linię w tym pliku. Możesz także uniknąć używania przestrzeni nazw, rejestrując określone katalogi lub określone pliki. Wszystkie te możliwości są opisane w dokumentacji loadera PhalconPHP.

Konfigurowanie kontenera wstrzykiwania zależności

Podobnie jak wiele innych współczesnych frameworków, Phalcon implementuje wzorzec wstrzykiwania zależności (DI). Obiekty zostaną zainicjowane w kontenerze DI i będą z niego dostępne. Podobnie kontener DI jest połączony z obiektem aplikacji i będzie dostępny ze wszystkich klas dziedziczących po klasie \Phalcon\DI\Injectable , takich jak nasze kontrolery i usługi.

Wzór DI Phalcona jest bardzo potężny. Uważam ten komponent za jeden z najważniejszych w tym frameworku i gorąco polecam przeczytanie całej jego dokumentacji, aby zrozumieć, jak działa. Stanowi klucz do wielu funkcji Phalcona.

Przyjrzyjmy się kilku z nich. Nasz plik di.php będzie wyglądał tak:

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

Jak widać, nasz plik wstrzykiwania zależności (DI) jest trochę bardziej skomplikowany i jest kilka szczegółów, o których powinieneś wiedzieć. Po pierwsze, rozważmy ciąg inicjujący: $di = new \Phalcon\DI\FactoryDefault(); . Tworzymy obiekt FactoryDefault , który dziedziczy \Phalcon\Di (Phalcon pozwala stworzyć dowolną fabrykę DI). Zgodnie z dokumentacją FactoryDefault „automatycznie rejestruje wszystkie usługi świadczone przez framework. Dzięki temu deweloper nie musi rejestrować każdej usługi z osobna, dostarczając framework full stack.” Oznacza to, że wspólne usługi, takie jak Request i Response , będą dostępne w ramach klas szkieletowych. Pełną listę takich usług można zobaczyć w dokumentacji serwisowej Phalcon.

Następną ważną rzeczą jest proces ustawiania: jest kilka sposobów zarejestrowania czegoś w kontenerze DI i wszystkie z nich są dokładnie opisane w dokumentacji rejestracyjnej PhalconPHP. W naszym projekcie używamy jednak trzech sposobów: funkcji anonimowej, zmiennej i ciągu.

Funkcja anonimowa pozwala nam robić wiele rzeczy podczas inicjowania klasy. W szczególności w tym projekcie najpierw zastępujemy obiekt Response , aby ustawić content-type jako JSON dla wszystkich odpowiedzi projektu, a następnie inicjujemy adapter bazy danych przy użyciu naszego obiektu konfiguracyjnego.

Jak wspomniałem wcześniej, ten projekt wykorzystuje PostgreSQL. Jeśli zdecydujesz się na użycie innego silnika bazy danych, wystarczy zmienić adapter bazy danych w funkcji db set. Więcej informacji o dostępnych adapterach baz danych oraz warstwie bazy danych można znaleźć w dokumentacji bazy danych PhalconPHP.

Trzecią rzeczą wartą odnotowania jest to, że rejestruję zmienną $config , która implementuje usługę \Phalcon\Config . Chociaż nie jest on faktycznie używany w naszym przykładowym projekcie, zdecydowałem się uwzględnić go tutaj, ponieważ jest to jedna z najczęściej używanych usług; inne projekty mogą wymagać dostępu do konfiguracji prawie wszędzie.

Ostatnią interesującą rzeczą jest sama metoda setShared . Nazywanie tego sprawia, że ​​usługa jest „wspólna”, co oznacza, że ​​zaczyna zachowywać się jak singleton. Zgodnie z dokumentacją: „Gdy usługa jest rozwiązana po raz pierwszy, ta sama jej instancja jest zwracana za każdym razem, gdy konsument pobiera usługę z kontenera”.

Konfiguracja routes.php …lub nie

Ostatnim dołączonym plikiem jest routes.php . Na razie zostawmy to puste — wypełnimy je razem z naszymi kontrolerami.

Wdrażanie rdzenia RESTful

Co sprawia, że ​​projekt internetowy jest RESTful? Według Wikipedii, aplikacja RESTful składa się z trzech głównych części: - Podstawowy adres URL - Internetowy typ mediów definiujący elementy danych przejścia między stanami - Standardowe metody HTTP ( GET , POST , PUT , DELETE ) i standardowe kody odpowiedzi HTTP (200, 403, 400, 500 itd.).

W naszym projekcie bazowe adresy URL zostaną umieszczone w pliku routes.php , a inne wymienione punkty zostaną teraz opisane.

Otrzymamy dane żądania jako application/x-www-form-urlencoded i wyślemy dane odpowiedzi jako application/json . Chociaż nie wierzę, że używanie x-www-form-urlencoded w prawdziwej aplikacji nie jest dobrym pomysłem (ponieważ będziesz miał trudności z wysyłaniem złożonych struktur danych i tablic asocjacyjnych za pomocą x-www-form-urlencoded ), mam postanowiliśmy wdrożyć ten standard dla uproszczenia.

Jeśli pamiętasz, ustawiliśmy już nasz nagłówek odpowiedzi JSON w naszym pliku DI:

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

Teraz musimy ustawić kody odpowiedzi i format odpowiedzi. Oficjalny samouczek sugeruje, że tworzymy odpowiedzi JSON w każdej metodzie, ale nie sądzę, żeby to był dobry pomysł. Dużo bardziej uniwersalne jest zwracanie wyników metody kontrolera w postaci tablic, a następnie konwertowanie ich na standardowe odpowiedzi JSON. Rozsądniej jest również tworzyć kody odpowiedzi HTTP w jednym miejscu w projekcie; zrobimy to w naszym pliku index.php .

W tym celu wykorzystamy zdolność Phalcon do wykonania kodu przed i po obsłudze żądania za pomocą metod $app->before() i $app->after() . W tym celu umieścimy wywołanie zwrotne w metodzie $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(); }

Tutaj otrzymujemy wartość zwracaną i transformację tablicy do JSON. Jeśli wszystko było w porządku, ale wartość zwracana była pusta (na przykład, gdybyśmy pomyślnie dodali nowego użytkownika), podawalibyśmy kod HTTP 204 i nie wysyłali żadnej treści. We wszystkich pozostałych przypadkach zgłaszamy wyjątek.

Wyjątki dotyczące obsługi

Jednym z najważniejszych aspektów aplikacji RESTful są prawidłowe i pouczające odpowiedzi. Aplikacje o dużym obciążeniu są zwykle duże, a błędy różnego rodzaju mogą wystąpić wszędzie: błędy walidacji, błędy dostępu, błędy połączenia, nieoczekiwane błędy itp. Chcemy przekształcić wszystkie te błędy w ujednolicone kody odpowiedzi HTTP. Można to łatwo zrobić z pomocą wyjątków.

W swoim projekcie zdecydowałem się na dwa różne rodzaje wyjątków: są to wyjątki „lokalne” – klasy specjalne dziedziczone z klasy \RuntimeException , rozdzielone usługami, modelami, adapterami itd. (taki podział pomaga traktować każdy poziom modelu MVC jako osobny) – wtedy są HttpExceptions dziedziczone z klasy AbstractHttpException . Te wyjątki są zgodne z kodami odpowiedzi HTTP, więc ich nazwy to Http400Exception , Http500Exception i tak dalej.

Klasa AbstractHttpException ma trzy właściwości: httpCode , httpMessage i appError . Pierwsze dwie właściwości są zastępowane w swoich dziedziczkach i zawierają podstawowe informacje o odpowiedzi, takie jak httpCode: 400 i httpMessage: Bad request . Właściwość appError to tablica szczegółowych informacji o błędach, w tym opis błędu.

Nasza ostateczna wersja index.php złapie trzy typy wyjątków: AbstractHttpExceptions , jak opisano powyżej; Wyjątki żądania Phalcon, które mogą wystąpić podczas analizowania żądania; i wszystkie inne nieoczekiwane wyjątki. Wszystkie są konwertowane do ładnego formatu JSON i wysyłane do klienta za pośrednictwem standardowej klasy 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(); }

Tworzenie modeli za pomocą Phalcon Dev Tools

Jeśli używasz współczesnego IDE, prawdopodobnie przywykłeś do wyróżniania i uzupełniania kodu. Podobnie w typowym frameworku PHP możesz jednym kliknięciem dołączyć folder ze strukturą, aby przejść do deklaracji funkcji. Biorąc pod uwagę, że Phalcon jest rozszerzeniem, nie otrzymujemy tej opcji automatycznie. Na szczęście istnieje narzędzie, które wypełnia tę lukę, zwane „Phalcon Dev Tools”, które można zainstalować za pomocą Composera (jeśli nadal nie wiesz, co to jest, nadszedł czas, aby poznać ten niesamowity menedżer pakietów). Phalcon Dev Tools składa się z kodów pośredniczących dla wszystkich klas i funkcji w Phalcon oraz zapewnia generatory kodu zarówno w wersji konsolowej, jak i GUI, udokumentowane na stronie internetowej PhalconPHP. Te narzędzia mogą pomóc w tworzeniu wszystkich części wzorca MVC, ale omówimy tylko generowanie modelu.

OK, zainstalujmy Phalcon Dev Tools przez Composer. Nasz plik composer.json będzie wyglądał tak:

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

Jak widać, potrzebujemy PHP 5.6, Phalcon 3 i rozszerzenia pgsql (które można zmienić na rozszerzenie bazy danych lub całkowicie je wykluczyć).

Upewnij się, że masz odpowiednie wersje PHP, Phalcon i rozszerzeń DB, i uruchom kompozytora:

 $ composer install

Kolejnym krokiem jest stworzenie naszej bazy danych. Jest bardzo prosty i składa się tylko z jednej tabeli users . Chociaż do projektu dołączyłem plik pg_dump , oto SQL w dialekcie 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 );

Po utworzeniu bazy danych możemy przystąpić do procesu generowania modelu. Phalcon Dev Tools używa pustego folderu .phalcon do wykrywania, czy aplikacja jest projektem Phalcon, więc będziesz musiał utworzyć ten pusty folder w katalogu głównym projektu. Wykorzystuje również niektóre ustawienia z pliku konfiguracyjnego, który utworzyliśmy — wszystkie zmienne przechowywane w sekcji application i adapter z sekcji database . Aby wygenerować nasz model, musimy wykonać następujące polecenie z folderu głównego projektu:

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

Jeśli wszystkie poprzednie kroki zostały wykonane poprawnie, otrzymasz działający plik modelu, Users.php , w folderze models , już umieszczony w przestrzeni nazw z getterami i setterami, jak wskazano w wierszu poleceń. Dalej jest kontroler.

Kontrolery i routing

Ponieważ nasza aplikacja tylko CRUD (tworzy, odczytuje, aktualizuje i usuwa) użytkowników, utworzymy tylko jeden kontroler, kontroler Users z następującymi operacjami:

  • Dodaj użytkownika
  • Pokaż listę użytkowników
  • Zaktualizuj użytkownika
  • Usuń użytkownika

Chociaż kontrolery można tworzyć za pomocą Phalcon Dev Tools, zrobimy to ręcznie i zaimplementujemy AbstractController i jego dziecko, UsersController .

Stworzenie AbstractController jest dobrą decyzją dla Phalcon, ponieważ możemy umieścić wszystkie niezbędne klasy, które otrzymamy z wstrzykiwania zależności, do bloku PHPDoc. Pomoże to z funkcją autouzupełniania IDE. Możemy również zaprogramować pewne stałe błędów, które są wspólne dla wszystkich potencjalnych sterowników.

Na razie nasz abstrakcyjny kontroler będzie wyglądał tak:

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

Tylko prosta klasa Phalcon do wstrzykiwania, jak określono w składni extends , nic więcej. Następnie stwórzmy szkielet 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) { } }

W tej chwili jest to po prostu klasa z pustymi akcjami, która ostatecznie przechowa odpowiednie żądania HTTP.

Teraz czas na wypełnienie pliku routes.php . W mikroaplikacjach Phalcon tworzymy kolekcje, po jednej dla każdego kontrolera, i dodajemy wszystkie obsłużone żądania jako metody get , post , put , delete , które jako argumenty przyjmują wzorzec trasy i funkcję postępującą. Należy zauważyć, że funkcja postępująca powinna być funkcją anonimową lub nazwą metody kontrolera. Oto jak wygląda nasz plik 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; } );

Ustawiamy również kontroler obsługi i prefiks URI. W naszym przykładzie identyfikator URI będzie wyglądał jak http://article.dev/user/add i musi być żądaniem post . Jeśli chcemy zmienić dane użytkownika, identyfikator URI musi być żądaniem put i będzie wyglądał jak http://article.dev/user/12 , aby zmienić dane dla użytkownika o identyfikatorze 12 . Definiujemy również procedurę obsługi adresu URL nie znalezionego, która zgłasza błąd. Aby uzyskać więcej informacji, zapoznaj się z dokumentacją PhalconPHP dotyczącą tras w aplikacji z pełnym stosem oraz tras w mikroaplikacji.

Przejdźmy do ciała kontrolera, a konkretnie metody addAction (wszystkie pozostałe są podobne; widać je w kodzie aplikacji). Metoda kontrolera robi pięć rzeczy:

  1. Pobiera i weryfikuje parametry żądania
  2. Przygotowuje dane do sposobu obsługi
  3. Wywołuje metodę obsługi
  4. Obsługuje wyjątki
  5. Wysyła odpowiedź

Przejdźmy przez każdy krok, zaczynając od walidacji. Chociaż Phalcon ma potężny komponent walidacji, w tym przypadku o wiele bardziej celowe jest walidowanie danych w starym stylu, więc nasz blok walidacji będzie wyglądał tak:

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

Tutaj sprawdzamy, czy parametr post jest ciągiem znaków, który pasuje do wyrażenia regularnego. Wszystkie wartości są umieszczane w tablicy $data , która jest następnie przekazywana do klasy UsersService . Wszystkie błędy są umieszczane w tablicy $errors , która następnie jest dodawana do tablicy szczegółów błędów wewnątrz Http400Exception , gdzie zostanie przekształcona w szczegółową odpowiedź widzianą w index.php :

Oto pełny kod metody addAction z całą jej walidacją, która obejmuje wywołanie metody createUser w UsersService (której jeszcze nie stworzyliśmy):

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

Jak widać, w tej ostatniej sekcji obsługujemy dwa znane wyjątki: user already exists i unable to create user z powodu jakiegoś wewnętrznego problemu, takiego jak błąd połączenia z bazą danych. Domyślnie nieznane wyjątki będą zgłaszane jako HTTP 500 (wewnętrzny błąd serwera). Chociaż nie podajemy żadnych szczegółów użytkownikowi końcowemu, zdecydowanie zaleca się przechowywanie wszystkich szczegółów błędów (w tym śladów) w dzienniku.

I proszę, nie zapomnij use wszystkich potrzebnych klas, zapożyczonych z innych przestrzeni nazw:

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

Logika biznesowa

Ostatnią częścią do stworzenia jest logika biznesowa. Podobnie jak w przypadku kontrolerów, stworzymy abstrakcyjną klasę usług:

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

Pomysł jest zupełnie taki sam jak w bloku kontrolera, więc nie będę go komentował. Oto szkielet naszej klasy 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 sama metoda createUser :

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

Ta metoda jest tak prosta, jak to tylko możliwe. Po prostu tworzymy nowy obiekt modelu, wywołujemy jego ustawiacze (co zwraca sam obiekt; to pozwala nam stworzyć łańcuch wywołań) i ServiceException w przypadku błędu. Otóż ​​to! Możemy teraz przejść do testów.

Testowanie

Przyjrzyjmy się teraz wynikom za pomocą Postmana. Przetestujmy najpierw niektóre dane dotyczące śmieci:

Listonosz z nieprawidłowymi danymi.

Prośba:

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

Odpowiedź (400: złe żądanie):

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

To się sprawdza. Teraz kilka poprawnych danych:

Listonosz z aktualnymi danymi.

Prośba:

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

Odpowiedź (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.

Niedogodności

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.