本文概述
假设你需要基于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和一些微框架。
因此, 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文件中(这是官方文档中显示的方式), 但是, 我认为在大型项目中这是不可读的, 所以我从一开始就更喜欢文件夹分离。
创建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' => "/", ], ]
);
该文件包含两种基本配置, 一种用于数据库, 另一种用于应用程序。显然, 数据库配置用于连接数据库, 而对于应用程序阵列, 我们稍后将需要它, 因为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”自动注册框架提供的所有服务。因此, 开发人员无需单独注册每个服务即可提供完整的堆栈框架。”这意味着可以在框架类中访问诸如请求和响应之类的通用服务。你可以在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类发送给客户端:
<?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开发工具”, 可以通过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自动完成功能。我们还可以编程所有潜在控制器共有的一些错误常数。
现在, 我们的抽象控制器将如下所示:
<?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可注入类, 仅此而已。接下来, 让我们创建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 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方法(所有其他方法都相似;你可以在应用程序代码中看到它们)。控制器方法执行五件事:
- 获取并验证请求参数
- 为服务方法准备数据
- 调用服务方法
- 处理异常
- 发送回复
让我们从验证开始, 逐步完成每个步骤。虽然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 **/
}
如你所见, 在最后一节中, 我们处理了两个已知的异常:用户已经存在并且由于某些内部问题(例如数据库连接错误)而无法创建用户。默认情况下, 未知异常将作为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;
商业逻辑
创建的最后一部分是业务逻辑。与控制器一样, 我们将创建一个抽象服务类:
<?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);
}
}
}
这种方法很容易。我们只是创建一个新的模型对象, 调用它的设置器(返回对象本身;这使我们能够建立调用链), 并在出现错误的情况下抛出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"
}
]
好吧, 它起作用了!
记录和缓存
很难想象没有日志和缓存的高负载应用程序, 而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, 它对于其他类型的应用程序也是不错的选择。