g4/aura-router

Aura Router 包实现了网页路由;给定一个 URI 路径和 $_SERVER 的副本,它将为特定路由提取控制器、动作和参数值。

安装量: 4,657

依赖者: 1

建议者: 0

安全: 0

星标: 0

关注者: 1

分支: 74

类型:aura-package

3.3.0 2023-06-12 04:42 UTC

README

Build Status

Aura Router 是一个 PHP 包,用于实现网页路由。给定一个 URI 路径和 $_SERVER 的副本,它将为特定应用程序路由提取控制器、动作和参数值。

您的应用程序基础或框架应使用匹配路由提供的信息并自行调度到控制器。只要您的系统可以提供 URI 路径字符串和代表 $_SERVER 的副本,您就可以使用 Aura Router。

Aura Router 受 Solar 重写规则http://routes.groovie.org 的启发。

此包符合 PSR-0PSR-1PSR-2。如果您发现合规性问题,请通过拉取请求发送补丁。

基本用法

实例化

实例化路由最简单的方法是包含 scripts/instance.php 文件

<?php
$router_map = include '/path/to/Aura.Router/scripts/instance.php';

或者,您可以添加 Aura Router 到您的自动加载器并手动实例化它

<?php
use Aura\Router\Map;
use Aura\Router\DefinitionFactory;
use Aura\Router\RouteFactory;

$router_map = new Map(new DefinitionFactory, new RouteFactory);

添加路由

要创建路由,请调用 add() 方法。

<?php
// create the router map object
$router_map = require '/path/to/Aura.Router/scripts/instance.php';

// add a simple named route without params
$router_map->add('home', '/');

// add a simple unnamed route with params
$router_map->add(null, '/{:controller}/{:action}/{:id:(\d+)}');

// add a complex named route
$router_map->add('read', '/blog/read/{:id}{:format}', [
    'params' => [
        'id'     => '(\d+)',
        'format' => '(\..+)?',
    ],
    'values' => [
        'controller' => 'blog',
        'action'     => 'read',
        'format'     => 'html',
    ],
]);

您需要将路由对象放置在您可以从应用程序访问的地方;例如,在注册表、服务定位器或依赖注入容器中。其中一个这样的系统是 Aura DI 包。

匹配路由

要匹配 URI 路径与您的路由,请使用路径字符串和 $_SERVER 值调用 match()

<?php
// get the incoming request URI path
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

// get the route based on the path and server
$route = $router_map->match($path, $_SERVER);

match() 方法不会解析 URI 或内部使用 $_SERVER。这是因为不同的系统可能有不同的方式来表示该信息;例如,通过 URI 对象或上下文对象。只要您可以传递字符串路径和服务器数组,您就可以在您的应用程序基础或框架中使用 Aura Router。

返回的 $route 对象将包含一个 $values 数组,其中包含由路由路径标识的每个参数的值。例如,匹配路径为 /{:controller}/{:action}/{:id} 的路由将填充 $route->values 数组,包含 controlleractionid 键。

调度路由

现在您有了路由,您可以调度它。以下是一个基础或框架系统可能会如何使用路由来调用页面控制器。

<?php
if (! $route) {
    // no route object was returned
    echo "No application route was found for that URI path.";
    exit();
}

// does the route indicate a controller?
if (isset($route->values['controller'])) {
    // take the controller class directly from the route
    $controller = $route->values['controller'];
} else {
    // use a default controller
    $controller = 'Index';
}

// does the route indicate an action?
if (isset($route->values['action'])) {
    // take the action method directly from the route
    $action = $route->values['action'];
} else {
    // use a default action
    $action = 'index';
}

// instantiate the controller class
$page = new $controller();

// invoke the action method with the route values
echo $page->$action($route->values);

再次注意,Aura Router 不会为您调度;上述内容仅作为如何使用路由值的示例。

生成路由路径

要从路由生成 URI 路径以便创建链接,请在路由对象上调用 generate() 并提供路由名称。

<?php
// $path => "/blog/read/42.atom"
$path = $router_map->generate('read', [
    'id' => 42,
    'format' => '.atom',
]);

$href = htmlspecialchars($path, ENT_QUOTES, 'UTF-8');
echo '<a href="$href">Atom feed for this blog entry</a>';

Aura Router 不会对路由进行动态匹配;必须有一个名称的路由才能从中生成路径。

示例显示,将数据数组作为第二个参数传递会导致该数据被插值到路由路径中。此数据数组是可选的。如果没有匹配数据键的路径参数,则这些参数将不会被替换,路径中会留下{:param}标记。如果没有匹配参数的数据键,则这些值将不会添加到路径中。

作为一个微框架

有时你可能希望将Aura.Router用作微框架。通过将匿名函数分配给控制器,这也是可能的。

<?php
$map->add("read", "/blog/read/{:id}{:format}", [
	"params" => [
		"id" => "(\d+)",
		"format" => "(\..+)?",
	],
	"values" => [
		"controller" => function ($args) {
		    if ($args['format'] == '.json') {
		        echo header('Content-Type:application/json');
		        echo json_encode($args);
		    } else {
    			$id = (int) $args["id"];
    			echo "Reading blog ID {$id}";
		    }
		},
		"format" => ".html",
	],
]);

