developermarius/simple-router

简单、快速且易于集成的PHP路由器,几乎适用于任何项目。深受Laravel路由器的启发。

5.1.6 2024-09-25 20:43 UTC

This package is auto-updated.

Last update: 2024-09-25 20:50:28 UTC


README

简单、快速且功能强大的PHP路由器,易于集成到任何项目中。深受Laravel处理路由的方式启发,既简单又易于扩展。

使用simple-router,您可以快速创建新项目,无需依赖框架。

只需要几行代码即可开始

SimpleRouter::get('/', function() {
    return 'Hello world';
});

这是一个分支

这是对路由器的分支,带来了许多错误修复和新特性。
遗憾的是,主要项目不再维护。
我们已将项目更新为需要php 8!

支持项目(初始维护者)

如果您喜欢simple-router并希望看到项目的持续开发和维护,请考虑通过购买我一杯咖啡来表示您的支持。支持者将列在此文档的致谢部分。

您可以通过 此处点击 捐赠任意金额。

目录

入门

使用以下命令添加运行simple-router项目的最新版本。

composer require developermarius/simple-router

从分支迁移

我完全重构了InputHandler。请求体中的输入参数将不再在$this->input()->post()函数中找到,而是在$this->input()->data()函数中。注意其他更改。
辅助函数已移动到特质中。请查看文档。
如果您有任何进一步的问题或发现了错误,请创建一个问题或拉取请求。

注意

本项目的目标是创建一个与Laravel文档至少100%兼容的路由器,同时尽可能简单,并易于集成和更改,而不会牺牲速度或复杂性。轻量级是首要任务。

我们包含了一个简单的路由器演示项目,可以在这里找到。这个项目应该能让你基本了解如何设置和使用simple-php-router项目。

请注意,演示项目仅涵盖了如何在没有现有框架的项目中集成simple-php-router。如果您在项目中使用框架,实现可能有所不同。

您可以在https://github.com/skipperbent/simple-router-demo找到演示项目。

我们不涵盖的内容

  • 如何设置满足您需求解决方案。这是一个基本的演示,以帮助您入门。
  • MVC的理解;包括控制器、中间件或异常处理器。
  • 如何集成到第三方框架中。

我们涵盖的内容

  • 如何快速启动 - 从零开始。
  • 如何使异常处理器、中间件和控制器工作。
  • 如何设置您的web服务器。

要求

  • PHP 8.0或更高版本
  • 启用PHP JSON扩展。

特性

  • 基本路由(GETPOSTPUTPATCHUPDATEDELETE)支持自定义多动词。
  • 参数的正则表达式约束。
  • 命名路由。
  • 生成路由的URL。
  • 路由组。
  • 中间件(在渲染路由之前拦截的类)。
  • 命名空间。
  • 路由前缀。
  • CSRF保护。
  • 可选参数
  • 子域名路由。
  • 自定义启动管理器以重写“更友好”的URL。
  • 输入管理器;轻松管理GETPOSTFILE值。
  • 基于IP的限制。
  • 易于扩展。

安装

  1. 在终端导航到您的项目文件夹并运行以下命令
composer require developermarius/simple-router

设置Nginx

如果您使用Nginx,请确保已启用url-rewriting。

您可以通过为演示项目的Nginx配置文件添加以下配置轻松启用url-rewriting。

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

设置Apache

对于Apache,不需要特殊要求。我们在public文件夹中包含了.htaccess文件。如果重写不起作用,请检查在Apache配置中是否启用了mod_rewrite模块(htaccess支持)。

.htaccess示例

以下是一个simple-php-router使用的有效.htaccess文件示例。

只需在项目的public目录中创建一个新的.htaccess文件,并将以下内容粘贴到新创建的文件中。这将重定向所有请求到您的index.php文件(见配置部分以下)。

RewriteEngine on
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-l
RewriteRule ^(.*)$ index.php/$1

设置IIS

在 IIS 上,您需要在公共文件夹中的 web.config 文件中添加一些行,或者创建一个新的文件。如果重写不起作用,请检查您的 IIS 版本是否已包含 url rewrite 模块,或者从 Microsoft 网站下载并安装它们。

web.config 示例

以下是 simple-php-router 所使用的有效 web.config 文件的一个示例。

只需在项目的 public 目录中创建一个新的 web.config 文件,然后将以下内容粘贴到新创建的文件中。这将使所有请求重定向到您的 index.php 文件(请参阅下面的配置部分)。如果 web.config 文件已经存在,请在 <system.webServer> 节点内添加 <rewrite> 部分。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<system.webServer>
		<rewrite>
			<rules>
				<!-- Remove slash '/' from the en of the url -->
				<rule name="RewriteRequestsToPublic">
					<match url="^(.*)$" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
					</conditions>
					<action type="Rewrite" url="/{R:0}" />
				</rule>

				<!-- When requested file or folder don't exists, will request again through index.php -->
				<rule name="Imported Rule 1" stopProcessing="true">
					<match url="^(.*)$" ignoreCase="true" />
					<conditions logicalGrouping="MatchAll">
						<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
						<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
					</conditions>
					<action type="Rewrite" url="/index.php/{R:1}" appendQueryString="true" />
				</rule>
			</rules>
		</rewrite>
	</system.webServer>
</configuration>

故障排除

如果您项目中没有 favicon.ico 文件,您可能会遇到 NotFoundHttpException(404 - 未找到)。

要将 favicon.ico 添加到 IIS 忽略列表中,请在 <conditions> 组中添加以下行

<add input="{REQUEST_FILENAME}" negate="true" pattern="favicon.ico" ignoreCase="true" />

您也可以为具有某些扩展名的文件设置一个例外

<add input="{REQUEST_FILENAME}" pattern="\.ico|\.png|\.css|\.jpg" negate="true" ignoreCase="true" />

如果您使用的是 $_SERVER['ORIG_PATH_INFO'],您将在返回值中获取 \index.php\ 作为部分。

示例

/index.php/test/mypage.php

配置

创建一个新文件,命名为 routes.php 并将其放置在库文件夹中。这将是一个用于定义项目所有路由的文件。

警告:绝对不要将 routes.php 放在公共文件夹中!

在您的 index.php 中引入新创建的 routes.php 并调用 SimpleRouter::start() 方法。这将触发并执行实际的请求路由。

这不是必须的,但您可以将 SimpleRouter::setDefaultNamespace('\Demo\Controllers'); 设置为为所有路由添加控制器的前缀命名空间。这将稍微简化一些事情,因为您不需要在每个路由上指定控制器命名空间。

这是一个基本 index.php 文件的示例

<?php
use Pecee\SimpleRouter\SimpleRouter;

/* Load external routes file */
require_once 'routes.php';

/**
 * The default namespace for route-callbacks, so we don't have to specify it each time.
 * Can be overwritten by using the namespace config option on your routes.
 */

SimpleRouter::setDefaultNamespace('\Demo\Controllers');

