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. 第九章、开发中间件

实施路由选择

路由是指接受用户友好的URL,将URL剖析成它的组成部分,然后做出决定应该调度哪个类和方法的过程。这种实现的好处是,不仅可以使你的URLs搜索引擎优化(SEO)友好,而且可以创建规则,结合正则表达式模式,可以提取参数的值。

如何做...

1.最流行的方法可能是利用支持URL重写的Web服务器。一个例子是配置为使用 mod_rewrite 的Apache网络服务器。然后你定义了重写规则,允许图形文件请求和CSS和JavaScript请求不受影响地通过。否则,请求将通过路由方法被漏过。

2. 另一种潜在的方法是简单地让您的Web服务器虚拟主机定义指向一个特定的路由脚本,然后调用路由类,做出路由决策,并适当地重定向。

3. 第一个要考虑的代码是如何定义路由配置。显而易见的答案是构造一个数组,其中每个键将指向一个正则表达式,URI路径将与之匹配,以及某种形式的操作。下面的代码片段显示了这种配置的一个例子。在这个例子中,我们定义了三个路径:主页、页面和默认值。默认值应该是最后一个,因为它将匹配任何之前没有匹配的内容。这个操作是以匿名函数的形式出现的,如果发生路由匹配,就会被执行。

$config = [
  'home' => [
    'uri' => '!^/$!',
    'exec' => function ($matches) {
      include PAGE_DIR . '/page0.php'; }
  ],
  'page' => [
    'uri' => '!^/(page)/(\d+)$!',
      'exec' => function ($matches) {
        include PAGE_DIR . '/page' . $matches[2] . '.php'; }
  ],
  Router::DEFAULT_MATCH => [
    'uri' => '!.*!',
    'exec' => function ($matches) {
      include PAGE_DIR . '/sorry.php'; }
  ],
];

4. 接下来,我们定义我们的Router类。我们首先定义了在检查和匹配路由的过程中会用到的常量和属性。

namespace Application\Routing;
use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface;
class Router
{
  const DEFAULT_MATCH = 'default';
  const ERROR_NO_DEF  = 'ERROR: must supply a default match';
  protected $request;
  protected $requestUri;
  protected $uriParts;
  protected $docRoot;
  protected $config;
  protected $routeMatch;

5. 构造函数接受一个兼容ServerRequestInterface的类、文档根目录的路径和前面提到的配置文件。请注意,如果没有提供默认配置,我们会抛出一个异常。

public function __construct(ServerRequestInterface $request, $docRoot, $config)
{
  $this->config = $config;
  $this->docRoot = $docRoot;
  $this->request = $request;
  $this->requestUri = 
    $request->getServerParams()['REQUEST_URI'];
  $this->uriParts = explode('/', $this->requestUri);
  if (!isset($config[self::DEFAULT_MATCH])) {
      throw new InvalidArgumentException(
        self::ERROR_NO_DEF);
  }
}

6. 接下来,我们有一系列的getter,可以让我们检索原始请求、文档根和最终的路由匹配。

public function getRequest()
{
  return $this->request;
}
public function getDocRoot()
{
  return $this->docRoot;
}
public function getRouteMatch()
{
  return $this->routeMatch;
}

7. isFileOrDir()方法用于确定我们是否正在尝试与CSS、JavaScript或图形请求进行匹配(以及其他可能性)。

public function isFileOrDir()
{
  $fn = $this->docRoot . '/' . $this->requestUri;
  $fn = str_replace('//', '/', $fn);
  if (file_exists($fn)) {
      return $fn;
  } else {
      return '';
  }
}

8. 最后,我们定义match(),它遍历配置数组,并通过preg_match()运行 uri 参数。如果是正值,那么由 preg_match() 填充的配置键和 $matches 数组将存储在 $routeMatch 中,并返回回调。如果没有匹配,则返回默认回调。

public function match()
{
  foreach ($this->config as $key => $route) {
    if (preg_match($route['uri'], 
        $this->requestUri, $matches)) {
        $this->routeMatch['key'] = $key;
        $this->routeMatch['match'] = $matches;
        return $route['exec'];
    }
  }
  return $this->config[self::DEFAULT_MATCH]['exec'];
}
}

