PhalconPHP:高负载RESTful API的解决方案

本文概述

假设你需要基于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基准测试

因此, 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的解决方案2

创建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 \ 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类发送给客户端:

<?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方法(所有其他方法都相似;你可以在应用程序代码中看到它们)。控制器方法执行五件事:

  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方法代码及其所有验证, 其中包括对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, 它对于其他类型的应用程序也是不错的选择。

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?