// Start the routing
SimpleRouter::start();

辅助函数

我们建议您将这些辅助函数添加到您的项目中。这些将允许您更轻松地访问路由器的功能。

只需将特质添加到您的控制器类中

class ArticleController{

    use \Pecee\SimpleRouter\RouterUtils;
    
    public function create(){
    	$data = $this->input()->values();
    	
    	[...]
    }
}

如果您不在控制器类中,您仍然可以像这样访问 InputHandler
SimpleRouter::request()->getInputHandler()->values();

路由

请记住,您在 index.php 中引入的 routes.php 文件。这将是一个放置所有自定义路由规则的地方。

基本路由

以下是设置路由的非常基本的示例。第一个参数是要匹配的路由 URL - 下一个参数是在路由匹配时将被触发的 Closure 或回调函数。

SimpleRouter::get('/', function() {
    return 'Hello world';
});

类提示

您可以使用类提示来加载类和方法,如下所示

SimpleRouter::get('/', [MyClass::class, 'myMethod']);

可用方法

在这里,您可以看到所有可用路由的列表

SimpleRouter::get($url, $callback, $settings);
SimpleRouter::post($url, $callback, $settings);
SimpleRouter::put($url, $callback, $settings);
SimpleRouter::patch($url, $callback, $settings);
SimpleRouter::delete($url, $callback, $settings);
SimpleRouter::options($url, $callback, $settings);

多个HTTP动词

有时您可能需要创建一个接受多个 HTTP-verbs 的路由。如果您需要匹配所有 HTTP-verbs,可以使用 any 方法。

SimpleRouter::match(['get', 'post'], '/', function() {
    // ...
});

SimpleRouter::any('foo', function() {
    // ...
});

我们创建了一个简单的方法来匹配 GETPOST,这是最常用的。

SimpleRouter::form('foo', function() {
    // ...
});

路由参数

必需参数

您可能想知道如何从 URL 解析参数。例如,您可能希望从 URL 中捕获用户 ID。您可以通过定义路由参数来实现这一点。

SimpleRouter::get('/user/{id}', function ($userId) {
    return 'User with id: ' . $userId;
});

您可以为您的路由定义所需数量的路由参数

SimpleRouter::get('/posts/{post}/comments/{comment}', function ($postId, $commentId) {
    // ...
});

注意:路由参数始终用 {} 括号括起来,并且应由字母字符组成。路由参数只能包含某些字符,如 A-Za-z0-9-_。如果您的路由包含其他字符,请参阅 自定义参数匹配正则表达式

可选参数

有时你可能需要指定一个路由参数,但同时使其可选。你可以通过在参数名称后放置一个问号(?)来做到这一点。请确保为路由的对应变量提供一个默认值。

SimpleRouter::get('/user/{name?}', function ($name = null) {
    return $name;
});

SimpleRouter::get('/user/{name?}', function ($name = 'Simon') {
    return $name;
});

正则表达式约束

你可以使用路由实例上的 where 方法来约束路由参数的格式。where 方法接受参数的名称和一个正则表达式,用于定义如何约束参数。

SimpleRouter::get('/user/{name}', function ($name) {
    
    // ... do stuff
    
})->where([ 'name' => '[A-Za-z]+' ]);

SimpleRouter::get('/user/{id}', function ($id) {
    
    // ... do stuff
    
})->where([ 'id' => '[0-9]+' ]);

