路由
有时,你会向扩展包的使用者公开路由信息。
因为我们提供了一个 Post
模型,因此我们添加了 RESTful 路由。为了方便演示,我们只实现了 RESTful 路由的 3 个方法。
- 显示所有帖子 (
index
) - 显示指定单个帖子 (
show
) - 保存新的天子 (
store
)
控制器(Controllers)
创建基本的控制器
我们需要创建一个 PostController
为了充分利用 Laravel 控制器提供的便利方法,我们首先在 src/Http/Controllers
目录下创建一个 Controller.php
文件(类似于 Laravel 项目的文件夹结构)。
// 'src/Http/Controllers/Controller.php'
<?php
namespace JohnDoe\BlogPackage\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
创建一个控制器并继承基本控制器
现在,让我们在 src/Http/Controllers
目录下创建一个 PostController
文件,首先让我们在 store
方法开始吧。
// 'src/Http/Controllers/PostController'
<?php
namespace JohnDoe\BlogPackage\Http\Controllers;
class PostController extends Controller
{
public function index()
{
//
}
public function show()
{
//
}
public function store()
{
// 首先进行用户的权限认证
// 如果权限不足 则不可以创建
if (! auth()->check()) {
abort (403, 'Only authenticated users can create new posts.');
}
request()->validate([
'title' => 'required',
'body' => 'required',
]);
// 通过权限认证后 当前登录的用户就是该帖子的作者
$author = auth()->user();
$post = $author->posts()->create([
'title' => request('title'),
'body' => request('body'),
]);
return redirect(route('posts.show', $post));
}
}
路由
定义路由信息
现在控制器我们已经写好了,接下来在 src
目录下创建一个 routes/
文件夹,并创建一个包含上面提到的 RESTful 路由信息的 web.php
文件。
// 'routes/web.php'
<?php
use Illuminate\Support\Facades\Route;
use JohnDoe\BlogPackage\Http\Controllers\PostController;
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');
Route::post('/posts', [PostController::class, 'store'])->name('posts.store');
在服务提供者中注册路由
如果要使用路由信息,我们需要在服务提供者的 boot()
方法中注册才可以:
// 'BlogPackageServiceProvider.php'
public function boot()
{
// ... other things
$this->loadRoutesFrom(__DIR__.'../../routes/web.php');
}
视图
PostController
的 index()
和 show()
方法需要有显示帖子信息的页面。
创建 blade 视图文件
在 src
目录下创建一个 resources/
文件夹,在该文件夹中,创建一个 views
的子文件夹用于存放所有的视图文件。在 views
文件夹中,我们需要创建一个 posts
的子文件夹,用于存放关于帖子的所有视图文件。
resources/views/posts/index.blade.php
:
<h1>Showing all Posts</h1>
@forelse ($posts as $post)
<li>{{ $post->title }}</li>
@empty
<p> 'No posts yet' </p>
@endforelse
resources/views/posts/show.blade.php
:
<h1>{{ $post->title }}</h1>
<p> {{ $post->body }}</p>
注意:实际项目中,这些视图文件都是扩展其他主布局文件的。
在服务提供者中注册视图
现在我们已经有了一些视图,接下来需要在服务提供者的 boot()
方法中注册,否则是无法使用的。
重要:我们需要为 loadViewsFrom()
方法的第二个参数提供一个 key,因为从控制器中调整到视图文件时需要使用(在下一节中有详细的示例)。
// 'BlogPackageServiceProvider.php'
public function boot()
{
// ... other things
$this->loadViewsFrom(__DIR__.'/../resources/views', 'blogpackage');
}
控制器跳转视图
现在我们可以在 PostController
中向帖子展示视图跳转了。(因为要查询帖子信息,不要忘记引入 Post
模型哦)
提示:主要 view()
方法的第一个参数中的 blogpackage::
,该前缀与我们在上一节中在服务提供者绑定的前缀一致才可以。
// 'src/Http/Controllers/PostController.php'
use JohnDoe\BlogPackage\Models\Post;
public function index()
{
$posts = Post::all();
return view('blogpackage::posts.index', compact('posts'));
}
public function show()
{
$post = Post::findOrFail(request('post'));
return view('blogpackage::posts.show', compact('post'));
}
自定义视图
你可以希望扩展包的视图用户可以自定义,与数据库迁移类似,我们使用 published 方法,需要注意 published
的第二个参数是 views
,需要将 published 方法添加到服务提供者的 boot()
方法里。
// 'BlogPackageServiceProvider.php'
if ($this->app->runningInConsole()) {
// publish database migrations
$this->publishes([
__DIR__.'/../resources/views' => resource_path('views/vendor/blogpackage'),
], 'views');
}
然后扩展包的用户就可以通过下面的命令来导出视图:
php artisan vendor:publish --provider="JohnDoe\BlogPackage\BlogPackageServiceProvider" --tag="views"
资源
在扩展包中使用视图时,我们可能希望包含 CSS 或 JavaScript 文件。
创建资源目录
如果视图文件需要包含 CSS 或 JavaScript 文件,请在 resources/
文件夹中创建 assets
目录。视图文件中可以包含多个 CSS 或 JavaScript 文件,为了方便管理,我们需要创建 2 个文件夹,分别是 css
和 js
来存储这些文件。有个不成文的约定是将主文件的文件名为 app.js
和 app.css
。
自定义资源
就像视图一样,我们可以让用户自定义资源。具体的代码如下,只需要注意 publishes
方法的第二个参数为 assets
即可。第一个参数的目录信息要根据实际情况情况进行设置即可。
// 'BlogPackageServiceProvider.php'
if ($this->app->runningInConsole()) {
// publish database migrations
// publish views
$this->publishes([
__DIR__.'/../resources/assets' => public_path('blogpackage'),
], 'assets');
}
用户可使用下面的命令自动导出资源:
php artisan vendor:publish --provider="JohnDoe\BlogPackage\BlogPackageServiceProvider" --tag="assets"
引用资源
在视图中引用 CSS 和 JavaScript 文件,代码如下:
<script src="{{ asset('blogpackage/js/app.js') }}"></script>
<link href="{{ asset('blogpackage/css/app.css') }}" rel="stylesheet">
测试路由
让我们验证是否可以显示帖子内容、创建一条新帖子以及显示所有的帖子。
Feature test
在 tests/Feature
目录创建一个 CreatePostTest.php
测试文件,并在文件中添加以下测试代码,代码中包含对用户权限的验证:
// 'tests/Feature/CreatePostTest.php'
<?php
namespace JohnDoe\BlogPackage\Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use JohnDoe\BlogPackage\Models\Post;
use JohnDoe\BlogPackage\Tests\TestCase;
use JohnDoe\BlogPackage\Tests\User;
class CreatePostTest extends TestCase
{
use RefreshDatabase;
/** @test */
function authenticated_users_can_create_a_post()
{
// 确保数据库中没有任何帖子
$this->assertCount(0, Post::all());
$author = factory(User::class)->create();
$response = $this->actingAs($author)->post(route('posts.store'), [
'title' => 'My first fake title',
'body' => 'My first fake body',
]);
$this->assertCount(1, Post::all());
tap(Post::first(), function ($post) use ($response, $author) {
$this->assertEquals('My first fake title', $post->title);
$this->assertEquals('My first fake body', $post->body);
$this->assertTrue($post->author->is($author));
$response->assertRedirect(route('posts.show', $post));
});
}
}
同时,我们还要验证在创建帖子时标题与内容不能为空:
// 'tests/Feature/CreatePostTest.php'
/** @test */
function a_post_requires_a_title_and_a_body()
{
$author = factory(User::class)->create();
$this->actingAs($author)->post(route('posts.store'), [
'title' => '',
'body' => 'Some valid body',
])->assertSessionHasErrors('title');
$this->actingAs($author)->post(route('posts.store'), [
'title' => 'Some valid title',
'body' => '',
])->assertSessionHasErrors('body');
}
接下来,让我们验证没有登录的用户(或游客)无法创建新帖子:
// 'tests/Feature/CreatePostTest.php'
/** @test */
function guests_can_not_create_posts()
{
// We're starting from an unauthenticated state
$this->assertFalse(auth()->check());
$this->post(route('posts.store'), [
'title' => 'A valid title',
'body' => 'A valid body',
])->assertForbidden();
}
最后,让我们一起验证帖子列表页面是否可以正确显示:
// 'tests/Feature/CreatePostTest.php'
/** @test */
function all_posts_are_shown_via_the_index_route()
{
// Given we have a couple of Posts
factory(Post::class)->create([
'title' => 'Post number 1'
]);
factory(Post::class)->create([
'title' => 'Post number 2'
]);
factory(Post::class)->create([
'title' => 'Post number 3'
]);
// We expect them to all show up
// with their title on the index route
$this->get(route('posts.index'))
->assertSee('Post number 1')
->assertSee('Post number 2')
->assertSee('Post number 3')
->assertDontSee('Post number 4');
}
/** @test */
function a_single_post_is_shown_via_the_show_route()
{
$post = factory(Post::class)->create([
'title' => 'The single post title',
'body' => 'The single post body',
]);
$this->get(route('posts.show', $post))
->assertSee('The single post title')
->assertSee('The single post body');
}
Tip: 在测试中获取异常、错误消息时,禁用正常的异常处理很有必要,这样可帮助我们了解异常的来源。你可以通过测试开始时声明
$this->withoutExceptionHandling();
来实现。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: