支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
Routing组件把一个HTTP请求映射到一组配置变量中。
你可以通过下述两种方式安装:
通过Composer安装 (Packagist上的symfony/routing
)
通过官方Git宝库(https://github.com/symfony/routing)
然后,包容vendor/autoload.php
文件,以开启Composer提供的自动加载机制。否则,你的程序将无法找到这个Symfony组件的类。
为了配置一个基本路由系统,你需要三部分内容:
RouteCollection
,包含路由定义( Route
类的实例)RequestContext
,包含请求信息UrlMatcher
,执行“把请求映射到单一路由”的操作下面是个速成例子。注意它假设你已经配好自动加载器(autoloader)来加载路由组件了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$route = new Route('/foo', array('controller' => 'MyController'));
$routes = new RouteCollection();
$routes->add('route_name', $route);
$context = new RequestContext('/');
$matcher = new UrlMatcher($routes, $context);
$parameters = $matcher->match('/foo');
// array('controller' => 'MyController', '_route' => 'route_name') |
RequestContext
中的参数(译注:原文parameters)可以利用存在$_SERVER
中的值来加载(译注:该类在构造时需要传入的值可以从$_SERVER中获得。同时该类有个getParameters()方法),但如果使用HttpFoundation组件会更容易,请参考下文。
只要你想,你可以添加任意多的路由到 RouteCollection
中。
RouteCollection::add()
方法接收两个参数。第一个是路由名称,第二个是 Route
对象,其构造器预期一个URL路径以及一些(译注:可变参数)“由一些自定义变量所组成的”数组。这个“自定义变量”之数组可以是你的程序认为重要的任何内容,当某路由匹配时该数组会被返回。
UrlMatcher::match()
返回你设置在路由中的变量和通配符(见下文)。你的程序现在可以使用这些信息来继续执行请求。除了那些你已经(在路由配置中)配好的变量之外,还有一个_route
键被添加(到数组中),它持有当前匹配的路由之名称(name)。
如果没有找到匹配的路由,一个 ResourceNotFoundException
异常被抛出。
一个完整的路由定义,包括以下七部分:
路由的URL路径。它要与传到RequestContenxt中的URL相匹配,同时要包含指定名称的通配符(如,{placeholders}
),以匹配URL中的动态部分。
一个“默认值”的数组。它包含一个“当请求与路由匹配时”所返回的“可以是任意值”的数组。
一个“所需条件”之数组。以正则表达式定义了通配符对应值的约束条件。
一个“选项”的数组。包含了路由的内部配置,是最轻量的常规需求。
一个host。它要匹配请求中的host部分。请参考中文路由组件之 如何基于Host来匹配路由。
一个schemes的数组。它们负责强制执行一个HTTP scheme(http
,https
)。
一个methods的数组。它们负责强制执行一个特定的HTTP方法(HEAD
,GET
,POST
...)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | $route = new Route(
'/archive/{month}', // path
array('controller' => 'showArchive'), // default values
array('month' => '[0-9]{4}-[0-9]{2}', 'subdomain' => 'www|m'), // requirements
array(), // options
'{subdomain}.example.com', // host
array(), // schemes
array() // methods
);
// ...
$parameters = $matcher->match('/archive/2012-01');
// array(
// 'controller' => 'showArchive',
// 'month' => '2012-01',
// 'subdomain' => 'www',
// '_route' => ...
// )
$parameters = $matcher->match('/archive/foo');
// throws ResourceNotFoundException |
上例中,路由被/archive/2012-01
匹配到了,因为{month}
能够匹配给定的“通配符正则”。但是,/archive/foo
并不匹配,因为“foo”无法与month匹配。
使用通配符的话,在调用match
方法时这些(参数/parameters)都会以“结果数组”被返回。通配符所“匹配到的”路径部分(如2012-01
)作为值来使用。
你可以添加路由或 RouteCollection
的其他实例到另一个 collection中。通过这种方式你可以打造路由树(a tree of routes)。而且,你还能对一个使用了“RouteCollection
类中的方法”的subtree(子树)的所有路由的参数(parameters)、正则条件(requirements)、选项(options)、schemes和host定义一个前缀和默认值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $rootCollection = new RouteCollection();
$subCollection = new RouteCollection();
$subCollection->add(...);
$subCollection->add(...);
$subCollection->addPrefix('/prefix');
$subCollection->addDefaults(array(...));
$subCollection->addRequirements(array(...));
$subCollection->addOptions(array(...));
$subCollection->setHost('admin.example.com');
$subCollection->setMethods(array('POST'));
$subCollection->setSchemes(array('https'));
$rootCollection->addCollection($subCollection); |
RequestContext
提供了关于当前请求的信息。你可以通过这个类的构造器来定义一个HTTP请求的所有参数:
1 2 3 4 5 6 7 8 9 10 | public function __construct(
$baseUrl = '',
$method = 'GET',
$host = 'localhost',
$scheme = 'http',
$httpPort = 80,
$httpsPort = 443,
$path = '/',
$queryString = ''
) |
通常情况下,你要传入的值来自于$_SERVER
变量,以便得到 RequestContext
实例。但是如果你使用HttpFoundation组件的话,你可以使用 Request
类来快速提供 RequestContext
所需之信息:
1 2 3 4 | use Symfony\Component\HttpFoundation\Request;
$context = new RequestContext();
$context->fromRequest(Request::createFromGlobals()); |
虽然 UrlMatcher
尝试找到一个路由来匹配给定的请求,但你也能从一个特定的路由来构造一个URL:
1 2 3 4 5 6 7 8 9 10 11 12 13 | use Symfony\Component\Routing\Generator\UrlGenerator;
$routes = new RouteCollection();
$routes->add('show_post', new Route('/show/{slug}'));
$context = new RequestContext('/');
$generator = new UrlGenerator($routes, $context);
$url = $generator->generate('show_post', array(
'slug' => 'my-blog-post',
));
// /show/my-blog-post |
如果你有定义一个scheme的话,若当前 RequestContext
没有匹配到条件(requirement),则一个绝对URL会被生成。
你已经看到,在PHP中正确地添加路由到collection(路由集合)中是多么容易了。但你还能够从多种(格式的)文件加载路由。
路由组件内置了一些“类加载器”,它们能令你有能力从“某种格式的外部文件”来加载(load)一组路由定义。loader预期一个 FileLocater
实例作为自己的构造参数。你可以使用 FileLocater
来定义一个“路径数组”以令其找寻对应的文件。如果文件被找到,loader返回一个 RouteCollection
。
如果你正使用YamlFileLoder
,那么路由定义看上去是下面这样:
1 2 3 4 5 6 7 8 | # routes.yml
route1:
path: /foo
defaults: { _controller: 'MyController::fooAction' }
route2:
path: /foo/bar
defaults: { _controller: 'MyController::foobarAction' } |
为了加载上面这个文件,你可以使用以下代码。它假定你的routes.yml
文件就在代码中的目录下:
1 2 3 4 5 6 7 | use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\Loader\YamlFileLoader;
// look inside *this* directory 锁定“这个”目录来查找
$locator = new FileLocator(array(__DIR__));
$loader = new YamlFileLoader($locator);
$collection = $loader->load('routes.yml'); |
除了 YamlFileLoader
,还有两个loaders以相同方式工作:
如果你使用 PhpFileLoader
的话,(在加载时)你需要提供那个PHP文件的名字,该文件返回的是一个 RouteCollection
对象:
1 2 3 4 5 6 7 8 9 10 11 12 | // RouteProvider.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add(
'route_name',
new Route('/foo', array('controller' => 'ExampleController'))
);
// ...
return $collection; |
还有一个closure加载器 ClosureLoader
,它调用一个匿名函数,将其返回值作为一个 RouteCollection
。
1 2 3 4 5 6 7 8 | use Symfony\Component\Routing\Loader\ClosureLoader;
$closure = function () {
return new RouteCollection();
};
$loader = new ClosureLoader();
$collection = $loader->load($closure); |
最后,还有 AnnotationDirectoryLoader
和 AnnotationFileLoader
用于从类的注释中(class annotations)来加载路由定义。具体细节暂且不提。
Route
类,是一个一体化的包,用以快速使用路由组件。其构造器预期一个loader实例、一个“相对于主路由定义”的路径(a path to main route definition path)以及一些其他设定:
通过cache_dir
选项,你可以开启路由缓存(route cahing,如果你提供一个path的话),或者关闭缓存(如果它被设为null
的话)。在你需要使用的时候,缓存于后台自动实现。 Router
的一个基本使用例程可参考下面:
如果你使用缓存,路由组件将编译新类,存放于cache_dir
目录下。这意味着你的脚本在那个目录下,必须有写入的权限。
// TODO...by Xtt1341...and RESUMED by 20160930...It's a real souvenir!
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。