PhalconPHP(高负载RESTful API的解决方案)

本文概述

  • 什么是PhalconPHP?
  • 构建一个RESTful API
  • 缺点
  • PhalconPHP:不仅适用于高负荷应用
假设你需要基于PHP MVC框架创建一个高负载项目。你可能会尽可能使用缓存。也许你可以在单个文件中构建项目, 或者甚至以最小的功能编写自己的MVC框架, 或者重写另一个框架的某些部分。是的, 这确实可行, 但是有点棘手, 不是吗?幸运的是, 还有另一种解决方案使大多数这些操作都不是必需的(也许保存到缓存中), 该解决方案称为PhalconPHP框架。
什么是PhalconPHP? PhalconPHP是一个用C编写的PHP MVC框架, 并作为已编译的PHP扩展提供。这就是使它成为可用最快的框架之一的原因(老实说, 最快的框架是Yaf, 但它是一个微型框架, 并且功能比Phalcon有限得多)。 PhalconPHP不需要对PHP文件进行任何长时间的操作, 也不需要在每次请求时都进行解释-当你的网络服务器启动时, 它会一次加载到RAM中, 并且消耗很少的资源。
长期以来, MVC框架一直被认为是Web开发中的最佳实践-到目前为止, 它是一种专业标准, 因此大多数Web开发人员都熟悉至少一个PHP MVC框架:Symfony, Yii, Laravel, CodeIgniter, Zend框架等。它们各有优点和缺点, 但是它们有什么共同点?它们都是用PHP编写的, 由许多包含的PHP文件组成, 这些文件具有大量的逻辑, 解释程序必须在每次运行代码时对每个请求运行该逻辑。尽管这样做可以提高透明度, 但我们以性能为代价。大量的代码和包含的大量文件会花费大量的内存和时间, 尤其是在PHP中(因为它是经过解释而不是编译的)。是的, PHP 7的情况已经好得多, 但是仍有很多地方需要改进, PhalconPHP将这些改进引入了表格。
让我们看一些基准。
PhalconPHP基准测试
官方基准已有5年历史了-太老了以至于现在都无法生效, 但是即使那样, 你仍然可以看到PhalconPHP的与众不同之处。让我们看一些新的东西。在2016年的比较中, Phalcon排名前五位-在专业框架中是明显的领导者, 并且只允许使用原始PHP和一些微框架。
PhalconPHP(高负载RESTful API的解决方案)

