jitsu/app

可扩展的路由和配置机制

0.2.2 2020-02-11 08:38 UTC

This package is auto-updated.

Last update: 2024-09-11 18:53:53 UTC


README

此包实现了一个简单但强大的架构,用于在PHP中开发模块化、可测试的Web应用程序。它主要由HTTP请求路由器、几个内置请求处理类型以及一个通用的配置机制组成,该机制用于驱动路由器的一些行为。

使用这里实现的路由机制,您可以完全控制URL路由(可以隐藏URL中的.php后缀),并且可以将配置设置从配置文件传播到请求处理器的一种简单方法。

此外,与传统PHP脚本通过全局变量(如$_GET$_COOKIE等)访问HTTP请求数据不同,一个Application实例接收代表HTTP请求和响应的对象。应用程序正是通过这些对象与外部世界交互,这使得在单元测试期间可以模拟它们。

因此,此模块旨在解决两个问题:需要一种更简单的方式来路由HTTP请求,以及需要以易于配置和测试的方式设计应用程序。

此包是Jitsu的一部分。

安装

使用Composer安装此包

composer require jitsu/app

关于

您可能熟悉PHP中常见的URL路由机制:以.php结尾的URL映射到同一名称的服务器上的PHP脚本。虽然这对于初学者项目或私人工具或您根本不介意使用以.php结尾的URL的项目来说很好,但这默认设置存在一些问题

  • 为每个页面使用单独的脚本需要在每个文件中添加样板代码以执行常见任务,例如设置自动加载、包含库、执行身份验证、打印页眉和页脚等。没有一个单一的入口点可以处理这些基本任务。
  • 虽然您可以使用.htaccess文件稍微自定义URL,但删除.php样式的URL并不直接。
  • 它使项目的源代码布局对局外人可见,这可能不是所希望的。
  • 请求除了其URL之外还有许多其他有趣的属性可能会影响路由,例如其方法或其Accept头。

此包实现了一种替代路由机制,可以用于替代URL重写。给定一个请求,路由器通过查询处理程序列表并将其传递给第一个声称负责的处理程序来分配它。通过配置您的Web服务器将所有请求都重定向到调用此路由器的单个PHP脚本(可以通过在.htaccess文件中执行简单命令来实现),您可以使用此包删除通常出现在所有URL上的难看的.php后缀。

路由器的一个重要设计特性是它通过一个抽象的面向对象的接口与HTTP请求和响应交互,这使得在测试您的应用程序时可以模拟这些对象。通过依赖注入,您可以将模拟的HTTP请求发送到您的应用程序,并在测试中捕获其响应,前提是它是围绕这些面向对象的接口设计的。请求和响应接口可以在jitsu/http中找到。

路由器的一个特色功能是它使用了一种配置机制,允许路由在任意外部挂载点托管。例如,通过在配置文件中设置

$config->path = 'api/';

,所有请求都将相对于外部路径 api/ 进行路由。所以如果你的域名是 www.example.com/,则对 http://www.example.com/api/test/path 的请求将映射到 test/path 路由。

所有处理程序都接受一个 $data 参数,这是一个 stdObject 的实例,可以用来在处理程序之间传递数据。通过这个对象,处理程序可以通过 $data->request$data->response 访问请求和响应对象,以及通过 $data->config 访问配置对象。处理程序可以给 $data 分配属性,以便与下游处理程序通信。同样,你可以在配置文件中设置任何额外的属性,以告知处理程序的行为。

$config->output_buffering = true;
$config->show_stack_traces = false;
$config->title = 'My Example Website';
$config->log_requests = true;

此包还包括一个可执行脚本 jitsu-config-template,可用于将配置设置注入到用 PHP 编写的文件模板中。此工具可以在预处理步骤中使用在网站配置文件中定义的属性生成 .htaccessrobots.txt 等。文件。此脚本简单地允许你执行 PHP 文件,并将变量 $config 设置为从命令行中列出的文件加载的配置设置。你可以编写这些模板文件,而无需包含配置文件或包含项目 vendor/autoload.php Composer 脚本的样板代码。

命名空间

所有类都定义在命名空间 Jitsu\App 下。