如何运行...

首先,改成 /path/to/source/for/this/chapter,并创建一个名为 routing 的目录。接下来,定义一个文件,index.php,它设置了自动加载并使用正确的类。可以定义一个常量PAGE_DIR,指向前面示例中创建的页面目录。

<?php
define('DOC_ROOT', __DIR__);
define('PAGE_DIR', DOC_ROOT . '/../pages');

require_once __DIR__ . '/../../Application/Autoload/Loader.php';
Application\Autoload\Loader::init(__DIR__ . '/../..');
use Application\MiddleWare\ServerRequest;
use Application\Routing\Router;

接下来,添加本示例第3步中讨论的配置数组。请注意,可以在模式的结尾添加(/)?,以说明一个可选的尾部斜杠。另外,对于主页路由,可以提供两个选项:/或/home。

$config = [
  'home' => [
    'uri' => '!^(/|/home)$!',
    'exec' => function ($matches) {
      include PAGE_DIR . '/page0.php'; }
  ],
  'page' => [
    'uri' => '!^/(page)/(\d+)(/)?$!',
    'exec' => function ($matches) {
      include PAGE_DIR . '/page' . $matches[2] . '.php'; }
  ],
  Router::DEFAULT_MATCH => [
    'uri' => '!.*!',
    'exec' => function ($matches) {
      include PAGE_DIR . '/sorry.php'; }
  ],
];

然后可以定义一个路由器实例,提供一个初始化的 ServerRequest 实例作为第一个参数。

$router = new Router((new ServerRequest())
  ->initialize(), DOC_ROOT, $config);
$execute = $router->match();
$params  = $router->getRouteMatch()['match'];

然后需要检查请求是文件还是目录,还需要检查路径匹配是否为/。

if ($fn = $router->isFileOrDir()
    && $router->getRequest()->getUri()->getPath() != '/') {
    return FALSE;
} else {
    include DOC_ROOT . '/main.php';
}

接下来,定义 main.php,类似这样。

<?php // demo using middleware for routing ?>
<!DOCTYPE html>
<head>
  <title>PHP 7 Cookbook</title>
  <meta http-equiv="content-type" 
  content="text/html;charset=utf-8" />
</head>
<body>
    <?php include PAGE_DIR . '/route_menu.php'; ?>
    <?php $execute($params); ?>
</body>
</html>

最后,还需要修订使用用户友好路由的菜单。

<?php // menu for routing ?>
<a href="/home">Home</a>
<a href="/page/1">Page 1</a>
<a href="/page/2">Page 2</a>
<a href="/page/3">Page 3</a>
<!-- etc. -->

要使用Apache测试配置,定义一个指向 /path/to/source/for/this/chapter/routing 的虚拟主机定义。另外,定义一个.htaccess文件,将任何不是文件、目录或链接的请求导向index.php。另外,您也可以使用内置的PHP webserver。在终端窗口或命令提示符下,键入这个命令。

cd /path/to/source/for/this/chapter/routing
php -S localhost:8080

另见

上一页使用高速缓存提高性能下一页进行框架间的系统调用

最后更新于4年前

在浏览器中,当请求 时的输出是这样的。

关于使用NGINX web服务器重写的信息,请看这篇文章:。有很多复杂的 PHP 路由库可以提供比这里介绍的简单路由器更强大的功能。这些包括Altorouter (), TreeRoute (), FastRoute (), 和Aura.Router. ()。此外,大多数框架(例如,Zend Framework 2或CodeIgniter)都有自己的路由功能。

http://localhost:8080/home
http://nginx.org/en/docs/http/ngx_http_rewrite_module.html
http://altorouter.com/
https://github.com/baryshev/TreeRoute
https://github.com/nikic/FastRoute
https://github.com/auraphp/Aura.Router