PhalconPHP:高負載 RESTful API 的解決方案
已發表: 2022-03-11假設您需要創建一個基於 PHP MVC 框架的高負載項目。 您可能會盡可能使用緩存。 也許您會在單個文件中構建項目,或者甚至編寫自己的具有最少功能的 MVC 框架,或者重寫另一個框架的某些部分。 雖然,是的,這行得通,但有點棘手,不是嗎? 幸運的是,還有另一種解決方案可以使大部分操作變得不必要(也許是為了緩存),這個解決方案稱為 PhalconPHP 框架。
什麼是 PhalconPHP?
PhalconPHP 是一個用 C 語言編寫的用於 PHP 的 MVC 框架,並作為已編譯的 PHP 擴展提供。 這就是使它成為可用的最快框架之一的原因(老實說,最快的是 Yaf,但它是一個微型框架,功能比 Phalcon 有限得多)。 PhalconPHP 不需要對 PHP 文件進行任何長時間的操作,也不需要在每次請求時都對其進行解釋——它會在您的 Web 服務器啟動時加載到 RAM 中,並且消耗很少的資源。
MVC 框架長期以來一直被認為是 Web 開發中的最佳實踐——現在它已成為一種專業標準,因此大多數 Web 開發人員至少熟悉一個用於 PHP 的 MVC 框架:Symfony、Yii、Laravel、CodeIgniter、Zend框架等。它們各有優缺點,但它們有什麼共同點呢? 它們都是用 PHP 編寫的,包含許多包含大量邏輯的 PHP 文件,解釋器必須在每次請求時運行代碼,每次運行代碼時都要運行這些邏輯。 雖然這帶來了極大的透明度,但我們以績效為代價。 大量代碼和大量包含文件會消耗大量內存和時間,尤其是在 PHP 中(因為它是解釋的,而不是編譯的)。 是的,在 PHP 7 中情況已經好很多了,但是還有很多需要改進的地方,PhalconPHP 將這些改進帶到了桌面上。
讓我們看一些基準。
PhalconPHP 基準測試
官方基準測試已經有 5 年曆史了——現在太老了,無法使用,但即便如此,您仍然可以顯著地看到 PhalconPHP 的不同之處。 讓我們看看更新的東西。 在 2016 年的比較中,Phalcon 排名前五——在專業框架中明顯領先,並且只讓步於原始 PHP 和一些微框架。
所以,Phalcon 很快。 原始 PHP 也很快,但我們需要 MVC 框架必須提供的所有功能,Phalcon 迎接挑戰,包括以下組件:
- 甲骨文
- Volt 模板引擎
- 依賴注入 (DI) 容器
- 緩存
- 日誌記錄
- 路由系統
- 安全塊
- 自動裝載機
- 表單模塊
僅舉幾例。 長話短說,PhalconPHP 擁有構建大型企業應用程序所需的一切,例如用於高負載系統的 RESTful API。
Phalcon 的另一個優點是它的小巧風格——只需比較 Phalcon ORM 和龐大的 Doctrine 2。
讓我們看一下如何創建一個 PhalconPHP 項目。
Phalcon 項目的兩種類型:全棧和微型
一般來說,有兩種類型的 MVC 框架:全棧框架(如 Symfony、Yii)和微框架(如 Lumen、Slim、Silex)。
全棧框架對於大型項目來說是一個不錯的選擇,因為它們提供了更多的功能,但是它們需要更多的資格和時間來運行。
微框架允許您非常快速地創建輕量級原型,但它們缺乏功能,因此通常最好避免將它們用於大型項目。 然而,微框架的優勢之一是它們的性能。 它們通常比全棧的要快得多(例如,Yaf 框架的性能僅比原始 PHP 差)。
PhalconPHP 兩者都支持:您可以創建全棧或微應用程序。 更好的是,當您在 PhalconPHP 中將項目開發為微應用程序時,您仍然可以訪問 Phalcon 的大部分強大功能,並且其性能仍然比作為全棧應用程序更快。
在過去的工作中,我的團隊需要構建一個高負載的 RESTful 系統。 我們所做的其中一件事是比較 Phalcon 中的全棧應用程序和 Phalcon 微應用程序之間的原型性能。 我們發現 PhalconPHP 的微應用程序往往要快得多。 由於 NDA 的原因,我無法向您展示任何基準,但在我看來,如果您想充分利用 Phalcon 的性能,請使用微應用程序。 雖然編寫微應用程序不如編寫全棧應用程序方便,但 PhalconPHP 的微應用程序仍然擁有項目所需的一切和更好的性能。 為了說明,讓我們在 Phalcon 中編寫一個非常簡單的 RESTful 微應用程序。
構建 RESTful API
幾乎所有 RESTful 應用程序的排列都有一個共同點: User
實體。 因此,對於我們的示例項目,我們將創建一個小型 REST 應用程序來創建、讀取、更新和刪除用戶(也稱為 CRUD)。
您可以在我的 GitLab 存儲庫中看到該項目已完全完成。 那裡有兩個分支,因為我決定將這個項目分成兩部分:第一個分支master
僅包含基本功能,沒有任何特定的 PhalconPHP 功能,而第二個分支logging-and-cache
包含 Phalcon 的日誌記錄和緩存功能。 您可以比較它們,看看在 Phalcon 中實現這些功能是多麼容易。
安裝
我不會詳細介紹安裝:您可以使用任何數據庫、任何操作系統和任何您想要的 Web 服務器。 官方安裝文檔中對此進行了很好的描述,因此請根據您的操作系統按照說明進行操作。
官方 Phalcon 文檔中也提供了 Web 服務器安裝說明。
請注意,您的 PHP 版本不應低於 5.6。
我使用 Ubuntu 16.10、PostgreSQL 9.5.6、Nginx 1.10.0、PHP 7 和 Phalcon 3.0。 我在項目中包含了一個 Nginx 配置示例和一個 PostgreSQL 轉儲文件,所以請隨意使用它們。 如果您更喜歡其他配置,更改它並不難。
項目結構和配置
首先,創建初始項目結構。
雖然 Phalcon 允許您使用您喜歡的任何結構,但我為本練習選擇的結構部分實現了 MVC 模式。 我們沒有視圖,因為它是一個 RESTful 項目,但我們有控制器和模型,每個都有自己的文件夾和服務。 服務是實現項目業務邏輯的類,將 MVC 的“模型”部分分為兩部分:數據模型(與數據庫通信)和業務邏輯模型。
index.php
位於public
文件夾中,是一個引導文件,用於加載所有必要的部分和配置。 請注意,我們所有的配置文件都放在config
文件夾中。 我們可以將它們放在引導文件中(這是官方文檔中顯示的方式),但是,在我看來,這在大型項目中變得不可讀,所以我更喜歡從一開始就將文件夾分開。
創建index.php
我們在index.php
的第一遍將加載配置和自動加載類,然後初始化路由、依賴注入容器和 PhalconPHP 微應用程序。 然後它將控制權交給那個微應用核心,它會根據路由處理請求,運行業務邏輯並返回結果。
讓我們看一下代碼:
<?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 }
配置\Phalcon\Config
對象
在 Phalcon 中存儲配置文件有幾種方法:
- 一個 YAML 文件
- 一個 JSON 文件
- 一個 INI 文件
- 一個 PHP 數組
將您的配置存儲在 PHP 數組中是最快的選擇,並且由於我們正在編寫一個高負載應用程序並且不需要降低我們的性能,這就是我們要做的。 具體來說,我們將使用\Phalcon\Config
對象將我們的配置選項加載到項目中。 我們將有一個非常短的配置對象:
<?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' => "/", ], ] );
該文件包含兩個基本配置,一個用於數據庫,一個用於應用程序。 顯然,數據庫配置是用來連接數據庫的,至於application
數組,我們以後會用到它,因為它是被Phalcon的系統工具使用的。 您可以在官方文檔中更詳細地了解 Phalcon 配置。
配置loader.php
讓我們看看我們的下一個配置文件loader.php
。 loader.php
文件通過\Phalcon\Loader
對象將命名空間註冊到相應的目錄。 它甚至更簡單:
<?php $loader = new \Phalcon\Loader(); $loader->registerNamespaces( [ 'App\Services' => realpath(__DIR__ . '/../services/'), 'App\Controllers' => realpath(__DIR__ . '/../controllers/'), 'App\Models' => realpath(__DIR__ . '/../models/'), ] ); $loader->register();
現在,這些命名空間中的所有類都將自動加載並可用。 如果要添加新的命名空間和目錄,只需在此文件中添加一行。 您還可以通過註冊特定目錄或特定文件來避免使用命名空間。 PhalconPHP 加載器文檔中描述了所有這些可能性。
配置依賴注入容器
與許多其他當代框架一樣,Phalcon 實現了依賴注入 (DI) 模式。 對象將在 DI 容器中初始化並可以從中訪問。 同樣,DI 容器連接到應用程序對象,並且可以從繼承自\Phalcon\DI\Injectable
類的所有類訪問,例如我們的控制器和服務。
Phalcon 的 DI 模式非常強大。 我認為這個組件是這個框架中最重要的組件之一,我強烈建議您閱讀它的整個文檔以了解它是如何工作的。 它為 Phalcon 的許多功能提供了關鍵。
讓我們來看看其中的幾個。 我們的di.php
文件將如下所示:
<?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;
如您所見,我們的依賴注入 (DI) 文件稍微複雜一些,您應該注意一些細節。 首先,考慮初始化字符串: $di = new \Phalcon\DI\FactoryDefault();
. 我們創建一個繼承\Phalcon\Di
的FactoryDefault
對象(Phalcon 允許您創建任何您想要的 DI 工廠)。 根據文檔, FactoryDefault
“自動註冊框架提供的所有服務。 多虧了這一點,開發人員不需要單獨註冊每個服務,從而提供完整的堆棧框架。” 這意味著可以在框架類中訪問諸如Request
和Response
之類的公共服務。 您可以在 Phalcon 服務文檔中查看此類服務的完整列表。
下一個重要的事情是設置過程: 有幾種方法可以在 DI 容器中註冊一些東西,所有這些方法都在 PhalconPHP 註冊文檔中有完整的描述。 但是,在我們的項目中,我們使用三種方式:匿名函數、變量和字符串。
匿名函數允許我們在初始化類的同時做很多事情。 具體來說,在這個項目中,我們首先覆蓋一個Response
對象,將項目所有響應的content-type
設置為JSON
,然後使用我們的配置對像初始化一個數據庫適配器。
正如我之前提到的,這個項目使用 PostgreSQL。 如果您決定使用其他數據庫引擎,只需更改db
set 函數中的數據庫適配器即可。 您可以在 PhalconPHP 的數據庫文檔中閱讀有關可用數據庫適配器和數據庫層的更多信息。
第三件事是我註冊了一個$config
變量,它實現了\Phalcon\Config
服務。 雖然它實際上並沒有在我們的示例項目中使用,但我決定將它包含在此處,因為它是最常用的服務之一; 其他項目可能幾乎在任何地方都需要訪問配置。
最後一個有趣的事情是實際的setShared
方法本身。 調用它會使服務“共享”,這意味著它開始像單例一樣運行。 根據文檔:“一旦服務第一次被解析,每次消費者從容器中檢索服務時,都會返回相同的實例。”
配置routes.php
...或不配置
最後包含的文件是routes.php
。 現在讓我們把它留空——我們將把它和我們的控制器一起填充。
實現一個 RESTful 核心
是什麼讓 Web 項目成為 RESTful? 根據維基百科,RESTful 應用程序有三個主要部分: - 基本 URL - 定義狀態轉換數據元素的互聯網媒體類型 - 標準 HTTP 方法( GET
、 POST
、 PUT
、 DELETE
)和標準 HTTP 響應代碼(200、 403、400、500 等)。
在我們的項目中,基本 URL 將被放置到routes.php
文件中,現在將描述其他提到的點。
我們將以application/x-www-form-urlencoded
接收請求數據,並以application/json
形式發送響應數據。 雖然我不認為在實際應用程序中使用x-www-form-urlencoded
是一個好主意(因為您將很難使用 x-www-form- x-www-form-urlencoded
發送複雜的數據結構和關聯數組),但我已經為簡單起見,決定實施此標準。
如果您還記得,我們已經在 DI 文件中設置了響應 JSON 標頭:
$di->setShared( 'response', function () { $response = new \Phalcon\Http\Response(); $response->setContentType('application/json', 'utf-8'); return $response; } );
現在我們必須設置響應代碼和響應格式。 官方教程建議我們在每個方法中形成 JSON 響應,但我認為這不是一個好主意。 將控制器方法結果作為數組返回,然後將它們轉換為標準 JSON 響應更為普遍。 在項目中的一個地方形成 HTTP 響應代碼也更明智; 我們將在index.php
文件中執行此操作。
為此,我們將利用 Phalcon 在$app->before()
和$app->after()
方法處理請求之前和之後執行代碼的能力。 為了我們的目的,我們將在$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(); }
在這裡,我們獲取返回值並將數組轉換為 JSON。 如果一切正常,但返回值為空(例如,如果我們成功添加了一個新用戶),我們將給出 204 HTTP 代碼並且不發送任何內容。 在所有其他情況下,我們都會拋出異常。
處理異常
RESTful 應用程序最重要的方面之一是正確且信息豐富的響應。 高負載應用程序通常很大,各種類型的錯誤隨處可見:驗證錯誤、訪問錯誤、連接錯誤、意外錯誤等。我們希望將所有這些錯誤轉換為統一的 HTTP 響應代碼。 借助異常可以輕鬆完成。
在我的項目中,我決定使用兩種不同的異常:存在“本地”異常——從\RuntimeException
類繼承的特殊類,由服務、模型、適配器等分隔(這種劃分有助於處理每個級別MVC 模型作為一個單獨的模型)- 然後是HttpExceptions
,繼承自AbstractHttpException
類。 這些異常與 HTTP 響應碼一致,因此它們的名稱分別為Http400Exception
、 Http500Exception
等。
AbstractHttpException
類具有三個屬性: httpCode
、 httpMessage
和appError
。 前兩個屬性在其繼承者中被覆蓋,並包含基本響應信息,例如httpCode: 400
和httpMessage: Bad request
。 appError
屬性是詳細錯誤信息的數組,包括錯誤描述。
我們最終版本的index.php
將捕獲三種類型的異常: AbstractHttpExceptions
,如上所述; Phalcon 請求異常,可能在解析請求時發生; 以及所有其他意料之外的例外情況。 所有這些都轉換為漂亮的 JSON 格式,並通過標準 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(); }
使用 Phalcon 開發工具創建模型
如果您使用現代 IDE,您可能習慣於代碼高亮和補全。 同樣,在典型的 PHP 框架中,您可以包含一個包含框架的文件夾,以便一鍵轉到函數聲明。 由於 Phalcon 是一個擴展,我們不會自動獲得這個選項。 幸運的是,有一個名為“Phalcon Dev Tools”的工具可以填補這個空白,它可以通過 Composer 安裝(如果您仍然不知道它是什麼,現在是時候了解這個神奇的包管理器了)。 Phalcon 開發工具包含 Phalcon 中所有類和函數的代碼存根,並提供一些帶有控制台和 GUI 版本的代碼生成器,記錄在 PhalconPHP 網站上。 這些工具可以幫助創建 MVC 模式的所有部分,但我們只會介紹模型生成。
好的,讓我們通過 Composer 安裝 Phalcon Dev Tools。 我們的composer.json
文件將如下所示:
{ "require": { "php": ">=5.6.0", "ext-phalcon": ">=3", "ext-pgsql": "*" }, "require-dev": { "phalcon/devtools": "3.*.*@dev" } }
如您所見,我們需要 PHP 5.6、Phalcon 3 和pgsql
擴展(您可以將其更改為您的數據庫擴展或完全排除)。
確保您擁有正確的 PHP、Phalcon 和 DB 擴展版本,然後運行 composer:
$ composer install
下一步是創建我們的數據庫。 它非常簡單,只包含一張表users
。 雖然我在項目中包含了一個pg_dump
文件,但這裡是 PostgreSQL 方言中的 SQL:
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 );
現在數據庫已創建,我們可以繼續進行模型生成過程。 Phalcon Dev Tools 使用一個空的.phalcon
文件夾來檢測應用程序是否是 Phalcon 項目,因此您必須在項目根目錄中創建這個空文件夾。 它還使用我們創建的配置文件中的一些設置——存儲在application
部分和database
部分的adapter
下的所有變量。 要生成我們的模型,我們需要從項目根文件夾執行以下命令:

$ php vendor/phalcon/devtools/phalcon.php model users --namespace="App\Models" --get-set
如果前面的所有步驟都正確完成,您將在您的models
文件夾中獲得一個工作模型文件Users.php
,該文件已放置在帶有 getter 和 setter 的命名空間中,如命令行所示。 接下來是控制器。
控制器和路由
由於我們的應用程序只有 CRUD(創建、讀取、更新和刪除)用戶,我們將只創建一個控制器,即具有以下操作的Users
控制器:
- 添加用戶
- 顯示用戶列表
- 更新用戶
- 刪除用戶
雖然可以在 Phalcon Dev Tools 的幫助下創建控制器,但我們將手動完成並實現AbstractController
及其子UsersController
。
創建AbstractController
對 Phalcon 來說是一個不錯的決定,因為我們可以將從依賴注入中獲得的所有必要類放入 PHPDoc 塊中。 這將有助於 IDE 自動完成功能。 我們還可以編寫所有潛在控制器共有的一些錯誤常量。
現在,我們的抽象控制器看起來像這樣:
<?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; }
只是一個簡單的 Phalcon 可注入類,由extends
語法指定,僅此而已。 接下來,讓我們創建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) { } }
目前,它只是一個帶有空操作的類,最終將保存相應的 HTTP 請求。
現在是時候填寫routes.php
文件了。 在 Phalcon 微應用程序中,我們為每個控制器創建集合,並將所有已處理的請求添加為get
、 post
、 put
、 delete
方法,這些方法將路由模式和進程函數作為參數。 請注意,處理函數應該是匿名函數或控制器的方法名稱。 這是我們的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; } );
我們還設置了一個處理控制器和一個 URI 前綴。 對於我們的示例,URI 看起來像http://article.dev/user/add
,它必須是一個post
請求。 如果我們想更改用戶數據,URI 必須是put
請求,並且看起來像http://article.dev/user/12
以更改 ID 為12
的用戶的數據。 我們還定義了一個未找到的 URL 處理程序,它會引發錯誤。 有關更多信息,請參閱 PhalconPHP 文檔以了解全棧應用程序中的路由以及微應用程序中的路由。
讓我們轉到控制器的主體,特別是addAction
方法(所有其他方法都類似;您可以在應用程序代碼中看到它們)。 控制器方法做了五件事:
- 獲取並驗證請求參數
- 為服務方法準備數據
- 調用服務方法
- 處理異常
- 發送響應
讓我們逐步完成每個步驟,從驗證開始。 雖然 Phalcon 具有強大的驗證組件,但在這種情況下以舊式方式驗證數據更為方便,因此,我們的驗證塊將如下所示:
$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'; }
這裡我們檢查post
參數是否是匹配正則表達式的字符串。 所有值都放入$data
數組,然後傳遞給UsersService
類。 所有錯誤都放入$errors
數組中,然後將其添加到Http400Exception
內的錯誤詳細信息數組中,在那裡它將轉換為index.php
中看到的詳細響應:
這是完整的addAction
方法代碼及其所有驗證,其中包括對UsersService
中createUser
方法的調用(我們尚未創建):
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 **/ }
如您所見,我們在最後一節中處理了兩個已知異常: user already exists
並且由於某些內部問題(例如數據庫連接錯誤)而unable to create user
。 默認情況下,未知異常將作為HTTP 500
(內部服務器錯誤)拋出。 儘管我們沒有向最終用戶提供任何詳細信息,但強烈建議將所有錯誤詳細信息(包括跟踪)存儲在日誌中。
並且,請不要忘記use
從其他命名空間借來的所有必要類:
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;
商業邏輯
最後要創建的部分是業務邏輯。 就像控制器一樣,我們將創建一個抽象服務類:
<?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; }
這個想法和控制器塊中的想法完全一樣,所以我不會評論它。 這是我們的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) { } }
以及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); } } }
這種方法盡可能簡單。 我們只是創建一個新的模型對象,調用它的 setter(它返回對象本身;這允許我們創建一個調用鏈)並在發生錯誤的情況下拋出一個ServiceException
。 而已! 我們現在可以進行測試了。
測試
現在讓我們看看使用 Postman 的結果。 讓我們先測試一些垃圾數據:
要求:
POST http://article.dev/user/add login:1 password:1 first_name:Name last_name:Sourname
響應(400:錯誤請求):
{ "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" } }
檢查出來。 現在獲取一些正確的數據:
要求:
POST http://article.dev/user/add login:user4 password:password4 first_name:Name last_name:Sourname
回應(204):
沒有內容,這是我們所期望的。 現在讓我們確保它正常工作並獲取完整的用戶列表(我們沒有在文章中描述,但您可以在應用程序示例中看到它):
要求:
GET http://article.dev/user/list
響應(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.
缺點
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.