SimpleRouter::get('/user/{id}/{name}', function ($id, $name) {
    
    // ... do stuff
    
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

正则表达式路由匹配

如果你希望定义整个路由的正则表达式匹配,你可以这样做。

这对于例如创建一个从 AJAX 加载 URL 的模型框很有用。

下面的例子使用了以下正则表达式:/ajax/([\w]+)/?([0-9]+)?/?,它基本上只匹配 /ajax/ 并期望下一个参数是一个字符串——下一个是一个数字(但可选)。

匹配: /ajax/abc//ajax/abc/123/

不匹配: /ajax/

正则表达式中指定的匹配组将被作为参数传递。

SimpleRouter::all('/ajax/abc/123', function($param1, $param2) {
	// param1 = abc
	// param2 = 123
})->setMatch('/\/ajax\/([\w]+)\/?([0-9]+)?\/?/is');

自定义正则表达式匹配参数

默认情况下,simple-php-router 使用 [\w\-]+ 正则表达式。它将匹配 A-Za-z0-9-_ 字符在参数中。这个决定是在速度和可靠性方面做出的,因为这个匹配将匹配字母、数字和互联网上使用的多数符号。

然而,有时有必要添加一个自定义的正则表达式来匹配更高级的字符,如外文字母 æ ø å 等。

你可以在 Regex101.com 网站上测试你的自定义正则表达式。

而不是将自定义正则表达式添加到所有参数中,你可以简单地添加一个全局正则表达式,该表达式将在所有路由参数上使用。

注意:如果你想让正则表达式在多个路由中可用,我们建议使用下面的示例中的全局参数。

示例

这个例子将确保所有参数在解析时都使用 [\w\-\æ\ø\å]+a-zA-Z-_0-9æøå)正则表达式。

SimpleRouter::get('/path/{parameter}', 'VideoController@home', ['defaultParameterRegex' => '[\w\-\æ\ø\å]+']);

如果你需要多个路由在解析参数时使用自定义正则表达式,你也可以将此设置应用于组。

SimpleRouter::group(['defaultParameterRegex' => '[\w\-\æ\ø\å]+'], function() {

    SimpleRouter::get('/path/{parameter}', 'VideoController@home');

});

命名路由

命名路由允许方便地生成特定路由的 URL 或重定向。你可以通过将 name 方法链接到路由定义上来指定路由的名称。

SimpleRouter::get('/user/profile', function () {
    // Your code here
})->name('profile');

你还可以为控制器操作指定名称。

SimpleRouter::get('/user/profile', 'UserController@profile')->name('profile');

生成命名路由的URL

一旦给一个路由分配了名称,你就可以在生成 URL 或重定向时使用该路由的名称,通过全局的 url 辅助函数(见辅助部分)。

// Generating URLs...
$url = url('profile');

如果命名路由定义了参数,你可以将参数作为 url 函数的第二个参数传递。给定的参数将自动插入到 URL 中正确的位置。

SimpleRouter::get('/user/{id}/profile', function ($id) {
    //
})->name('profile');

$url = url('profile', ['id' => 1]);

有关 URL 的更多信息,请参阅 Urls 部分。

路由分组

路由组允许你在大量路由之间共享路由属性,如中间件或命名空间,而无需在每个单独的路由上定义这些属性。共享属性以数组格式指定为 SimpleRouter::group 方法的第一个参数。

中间件

要为组内的所有路由分配中间件,你可以在组属性数组中使用 middleware 键。中间件将按照它们在数组中列出的顺序执行。

SimpleRouter::group(['middleware' => \Demo\Middleware\Auth::class], function () {
    SimpleRouter::get('/', function ()    {
        // Uses Auth Middleware
    });

    SimpleRouter::get('/user/profile', function () {
        // Uses Auth Middleware
    });
});

命名空间

路由组的另一个常见用例是使用组数组中的 namespace 参数为控制器组分配相同的 PHP 命名空间。

注意

仅将组命名空间添加到具有相对回调的路由。例如,如果您的路由具有绝对回调\Demo\Controller\DefaultController@home,则路由中的命名空间不会被添加前缀。要修复此问题,您可以通过从回调的开头删除\来使回调相对。

SimpleRouter::group(['namespace' => 'Admin'], function () {
    // Controllers Within The "App\Http\Controllers\Admin" Namespace
});

您还可以将参数添加到路由的前缀。

先前路由的参数将在任何路由所需参数之后注入到您的路由中,从最早的到最新的。

SimpleRouter::group(['prefix' => '/lang/{lang}'], function ($language) {
    
    SimpleRouter::get('/about', function($language) {
    	
    	// Will match /lang/da/about
    	
    });
    
});

子域名路由

路由组还可以用于处理子域名路由。子域名可以像路由URL一样分配路由参数,允许您捕获子域的一部分用于您的路由或控制器。您可以使用组属性数组中的domain键指定子域名

SimpleRouter::group(['domain' => '{account}.myapp.com'], function () {
    SimpleRouter::get('/user/{id}', function ($account, $id) {
        //
    });
});

路由前缀

prefix组属性可用于在组中的每个路由前添加给定的URL。例如,您可能希望在该组中为所有路由URL添加前缀admin

SimpleRouter::group(['prefix' => '/admin'], function () {
    SimpleRouter::get('/users', function ()    {
        // Matches The "/admin/users" URL
    });
});

您还可以在组中使用参数

SimpleRouter::group(['prefix' => '/lang/{language}'], function ($language) {
    SimpleRouter::get('/users', function ($language)    {
        // Matches The "/lang/da/users" URL
    });
});

或使用简写,如果您不想传递额外的设置

SimpleRouter::group('/lang/{language}', function ($language) {
    SimpleRouter::get('/users', function ($language)    {
        // Matches The "/lang/da/users" URL
    });
});

表单方法欺骗

HTML表单不支持PUTPATCHDELETE操作。因此,当定义从HTML表单调用PUTPATCHDELETE路由时,您需要在表单中添加一个隐藏的_method字段。将使用_method字段的值作为HTTP请求方法

<input type="hidden" name="_method" value="PUT" />

访问当前路由

您可以使用以下方法访问当前路由加载的信息

SimpleRouter::request()->getLoadedRoute();
request()->getLoadedRoute();

其他示例

您可以在下面的routes.php示例文件中找到更多示例

<?php
use Pecee\SimpleRouter\SimpleRouter;

/* Adding custom csrfVerifier here */
SimpleRouter::csrfVerifier(new \Demo\Middlewares\CsrfVerifier());

SimpleRouter::group(['middleware' => \Demo\Middlewares\Site::class, 'exceptionHandler' => \Demo\Handlers\CustomExceptionHandler::class], function() {


    SimpleRouter::get('/answers/{id}', 'ControllerAnswers@show', ['where' => ['id' => '[0-9]+']]);

	/**
     * Class hinting is supported too
     */
     
     SimpleRouter::get('/answers/{id}', [ControllerAnswers::class, 'show'], ['where' => ['id' => '[0-9]+']]);

    /**
     * Restful resource (see IRestController interface for available methods)
     */

    SimpleRouter::resource('/rest', ControllerResource::class);


    /**
     * Load the entire controller (where url matches method names - getIndex(), postIndex(), putIndex()).
     * The url paths will determine which method to render.
     *
     * For example:
     *
     * GET  /animals         => getIndex()
     * GET  /animals/view    => getView()
     * POST /animals/save    => postSave()
     *
     * etc.
     */

    SimpleRouter::controller('/animals', ControllerAnimals::class);

});

SimpleRouter::get('/page/404', 'ControllerPage@notFound', ['as' => 'page.notfound']);

验证

默认情况下,验证将抛出InputValidationException。要禁用此行为,请使用:InputValidator::setThrowExceptions(false);

正常验证

对于所有验证功能,我们使用了项目somnambulist-tech/validation。感谢@dave-redfern!您可以阅读他的文档以获取更多详细信息。

对于我们的目的,我们将他的验证工厂放入包装函数InputValidator中。您可以通过

InputValidator::make()->setRules([
	'title' => 'string|min:3|max:200',
	'description' => 'string|min:20|max:500'
])->validateData(array(
	'title' => 'Example title',
	'description' => 'This is an example description.'
))->passes();

访问Factory

您可以轻松访问验证工厂来更改语言或添加自定义规则

use \Pecee\Http\Input\InputValidator;

InputValidator::getFactory()->messages()->default('de');
InputValidator::getFactory()->addRule('example', new ExampleRule());

更改/差异

  • 对于输入验证的主要目的,如果我们没有nullable,我们始终自动将required添加到规则中。

php属性

您可以使用\Pecee\Http\Input\InputValidator::$parseAttributes = true;启用验证属性解析。这有助于提高不希望使用属性进行验证的性能。

路由

您还可以在控制器类中使用ValidatorAttributes。只需在函数上方添加php8属性,所有定义的输入都将进行验证并转换为定义的类型。
然后,您可以使用$this->input()->requireAttributeValues()接收所有定义的输入参数。
信息:当验证失败时,路由将不会渲染。

您还可以将ValidatorAttributes添加到通过路由传递的参数。这在创建一个自定义规则时非常有用,例如检查对象是否存在于您的数据库中(不包括在此项目中)。如果已定义,将自动检测参数名称和类型。请定义php类型或使用属性的构造函数传递类型

use Pecee\Http\Input\Attributes\ValidatorAttribute;

#[
	ValidatorAttribute('title', 'string', 'min:3|max:200'),
	ValidatorAttribute('description', 'string', 'min:20|max:500'),
]
public function createArticle(#[ValidatorAttribute(validator: 'model:User')] int $user){
	$data = $this->input()->requireAttributeValues();
}

路由函数validateInputs()

除了使用php属性外,您还可以使用validateInputs()函数将验证添加到路由。

TestRouter::get('/my/test/url', 'ArticleController@createArticle')
	->validateInputs([
		'title' => 'string|min:3|max:200',
		'description' => 'string|min:20|max:500'
	]);

您还可以传递一个InputValidator实例,并利用InputValidator对象的rewriteCallbackOnFailure()函数。

CSRF保护

POSTPUTDELETE路由提交表单的任何表单都应该包含CSRF-token。我们强烈建议您在网站上启用CSRF-验证以最大限度地提高安全性。

您可以使用 BaseCsrfVerifier 在所有请求上启用 CSRF 验证。如果您需要为特定 URL 禁用验证,请参阅下文的“自定义 CSRF 验证器”部分。