用法

以下示例中,让我们假设我们有一个以下配置文件

config.php

$config->scheme = 'http';
$config->host = 'www.example.com';
$config->path = '/api/';
$config->document_root = '/var/www/example';

或等价于:

$config->base_url = 'http://www.example.com/api/';
$config->document_root = '/var/www/example';

这个文件只是一个正常的 PHP 脚本,它在一个预定义的 $config 变量上设置属性。

现在,让我们编写一个 .htaccess 文件,将所有传入的请求重定向到文件 index.php,对于使用 Apache 的用户。

.htaccess

RewriteEngine On
RewriteRule ^ index.php [L]

很简单。我们可以通过告诉 Apache 将 CSS、JavaScript 等.作为静态资源处理,以及通过防止 Apache 列出目录内容来改进这一点。现在让我们这样做。

Options -Indexes
IndexIgnore *
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f [OR]
RewriteCond %{REQUEST_FILENAME} !^/var/www/example/(.*\.(?:css|js)|favicon\.ico|robots\.txt|assets/.*)$
RewriteRule ^ index.php [L]

这将请求重定向到 index.php除非 Apache 将 URL 匹配到文件系统上的文件,并且该文件以 .css.js 结尾,名为 favicon.icorobots.txt,或位于 assets/ 目录下。

如果 /var/www/example 不是硬编码的会更好。我们可以将 .htaccess 写成模板来做到这一点

htaccess.php

Options -Indexes
IndexIgnore *
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f [OR]
RewriteCond %{REQUEST_FILENAME} !^<?= $config->document_root ?>/(.*\.(?:css|js)|favicon\.ico|robots\.txt|assets/.*)$
RewriteRule ^ index.php [L]

实际上,这将允许我们在不同的文档根同时托管网站的多个构建。

我们可以将此 PHP 文件存入版本控制,并使用以下命令生成真正的 .htaccess 文件:

./vendor/bin/jitsu-config-template -c config.php htaccess.php > .htaccess

现在,让我们继续定义路由器。我们定义一个继承自 Jitsu\App\Application 的应用程序类。然后,我们重写 initialize 方法以添加路由和处理程序。

MyApplication.php

<?php
class MyApplication extends \Jitsu\App\Application {
  public function initialize() {
    $this->get('', function() {
      include __DIR__ . '/index.html.php';
    });
    $this->get('hello', function($data) {
      $data->response->setContentType('text/plain');
      echo "Hello World\n";
    });
    $this->get('users/:id', function($data) {
      list($user_id) = $data->parameters;
      // Just pretend this is defined somewhere
      $user = getUserFromDatabase($user_id);
      if($user) {
        include __DIR__ . '/show_user.html.php';
      } else {
        $data->response->setStatusCode(404);
	include __DIR__ . '/404.html.php';
      }
    });
    $this->notFound(function($data) {
      $data->response->setStatusCode(404);
      $uri = $data->request->uri();
      include __DIR__ . '/404.html.php';
    });
    $this->error(function($data) {
      $data->response->setStatusCode(500);
      $exception = $data->exception;
      include __DIR__ . '/500.html.php';
    });
  }
}

使用 get 定义的路线对其各自的 URL 上的 GET 请求做出响应。当请求无法匹配上述任何路由时,将触发 notFound 处理程序作为后备。最后,将 error 处理程序放入一个单独的回调队列中,该队列在处理程序抛出异常时执行。

