我们在前面的文章已经讲了整个路由与控制器的源码,我们今天这个文章开始向大家介绍在 laravel 中创建 RESTFul 风格的控制器。
laravel
RESTFul
关于什么是RESTFul风格及其规范可参考这篇文章:理解RESTful架构。
关于 laravel 中 RESTFul 风格控制器的创建简要介绍 : HTTP控制器实例教程 —— 创建 RESTFul 风格控制器实现文章增删改查
要想在 laravel 中创建 RESTFul 风格控制器,只需要一句:
Route::resource('post','PostController');
该路由包含了指向多个动作的子路由:
这种用法既简单又方便,接下来,我们将会说一下 laravel 为我们提供的更加灵活的用法。
可以为 RESTFul 路由定义前缀:
$router->resource('prefix/foos', 'FooController'); $this->assertEquals('prefix/foos/{foo}', $routes[3]->uri());
laravel 允许定义拥有两个参数的 RESTFul 路由:
$router->resource('foos.bars', 'FooController'); $this->assertEquals('foos/{foo}/bars/{bar}', $routes[3]->uri());
一般来说,RESTFul 路由的参数命名规则是路由单数,符号 - 转为 _,例如下面例子中 bars,和 foo-baz。
-
_
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 强制这种单数模式:
parameters
$router->resource('foos', 'FooController', ['parameters' => 'singular']); $this->assertEquals('foos/{foo}', $routes[3]->uri());
我们也可以利用 singularParameters 来强制:
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 函数来自定义参数命名:
setParameters
ResourceRegistrar::setParameters(['foos' => 'oof', 'bazs' => 'b']); $router->resource('bars.foos.bazs', 'FooController'); $this->assertEquals('bars/{bar}/foos/{oof}/bazs/{b}', $routes[3]->uri());
laravel 为 RESTFul 路由生成了两个带有动词的路由: create 、 edit,分别用于加载订单的创建页面与编辑页面,这两个动词 laravel 是允许修改的:
create
edit
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:
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 路由的每个路由都要自己默认的路由名称,laravel 允许我们对路由名称进行修改:
我们可以用 as 来为路由名称添加前缀:
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 为单个路由来命名:
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 路由的创建工作由类 ResourceRegistrar 负责,这个类为默认为用户创建七个路由,函数方法 register 是创建路由的主函数:
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 路由添加了前缀,那么 laravel 将会以 group 的形式添加路由:
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]; }
在添加各种路由之前,我们需要先获取路由的基础参数,也就是当存在多参数情况下,最后的参数。获取参数后,如果用户有自定义命名,则获取自定义命名:
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' 的影响。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8