默认情况下,simple-php-router 将使用 CookieTokenProvider 类。此提供程序将在客户端机器上的 cookie 中存储安全令牌。如果您想在其他地方存储令牌,请参阅下文的“创建自定义令牌提供程序”部分。

添加CSRF验证器

当您创建 CSRF 验证器后,您需要告诉 simple-php-router 应该使用它。您可以在您的 routes.php 文件中添加以下行来完成此操作

SimpleRouter::csrfVerifier(new \Demo\Middlewares\CsrfVerifier());

获取CSRF令牌

当您向启用了 CSRF 验证的所有 URL 提交时,您需要提交您的 CSRF 令牌,否则请求将被拒绝。

您可以通过调用辅助方法来获取 CSRF 令牌

csrf_token();

您也可以直接获取令牌

return SimpleRouter::router()->getCsrfVerifier()->getTokenProvider()->getToken();

默认的输入字段名称/键为 csrf_token,并在 BaseCsrfVerifier 类中的 POST_KEY 常量中定义。您可以通过覆盖您自己的 CSRF 验证器类中的常量来更改键。

示例

以下示例将使用隐藏字段 "csrf_token" 将内容提交到当前 URL。

<form method="post" action="<?= url(); ?>">
	<input type="hidden" name="csrf_token" value="<?= csrf_token(); ?>">
	<!-- other input elements here -->
</form>

自定义CSRF验证器

创建一个新的类,并扩展 simple-php-router 库提供的默认 BaseCsrfVerifier 中间件类。

except 属性(包含要排除/白名单的 URL 数组)添加到您想要排除 CSRF 验证的路线中。使用 * 结尾的 URL 将匹配整个 URL。

以下是一个 CSRF 验证器类的简单示例

namespace Demo\Middlewares;

use Pecee\Http\Middleware\BaseCsrfVerifier;

class CsrfVerifier extends BaseCsrfVerifier
{
	/**
	 * CSRF validation will be ignored on the following urls.
	 */
	protected $except = ['/api/*'];
}

自定义令牌提供程序

默认情况下,BaseCsrfVerifier 将使用 CookieTokenProvider 在客户端机器上的 cookie 中存储令牌。

如果您需要将令牌存储在其他地方,您可以通过创建自己的类并实现 ITokenProvider 类来完成此操作。

class SessionTokenProvider implements ITokenProvider
{

    /**
     * Refresh existing token
     */
    public function refresh(): void
    {
        // Implement your own functionality here...
    }

    /**
     * Validate valid CSRF token
     *
     * @param string $token
     * @return bool
     */
    public function validate($token): bool
    {
        // Implement your own functionality here...
    }
    
    /**
     * Get token token
     *
     * @param string|null $defaultValue
     * @return string|null
     */
    public function getToken(?string $defaultValue = null): ?string 
    {
        // Implement your own functionality here...
    }

}

接下来,您需要在您的路由文件中将您的自定义 ITokenProvider 实现设置为您的 BaseCsrfVerifier 类。

$verifier = new \Demo\Middlewares\CsrfVerifier();
$verifier->setTokenProvider(new SessionTokenProvider());

SimpleRouter::csrfVerifier($verifier);

中间件

中间件是加载在路由渲染之前执行的类。中间件可以用来验证用户是否登录,或为当前请求/路由设置特定参数。中间件必须实现 IMiddleware 接口。

示例

namespace Demo\Middlewares;

use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;

class CustomMiddleware implements IMiddleware {

    public function handle(Request $request): void 
    {
    
        // Authenticate user, will be available using request()->user
        $request->user = User::authenticate();

        // If authentication failed, redirect request to user-login page.
        if($request->user === null) {
            $request->setRewriteUrl(url('user.login'));
        }

    }
}

异常处理程序

异常处理器是处理所有异常的类。异常处理器必须实现 IExceptionHandler 接口。

处理404、403和其他错误

如果您只想捕获 404(页面未找到)等错误,您可以使用 SimpleRouter::error($callback) 静态辅助方法。

这将添加一个回调方法,当所有路由上发生错误时,该方法将被触发。

以下基本示例在发生 NotFoundHttpException(404)时将页面重定向到 /not-found。代码应放置在包含您的路由的文件中。

SimpleRouter::get('/not-found', 'PageController@notFound');
SimpleRouter::get('/forbidden', 'PageController@notFound');

SimpleRouter::error(function(Request $request, \Exception $exception) {

    switch($exception->getCode()) {
        // Page not found
        case 404:
            response()->redirect('/not-found');
        // Forbidden
        case 403:
            response()->redirect('/forbidden');
    }
    
});

上述示例将所有具有 http-code 404(页面未找到)和 403(禁止访问)错误的错误重定向到 /not-found

如果您不想重定向,而是希望在当前 URL 上渲染错误页面,您可以告诉路由器执行一个重写回调,如下所示

$request->setRewriteCallback('ErrorController@notFound');

使用自定义异常处理程序

这是异常处理器实现的基本示例(请参阅 "轻松覆盖即将加载的路由" 了解如何更改回调的示例)。

namespace Demo\Handlers;

use Pecee\Http\Request;
use Pecee\SimpleRouter\Handlers\IExceptionHandler;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;

class CustomExceptionHandler implements IExceptionHandler
{
	public function handleError(Request $request, \Exception $error): void
	{

		/* You can use the exception handler to format errors depending on the request and type. */

		if ($request->getUrl()->contains('/api')) {

			response()->json([
				'error' => $error->getMessage(),
				'code'  => $error->getCode(),
			]);

		}

		/* The router will throw the NotFoundHttpException on 404 */
		if($error instanceof NotFoundHttpException) {

			// Render custom 404-page
			$request->setRewriteCallback('Demo\Controllers\PageController@notFound');
			return;
			
		}

		throw $error;

	}

}

您可以通过使用 exceptionHandler 设置属性将您自定义的异常处理器类添加到您的组中。exceptionHandler 可以是类名或类名数组。

SimpleRouter::group(['exceptionHandler' => \Demo\Handlers\CustomExceptionHandler::class], function() {

    // Your routes here

});

防止合并父异常处理程序

默认情况下,路由器将合并异常处理器与父组提供的任何处理器,并将按从新到旧的顺序执行。

如果您想要您的组异常处理器独立执行,您可以添加 mergeExceptionHandlers 属性并将其设置为 false

SimpleRouter::group(['prefix' => '/', 'exceptionHandler' => \Demo\Handlers\FirstExceptionHandler::class, 'mergeExceptionHandlers' => false], function() {

	SimpleRouter::group(['prefix' => '/admin', 'exceptionHandler' => \Demo\Handlers\SecondExceptionHandler::class], function() {
	
		// Both SecondExceptionHandler and FirstExceptionHandler will trigger (in that order).
	
	});
	
	SimpleRouter::group(['prefix' => '/user', 'exceptionHandler' => \Demo\Handlers\SecondExceptionHandler::class, 'mergeExceptionHandlers' => false], function() {
	
		// Only SecondExceptionHandler will trigger.
	
	});

});

