本文概述
- RESTful API
- 设置一个Laravel Web服务项目
- 迁移与模型
- 数据库播种
- 路线和控制器
- 认证方式
- 创建注册端点
- 创建一个登录端点
- 注销
- 使用中间件限制访问
- 测试我们的端点
- 建立我们的测试工厂
- 我们的首次测试
- 下一步
随着移动开发和JavaScript框架的兴起, 使用RESTful API是在数据和客户端之间构建单个接口的最佳选择。
Laravel是一个考虑到PHP开发人员生产力而开发的PHP框架。该框架是由Taylor Otwell撰写和维护的, 非常有思想性, 并且通过优先考虑约定而不是配置来努力节省开发人员的时间。该框架还旨在与Web一起发展, 并且已经在Web开发领域中合并了一些新功能和思想, 例如作业队列, 即用型API身份验证, 实时通信等等。
在本教程中, 我们将探讨如何使用Laravel和身份验证来构建和测试强大的API。我们将使用Laravel 5.4, 所有代码都可以在GitHub上参考。
RESTful API
首先, 我们需要了解到底什么是RESTful API。 REST代表REpresentational State Transfer, 它是应用程序之间网络通信的体系结构样式, 它依赖于无状态协议(通常为HTTP)进行交互。
HTTP动词代表动作
在RESTful API中, 我们将HTTP动词用作操作, 而端点是操作的资源。我们将使用HTTP动词的语义:
- GET:检索资源
- POST:创建资源
- PUT:更新资源
- 删除:删除资源
更新操作:PUT与POST
RESTful API争论不休, 关于是否最好用POST, PATCH或PUT更新, 或者create动作最好留给PUT动词, 有很多意见。在本文中, 我们将使用PUT进行更新操作, 因为根据HTTP RFC, PUT意味着在特定位置创建/更新资源。 PUT动词的另一个要求是幂等, 在这种情况下, 基本上意味着你可以发送该请求1、2或1000次, 结果将是相同的:数据库中的一个更新资源。
资源资源
资源将是操作的目标, 在我们的案例中是”文章和用户”, 它们有自己的端点:
- /文章
- /用户
在此laravel API教程中, 资源在我们的数据模型上将具有1:1的表示形式, 但这不是必需的。你可以在多个数据模型中表示资源(或者根本不在数据库中表示资源), 并且完全为用户建模。最后, 你将决定如何以适合你的应用程序的方式来构造资源和模型。
关于一致性的注意事项
使用诸如REST之类的约定的最大优势在于, 你的API将更易于使用和开发。一些端点非常简单, 因此, 与使用诸如GET / get_article?id_article = 12和POST / delete_article?number = 40之类的端点相比, 你的API将更易于使用和维护。过去, 我已经建立了如此糟糕的API, 但我仍然为此感到讨厌。
但是, 在某些情况下, 将很难映射到”创建/检索/更新/删除”架构。请记住, URL不应包含动词, 并且资源不一定是表中的行。要记住的另一件事是, 你不必为每种资源实施所有操作。
设置一个Laravel Web服务项目
与所有现代PHP框架一样, 我们将需要Composer来安装和处理我们的依赖项。遵循下载说明(并添加到路径环境变量)之后, 使用以下命令安装Laravel:
$ composer global require laravel/installer
安装完成后, 你可以像这样搭建新的应用程序:
$ laravel new myapp
对于以上命令, 你需要在$ PATH中包含〜/ composer / vendor / bin。如果你不想处理这些问题, 也可以使用Composer创建一个新项目:
$ composer create-project --prefer-dist laravel/laravel myapp
安装了Laravel之后, 你应该能够启动服务器并测试一切是否正常:
$ php artisan serve
Laravel development server started: <http://127.0.0.1:8000>
在浏览器上打开localhost:8000时, 应该会看到此示例页面。
迁移与模型
在实际编写首次迁移之前, 请确保已为此应用程序创建了一个数据库, 并将其凭据添加到位于项目根目录的.env文件中。
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
你还可以使用Homestead, 这是专门为Laravel设计的Vagrant盒子, 但这超出了本文的范围。如果你想了解更多信息, 请参阅Homestead文档。
让我们开始第一个模型和迁移-本文。该文章应具有标题和正文字段, 以及创建日期。 Laravel通过Artisan(Laravel的命令行工具)提供了一些命令, 它们可以通过生成文件并将它们放置在正确的文件夹中来帮助我们。要创建Article模型, 我们可以运行:
$ php artisan make:model Article -m
-m选项是–migration的缩写, 它告诉Artisan为我们的模型创建一个。这是生成的迁移:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateArticlesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('articles', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('articles');
}
}
让我们对此进行剖析:
- 当我们分别迁移和回滚时, 将运行up()和down()方法。
- $ table-> increments(‘id’)设置名称为id的自动递增整数;
- $ table-> timestamps()将为我们设置时间戳-created_at和updated_at, 但是不必担心设置默认值, Laravel会在需要时负责更新这些字段。
- 最后, Schema :: dropIfExists()当然会删除表(如果存在)。
不用担心, 让我们在up()方法中添加两行:
public function up()
{
Schema::create('articles', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->text('body');
$table->timestamps();
});
}
string()方法创建等效的VARCHAR列, 而text()方法创建等效的TEXT。完成后, 让我们继续进行迁移:
$ php artisan migrate
你还可以在此处使用–step选项, 它将将每个迁移分为自己的批处理, 以便你可以根据需要单独回滚。
Laravel开箱即用提供了两个迁移, 即create_users_table和create_password_resets_table。我们不会使用password_resets表, 但为我们准备好users表将很有帮助。
现在让我们回到模型, 并将这些属性添加到$ fillable字段中, 以便我们可以在Article :: create和Article :: update模型中使用它们:
class Article extends Model
{
protected $fillable = ['title', 'body'];
}
可以使用Eloquent的create()和update()方法批量分配$ fillable属性中的字段。你还可以使用$ guarded属性, 以允许少数几个属性。
数据库播种
数据库播种是使用可用来测试数据库的虚拟数据填充数据库的过程。 Laravel带有Faker, 这是一个很棒的库, 用于为我们生成正确格式的伪数据。因此, 让我们创建我们的第一个种子:
$ php artisan make:seeder ArticlesTableSeeder
播种器将位于/ database / seeds目录中。设置好几篇文章后的样子如下:
class ArticlesTableSeeder extends Seeder
{
public function run()
{
// Let's truncate our existing records to start from scratch.
Article::truncate();
$faker = \Faker\Factory::create();
// And now, let's create a few articles in our database:
for ($i = 0; $i < 50; $i++) {
Article::create([
'title' => $faker->sentence, 'body' => $faker->paragraph, ]);
}
}
}
因此, 我们运行seed命令:
$ php artisan db:seed --class=ArticlesTableSeeder
让我们重复一下创建用户播种器的过程:
class UsersTableSeeder extends Seeder
{
public function run()
{
// Let's clear the users table first
User::truncate();
$faker = \Faker\Factory::create();
// Let's make sure everyone has the same password and
// let's hash it before the loop, or else our seeder
// will be too slow.
$password = Hash::make('srcmini');
User::create([
'name' => 'Administrator', 'email' => '[email protected]', 'password' => $password, ]);
// And now let's generate a few dozen users for our app:
for ($i = 0; $i < 10; $i++) {
User::create([
'name' => $faker->name, 'email' => $faker->email, 'password' => $password, ]);
}
}
}
通过将播种器添加到database / seeds文件夹内的主DatabaseSeeder类, 我们可以使其更容易:
class DatabaseSeeder extends Seeder
{
public function run()
{
$this->call(ArticlesTableSeeder::class);
$this->call(UsersTableSeeder::class);
}
}
这样, 我们可以简单地运行$ php artisan db:seed, 它将运行run()方法中所有被调用的类。
路线和控制器
让我们为我们的应用程序创建基本端点:创建, 检索列表, 检索单个, 更新和删除。在routes / api.php文件上, 我们可以简单地执行以下操作:
Use App\Article;
Route::get('articles', function() {
// If the Content-Type and Accept headers are set to 'application/json', // this will return a JSON structure. This will be cleaned up later.
return Article::all();
});
Route::get('articles/{id}', function($id) {
return Article::find($id);
});
Route::post('articles', function(Request $request) {
return Article::create($request->all);
});
Route::put('articles/{id}', function(Request $request, $id) {
$article = Article::findOrFail($id);
$article->update($request->all());
return $article;
});
Route::delete('articles/{id}', function($id) {
Article::find($id)->delete();
return 204;
})
api.php中的路由将以/ api /作为前缀, 并且API限制中间件将自动应用于这些路由(如果要删除前缀, 可以在/app/Providers/RouteServiceProvider.php上编辑RouteServiceProvider类)。
现在, 将以下代码移至其自己的Controller:
$ php artisan make:controller ArticleController
ArticleController.php:
use App\Article;
class ArticleController extends Controller
{
public function index()
{
return Article::all();
}
public function show($id)
{
return Article::find($id);
}
public function store(Request $request)
{
return Article::create($request->all());
}
public function update(Request $request, $id)
{
$article = Article::findOrFail($id);
$article->update($request->all());
return $article;
}
public function delete(Request $request, $id)
{
$article = Article::findOrFail($id);
$article->delete();
return 204;
}
}
的routes / api.php文件:
Route::get('articles', '[email protected]');
Route::get('articles/{id}', '[email protected]');
Route::post('articles', '[email protected]');
Route::put('articles/{id}', '[email protected]');
Route::delete('articles/{id}', '[email protected]');
我们可以通过使用隐式路由模型绑定来改进端点。这样, Laravel将在我们的方法中插入Article实例, 如果找不到则自动返回404。我们必须在路线文件和控制器上进行更改:
Route::get('articles', '[email protected]');
Route::get('articles/{article}', '[email protected]');
Route::post('articles', '[email protected]');
Route::put('articles/{article}', '[email protected]');
Route::delete('articles/{article}', '[email protected]');
class ArticleController extends Controller
{
public function index()
{
return Article::all();
}
public function show(Article $article)
{
return $article;
}
public function store(Request $request)
{
$article = Article::create($request->all());
return response()->json($article, 201);
}
public function update(Request $request, Article $article)
{
$article->update($request->all());
return response()->json($article, 200);
}
public function delete(Article $article)
{
$article->delete();
return response()->json(null, 204);
}
}
关于HTTP状态代码和响应格式的注释
我们还添加了response()-> json()调用到端点。这使我们可以显式返回JSON数据, 以及发送可由客户端解析的HTTP代码。你将返回的最常见的代码是:
- 200:好的标准成功代码和默认选项。
- 201:创建对象。对存储操作有用。
- 204:没有内容成功执行动作后, 没有任何内容可返回。
- 206:部分内容。当你必须返回分页的资源列表时很有用。
- 400:错误的请求。无法通过验证的请求的标准选项。
- 401:未经授权。用户需要认证。
- 403:禁止用户已通过身份验证, 但没有执行操作的权限。
- 404:找不到。找不到资源时, Laravel将自动返回该值。
- 500内部服务器错误。理想情况下, 你不会明确返回此值, 但是如果意外中断, 这就是你的用户将收到的内容。
- 503服务不可用。很容易说明, 但另一个代码不会由应用程序显式返回。
发送正确的404响应
如果你尝试获取不存在的资源, 则会引发异常, 并且会收到整个堆栈跟踪, 如下所示:
我们可以通过编辑位于app / Exceptions / Handler.php中的异常处理程序类以返回JSON响应来解决此问题:
public function render($request, Exception $exception)
{
// This will replace our 404 response with
// a JSON response.
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'error' => 'Resource not found'
], 404);
}
return parent::render($request, $exception);
}
这是退货的一个例子:
{
data: "Resource not found"
}
如果你使用Laravel服务其他页面, 则必须编辑代码以使用Accept标头, 否则常规请求中的404错误也将返回JSON。
public function render($request, Exception $exception)
{
// This will replace our 404 response with
// a JSON response.
if ($exception instanceof ModelNotFoundException &&
$request->wantsJson())
{
return response()->json([
'data' => 'Resource not found'
], 404);
}
return parent::render($request, $exception);
}
在这种情况下, API请求将需要标头Accept:application / json。
认证方式
有很多方法可以在Laravel中实现API身份验证(其中之一是Passport, 这是实现OAuth2的绝佳方法), 但是在本文中, 我们将采用一种非常简化的方法。
首先, 我们需要在users表中添加api_token字段:
$ php artisan make:migration --table=users adds_api_token_to_users_table
然后实施迁移:
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('api_token', 60)->unique()->nullable();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['api_token']);
});
}
之后, 只需使用以下命令运行迁移:
$ php artisan migrate
创建注册端点
我们将利用RegisterController(位于Auth文件夹中)在注册时返回正确的响应。 Laravel开箱即用提供了身份验证, 但是我们仍然需要对其进行一些调整以返回所需的响应。
控制器利用特征RegistersUsers来实现注册。运作方式如下:
public function register(Request $request)
{
// Here the request is validated. The validator method is located
// inside the RegisterController, and makes sure the name, email
// password and password_confirmation fields are required.
$this->validator($request->all())->validate();
// A Registered event is created and will trigger any relevant
// observers, such as sending a confirmation email or any
// code that needs to be run as soon as the user is created.
event(new Registered($user = $this->create($request->all())));
// After the user is created, he's logged in.
$this->guard()->login($user);
// And finally this is the hook that we want. If there is no
// registered() method or it returns null, redirect him to
// some other URL. In our case, we just need to implement
// that method to return the correct response.
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
我们只需要在RegisterController中实现registered()方法。该方法接收$ request和$ user, 所以这就是我们想要的。该方法在控制器内部的外观如下:
protected function registered(Request $request, $user)
{
$user->generateToken();
return response()->json(['data' => $user->toArray()], 201);
}
我们可以在路由文件上链接它:
Route::post(register, 'Auth\[email protected]);
在上面的部分中, 我们在User模型上使用了一种方法来生成令牌。这很有用, 因此我们只有一种生成令牌的方式。将以下方法添加到你的用户模型:
class User extends Authenticatable
{
...
public function generateToken()
{
$this->api_token = str_random(60);
$this->save();
return $this->api_token;
}
}
就是这样。现在, 该用户已注册, 并且由于Laravel的验证和开箱即用的身份验证, 因此需要名称, 电子邮件, 密码和password_confirmation字段, 并且反馈会自动进行处理。在RegisterController内检出validateator()方法以查看规则的实现方式。
这是我们到达那个终点时得到的:
$ curl -X POST http://localhost:8000/api/register \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "John", "email": "[email protected]", "password": "srcmini123", "password_confirmation": "srcmini123"}'
{
"data": {
"api_token":"0syHnl0Y9jOIfszq11EC2CBQwCfObmvscrZYo5o2ilZPnohvndH797nDNyAT", "created_at": "2017-06-20 21:17:15", "email": "[email protected]", "id": 51, "name": "John", "updated_at": "2017-06-20 21:17:15"
}
}
创建一个登录端点
就像注册端点一样, 我们可以编辑LoginController(在Auth文件夹中)以支持我们的API身份验证。 AuthenticatesUsers特征的登录方法可以重写以支持我们的API:
public function login(Request $request)
{
$this->validateLogin($request);
if ($this->attemptLogin($request)) {
$user = $this->guard()->user();
$user->generateToken();
return response()->json([
'data' => $user->toArray(), ]);
}
return $this->sendFailedLoginResponse($request);
}
我们可以在路由文件上链接它:
Route::post('login', 'Auth\[email protected]');
现在, 假设播种机已运行, 这是向该路由发送POST请求时得到的结果:
$ curl -X POST localhost:8000/api/login \
-H "Accept: application/json" \
-H "Content-type: application/json" \
-d "{\"email\": \"[email protected]\", \"password\": \"srcmini\" }"
{
"data": {
"id":1, "name":"Administrator", "email":"[email protected]", "created_at":"2017-04-25 01:05:34", "updated_at":"2017-04-25 02:50:40", "api_token":"Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw"
}
}
要在请求中发送令牌, 可以通过在有效负载中发送属性api_token或以授权形式在请求标头中作为承载令牌来实现:Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw。
注销
按照我们目前的策略, 如果令牌有误或丢失, 则用户应该收到未经身份验证的响应(我们将在下一部分中实现)。因此, 对于简单的注销端点, 我们将发送令牌, 令牌将在数据库中删除。
路线/api.php:
Route::post('logout', 'Auth\[email protected]');
Auth \ LoginController.php:
public function logout(Request $request)
{
$user = Auth::guard('api')->user();
if ($user) {
$user->api_token = null;
$user->save();
}
return response()->json(['data' => 'User logged out.'], 200);
}
使用此策略, 用户拥有的任何令牌都将无效, 并且API将拒绝访问(使用中间件, 如下一节所述)。这需要与前端协调, 以避免用户保持登录状态而无法访问任何内容。
使用中间件限制访问
创建api_token后, 我们可以在路由文件中切换身份验证中间件:
Route::middleware('auth:api')
->get('/user', function (Request $request) {
return $request->user();
});
我们可以使用$ request-> user()方法或通过Auth门面访问当前用户
Auth::guard('api')->user(); // instance of the logged user
Auth::guard('api')->check(); // if a user is authenticated
Auth::guard('api')->id(); // the id of the authenticated user
我们得到这样的结果:
这是因为我们需要在Handler类上编辑当前未经身份验证的方法。当前版本仅在请求具有Accept:application / json标头的情况下才返回JSON, 因此让我们对其进行更改:
protected function unauthenticated($request, AuthenticationException $exception)
{
return response()->json(['error' => 'Unauthenticated'], 401);
}
修复此问题后, 我们可以返回到文章端点, 将它们包装在auth:api中间件中。我们可以通过使用路由组来做到这一点:
Route::group(['middleware' => 'auth:api'], function() {
Route::get('articles', '[email protected]');
Route::get('articles/{article}', '[email protected]');
Route::post('articles', '[email protected]');
Route::put('articles/{article}', '[email protected]');
Route::delete('articles/{article}', '[email protected]');
});
这样, 我们不必为每个路由都设置中间件。现在并不能节省很多时间, 但是随着项目的发展, 它有助于使路线保持干燥。
测试我们的端点
Laravel开箱即用地包含了与PHPUnit的集成, 并且已经设置了phpunit.xml。该框架还为我们提供了一些帮助程序和额外的断言, 这使我们的生活更加轻松, 尤其是对于测试API。
你可以使用许多外部工具来测试API。但是, 在Laravel中进行测试是更好的选择-我们可以在保留对数据库的完全控制的同时, 获得测试API结构和结果的所有好处。例如, 对于列表端点, 我们可以运行几个工厂并断言响应包含这些资源。
首先, 我们需要调整一些设置以使用内存中的SQLite数据库。使用它可以使我们的测试快速运行, 但是要权衡的是某些迁移命令(例如约束)在该特定设置中将无法正常工作。我建议你在开始出现迁移错误时, 或者如果你希望使用更强大的测试集而不是性能更高的测试时, 在测试中不要使用SQLite。
我们还将在每次测试之前运行迁移。这种设置将使我们能够为每个测试构建数据库, 然后销毁它, 从而避免了测试之间的任何类型的依赖性。
在我们的config / database.php文件中, 我们需要将sqlite配置中的数据库字段设置为:memory ::
...
'connections' => [
'sqlite' => [
'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], ...
]
然后通过添加环境变量DB_CONNECTION在phpunit.xml中启用SQLite:
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
<env name="DB_CONNECTION" value="sqlite"/>
</php>
这样, 剩下的就是配置我们的基本TestCase类, 以使用迁移并在每次测试之前为数据库添加种子。为此, 我们需要添加DatabaseMigrations特征, 然后在setUp()方法上添加Artisan调用。更改后的课程如下:
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Artisan;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, DatabaseMigrations;
public function setUp()
{
parent::setUp();
Artisan::call('db:seed');
}
}
我最后要做的一件事是将测试命令添加到composer.json中:
"scripts": {
"test" : [
"vendor/bin/phpunit"
], ...
},
测试命令将如下所示:
$ composer test
建立我们的测试工厂
工厂将使我们能够快速创建具有正确数据以进行测试的对象。它们位于database / factories文件夹中。 Laravel开箱即用, 带有User类的工厂, 因此让我们为Article类添加一个:
$factory->define(App\Article::class, function (Faker\Generator $faker) {
return [
'title' => $faker->sentence, 'body' => $faker->paragraph, ];
});
已经注入了Faker库, 以帮助我们为模型创建正确的随机数据格式。
我们的首次测试
我们可以使用Laravel的assert方法轻松命中端点并评估其响应。让我们使用以下命令创建第一个测试, 即登录测试:
$ php artisan make:test Feature/LoginTest
这是我们的测试:
class LoginTest extends TestCase
{
public function testRequiresEmailAndLogin()
{
$this->json('POST', 'api/login')
->assertStatus(422)
->assertJson([
'email' => ['The email field is required.'], 'password' => ['The password field is required.'], ]);
}
public function testUserLoginsSuccessfully()
{
$user = factory(User::class)->create([
'email' => '[email protected]', 'password' => bcrypt('srcmini123'), ]);
$payload = ['email' => '[email protected]', 'password' => 'srcmini123'];
$this->json('POST', 'api/login', $payload)
->assertStatus(200)
->assertJsonStructure([
'data' => [
'id', 'name', 'email', 'created_at', 'updated_at', 'api_token', ], ]);
}
}
这些方法测试了几个简单的案例。 json()方法命中端点, 而其他断言则很容易解释。有关assertJson()的一个细节:此方法将响应转换为数组搜索参数, 因此顺序很重要。在这种情况下, 你可以链接多个assertJson()调用。
现在, 让我们创建注册端点测试, 并为该端点编写一对:
$ php artisan make:test RegisterTest
class RegisterTest extends TestCase
{
public function testsRegistersSuccessfully()
{
$payload = [
'name' => 'John', 'email' => '[email protected]', 'password' => 'srcmini123', 'password_confirmation' => 'srcmini123', ];
$this->json('post', '/api/register', $payload)
->assertStatus(201)
->assertJsonStructure([
'data' => [
'id', 'name', 'email', 'created_at', 'updated_at', 'api_token', ], ]);;
}
public function testsRequiresPasswordEmailAndName()
{
$this->json('post', '/api/register')
->assertStatus(422)
->assertJson([
'name' => ['The name field is required.'], 'email' => ['The email field is required.'], 'password' => ['The password field is required.'], ]);
}
public function testsRequirePasswordConfirmation()
{
$payload = [
'name' => 'John', 'email' => '[email protected]', 'password' => 'srcmini123', ];
$this->json('post', '/api/register', $payload)
->assertStatus(422)
->assertJson([
'password' => ['The password confirmation does not match.'], ]);
}
}
最后, 注销端点:
$ php artisan make:test LogoutTest
class LogoutTest extends TestCase
{
public function testUserIsLoggedOutProperly()
{
$user = factory(User::class)->create(['email' => '[email protected]']);
$token = $user->generateToken();
$headers = ['Authorization' => "Bearer $token"];
$this->json('get', '/api/articles', [], $headers)->assertStatus(200);
$this->json('post', '/api/logout', [], $headers)->assertStatus(200);
$user = User::find($user->id);
$this->assertEquals(null, $user->api_token);
}
public function testUserWithNullToken()
{
// Simulating login
$user = factory(User::class)->create(['email' => '[email protected]']);
$token = $user->generateToken();
$headers = ['Authorization' => "Bearer $token"];
// Simulating logout
$user->api_token = null;
$user->save();
$this->json('get', '/api/articles', [], $headers)->assertStatus(401);
}
}
请务必注意, 在测试过程中, Laravel应用程序不会在新请求时再次实例化。这意味着, 当我们点击身份验证中间件时, 它将当前用户保存在TokenGuard实例中, 以避免再次点击数据库。但是, 明智的选择-在这种情况下, 这意味着我们必须将注销测试分成两个, 以避免先前缓存的用户遇到任何问题。
测试Article端点也很简单:
class ArticleTest extends TestCase
{
public function testsArticlesAreCreatedCorrectly()
{
$user = factory(User::class)->create();
$token = $user->generateToken();
$headers = ['Authorization' => "Bearer $token"];
$payload = [
'title' => 'Lorem', 'body' => 'Ipsum', ];
$this->json('POST', '/api/articles', $payload, $headers)
->assertStatus(200)
->assertJson(['id' => 1, 'title' => 'Lorem', 'body' => 'Ipsum']);
}
public function testsArticlesAreUpdatedCorrectly()
{
$user = factory(User::class)->create();
$token = $user->generateToken();
$headers = ['Authorization' => "Bearer $token"];
$article = factory(Article::class)->create([
'title' => 'First Article', 'body' => 'First Body', ]);
$payload = [
'title' => 'Lorem', 'body' => 'Ipsum', ];
$response = $this->json('PUT', '/api/articles/' . $article->id, $payload, $headers)
->assertStatus(200)
->assertJson([
'id' => 1, 'title' => 'Lorem', 'body' => 'Ipsum'
]);
}
public function testsArtilcesAreDeletedCorrectly()
{
$user = factory(User::class)->create();
$token = $user->generateToken();
$headers = ['Authorization' => "Bearer $token"];
$article = factory(Article::class)->create([
'title' => 'First Article', 'body' => 'First Body', ]);
$this->json('DELETE', '/api/articles/' . $article->id, [], $headers)
->assertStatus(204);
}
public function testArticlesAreListedCorrectly()
{
factory(Article::class)->create([
'title' => 'First Article', 'body' => 'First Body'
]);
factory(Article::class)->create([
'title' => 'Second Article', 'body' => 'Second Body'
]);
$user = factory(User::class)->create();
$token = $user->generateToken();
$headers = ['Authorization' => "Bearer $token"];
$response = $this->json('GET', '/api/articles', [], $headers)
->assertStatus(200)
->assertJson([
[ 'title' => 'First Article', 'body' => 'First Body' ], [ 'title' => 'Second Article', 'body' => 'Second Body' ]
])
->assertJsonStructure([
'*' => ['id', 'body', 'title', 'created_at', 'updated_at'], ]);
}
}
下一步
这里的所有都是它的。绝对有改进的余地-你可以使用Passport包实现OAuth2, 集成分页和转换层(我建议使用Fractal), 列表会继续进行-但我想了解在Laravel中创建和测试API的基本知识, 外部软件包。
Laravel的开发无疑提高了我在PHP方面的经验, 并且通过它的易于测试也巩固了我对该框架的兴趣。它并不完美, 但是足够灵活, 可以让你解决其问题。
如果你要设计公共API, 请查看5个伟大的Web API设计的黄金规则。
评论前必须登录!
注册