前言
本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/laravel-source-analysis
我們在前面的文章已經講了整個路由與控制器的原始碼,我們今天這個文章開始向大家介紹在 laravel
中建立 RESTFul
風格的控制器。
關於什麼是RESTFul風格及其規範可參考這篇文章:理解RESTful架構。
關於 laravel
中 RESTFul
風格控制器的建立簡要介紹 : HTTP控制器例項教程 —— 建立 RESTFul
風格控制器實現文章增刪改查
建立 RESTFul
風格控制器
要想在 laravel
中建立 RESTFul
風格控制器,只需要一句:
Route::resource('post','PostController');
該路由包含了指向多個動作的子路由:
方法 | 路徑 | 動作 | 路由名稱 |
---|---|---|---|
GET | /post | index | post.index |
GET | /post/create | create | post.create |
POST | /post | store | post.store |
GET | /post/{post} | show | post.show |
GET | /post/{post}/edit | edit | post.edit |
PUT/PATCH | /post/{post} | update | post.update |
DELETE | /post/{post} | destroy | post.destroy |
這種用法既簡單又方便,接下來,我們將會說一下 laravel
為我們提供的更加靈活的用法。
字首 RESTFul
路由
可以為 RESTFul
路由定義字首:
$router->resource('prefix/foos', 'FooController');
$this->assertEquals('prefix/foos/{foo}', $routes[3]->uri());
多引數 RESTFul
路由
laravel
允許定義擁有多個引數的 RESTFul
路由:
$router->resource('foos.bars', 'FooController');
$this->assertEquals('foos/{foo}/bars/{bar}', $routes[3]->uri());
引數自定義命名
一般來說,RESTFul
路由的引數命名規則是路由單數,符號 -
轉為 _
,例如下面例子中 bars
,和 foo-baz
。
$router->resource('foos', 'FooController');
$this->assertEquals('foos/{foo}', $routes[3]->uri());
$router->resource('foo-bar.foo-baz', 'FooController', ['only' => ['show']]);
$this->assertEquals('foo-bar/{foo_bar}/foo-baz/{foo_baz}', $routes[0]->uri());
我們可以利用 parameters
強制這種單數模式:
$router->resource('foos', 'FooController', ['parameters' => 'singular']);
$this->assertEquals('foos/{foo}', $routes[3]->uri());
我們也可以利用 singularParameters
來強制:
ResourceRegistrar::singularParameters(true);
$router->resource('foos', 'FooController', ['parameters' => 'singular']);
$this->assertEquals('foos/{foo}', $routes[3]->uri());
我們還可以不使用單數,利用 parameters
用自己自定義的名字來定義引數:
$router->resource('bars.foos.bazs', 'FooController', ['parameters' => ['foos' => 'oof', 'bazs' => 'b']]);
$this->assertEquals('bars/{bar}/foos/{oof}/bazs/{b}', $routes[3]->uri());
同時,我們仍然可以利用 setParameters
函式來自定義引數命名:
ResourceRegistrar::setParameters(['foos' => 'oof', 'bazs' => 'b']);
$router->resource('bars.foos.bazs', 'FooController');
$this->assertEquals('bars/{bar}/foos/{oof}/bazs/{b}', $routes[3]->uri());
RESTFul
路由動詞控制
laravel
為 RESTFul
路由生成了兩個帶有動詞的路由: create
、 edit
,分別用於載入訂單的建立頁面與編輯頁面,這兩個動詞 laravel
是允許修改的:
ResourceRegistrar::verbs([
'create' => 'ajouter',
'edit' => 'modifier',
]);
$router->resource('foo', 'FooController');
$routes = $router->getRoutes();
$this->assertEquals('foo/ajouter', $routes->getByName('foo.create')->uri());
$this->assertEquals('foo/{foo}/modifier', $routes->getByName('foo.edit')->uri());
控制器方法約束
一般情況下,我們都會一次性想要上面所生成的七個路由,然而,有時候,我們只需要其中幾個,或者不想要其中幾個。這時候就可以利用 only
或者 except
:
$router = $this->getRouter();
$router->resource('foo', 'FooController', ['only' => ['show', 'destroy']]);
$routes = $router->getRoutes();
$this->assertCount(2, $routes);
$router = $this->getRouter();
$router->resource('foo', 'FooController', ['except' => ['show', 'destroy']]);
$routes = $router->getRoutes();
$this->assertCount(5, $routes);
RESTFul
路由名稱自定義
RESTFul
路由的每個路由都要自己預設的路由名稱,laravel
允許我們對路由名稱進行修改:
我們可以用 as
來為路由名稱新增字首:
$router->resource('foo-bars', 'FooController', ['only' => ['show'], 'as' => 'prefix']);
$this->assertEquals('prefix.foo-bars.show', $routes[0]->getName());
當有多個路由引數的時候,路由引數預設新增到了路由名稱中:
$router->resource('prefix/foo.bar', 'FooController');
$this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.index'));
可以利用 names
為單個路由來命名:
$router->resource('foo', 'FooController', ['names' => [
'index' => 'foo',
'show' => 'bar',
]]);
$this->assertTrue($router->getRoutes()->hasNamedRoute('foo'));
$this->assertTrue($router->getRoutes()->hasNamedRoute('bar'));
還可以利用 names
為所有路由來命名:
$router->resource('foo', 'FooController', ['names' => 'bar']);
$this->assertTrue($router->getRoutes()->hasNamedRoute('bar.index'));
RESTFul
路由原始碼分析
RESTFul
路由的建立工作由類 ResourceRegistrar
負責,這個類為預設為使用者建立七個路由,函式方法 register
是建立路由的主函式:
class ResourceRegistrar
{
public function register($name, $controller, array $options = [])
{
if (isset($options['parameters']) && ! isset($this->parameters)) {
$this->parameters = $options['parameters'];
}
if (Str::contains($name, '/')) {
$this->prefixedResource($name, $controller, $options);
return;
}
$base = $this->getResourceWildcard(last(explode('.', $name)));
$defaults = $this->resourceDefaults;
foreach ($this->getResourceMethods($defaults, $options) as $m) {
$this->{'addResource'.ucfirst($m)}($name, $base, $controller, $options);
}
}
}
這個函式主要流程分為三段:
- 判斷是否由字首
- 獲取路由的基礎引數
- 新增路由
擁有字首的 RESTFul
路由
如果我們為 RESTFul
路由新增了字首,那麼 laravel
將會以 group
的形式新增路由:
protected function prefixedResource($name, $controller, array $options)
{
list($name, $prefix) = $this->getResourcePrefix($name);
$callback = function ($me) use ($name, $controller, $options) {
$me->resource($name, $controller, $options);
};
return $this->router->group(compact('prefix'), $callback);
}
protected function getResourcePrefix($name)
{
$segments = explode('/', $name);
$prefix = implode('/', array_slice($segments, 0, -1));
return [end($segments), $prefix];
}
獲取基礎 RESTFul
路由引數
在新增各種路由之前,我們需要先獲取路由的基礎引數,也就是當存在多引數情況下,最後的引數。獲取引數後,如果使用者有自定義命名,則獲取自定義命名:
public function getResourceWildcard($value)
{
if (isset($this->parameters[$value])) {
$value = $this->parameters[$value];
} elseif (isset(static::$parameterMap[$value])) {
$value = static::$parameterMap[$value];
} elseif ($this->parameters === 'singular' || static::$singularParameters) {
$value = Str::singular($value);
}
return str_replace('-', '_', $value);
}
新增各種路由
新增路由主要有三個步驟:
- 計算路由
uri
- 獲取路由屬性
- 建立路由
protected function addResourceIndex($name, $base, $controller, $options)
{
$uri = $this->getResourceUri($name);
$action = $this->getResourceAction($name, $controller, 'index', $options);
return $this->router->get($uri, $action);
}
當計算路由 uri
時,由於存在多引數的情況,需要迴圈計算路由引數:
public function getResourceUri($resource)
{
if (! Str::contains($resource, '.')) {
return $resource;
}
$segments = explode('.', $resource);
$uri = $this->getNestedResourceUri($segments);
return str_replace('/{'.$this->getResourceWildcard(end($segments)).'}', '', $uri);
}
protected function getNestedResourceUri(array $segments)
{
return implode('/', array_map(function ($s) {
return $s.'/{'.$this->getResourceWildcard($s).'}';
}, $segments));
}
當計算路由的屬性時,最重要的是獲取路由的名字,路由的名字可以是預設,也可以是使用者利用 names
或者 as
屬性來自定義:
protected function getResourceAction($resource, $controller, $method, $options)
{
$name = $this->getResourceRouteName($resource, $method, $options);
$action = ['as' => $name, 'uses' => $controller.'@'.$method];
if (isset($options['middleware'])) {
$action['middleware'] = $options['middleware'];
}
return $action;
}
protected function getResourceRouteName($resource, $method, $options)
{
$name = $resource;
if (isset($options['names'])) {
if (is_string($options['names'])) {
$name = $options['names'];
} elseif (isset($options['names'][$method])) {
return $options['names'][$method];
}
}
$prefix = isset($options['as']) ? $options['as'].'.' : '';
return trim(sprintf('%s%s.%s', $prefix, $name, $method), '.');
}
值得注意的是,如果單獨為某一個方法命名,那麼直接回返回命名,而不會受 as
和方法名 'method' 的影響。