URL

默认情况下,所有控制器和资源路由将使用它们URL的简化版作为名称。

您可以使用url()快捷助手函数轻松获取路由的URL或操作当前URL。

url()将返回一个Url对象,该对象在渲染时返回一个string,因此可以在模板等地方安全使用,但它包含了Url类中的所有有用辅助方法,如containsindexOf等。请查看下面的有用的URL技巧

获取当前URL

获取和/或操作当前URL从未如此简单。

以下示例展示了如何获取当前URL

# output: /current-url
url();

按名称获取(单个路由)

SimpleRouter::get('/product-view/{id}', 'ProductsController@show', ['as' => 'product']);

# output: /product-view/22/?category=shoes
url('product', ['id' => 22], ['category' => 'shoes']);

# output: /product-view/?category=shoes
url('product', null, ['category' => 'shoes']);

按名称获取(控制器路由)

SimpleRouter::controller('/images', ImagesController::class, ['as' => 'picture']);

# output: /images/view/?category=shows
url('picture@getView', null, ['category' => 'shoes']);

# output: /images/view/?category=shows
url('picture', 'getView', ['category' => 'shoes']);

# output: /images/view/
url('picture', 'view');

按类获取

SimpleRouter::get('/product-view/{id}', 'ProductsController@show', ['as' => 'product']);
SimpleRouter::controller('/images', 'ImagesController');

# output: /product-view/22/?category=shoes
url('ProductsController@show', ['id' => 22], ['category' => 'shoes']);

# output: /images/image/?id=22
url('ImagesController@getImage', null, ['id' => 22]);

为控制器/资源路由上的方法使用自定义名称

SimpleRouter::controller('gadgets', GadgetsController::class, ['names' => ['getIphoneInfo' => 'iphone']]);

url('gadgets.iphone');

# output
# /gadgets/iphoneinfo/

获取REST/资源控制器URL

SimpleRouter::resource('/phones', PhonesController::class);

# output: /phones/
url('phones');

# output: /phones/
url('phones.index');

# output: /phones/create/
url('phones.create');

# output: /phones/edit/
url('phones.edit');

操作URL

您可以通过添加您的get参数参数轻松操作查询字符串。

# output: /current-url?q=cars

url(null, null, ['q' => 'cars']);

您可以通过将值设置为null来删除查询字符串参数。

以下示例将删除任何名为q的查询字符串参数,但保留所有其他查询字符串参数

$url = url()->removeParam('q');

有关更多信息,请参阅文档中的有用的URL技巧部分。

有用的URL技巧

调用url将始终返回一个Url对象。在渲染后,它将返回相对urlstring,因此可以在模板等地方安全使用。

然而,这允许我们使用Url对象上的有用方法,如indexOfcontains,或检索URL的特定部分,如路径、查询字符串参数、主机等。您还可以像删除或添加参数、更改主机等一样操作URL。

以下示例展示了我们如何检查当前URL是否包含/api部分。

if(url()->contains('/api')) {
    // ... do stuff
}

如前所述,您还可以使用Url对象来显示URL的特定部分或控制您想要控制的URL部分。

# Grab the query-string parameter id from the current-url.
$id = url()->getParam('id');

# Get the absolute url for the current url.
$absoluteUrl = url()->getAbsoluteUrl();

有关更多方法,请查看Pecee\Http\Url类。

输入 & 参数

simple-router提供了库和助手,使得管理和管理输入参数(如$_POST$_GET$_FILE)变得容易。

使用Input类管理参数

您可以使用InputHandler类轻松访问和管理请求中的参数。该InputHandler类提供了扩展功能,例如在对象上直接复制/移动上传的文件,获取文件扩展名、MIME类型等。

获取单个参数值

$this->input($index, $defaultValue, ...$methods);

要快速获取参数的值,您可以使用input助手函数。

这将自动去除值的前后空格,并确保它不为空。如果它为空,则返回$defaultValue

注意:此函数除非参数分组在一起,否则返回一个string,否则返回一个包含值的array

示例

此示例匹配POST和GET请求方法,如果名称为空,则返回默认值"Guest"。

$name = $this->input('name', 'Guest', 'post', 'get');

获取多个参数值

$this->input()->values($filter = array());

要接收所有输入值,可以留空过滤器。如果您只想获取特定输入,可以传递它们的键。

获取参数对象

在处理文件上传时,检索原始参数对象可能很有用。

在多个或特定请求方法中搜索具有默认值的对象

以下示例如果找到参数,将返回一个InputItem对象,否则将返回具有$defaultValueInputItem对象。

$object = $this->input()->find($index, $defaultValue = null, ...$methods);

获取特定的$_GET参数作为InputItem对象

以下示例如果找到参数,将返回一个InputItem对象,否则将返回具有$defaultValueInputItem对象。

$object = $this->input()->get($index, $defaultValue = null);

获取特定的$_POST参数作为InputItem对象

以下示例如果找到参数,将返回一个InputItem对象,否则将返回具有$defaultValueInputItem对象。

$object = $this->input()->post($index, $defaultValue = null);

获取特定的请求体参数作为InputItem对象

以下示例如果找到参数,将返回一个InputItem对象,否则将返回具有$defaultValueInputItem对象。

$object = $this->input()->data($index, $defaultValue = null);

获取特定的$_FILE参数作为InputFile对象

以下示例如果找到参数,将返回一个 InputFile 对象,否则将返回具有 $defaultValueInputFile

$object = $this->input()->file($index, $defaultValue = null);

管理文件

/**
 * Loop through a collection of files uploaded from a form on the page like this
 * <input type="file" name="images[]" />
 */

/* @var $image \Pecee\Http\Input\InputFile */
foreach($this->input()->file('images', [])->getInputItems() as $image)
{
    if($image->getMime() === 'image/jpeg') 
    {
        $destinationFilname = sprintf('%s.%s', uniqid(), $image->getExtension());
        $image->move(sprintf('/uploads/%s', $destinationFilename));
    }
}

获取所有参数

# Get all
$values = $this->input()->all();

# Only match specific keys
$values = $this->input()->all([
    'company_name',
    'user_id'
]);

所有对象都实现了 IInputItem 接口,并始终包含以下方法

  • getIndex() - 返回输入的索引/键。
  • setIndex() - 设置输入的索引/键。
  • getName() - 返回输入的友好名称(例如company_name将显示为Company Name等)。
  • setName() - 设置输入的友好名称(例如company_name将显示为Company Name等)。
  • getValue() - 返回输入的值。
  • setValue() - 设置输入的值。
  • hasInputItems() - 如果值是关联数组,则返回true。
  • getInputItems() - 如果值是关联数组,则返回InputItems集合。

