PHP 7: 真实世界的应用开发
  • 前言
  • 模块一
    • 第一章、建立基础
      • PHP 7 安装注意事项
      • 使用内置的 PHP web 服务器
      • 创建一个 MySQL 测试数据库
      • 安装 PHPUnit
      • 实现类的自动加载
      • 抓取一个网站
      • 建立一个深度网络扫描器
      • 创建一个 PHP 5 到 PHP 7 代码转换器
    • 第二章、使用 PHP 7 高性能特性
      • 了解抽象语法树
      • 理解句法分析中的差异
      • 理解 foreach() 处理中的差异
      • 使用 PHP 7 增强功能提高性能
      • 遍历海量文件
      • 将电子表格上传到数据库
      • 递归目录迭代器
    • 第三章、使用 PHP 函数
      • 函数开发
      • 数据类型提示
      • 使用返回值数据类型
      • 使用迭代器
      • 使用生成器编写自己的迭代器
    • 第四章、使用 PHP 面向对象程序设计
      • 类的开发
      • 类的扩展
      • 使用静态属性和方法
      • 使用命名空间
      • 定义可见性
      • 使用接口
      • 使用特性
      • 实现匿名类
    • 第五章、与数据库的交互
      • 使用PDO连接数据库
      • 构建一个 OOP SQL 查询生成器
      • 处理分页
      • 定义实体以匹配数据库表
      • 将实体类与RDBMS查询绑定
      • 将二次查找嵌入到查询结果中
      • 实现jQuery DataTables的PHP查找
    • 第六章、建立可扩展的网站
      • 创建通用表单元素生成器
      • 创建一个HTML单选元素生成器
      • 创建一个HTML选择元素生成器
      • 实现表单工厂
      • 链式 $_POST 过滤器
      • 链式 $_POST 验证器
      • 将验证绑定到表单
    • 第七章、访问Web服务
      • 在PHP和XML之间转换
      • 创建一个简单的REST客户端
      • 创建一个简单的REST服务器
      • 创建一个简单的SOAP客户端
      • 创建一个简单的SOAP服务器
    • 第八章、处理日期/时间和国际化方面
      • 在视图脚本中使用 emoji
      • 转换复杂字符
      • 从浏览器数据获取语言环境
      • 按地区设置数字格式
      • 按地区处理货币
      • 按地区设置日期/时间格式
      • 创建一个HTML国际日历生成器
      • 构建一个周期性事件生成器
      • 不使用gettext处理翻译
    • 第九章、开发中间件
      • 使用中间件进行认证
      • 使用中间件实现访问控制
      • 使用高速缓存提高性能
      • 实施路由选择
      • 进行框架间的系统调用
      • 使用中间件来跨语言
    • 第十章、高级算法
      • 使用 getter 和 setter
      • 实现一个链表
      • 建立冒泡排序
      • 实现一个堆栈
      • 构建一个二分法查找类
      • 实现一个搜索引擎
      • 显示多维数组并累计总数
    • 第十一章、软件设计模式的实现
      • 创建数组到对象的转化器
      • 构建对象到数组到转化器
      • 实施策略模式
      • 定义一个映射器
      • 实现对象关系映射
      • 实施发布/订阅设计模式
    • 第十二章、提高网站安全
      • 过滤$_POST数据
      • 验证$_POST数据
      • 保护PHP session
      • 用令牌保护表格的安全
      • 建立一个安全的密码生成器
      • 带有验证码的安全保护表格
      • 不使用mcrypt进行加密/解密
    • 第十三章、最佳实践、测试和调试
      • 使用特征和接口
      • 通用异常处理程序
      • 通用错误处理程序
      • 编写一个简单的测试
      • 编写测试套件
      • 生成虚假的测试数据
      • 使用session_start参数自定义会话
    • PSR-7
  • 模块二
  • 模块三
    • GoF 设计模式
      • 结构型
      • 行为型
      • 小结
    • SOLID 设计原则
      • 开闭原则
      • 里氏替换原则
      • 接口隔离原则
      • 依赖反转原则
      • 小结
    • 模块化网店应用的需求规范
      • 线框设计
      • 定义技术栈
      • 小结
    • Symfony 概述
      • 创建一个空白项目
      • 使用 Symfony 控制台
      • 控制器
      • 路由
      • 模板
      • 表单
      • 配置 Symfony
      • bundle 系统
      • 数据库和 Doctrine
      • 测试
      • 验证
      • 小结
    • 构建核心模块
    • 构建目录模块
    • 构建客户模块
    • 构建支付模块
    • 构建发货模块
    • 构建销售模块
    • 总结
