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 和一些微框架。

PhalconPHP 基准测试

所以,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.phploader.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\DiFactoryDefault对象(Phalcon 允许您创建任何您想要的 DI 工厂)。 根据文档, FactoryDefault “自动注册框架提供的所有服务。 多亏了这一点,开发人员不需要单独注册每个服务,从而提供完整的堆栈框架。” 这意味着可以在框架类中访问诸如RequestResponse之类的公共服务。 您可以在 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 方法( GETPOSTPUTDELETE )和标准 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 响应码一致,因此它们的名称分别为Http400ExceptionHttp500Exception等。

AbstractHttpException类具有三个属性: httpCodehttpMessageappError 。 前两个属性在其继承者中被覆盖,并包含基本响应信息,例如httpCode: 400httpMessage: Bad requestappError属性是详细错误信息的数组,包括错误描述。

我们最终版本的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 微应用程序中,我们为每个控制器创建集合,并将所有已处理的请求添加为getpostputdelete方法,这些方法将路由模式和进程函数作为参数。 请注意,处理函数应该是匿名函数或控制器的方法名称。 这是我们的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方法(所有其他方法都类似;您可以在应用程序代码中看到它们)。 控制器方法做了五件事:

  1. 获取并验证请求参数
  2. 为服务方法准备数据
  3. 调用服务方法
  4. 处理异常
  5. 发送响应

让我们逐步完成每个步骤,从验证开始。 虽然 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方法代码及其所有验证,其中包括对UsersServicecreateUser方法的调用(我们尚未创建):

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

好吧,它有效!

记录和缓存

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.