当你使用Aura.Router作为微框架时,分发器将看起来像

<?php
$params = $route->values;
$controller = $params["controller"];
unset($params["controller"]);
$controller($params);

因此,当你请求/blog/read/1.json时,你会得到json,对于/blog/read/1,你会得到Reading blog ID 1作为输出。

高级用法

复杂的路由指定

当你添加复杂的路由指定时,你将作为数组描述与路径相关的额外信息,该数组包含以下一个或多个已识别键

  • params -- 路径参数的正则表达式子模式;内联参数将覆盖这些设置。例如

      'params' => [
          'id' => '(\d+)',
      ]
    

    请注意,路径本身允许包含带有内联正则表达式的参数标记;例如,/read/{:id:(\d+)}。在某些情况下,这可能会更容易阅读。

  • values -- 路由的默认值。这些值将由路径中匹配的参数覆盖。

      'values' => [
          'controller' => 'blog',
          'action' => 'read',
          'id' => 1,
      ]
    
  • method -- $server['REQUEST_METHOD']必须匹配这些值之一。

  • secure -- 当true时,$server['HTTPS']值必须开启,或者请求必须是在端口443上;当false时,这些都不必须存在。

  • routable -- 当false时,该路由将不会用于匹配,而只会用于生成路径。

  • is_match -- 一个具有签名function(array $server, \ArrayObject $matches)的自定义回调或闭包,在匹配时返回true,如果不匹配则返回false。这允许开发者为路由构建任何类型的匹配逻辑,并从路径中更改参数值。

  • generate -- 一个具有签名function(\aura\router\Route $route, array $data)的自定义回调或闭包,返回一个修改后的$data数组,用于生成路径。

以下是一个完整的路由指定,名为read,所有键都已就位

<?php
$router_map->add('read', '/blog/read/{:id}{:format}', [
    'params' => [
        'id' => '(\d+)',
        'format' => '(\..+)?',
    ],
    'values' => [
        'controller' => 'blog',
        'action' => 'read',
        'id' => 1,
        'format' => '.html',
    ],
    'secure' => false,
    'method' => ['GET'],
    'routable' => true,
    'is_match' => function(array $server, \ArrayObject $matches) {
            
        // disallow matching if referred from example.com
        if ($server['HTTP_REFERER'] == 'http://example.com') {
            return false;
        }
        
        // add the referer from $server to the match values
        $matches['referer'] = $server['HTTP_REFERER'];
        return true;
        
    },
    'generate' => function(\Aura\Router\Route $route, array $data) {
        $data['foo'] = 'bar';
        return $data;
    }
]);

请注意,使用闭包而不是回调意味着你将无法serialize()var_export()路由进行缓存。

简单路由

你不需要指定复杂的路由指定。如果你传递一个字符串作为路由而不是一个数组...

<?php
$router_map->add('archive', '/archive/{:year}/{:month}/{:day}');

...那么Aura Router将使用默认子模式,该模式匹配路径参数之外的所有内容,并使用路由名称作为默认的'action'值。因此,上述简写形式的路由等同于以下长格式路由

<?php
$router_map->add('archive', '/archive/{:year}/{:month}/{:day}', [
    'params' => [
        'year'  => '([^/]+)',
        'month' => '([^/]+)',
        'day'   => '([^/]+)',
    ],
    'values' => [
        'action' => 'archive',
    ],
]);

通配符路由

有时允许路径的末尾部分是任何内容是有用的。有两种这样的“通配符”路由。(此类通配符路由仅在路径末尾指定时才有效。)

第一种是“可选值”的命名通配符,表示为在路径末尾添加/{:foo*}。这将允许路由匹配该点之后的所有内容,包括什么都不匹配。在匹配时,它将收集剩余的斜杠分隔值到一个名为'foo'的顺序数组中。值得注意的是,没有通配符值的匹配路径可能在末尾有斜杠或不带斜杠。

<?php
$router_map->add('wild_post', '/post/{:id}/{:other*}');

// this matches, with the following values
$route = $router_map->match('/post/88/foo/bar/baz', $_SERVER);
// $route->values['id'] = 88;
// $route->values['other'] = ['foo', 'bar', 'baz'];

// this also matches, with the following values; note the trailing slash
$route = $router_map->match('/post/88/', $_SERVER);
// $route->values['id'] = 88;
// $route->values['other'] = [];

// this also matches, with the following values; note the missing slash
$route = $router_map->match('/post/88', $_SERVER);
// $route->values['id'] = 88;
// $route->values['other'] = [];

第二种是“必需值”的通配符,表示为在路径末尾添加/{:foo+}。这将允许路由匹配该点之后的所有内容,但必须有至少一个斜杠和额外的值。在匹配时,它将收集剩余的斜杠分隔值到一个名为'foo'的顺序数组中。

<?php
$router_map->add('wild_post', '/post/{:id}/{:other+}');

// this matches, with the following values
$route = $router_map->match('/post/88/foo/bar/baz', $_SERVER);
// $route->values['id'] = 88;
// $route->values['other'] = ['foo', 'bar', 'baz'];