InputFile 除了上述方法外,还有一些文件特定方法,如

  • getFilename()getValue() - 获取文件名。
  • getTmpName() - 获取文件临时名称。
  • getSize() - 获取文件大小。
  • move($destination) - 将文件移动到目标位置。
  • getContents() - 获取文件内容。
  • getType() - 获取文件的MIME类型。
  • getError() - 获取文件上传错误。
  • hasError() - 如果上传过程中发生错误(如果 getError 不为0),则返回 bool
  • toArray() - 返回原始数组。

检查参数是否存在

您可以使用 exists 方法轻松地检查是否存在多个项。它与 value 类似,可以用于根据请求方法进行筛选,并支持 stringarray 作为参数值。

示例

if($this->input()->exists(['name', 'lastname'])) {
	// Do stuff
}

/* Similar to code above */
if($this->input()->exists('name') && $this->input()->exists('lastname')) {
	// Do stuff
}

事件

本节将帮助您了解如何将自定义回调注册到路由器的事件中。它还将涵盖事件处理程序的基本知识;如何使用路由器提供的事件处理程序以及如何创建自己的自定义事件处理程序。

可用事件

本节包含使用 EventHandler 可以注册的所有可用事件。

所有事件回调都将获取一个 EventArgument 对象作为参数。此对象可以轻松访问事件名称、路由器和请求实例以及与给定事件相关的任何特殊事件参数。您可以在下面的列表中查看每个事件返回的特殊事件参数。

注册新事件

要注册新事件,您需要创建一个新的 EventHandler 对象实例。在此对象上,您可以调用 registerEvent 方法添加任意数量的回调。

您已注册事件后,请确保通过调用 SimpleRouter::addEventHandler() 将其添加到路由器。我们建议您在 routes.php 文件中添加事件处理程序。

示例

use Pecee\SimpleRouter\Handlers\EventHandler;
use Pecee\SimpleRouter\Event\EventArgument;

// --- your routes goes here ---

$eventHandler = new EventHandler();

// Add event that fires when a route is rendered
$eventHandler->register(EventHandler::EVENT_RENDER_ROUTE, function(EventArgument $argument) {
   
   // Get the route by using the special argument for this event.
   $route = $argument->route;
   
   // DO STUFF...
    
});

SimpleRouter::addEventHandler($eventHandler);

自定义事件处理程序

EventHandler 是管理事件的类,必须继承自 IEventHandler 接口。处理程序知道如何处理给定处理程序类型的事件。

大多数情况下,基本的 \Pecee\SimpleRouter\Handler\EventHandler 类将足够满足大多数人,因为您只需注册一个在触发时执行的事件。

让我们来了解一下如何创建您自己的事件处理程序类。

以下是一个自定义事件处理程序的示例,名为 DatabaseDebugHandler。以下示例的思路是在触发时将所有事件记录到数据库中。希望这足以让您了解事件处理程序的工作方式。

namespace Demo\Handlers;

use Pecee\SimpleRouter\Event\EventArgument;
use Pecee\SimpleRouter\Router;

class DatabaseDebugHandler implements IEventHandler
{

    /**
     * Debug callback
     * @var \Closure
     */
    protected $callback;

    public function __construct()
    {
        $this->callback = function (EventArgument $argument) {
            // todo: store log in database
        };
    }

    /**
     * Get events.
     *
     * @param string|null $name Filter events by name.
     * @return array
     */
    public function getEvents(?string $name): array
    {
        return [
            $name => [
                $this->callback,
            ],
        ];
    }

    /**
     * Fires any events registered with given event-name
     *
     * @param Router $router Router instance
     * @param string $name Event name
     * @param array ...$eventArgs Event arguments
     */
    public function fireEvents(Router $router, string $name, ...$eventArgs): void
    {
        $callback = $this->callback;
        $callback(new EventArgument($router, $eventArgs));
    }

    /**
     * Set debug callback
     *
     * @param \Closure $event
     */
    public function setCallback(\Closure $event): void
    {
        $this->callback = $event;
    }

}

高级

禁用多个路由渲染

默认情况下,路由器将尝试执行与给定URL匹配的所有路由。为了阻止路由器执行任何其他路由,任何方法都可以返回一个值。

您可以通过在 routes.php 文件中设置 SimpleRouter::enableMultiRouteRendering(false) 来轻松禁用此行为。这与版本3及以下相同。

限制IP访问

您可以使用内置的 IpRestrictAccess 中间件来白名单和/或黑名单IP的访问。

创建您自己的自定义中间件并扩展 IpRestrictAccess 类。

IpRestrictAccess 类包含两个属性 ipBlacklistipWhitelist,可以将它们添加到中间件中,以更改哪些IP可以访问您的路由。

您可以使用 * 来限制对一系列IP的访问。

use \Pecee\Http\Middleware\IpRestrictAccess;

class IpBlockerMiddleware extends IpRestrictAccess 
{

    protected $ipBlacklist = [
        '5.5.5.5',
        '8.8.*',
    ];

    protected $ipWhitelist = [
        '8.8.2.2',
    ];

}

您可以通过将 中间件添加到一组中,将中间件添加到多个路由。

设置自定义基本路径

有时,为添加的所有路由添加自定义基本路径可能很有用。

这可以通过利用项目的 事件处理器 支持轻松完成。

$basePath = '/basepath';

$eventHandler = new EventHandler();
$eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function(EventArgument $event) use($basePath) {

	$route = $event->route;

	// Skip routes added by group as these will inherit the url
	if(!$event->isSubRoute) {
		return;
	}
	
	switch (true) {
		case $route instanceof ILoadableRoute:
			$route->prependUrl($basePath);
			break;
		case $route instanceof IGroupRoute:
			$route->prependPrefix($basePath);
			break;

	}
	
});

SimpleRouter::addEventHandler($eventHandler);

在上面的示例中,我们创建了一个新的 EVENT_ADD_ROUTE 事件,当添加新的路由时触发。我们跳过所有子路由,因为这些将从其父路由继承URL。然后,如果该路由是分组,我们更改前缀
否则我们更改URL。

URL重写

更改当前路由

有时,可能需要操作即将加载的路由。simple-php-router 允许您轻松操作和更改即将呈现的路由。有关当前路由的所有信息都存储在 \Pecee\SimpleRouter\Router 实例的 loadedRoute 属性中。

为了方便访问,您可以使用快捷助手函数 request() 而不是直接调用类 \Pecee\SimpleRouter\SimpleRouter::router()

request()->setRewriteCallback('Example\MyCustomClass@hello');

// -- or you can rewrite by url --

request()->setRewriteUrl('/my-rewrite-url');

Bootmanager:动态加载路由

有时,可能需要在数据库、文件或类似的地方保存URL。在这个例子中,我们希望URL /my-cat-is-beatiful 加载已知的路由 /article/view/1,因为它定义在 routes.php 文件中。