请注意,URL 以 Rails 风格的模式指定,允许捕获某些路径段。支持的模式语法如下

  • :name 是一个变量,捕获除 / 之外的所有内容,并将捕获的文本与名为 name 的参数关联。URL 编码的斜杠仍然可以通过。1 示例:users/:id 匹配 users/42 并将 id 设置为 42,但它不会匹配 users/a/b/c
  • *name 是一个通配符,它可以捕获包括斜杠在内的所有内容,并将捕获的文本与名为 name 的参数相关联。示例:assets/*path 匹配 assets/path/to/img.jpg 并将 path = path/to/img.jpg 赋值。
  • () 对一对括号内的可选部分进行封装。示例:users/(index.html) 匹配 users/users/index.html

模式按顺序进行测试,因此应该按照特定性递减的顺序列出。

$this->get('users/me', 'showCurrentUser');
$this->get('users/:id', 'showUser');
$this->get('*path', 'pageNotFound');

所有参数都收集在 $data->parameters 中,这是一个有序数组,其键是参数名称。所有值都会自动进行 URL 解码。

要调用路由器,实例化您的应用程序,并传递请求、响应和配置对象。

index.php

require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/MyApplication.php';
(new MyApplication())->respond(
  new \Jitsu\Http\CurrentRequest(),
  new \Jitsu\Http\CurrentResponse(),
  new \Jitsu\App\SiteConfig(__DIR__ . '/config.php'));

如果您没有模拟 HTTP 请求和响应对象,可以使用以下简写

MyApplication::main(new \Jitsu\App\SiteConfig(__DIR__ . '/config.php'));

1 路由器本身允许在路径组件中编码正斜杠,但某些服务器配置可能不允许这样做。例如,Apache 默认可能会重新编码传入 URL 中的编码正斜杠,或者拒绝此类请求并返回 404 未找到。这是一项安全措施,旨在防止那些有不良意图的人通过未清洗的输入获得对文件系统路径的访问。您可以通过在 Apache 配置文件中的虚拟主机中添加以下指令来允许编码的斜杠

AllowEncodedSlashes NoDecode

API

class Jitsu\App\Application

扩展 Router

一个可扩展的顶级 HTTP 请求路由器。

new Application()

自动添加配置处理程序。

class Jitsu\App\SubRouter

扩展 Router

一个可扩展的子路由器,可以挂载到另一个路由器中。

new SubRouter()

class Jitsu\App\Router

扩展 BasicRouter

一个可扩展的 HTTP 请求路由器。

类似于 BasicRouter,但具有一些快捷方法。

abstract protected function initialize()

覆盖此方法以在此路由器上配置路由和处理程序。

$router->respond($request, $response, $config)

将请求分发给此路由器。

Router::main($config)

respond 的简写,它使用当前的 HTTP 请求和响应调用路由器。

$router->callback($callback)

将回调添加到请求处理程序队列中。

回调将接收一个 stdObject 参数($data),该参数已通过早期的处理程序传递。处理程序可以从该对象中读取属性以访问早期处理程序的数据,并将属性分配给传递给后期处理程序。路由器最初添加了属性 requestresponseconfig

处理程序应该返回 true 如果路由应该由此处理程序停止(表示匹配)或返回 false 如果路由器应该继续尝试匹配后面的路由。处理程序可以在不返回 true 的情况下执行一些操作或向 $data 添加内容,这样它仅仅用于与后面的处理程序通信。

回调按照它们添加的顺序执行。

$router->setNamespace($value)

设置将自动添加到传递给所有处理程序函数的回调名称的前缀的命名空间。

在此处所有回调注册方法中,接受函数名作为回调。如果您的所有回调都在一个命名空间下,则可以使用此方法避免在所有函数名中重复命名空间。

$router->route($route, $callback)

处理所有对特定路径的请求。

路径指定为模式,可能包含命名参数。以下语法受支持

  • :name,它捕获称为 name 的路径段的一部分。这将不会匹配斜杠(/)字符。
  • *name,它捕获称为 name 的文本的一部分,它可以跨越多个路径段。这将匹配任何字符。
  • (可选),其中括号内的部分可选。可选部分可以包含任何模式语法。

任何命名参数将自动进行URL解码,并存储在$data->parameters数组中,该数组将参数名称映射到捕获的值。键值对将按照指定的参数顺序出现。

$router->endpoint($method, $route, $callback)

处理特定方法和路径组合的所有请求。

$router->get($route, $callback)

处理特定路径的GET请求。

$router->post($route, $callback)

处理特定路径的POST请求。

$router->put($route, $callback)

处理特定路径的PUT请求。

$router->delete($route, $callback)

处理特定路径的DELETE请求。

$router->mount($route, $router)

在特定路径上挂载子路由器。

仅在子路由器匹配时停止路由。如果子路由器不匹配,即使挂载点匹配,路由将继续。

$router->badMethod($callback)

处理URL在早期处理程序中匹配但未处理的任何请求,因为方法不匹配。

属性$data->matched_methods将包含此URL的允许方法列表。

$router->notFound($callback)

处理在早期处理程序中没有匹配的任何请求。

$router->error($callback)

处理请求处理程序抛出的任何异常。

属性$data->exception将被设置为抛出的异常。

class Jitsu\App\BasicRouter

基本路由器类。

路由器由两个队列组成:正常请求处理程序队列和错误处理程序队列。路由器按照注册顺序调用请求处理程序,直到其中一个返回true。如果处理程序抛出异常,控制将不可逆转地传递到错误处理程序队列,其行为方式相同,但没有异常的救援策略。

new BasicRouter()

$basic_router->handler($handler)

将处理程序添加到请求处理程序队列。

$basic_router->errorHandler($handler)

将处理程序添加到错误处理程序队列。

处理程序的数据参数将具有属性exception设置为抛出的异常。

$basic_router->run($data)

使用要传递处理程序到处理程序的数据调用路由器。

interface Jitsu\App\Handler

路由处理程序接口。

$handler->handle($data)

响应请求。

class Jitsu\App\SiteConfig

扩展Config

Config的子类,专门用于网站。

添加以下属性

  • base_url:路由器挂载的外部URL。与schemehostpath属性相关。
  • scheme:网站使用的方案或协议(httphttps)。
  • host:网站的域名(例如example.com)。
  • pathbase_url的路径部分。
  • base_path:与path类似,但格式化为始终以斜杠开始和结束。
  • locale:当前运行PHP脚本的区域设置。设置时,使用值数组来指示一系列后备选项。

$site_config->set_base_url($url)

设置网站的基准URL。

如果可以从新值中解析出schemehostpath,它们将相应地设置。

$site_config->get_base_url()

获取基准URL。

<scheme>://<host>/<path>组成。

$site_config->get_base_path()

获取格式化为始终以斜杠开始和结束的path

$site_config->makePath($rel_path)

给定一个相对路径,将其附加到path以形成一个绝对路径。

存在一个特例,如果两者都是空字符串,则返回空字符串。

$site_config->removePath($abs_path)

从绝对路径中移除path,或者如果路径不匹配,则返回null

$site_config->makeUrl($rel_path)

给定一个相对路径,将其附加到base_url以形成一个绝对URL。

一个小型特殊情况:如果配置的path设置为空字符串,则一个空的$rel_path将生成一个不包含尾随斜杠的域名URL(例如http://www.example.com)。这与将path设置为/不同,后者总是会产生一个包含尾随斜杠的URL,即使$rel_path为空(例如http://www.example.com/)。前者在技术上是不正确的,尽管可能被需要,并且浏览器通常会将其作为超链接接受。

$site_config->set_locale($value)

设置区域设置。

如果值是一个字符串数组,则每个字符串都作为前一个字符串的备用,以防它们不可用。

$site_config->get_locale()

获取区域设置。

class Jitsu\App\Config

一个对象,其属性存储配置设置。

用法非常简单。它就像一个stdObject,属性可以随意设置和访问。

$config = new Config(['a' => 1]);
$config->b = 2;
echo $config->a, ' ', $config->b, "\n";

子类可以通过定义以get_set_为前缀的方法来为特定属性添加动态获取器和设置器函数,后跟模拟属性的名称。

配置设置可以从任何将属性分配给预定义变量$config的PHP文件中读取。例如

config.php

$config->a = 1;
$config->b = 2;

读取文件

$config = new Config('config.php');

new Config($args,...)

使用PHP文件名称或属性数组进行初始化。

$config->read($filename)

从PHP文件中读取设置。

这仅仅是将对象分配给$config后评估PHP文件。

$config->set($name, $value = null)

设置/添加一个属性。

$config->merge($arg)

设置/添加多个属性。

$config->__set($name, $value)

$config->get($name, $default = null)

获取一个属性。

$config->__get($name)

$config->has($name)

判断某个属性是否存在。

$config->__isset($name)