// these do not match
$route = $router_map->match('/post/88/', $_SERVER);
$route = $router_map->match('/post/88', $_SERVER);

注意:在路由的先前版本中,'/*'是通配符指示符,通配符值收集到一个名为'*'的数组中。此行为仍然可用,但已弃用。

附加路由组

您可以在应用程序中的一个“挂载点”下一次性添加一系列路由。例如,如果您想将所有与博客相关的路由都挂载到应用程序中的'/blog',您可以这样做

<?php
$router_map->attach('/blog', [
    
    // the routes to attach
    'routes' => [
        
        // a short-form route named 'browse'
        'browse' => '/',
        
        // a long-form route named 'read'
        'read' => [
            'path' => '/{:id}{:format}',
            'params' => [
                'id'     => '(\d+)',
                'format' => '(\.json|\.atom)?'
            ],
            'values' => [
                'format' => '.html',
            ],
        ],
        
        // a short-form route named 'edit'
        'edit' => '/{:id:(\d+)}/edit',
    ],
]);

每个路由路径都将带有前缀/blog,因此有效路径变为

  • 浏览:/blog/
  • 阅读:/blog/{:id}{:format}
  • 编辑:/blog/{:id}/edit

您可以将其他路由指定键作为附件指定的部分;这些将用作每个附加路由的默认值,因此您不需要重复常见信息

<?php
$router_map->attach('/blog', [
    
    // common params for the routes
    'params' => [
        'id'     => '(\d+)',
        'format' => '(\.json|\.atom)?',
    ],
    
    // common values for the routes
    'values' => [
        'controller' => 'blog',
        'format'     => '.html',
    ],
    
    // the routes to attach
    'routes' => [
        'browse' => '/',
        'read'   => '/{:id}{:format}',
        'edit'   => '/{:id}/edit',
    ],
]);

构造时附件

您可以在单个附件组数组中配置您的路由,然后一次性将它们传递给路由构造函数。这允许您分离路由的配置和构建。

注意,您可以将name_prefix指定为每个附加路由组公共路由信息的一部分;该组中的路由名称将带有该前缀。这有助于解决不同组中具有相同名称的路由冲突。

<?php
$attach = [
    // attach to /blog
    '/blog' => [
        
        // prefix for route names
        'name_prefix' => 'projectname.blog.',
        
        // common params for the routes
        'params' => [
            'id' => '(\d+)',
            'format' => '(\.json|\.atom)?',
        ],
    
        // common values for the routes
        'values' => [
            'controller' => 'blog',
            'format' => '.html',
        ],
    
        // the routes to attach
        'routes' => [
            'browse' => '/',
            'read'   => '/read/{:id}{:format}',
            'edit' => '/{:id}/edit',
        ],
    ],
    
    // attach to '/forum'
    '/forum' => [
        // prefix for route names
        'name_prefix' => 'projectname.forum.',
        // ...
    ],

    // attach to '/wiki'
    '/wiki' => [
        // prefix for route names
        'name_prefix' => 'projectname.wiki.',
        // ...
    ],
];

// create the route factory
$route_factory = new \Aura\Router\RouteFactory;

// create the definition factory
$definition_factory = new \Aura\Router\DefinitionFactory;

// create a router map with attached route groups
$router_map = new \Aura\Router\Map($definition_factory, $route_factory, $attach);

此技术对于模块化应用程序包非常有效。每个包都可以为其自己的路由组指定返回一个数组,系统特定的配置机制可以将每个指定收集到一个公共数组中,用于路由器。例如

<?php
// get a routes array from each application packages
$attach = [
    '/blog'  => require 'projectname/blog/routes.php',
    '/forum' => require 'projectname/forum/routes.php',
    '/wiki'  => require 'projectname/wiki/routes.php',
];

// create the route factory
$route_factory = new \Aura\Router\RouteFactory;

// create the definition factory
$definition_factory = new \Aura\Router\DefinitionFactory;

// create a router map with attached route groups
$router_map = new \Aura\Router\Map($definition_factory, $route_factory, $attach);

缓存

您可能希望对生产部署中的路由器映射进行缓存,以便路由器不需要在每个页面加载时从定义中构建路由对象。可以使用getRoutes()setRoutes()方法进行此目的。

以下是基于文件缓存的路线存储和恢复的简单示例

<?php
// create a router map object
$router_map = require '/path/to/Aura.Router/scripts/instance.php';

// the cache file location
$cache = '/path/to/routes.cache';

// does the cache exist?
if (file_exists($cache)) {
    
    // restore from the cache
    $routes = unserialize(file_get_contents($cache));
    $router_map->setRoutes($routes);
    
} else {
    
    // build the routes using add() and attach() ...
    // ... ... ...
    // ... then save to the cache for the next page load
    $routes = $router_map->getRoutes();
    file_put_contents($cache, serialize($routes));
    
}

请注意,如果路由定义中有闭包,则无法缓存路由;这是因为闭包无法正确表示用于缓存。如果您想追求缓存策略,请使用传统的回调而不是闭包。