由 GitBook 提供支持
在本页
  • 如何做...
  • 如何运行...
  1. 模块一
  2. 第九章、开发中间件

进行框架间的系统调用

开发 PSR-7(和中间件)的主要原因之一是对框架之间调用的需求越来越大。值得注意的是,PSR-7 的主要文档由 PHP Framework Interop Group (PHP-FIG) 主持。

如何做...

1.在中间件框架间调用中使用的主要机制是创建一个驱动程序,连续执行框架调用,维护一个共同的请求和响应对象。请求和响应对象有望分别代表 Psr\Http\Message\ServerRequestInterface 和Psr\Http\Message\ResponseInterface。

2. 为了这个说明的目的,我们定义了一个中间件会话验证器。常量和属性反映了会话指纹,这是我们用来整合网站访问者的 IP 地址、浏览器和语言设置等因素的术语。

namespace Application\MiddleWare\Session;
use InvalidArgumentException;
use Psr\Http\Message\ { 
  ServerRequestInterface, ResponseInterface };
use Application\MiddleWare\ { Constants, Response, TextStream };
class Validator
{
  const KEY_TEXT = 'text';
  const KEY_SESSION = 'thumbprint';
  const KEY_STATUS_CODE = 'code';
  const KEY_STATUS_REASON = 'reason';
  const KEY_STOP_TIME = 'stop_time';
  const ERROR_TIME = 'ERROR: session has exceeded stop time';
  const ERROR_SESSION = 'ERROR: thumbprint does not match';
  const SUCCESS_SESSION = 'SUCCESS: session validates OK';
  protected $sessionKey;
  protected $currentPrint;
  protected $storedPrint;
  protected $currentTime;
  protected $storedTime;

3. 构造函数把ServerRequestInterface实例和session作为参数。如果session是一个数组(比如$_SESSION),我们就把它封装在一个类中。我们这样做的原因是为了防止传递给我们一个会话对象,比如Joomla中使用的JSession。然后,我们使用前面提到的因素创建拇指指纹。如果存储的拇指印不可用,我们就假设这是第一次,如果设置了这个参数,就存储当前打印以及停止时间。我们使用了md5(),因为它是一种快速的哈希值,不对外暴露,因此对这个应用很有用。

public function __construct(
  ServerRequestInterface $request, $stopTime = NULL)
{
  $this->currentTime  = time();
  $this->storedTime   = $_SESSION[self::KEY_STOP_TIME] ?? 0;
  $this->currentPrint = 
    md5($request->getServerParams()['REMOTE_ADDR']
      . $request->getServerParams()['HTTP_USER_AGENT']
      . $request->getServerParams()['HTTP_ACCEPT_LANGUAGE']);
        $this->storedPrint  = $_SESSION[self::KEY_SESSION] 
      ?? NULL;
  if (empty($this->storedPrint)) {
      $this->storedPrint = $this->currentPrint;
      $_SESSION[self::KEY_SESSION] = $this->storedPrint;
      if ($stopTime) {
          $this->storedTime = $stopTime;
          $_SESSION[self::KEY_STOP_TIME] = $stopTime;
      }
  }
}

4. 虽然不需要定义__invoke(),但这个神奇的方法对于独立的中间件类来说相当方便。按照惯例,我们接受 ServerRequestInterface 和 ResponseInterface 实例作为参数。在这个方法中,我们只需检查当前的拇指印是否与存储的拇指印一致。第一次,当然,它们会匹配。但在随后的请求中,意图劫持会话的攻击者有可能会被发现。此外,如果会话时间超过了停止时间(如果设置了),同样,也会发送401代码。

public function __invoke(
  ServerRequestInterface $request, Response $response)
{
  $code = 401;  // unauthorized
  if ($this->currentPrint != $this->storedPrint) {
      $text[self::KEY_TEXT] = self::ERROR_SESSION;
      $text[self::KEY_STATUS_REASON] = 
        Constants::STATUS_CODES[401];
  } elseif ($this->storedTime) {
      if ($this->currentTime > $this->storedTime) {
          $text[self::KEY_TEXT] = self::ERROR_TIME;
          $text[self::KEY_STATUS_REASON] = 
            Constants::STATUS_CODES[401];
      } else {
          $code = 200; // success
      }
  }
  if ($code == 200) {
      $text[self::KEY_TEXT] = self::SUCCESS_SESSION;
      $text[self::KEY_STATUS_REASON] = 
        Constants::STATUS_CODES[200];
  }
  $text[self::KEY_STATUS_CODE] = $code;
  $body = new TextStream(json_encode($text));
  return $response->withStatus($code)->withBody($body);
}

5. 现在我们可以把我们新的中间件类用起来了。这里总结了框架间调用的主要问题,至少在这一点上是这样。相应地,我们如何实现中间件,很大程度上取决于最后一点。