为了干扰路由器,我们创建了一个实现 IRouterBootManager 接口的类。这个类将在 routes.php 中的任何其他规则之前加载,并允许我们在满足我们的某些标准(如来自URL /my-cat-is-beatiful)的情况下“更改”当前路由。

use Pecee\Http\Request;
use Pecee\SimpleRouter\IRouterBootManager;
use Pecee\SimpleRouter\Router;

class CustomRouterRules implement IRouterBootManager 
{

    /**
     * Called when router is booting and before the routes is loaded.
     *
     * @param \Pecee\SimpleRouter\Router $router
     * @param \Pecee\Http\Request $request
     */
    public function boot(\Pecee\SimpleRouter\Router $router, \Pecee\Http\Request $request): void
    {

        $rewriteRules = [
            '/my-cat-is-beatiful' => '/article/view/1',
            '/horses-are-great'   => '/article/view/2',
        ];

        foreach($rewriteRules as $url => $rule) {

            // If the current url matches the rewrite url, we use our custom route

            if($request->getUrl()->contains($url)) {
                $request->setRewriteUrl($rule);
            }
        }

    }

}

上述内容应该相当直观,并且可以轻松地更改以遍历数据库、文件或缓存中存储的URL。

发生的情况是,如果当前路由与我们的 $rewriteRules 数组的索引中定义的路由相匹配,我们将路由设置为数组值。

通过这样做,路由现在将加载URL /article/view/1 而不是 /my-cat-is-beatiful

最后一件我们需要做的事情,是将我们的自定义启动管理器添加到 routes.php 文件。您可以创建尽可能多的启动管理器,并且可以轻松地将它们添加到您的 routes.php 文件中。

SimpleRouter::addBootManager(new CustomRouterRules());

手动添加路由

前一个示例中引用的 SimpleRouter 类只是一个简单的助手类,它知道如何与 Router 类通信。如果您想接受挑战、想要完全控制或者只是想创建自己的 Router 助手类,这个示例就是为您准备的。

use \Pecee\SimpleRouter\Router;
use \Pecee\SimpleRouter\Route\RouteUrl;

/* Create new Router instance */
$router = new Router();

$route = new RouteUrl('/answer/1', function() {

    die('this callback will match /answer/1');

});

$route->addMiddleware(\Demo\Middlewares\AuthMiddleware::class);
$route->setNamespace('\Demo\Controllers');
$route->setPrefix('v1');

/* Add the route to the router */
$router->addRoute($route);

自定义类加载器

您可以通过利用添加自定义类加载器的功能来扩展 simple-router,以支持像 php-di 这样的自定义注入框架。

类加载器必须继承 IClassLoader 接口。

示例

class MyCustomClassLoader implements IClassLoader
{
    /**
     * Load class
     *
     * @param string $class
     * @return object
     * @throws NotFoundHttpException
     */
    public function loadClass(string $class)
    {
        if (\class_exists($class) === false) {
            throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404);
        }

        return new $class();
    }
    
    /**
     * Called when loading class method
     * @param object $class
     * @param string $method
     * @param array $parameters
     * @return object
     */
    public function loadClassMethod($class, string $method, array $parameters)
    {
        return call_user_func_array([$class, $method], array_values($parameters));
    }

    /**
     * Load closure
     *
     * @param Callable $closure
     * @param array $parameters
     * @return mixed
     */
    public function loadClosure(Callable $closure, array $parameters)
    {
        return \call_user_func_array($closure, array_values($parameters));
    }

}

接下来,我们需要配置我们的 routes.php,以便路由器使用我们的 MyCustomClassLoader 类来加载类。这可以通过向您的 routes.php 文件中添加以下行来完成。

SimpleRouter::setCustomClassLoader(new MyCustomClassLoader());

与php-di集成

php-di 的支持从版本 4.3 开始已停止,但是您可以轻松地再次添加它,通过创建自己的类加载器,如下面的示例所示

use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;

class MyCustomClassLoader implements IClassLoader
{

    protected $container;

    public function __construct()
    {
        // Create our new php-di container
        $this->container = (new \DI\ContainerBuilder())
                    ->useAutowiring(true)
                    ->build();
    }

    /**
     * Load class
     *
     * @param string $class
     * @return object
     * @throws NotFoundHttpException
     */
    public function loadClass(string $class)
    {
        if (class_exists($class) === false) {
            throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404);
        }

		try {
			return $this->container->get($class);
		} catch (\Exception $e) {
			throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
		}
    }
    
    /**
     * Called when loading class method
     * @param object $class
     * @param string $method
     * @param array $parameters
     * @return object
     */
    public function loadClassMethod($class, string $method, array $parameters)
    {
		try {
			return $this->container->call([$class, $method], $parameters);
		} catch (\Exception $e) {
			throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
		}
    }

    /**
     * Load closure
     *
     * @param Callable $closure
     * @param array $parameters
     * @return mixed
     */
    public function loadClosure(callable $closure, array $parameters)
    {
		try {
			return $this->container->call($closure, $parameters);
		} catch (\Exception $e) {
			throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
		}
    }
}

参数

本节包含有关扩展参数使用的进阶技巧和窍门。

扩展

这是一个框架集成的简单示例。

该框架有自己的 Router 类,该类继承自 SimpleRouter 类。这允许框架添加自定义功能,如加载自定义 routes.php 文件或添加调试信息等。

namespace Demo;

use Pecee\SimpleRouter\SimpleRouter;

class Router extends SimpleRouter {

    public static function start() {

        // change this to whatever makes sense in your project
        require_once 'routes.php';

        // change default namespace for all routes
        parent::setDefaultNamespace('\Demo\Controllers');

        // Do initial stuff
        parent::start();

    }

}

帮助和支持

本节将详细介绍如何调试路由器,并回答一些常见的问题和问题。

常见问题和解决方案

本节将介绍常见问题和解决方法。

参数不匹配或特殊字符的路线无法工作

当一个或多个参数包含特殊字符时,人们通常会遇到这个问题。在匹配参数时,路由器使用一个稀疏的正则表达式来匹配字母a-z以及数字,以提高性能。

所有其他字符必须通过您路由中的defaultParameterRegex选项进行定义。

有关如何添加自己的自定义正则表达式以匹配参数的更多信息,请点击此处

多个路由匹配?哪个优先?

路由器将按照添加的顺序匹配路由,如果它们匹配,则渲染多个路由。

如果您想使路由器在匹配到路由时停止,您只需在回调中返回一个值或在手动停止执行(使用response()->json()等)或简单地返回一个结果。

任何实现了__toString()魔术方法的返回对象也将阻止渲染其他路由。

如果您想使路由器在每次请求中仅执行一个路由,您可以通过禁用多路由渲染

在子路径上使用路由器

请参阅文档中的设置自定义基本路径部分。

调试

本节将向您展示如何编写路由器的单元测试,查看有用的调试信息,并回答一些常见问题。

它还将涵盖如何报告可能遇到的问题。