文章图片
因此, Phalcon速度很快。原始PHP也很快, 但是我们需要MVC框架必须提供的所有功能, Phalcon面临挑战, 其中包括以下组件:
  • 电压模板引擎
  • 依赖注入(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 micro应用程序之间的原型性能。我们发现PhalconPHP的微型应用程序往往要快得多。由于NDA的原因, 我无法向你显示任何基准测试, 但是我认为, 如果你想充分利用Phalcon的性能, 请使用微型应用程序。虽然编写一个微应用程序比编写一个完整的应用程序不那么方便, 但是PhalconPHP的微应用程序仍然具有你项目所需的一切以及更好的性能。为了说明这一点, 让我们用Phalcon编写一个非常简单的RESTful微型应用程序。
构建一个RESTful API RESTful应用程序的几乎所有排列都有一个共同点:用户实体。因此, 对于示例项目, 我们将创建一个微型REST应用程序来创建, 读取, 更新和删除用户(也称为CRUD)。
你可以在我的GitLab存储库中看到该项目已完全完成。之所以有两个分支, 是因为我决定将这个项目分为两个部分:第一个分支, master, 仅包含基本功能, 而没有任何特定的PhalconPHP功能, 而第二个分支, logging-and-cache, 包含Phalcon的日志记录和缓存功能。你可以将它们进行比较, 看看在Phalcon中实现这些功能有多么容易。
安装
我将不涉及安装:你可以使用任何数据库, 任何操作系统和任何所需的网络服务器。官方安装文档中对此进行了很好的描述, 因此请根据你的操作系统按照说明进行操作。
Web服务器安装说明也可在官方Phalcon文档中找到。
请注意, 你的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是一个引导程序文件, 它加载所有必要的部分和配置。请注意, 我们所有的配置文件都放在config文件夹中。我们可以将它们放在bootstrap文件中(这是官方文档中显示的方式), 但是, 我认为在大型项目中这是不可读的, 所以我从一开始就更喜欢文件夹分离。
PhalconPHP(高负载RESTful API的解决方案)

文章图片
创建index.php 我们在index.php的第一遍将加载配置和自动加载类, 然后初始化路由, 依赖项注入容器和PhalconPHP微型应用程序。然后, 它将控制权交给该微型应用程序核心, 该微型应用程序核心将根据路由处理请求, 运行业务逻辑并返回结果。
让我们看一下代码:
< ?phptry { // 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对象将配置选项加载到项目中。我们将有一个非常简短的配置对象:
< ?phpreturn new \Phalcon\Config( [ 'database' => [ 'adapter' => 'Postgresql', 'host' => 'localhost', 'port' => 5432, 'username' => 'postgres', 'password' => '12345', 'dbname' => 'articledemo', ], 'application' => [ 'controllersDir' => "app/controllers/", 'modelsDir' => "app/models/", 'baseUri' => "/", ], ] );

该文件包含两种基本配置, 一种用于数据库, 另一种用于应用程序。显然, 数据库配置用于连接数据库, 而对于应用程序阵列, 我们稍后将需要它, 因为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文件将如下所示:
< ?phpuse 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 \ FactoryDe??fault(); 。我们创建一个继承\ Phalcon \ Di的FactoryDe??fault对象(Phalcon允许你创建所需的任何DI工厂)。根据文档, FactoryDe??fault” 自动注册框架提供的所有服务。因此, 开发人员无需单独注册每个服务即可提供完整的堆栈框架。” 这意味着可以在框架类中访问诸如请求和响应之类的通用服务。你可以在Phalcon服务文档中查看此类服务的完整列表。
下一个重要的事情是设置过程:有几种方法可以在DI容器中进行注册, 所有这些方法在PhalconPHP注册文档中都有完整描述。但是, 在我们的项目中, 我们使用三种方式:匿名函数, 变量和字符串。
匿名函数允许我们在初始化类时做很多事情。具体来说, 在此项目中, 我们首先重写Response对象, 以将项目所有响应的内容类型设置为JSON, 然后使用我们的配置对象初始化数据库适配器。
如前所述, 该项目使用PostgreSQL。如果决定使用其他数据库引擎, 只需在db set函数中更改数据库适配器即可。你可以在PhalconPHP的数据库文档中阅读有关可用数据库适配器和数据库层的更多信息。
第三点需要注意的是, 我注册了一个实现\ Phalcon \ Config服务的$ config变量。尽管我们的示例项目中并未实际使用它, 但由于它是最常用的服务之一, 因此我决定将其包含在此处。其他项目可能几乎需要在任何地方都可以访问该配置。
最后有趣的是实际的setShared方法本身。调用此服务会使服务” 共享” , 这意味着它开始像单例一样工作。根据文档所述:” 第一次解析服务后, 每次消费者从容器中检索服务时, 都会返回相同的实例。”
配置routes.php…或不 最后一个包含的文件是routes.php。现在暂时将其留空-我们将其与控制器一起填充。
实施RESTful核心
是什么使Web项目成为RESTful?根据Wikipedia所述, RESTful应用程序包含三个主要部分:-基本URL-定义状态转换数据元素的互联网媒体类型-标准HTTP方法(GET, POST, PUT, DELETE)和标准HTTP响应代码(200, 403、400、500等)。
在我们的项目中, 基本URL将放置在route.php文件中, 下面将介绍其他提到的要点。
我们将以application / x-www-form-urlencoded的形式接收请求数据, 并以application / json的形式发送响应数据。尽管我认为在实际应用程序中使用x-www-form-urlencoded是个好主意(因为你将很难使用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:错误的请求。 appError属性是详细错误信息的数组, 包括错误说明。
我们的最终版本index.php将捕获三种类型的异常:如上所述的AbstractHttpExceptions; Phalcon请求异常, 可能在解析请求时发生;以及所有其他意外的例外。所有这些都转换为漂亮的JSON格式, 并通过标准的Phalcon Response类发送给客户端:
< ?phpuse 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开发工具创建模型
【PhalconPHP(高负载RESTful API的解决方案)】如果你使用的是现代IDE, 则可能会习惯于突出显示和完成代码。同样, 在典型的PHP框架中, 你可以包括一个带有框架的文件夹, 只需单击一下即可转到函数声明。由于Phalcon是扩展程序, 因此我们不会自动获得此选项。幸运的是, 有一个工具可以填补这一空白, 称为” Phalcon开发工具” , 可以通过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

下一步是创建数据库。它非常简单, 仅由一个用户表组成。尽管我在项目中包含了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开发工具使用空的.phalcon文件夹来检测应用程序是否为Phalcon项目, 因此你必须在项目根目录中创建此空文件夹。它还使用我们创建的配置文件中的一些设置-所有变量存储在应用程序部分下, 而适配器存储在数据库部分下。要生成我们的模型, 我们需要从项目根文件夹执行以下命令:
$ php vendor/phalcon/devtools/phalcon.php model users --namespace="App\Models" --get-set

如果上述所有步骤均已正确完成, 则将在models文件夹中获得一个有效的模型文件Users.php, 该文件已放置在具有getter和setter作为命令行所示的名称空间中。接下来是控制器。
控制器和路由 由于我们的应用程序仅CRUD(创建, 读取, 更新和删除)用户, 因此我们将仅创建一个控制器, 即Users控制器, 其操作如下:
  • 添加用户
  • 显示用户列表
  • 更新用户
  • 删除用户
虽然可以在Phalcon开发工具的帮助下创建控制器, 但我们将手动完成并实现AbstractController及其子级UsersController。
对于Phalcon而言, 创建AbstractController是一个不错的决定, 因为我们可以将所有从依赖项注入中获得的必要类都放入PHPDoc块中。这将有助于IDE自动完成功能。我们还可以编程所有潜在控制器共有的一些错误常数。
现在, 我们的抽象控制器将如下所示:
< ?phpnamespace 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可注入类, 仅此而已。接下来, 让我们创建UsersController骨架:
< ?phpnamespace 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 micro应用程序中, 我们创建一个集合, 每个控制器一个集合, 并将所有处理的请求添加为get, post, put, delete方法, 这些方法以路由模式和一个procedure函数作为参数。请注意, 后续函数应为匿名函数或控制器的方法名称。我们的route.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, 它必须是发布请求。如果要更改用户数据, 则URI必须是放置请求, 并且看起来像http://article.dev/user/12才能为ID为12的用户更改数据。我们还定义了一个未找到的URL处理程序, 从而引发错误。有关更多信息, 请参阅PhalconPHP文档, 以获取完整堆栈应用程序中的路由以及微型应用程序中的路由。
让我们转到控制器的主体, 尤其是addAction方法(所有其他方法都相似;你可以在应用程序代码中看到它们)。控制器方法执行五件事:
  1. 获取并验证请求参数
  2. 为服务方法准备数据
  3. 调用服务方法
  4. 处理异常
  5. 发送回复
让我们从验证开始, 逐步完成每个步骤。虽然Phalcon具有强大的验证组件, 但在这种情况下以一种老式的方式验证数据更为方便, 因此, 我们的验证块如下所示:
$errors = []; $data = http://www.srcmini.com/[]; $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 = http://www.srcmini.com/[]; /** 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**/ }

如你所见, 在最后一节中, 我们处理了两个已知的异常:用户已经存在并且由于某些内部问题(例如数据库连接错误)而无法创建用户。默认情况下, 未知异常将作为HTTP 500引发(内部服务器错误)。尽管我们不向最终用户提供任何详细信息, 但强烈建议将所有错误详细信息(包括跟踪)存储在日志中。
而且, 请不要忘记使用从其他命名空间借来的所有必需类:
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;

商业逻辑 创建的最后一部分是业务逻辑。与控制器一样, 我们将创建一个抽象服务类:
< ?phpnamespace 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类的框架:
< ?phpnamespace 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); } } }