  • 并非所有的PHP框架都符合PSR-7标准

  • 现有的PSR-7实施工作不完整

  • 所有框架都想当 "老大"

6. 举个例子,看看 Zend Expressive 的配置文件,它是一个自称 PSR7 的中间件微框架。这里有一个文件, middleware-pipeline.global.php,它位于标准Expressive应用程序的config/autoload文件夹中。依赖关系键用于识别将在管道中激活的中间件包装类。

<?php
use Zend\Expressive\Container\ApplicationFactory;
use Zend\Expressive\Helper;
return [  
  'dependencies' => [
     'factories' => [
        Helper\ServerUrlMiddleware::class => 
        Helper\ServerUrlMiddlewareFactory::class,
        Helper\UrlHelperMiddleware::class => 
        Helper\UrlHelperMiddlewareFactory::class,
        // insert your own class here
     ],
  ],

7. 在 middleware_pipline 键下,您可以确定将在路由过程发生之前或之后执行的类。可选参数包括路径、错误和优先级。

'middleware_pipeline' => [
   'always' => [
      'middleware' => [
         Helper\ServerUrlMiddleware::class,
      ],
      'priority' => 10000,
   ],
   'routing' => [
      'middleware' => [
         ApplicationFactory::ROUTING_MIDDLEWARE,
         Helper\UrlHelperMiddleware::class,
         // insert reference to middleware here
         ApplicationFactory::DISPATCH_MIDDLEWARE,
      ],
      'priority' => 1,
   ],
   'error' => [
      'middleware' => [
         // Add error middleware here.
      ],
      'error'    => true,
      'priority' => -10000,
    ],
  ],
];

8. 另一种技术是修改现有框架模块的源代码,并向符合PSR-7的中间件应用程序提出请求。下面是一个修改Joomla!安装的例子,以包含一个中间件会话验证器。

9. 接下来,在 /path/to/joomla 文件夹的 index.php 文件末尾添加这段代码。由于Joomla!使用Composer,我们可以利用Composer的自动加载器。

session_start();    // to support use of $_SESSION
$loader = include __DIR__ . '/libraries/vendor/autoload.php';
$loader->add('Application', __DIR__ . '/libraries/vendor');
$loader->add('Psr', __DIR__ . '/libraries/vendor');

10. 然后我们可以创建一个中间件会话验证器的实例,并在 $app = JFactory::getApplication('site') 之前发出验证请求。

$session = JFactory::getSession();
$request = 
  (new Application\MiddleWare\ServerRequest())->initialize();
$response = new Application\MiddleWare\Response();
$validator = new Application\Security\Session\Validator(
  $request, $session);
$response = $validator($request, $response);
if ($response->getStatusCode() != 200) {
  // take some action
}

如何运行...

cd /path/to/source/for/this/chapter
php composer.phar create-project zendframework/zend-expressive-skeleton expressive

这将创建一个文件夹/path/to/source/for/this/chapter/expressive。改成这个目录。修改public/index.php如下。

<?php
if (php_sapi_name() === 'cli-server'
    && is_file(__DIR__ . parse_url(
$_SERVER['REQUEST_URI'], PHP_URL_PATH))
) {
    return false;
}
chdir(dirname(__DIR__));
session_start();
$_SESSION['time'] = time();
$appDir = realpath(__DIR__ . '/../../..');
$loader = require 'vendor/autoload.php';
$loader->add('Application', $appDir);
$container = require 'config/container.php';
$app = $container->get(\Zend\Expressive\Application::class);
$app->run();

然后,需要创建一个包装器类来调用我们的会话验证器中间件。创建一个SessionValidateAction.php文件,需要放在/path/to/source/for/this/chapter/expressive/src/App/Action文件夹中。在本例中,将停止时间参数设置为一个较短的持续时间。在本例中,time() + 10得到10秒。

namespace App\Action;
use Application\MiddleWare\Session\Validator;
use Zend\Diactoros\ { Request, Response };
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class SessionValidateAction
{
  public function __invoke(ServerRequestInterface $request, 
  ResponseInterface $response, callable $next = null)
  {
    $inbound   = new Response();
    $validator = new Validator($request, time()+10);
    $inbound   = $validator($request, $response);
    if ($inbound->getStatusCode() != 200) {
        session_destroy();
        setcookie('PHPSESSID', 0, time()-300);
        $params = json_decode(
          $inbound->getBody()->getContents(), TRUE);
        echo '<h1>',$params[Validator::KEY_TEXT],'</h1>';
        echo '<pre>',var_dump($inbound),'</pre>';
        exit;
    }
    return $next($request,$response);
  }
}

现在需要将新类添加到中间件管道中。修改config/autoload/middleware-pipeline.global.php如下。修改的内容以粗体显示。

<?php
use Zend\Expressive\Container\ApplicationFactory;
use Zend\Expressive\Helper;
return [
  'dependencies' => [
     'invokables' => [
        App\Action\SessionValidateAction::class => 
        App\Action\SessionValidateAction::class,
     ],
   'factories' => [
      Helper\ServerUrlMiddleware::class => 
      Helper\ServerUrlMiddlewareFactory::class,
      Helper\UrlHelperMiddleware::class => 
      Helper\UrlHelperMiddlewareFactory::class,
    ],
  ],
  'middleware_pipeline' => [
      'always' => [
         'middleware' => [
            Helper\ServerUrlMiddleware::class,
         ],
         'priority' => 10000,
      ],
      'routing' => [
         'middleware' => [
            ApplicationFactory::ROUTING_MIDDLEWARE,
            Helper\UrlHelperMiddleware::class,
            App\Action\SessionValidateAction::class,
            ApplicationFactory::DISPATCH_MIDDLEWARE,
         ],
         'priority' => 1,
      ],
    'error' => [
       'middleware' => [
          // Add error middleware here.
       ],
       'error'    => true,
       'priority' => -10000,
    ],
  ],
];

也可以考虑修改主页模板来显示$_SESSION的状态。这个文件是/path/to/source/for/this/chapter/expressive/templates/app/home-page.phtml。只需添加var_dump($_SESSION)就可以了。

最初,应该看到这样的东西。

10秒后,刷新浏览器。现在你应该看到这个。

上一页实施路由选择下一页使用中间件来跨语言

最后更新于4年前

首先,创建步骤2-5中描述的 Application\MiddleWare\Session\Validator 测试中间件类。然后,你需要去 ,并按照指示获得Composer。将其下载到/path/to/source/for/this/chapter文件夹中。接下来,构建一个基本的Zend Expressive应用程序,如下图所示。当提示需要最小框架时,一定要选择No。

https://getcomposer.org/