创建单元测试

调试路由器中任何问题的最简单、最快的方法是创建一个表示您遇到的问题的单元测试。

单元测试使用特殊的TestRouter类,该类模拟浏览器的请求方法和请求URL。

TestRouter类可以直接返回输出或静默渲染路由。

public function testUnicodeCharacters()
{
    // Add route containing two optional paramters with special spanish characters like "í".
    TestRouter::get('/cursos/listado/{listado?}/{category?}', 'DummyController@method1', ['defaultParameterRegex' => '[\w\p{L}\s-]+']);
    
    // Start the routing and simulate the url "/cursos/listado/especialidad/cirugía local".
    TestRouter::debugNoReset('/cursos/listado/especialidad/cirugía local', 'GET');
    
    // Verify that the url for the loaded route matches the expected route.
    $this->assertEquals('/cursos/listado/{listado?}/{category?}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
    
    // Start the routing and simulate the url "/test/Dermatología" using "GET" as request-method.
    TestRouter::debugNoReset('/test/Dermatología', 'GET');

    // Another route containing one parameter with special spanish characters like "í".
    TestRouter::get('/test/{param}', 'DummyController@method1', ['defaultParameterRegex' => '[\w\p{L}\s-\í]+']);

    // Get all parameters parsed by the loaded route.
    $parameters = TestRouter::request()->getLoadedRoute()->getParameters();

    // Check that the parameter named "param" matches the exspected value.
    $this->assertEquals('Dermatología', $parameters['param']);

    // Add route testing danish special characters like "ø".
    TestRouter::get('/category/økse', 'DummyController@method1', ['defaultParameterRegex' => '[\w\ø]+']);
    
    // Start the routing and simulate the url "/kategory/økse" using "GET" as request-method.
    TestRouter::debugNoReset('/category/økse', 'GET');
    
    // Validate that the URL of the loaded-route matches the expected url.
    $this->assertEquals('/category/økse/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());

    // Reset the router, so other tests wont inherit settings or the routes we've added.
    TestRouter::router()->reset();
}

使用TestRouter助手

根据您的测试,您可以在单元测试中渲染路由时使用以下方法。

调试信息

该库可以输出调试信息,其中包含有关已加载的路由、解析的请求URL等信息。它还包含在报告新问题时重要的信息,例如PHP版本、库版本、服务器变量、路由调试日志等。

您可以通过调用替代的启动方法来激活调试信息。

以下示例将启动路由并返回包含调试信息的数组

示例

$debugInfo = SimpleRouter::startDebug();
echo sprintf('<pre>%s</pre>', var_export($debugInfo));
exit;

上述示例将提供一个包含以下内容的输出

基准和日志记录

您可以通过在Router实例上调用setDebugEnabled方法来激活基准调试/日志记录。

您必须在启动路由之前启用调试。

示例

SimpleRouter::router()->setDebugEnabled(true);
SimpleRouter::start();

当路由完成时,您可以通过在Router实例上调用getDebugLog()来获取调试日志。这将返回一个包含日志消息的array,每个日志消息都包含执行时间、跟踪信息和调试消息。

示例

$messages = SimpleRouter::router()->getDebugLog();

报告新问题

在报告问题之前,请确保您遇到的问题在常见错误部分或GitHub上的已关闭问题页面上已有解答。

为了避免混淆并帮助您尽快解决问题,您应该提供您遇到问题的详细说明。

报告新问题的程序

  1. 转到此页面创建新问题。
  2. 尽可能用最少的词语添加一个描述您问题的标题。
  3. 将以下模板复制并粘贴到您问题的描述中,并用您自己的信息替换每个步骤。如果步骤与您的问题不相关,您可以删除它。

问题模板

将以下模板复制并粘贴到您新问题的描述中,并用您自己的信息替换它。

您可以通过查看调试信息部分来了解如何生成调试信息。

### Description

The library fails to render the route `/user/æsel` which contains one parameter using a custom regular expression for matching special foreign characters. Routes without special characters like `/user/tom` renders correctly.

### Steps to reproduce the error

1. Add the following route:

```php
SimpleRouter::get('/user/{name}', 'UserController@show')->where(['name' => '[\w]+']);
```

2. Navigate to `/user/æsel` in browser.

3. `NotFoundHttpException` is thrown by library.

### Route and/or callback for failing route

*Route:*

```php
SimpleRouter::get('/user/{name}', 'UserController@show')->where(['name' => '[\w]+']);
```

*Callback:*

```php
public function show($username) {
    return sprintf('Username is: %s', $username);
}
```

### Debug info

```php

[PASTE YOUR DEBUG-INFO HERE]

```

请记住,更详细的问题描述和调试信息可能难以撰写,但它将帮助他人理解并解决您的问题,而无需您提供信息。

注意:在创建新问题时,请尽可能详细地描述。这将帮助他人更容易理解并解决问题。在描述中提供重现错误的必要步骤,添加有用的调试信息等,将帮助他人快速解决您报告的问题。

已知问题

  • 域名参数总是附加在传递给函数的参数的末尾。(忽略$parameterReverseOrder

待办事项

  • 添加类似于 ->controller() 的函数,使用 PHP 属性 Route: SimpleRouter::loadRoutes(ArticleController::class); SampleController.class
class ArticleController{

	use RouterUtils;
	
	#[Route(Route::POST, '/')]
	public function createArticle(){
		...
	}
	
	#[Route(Route::GET, '/{id}')]
	public function getArticle(#[ValidatorAttribute] int $id){
		...
	}

}
  • 使用 trim 的危险路由匹配行为。请参阅RouterUrlTest@testDangerousTrimBehavior

反馈和发展

如果您在项目中缺少所需的特性,或者如果您有反馈,我们非常愿意听取您的意见。请随时通过创建新问题来给我们反馈。

遇到问题了吗?

在报告新问题之前,请先参考我们的帮助和支持部分。

贡献开发指南

  • 请尽可能遵循 PSR-2 代码风格指南。

  • 请将您的拉取请求创建到与您想要更改的版本号匹配的开发基础。例如,当推送更改到版本 3 时,拉取请求应使用 v3-development 基础/分支。

  • 为您的提交创建详细的描述,因为这些将被用于新版本发布时的变更日志。

  • 当更改现有功能时,请确保单元测试正常工作。

  • 当添加新内容时,请记住为该功能添加新的单元测试。

致谢

许可证

MIT 许可证 (MIT)

版权所有 (c) 2016 Simon Sessingø / simple-php-router

特此授予任何人无限制、免费地获得本软件及其相关文档文件(“软件”)副本(“软件”)的权利,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件副本,并允许向软件提供副本的个人这样做,但受以下条件约束

上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。

软件按“原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性、针对特定目的的适用性和非侵权性。在任何情况下,作者或版权所有者均不对任何索赔、损害或其他责任负责,无论是在合同、侵权或其他行为中,无论是否因软件或其使用或其他交易而产生的。