本文概述
- RESTful API
- 设置一个Laravel Web服务项目
- 迁移与模型
- 数据库播种
- 路线和控制器
- 认证方式
- 创建注册端点
- 创建一个登录端点
- 注销
- 使用中间件限制访问
- 测试我们的端点
- 建立我们的测试工厂
- 我们的首次测试
- 下一步
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次, 结果将是相同的:数据库中的一个更新资源。
资源资源
资源将是操作的目标, 在我们的案例中是” 文章和用户” , 它们有自己的端点:
- /文章
- /用户
关于一致性的注意事项
使用诸如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为我们的模型创建一个。这是生成的迁移:
<
?phpuse 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()当然会删除表(如果存在)。
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服务不可用。很容易说明, 但另一个代码不会由应用程序显式返回。
如果你尝试获取不存在的资源, 则会引发异常, 并且会收到整个堆栈跟踪, 如下所示:
文章图片
我们可以通过编辑位于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="http://www.srcmini.com/testing"/>
<
env name="CACHE_DRIVER" value="http://www.srcmini.com/array"/>
<
env name="SESSION_DRIVER" value="http://www.srcmini.com/array"/>
<
env name="QUEUE_DRIVER" value="http://www.srcmini.com/sync"/>
<
env name="DB_CONNECTION" value="http://www.srcmini.com/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设计的黄金规则。
推荐阅读
- Spring Batch教程(使用Spring轻松进行批处理)
- 使用Google Cloud实现无服务器Node.js功能
- 创建Ruby DSL(高级元编程指南)
- 如何修复Firefox不加载页面(详细解决办法分步指南)
- Windows 11如何修复Realtek音频控制台不起作用(解决办法)
- 如何修复Chrome配置文件错误(14种方法分步指南)
- Chrome的16个最佳广告拦截扩展下载推荐合集(哪个最好用())
- 如何修复AirPods已连接但没有声音问题(解决办法教程)
- 15个小型企业最佳免费电子邮件提供商推荐合集(哪个适合你())