这种方法很容易。我们只是创建一个新的模型对象, 调用它的设置器(返回对象本身;这使我们能够建立调用链), 并在出现错误的情况下抛出ServiceException。而已!现在, 我们可以进行测试。
测试中 现在, 让我们看一下使用Postman的结果。让我们先测试一些垃圾数据:
PhalconPHP(高负载RESTful API的解决方案)

文章图片
请求:
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" } }

结帐。现在获取一些正确的数据:
PhalconPHP(高负载RESTful API的解决方案)

文章图片
请求:
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" } ]

好吧, 它起作用了!
记录和缓存 很难想象没有日志和缓存的高负载应用程序, 而Phalcon为此提供了非常诱人的类。但是我在这里写的不是书, 而是书。我已将日志记录和缓存添加到示例应用程序中, 但已将此代码放置到另一个称为” 日志记录和缓存” 的分支中, 以便你可以轻松查看它并查看代码中的区别。就像其他Phalcon功能一样, 这两个文档也有据可查:日志记录和缓存。
缺点 如你所见, Phalcon确实很酷, 但是与其他框架一样, 它也有缺点, 第一个与其主要优点相同, 它是已编译的C扩展。因此, 你无法轻松更改其代码。好吧, 如果你了解C, 则可以尝试理解其代码并进行一些更改, 运行make并获得自己对Phalcon的修改, 但是它比在PHP代码中进行一些调整要复杂得多。因此, 通常来说, 如果你在Phalcon中发现错误, 则修复起来就不会那么容易。
这在Phalcon 2和Phalcon 3中得到部分解决, 这使你可以在Zephir中编写对Phalcon的扩展。 Zephir是一种编程语言, 旨在简化PHP扩展的创建和可维护性, 并着重于类型和内存安全性。它的语法非常接近PHP, 并且Zephir代码被编译到共享库中, 与PHP扩展相同。因此, 如果你想增强Phalcon, 现在可以。
第二个缺点是自由框架结构。尽管Symfony使开发人员使用牢固的项目结构, 但Phalcon却没有非常严格的规则。开发人员可以创建他们喜欢的任何结构, 尽管有其作者推荐的结构。这并不是一个严重的缺点, 但是当你在引导文件中手动写入所有目录的路径时, 某些人可能认为它太原始了。
PhalconPHP:不仅适用于高负荷应用 希望你对PhalconPHP的杀手功能有一个简短的概述, 并附带了一个简单的Phalcon项目示例。显然, 由于无法在一篇文章中描述所有框架, 因此我并未涵盖该框架的所有可能性, 但幸运的是, Phalcon拥有非常详尽的文档, 其中包含七个出色的教程, 可帮助你了解有关Phalcon的几乎所有内容。
现在, 你已经有了一种全新的方式来轻松创建高负载应用程序, 并且你会发现, 如果你喜欢Phalcon, 它对于其他类型的应用程序也是不错的选择。

    推荐阅读