Laravel: Hướng Dẫn Laravel API: Cách Xây Dựng Và Kiểm Tra Một ...

Với sự phát triển của khung phát triển di động và JavaScript, sử dụng API RESTful là tùy chọn tốt nhất để xây dựng một giao diện duy nhất giữa dữ liệu của bạn và máy khách của bạn.

Laravel là một khung công tác PHP được phát triển với năng suất của nhà phát triển PHP. Được viết và duy trì bởi Taylor Otwell, framework rất được quan tâm và cố gắng tiết kiệm thời gian của nhà phát triển bằng cách ủng hộ quy ước về cấu hình. Framework này cũng nhằm phát triển với web và đã tích hợp một số tính năng và ý tưởng mới trong thế giới phát triển web, ví dụ như hàng đợi công việc, xác thực API, giao tiếp thời gian thực và nhiều hơn nữa.

Hướng dẫn API của Laravel - Xây dựng dịch vụ Web RESTful

Trong hướng dẫn này, chúng ta sẽ khám phá những cách bạn có thể xây dựng và thử nghiệm API mạnh mẽ bằng cách sử dụng Laravel với xác thực. Chúng ta sẽ sử dụng Laravel 5.x.

API RESTful

Trước tiên, chúng ta cần hiểu chính xác những gì được coi là API RESTful. REST là viết tắt của REpresentational State Transfer và là một kiểu kiến ​​trúc để giao tiếp mạng giữa các ứng dụng, dựa trên giao thức không trạng thái (thường là HTTP) để tương tác.

Động từ HTTP đại diện cho hành động

Trong API RESTful, chúng ta sử dụng các động từ HTTP làm hành động và các điểm cuối là tài nguyên được tác động. Chúng ta sẽ sử dụng các động từ HTTP cho ý nghĩa ngữ nghĩa của chúng:

  • GET: lấy tài nguyên
  • POST: tạo tài nguyên
  • PUT: cập nhật tài nguyên
  • DELETE: xóa tài nguyên

Động từ HTTP: GET, POST, PUT và DELETE là các hành động trong API RESTful

Cập nhật hành động: PUT so với POST

API RESTful là một vấn đề của nhiều cuộc tranh luận và có rất nhiều ý kiến trên mạng về việc là tốt nhất để cập nhật với POST, PATCH hay PUT, hoặc nếu hành động tạo ra là tốt nhất còn lại để các PUT động từ. Trong bài viết này, chúng tôi sẽ sử dụng PUT cho hành động cập nhật, theo HTTP RFC, PUT có nghĩa là tạo/cập nhật tài nguyên tại một vị trí cụ thể. Một yêu cầu khác cho động từ PUT là idempotence, trong trường hợp này về cơ bản có nghĩa là bạn có thể gửi yêu cầu đó 1, 2 hoặc 1000 lần và kết quả sẽ giống nhau: một tài nguyên được cập nhật trong cơ sở dữ liệu.

Tài nguyên

Tài nguyên sẽ là mục tiêu của các hành động, trong trường hợp của chúng tôi là Bài viết và Người dùng và chúng có điểm cuối riêng:

  • /articles
  • /users

Trong hướng dẫn api laravel này, các tài nguyên sẽ có tỷ lệ 1:1 trên các mô hình dữ liệu của chúng ta, nhưng đó không phải là một yêu cầu. Bạn có thể có các tài nguyên được thể hiện trong nhiều hơn một mô hình dữ liệu (hoặc hoàn toàn không được đại diện trong cơ sở dữ liệu) và các mô hình hoàn toàn vượt quá giới hạn cho người dùng. Cuối cùng, bạn có thể quyết định cách kiến ​​trúc sư tài nguyên và mô hình theo cách phù hợp với ứng dụng của bạn.

Lưu ý về tính nhất quán

Ưu điểm lớn nhất của việc sử dụng một tập hợp các quy ước như REST là API của bạn sẽ dễ dàng tiêu thụ và phát triển hơn nhiều. Một số điểm cuối khá đơn giản và do đó, API của bạn sẽ dễ sử dụng và bảo trì hơn nhiều so với việc có các điểm cuối như GET /get_article?id_article=12 và POST /delete_article?number=40. Tôi đã xây dựng các API khủng khiếp như thế trong quá khứ và tôi vẫn ghét bản thân mình vì nó.

Tuy nhiên, sẽ có những trường hợp khó có thể ánh xạ tới lược đồ Create/Retrieve/Update/Delete. Hãy nhớ rằng các URL không được chứa động từ và tài nguyên không nhất thiết phải là hàng trong một bảng. Một lưu ý khác là bạn không phải thực hiện mọi hành động cho mọi tài nguyên.

Thiết lập dự án web service của Laravel

Như với tất cả các framework PHP hiện đại, chúng tôi sẽ cần Composer để cài đặt và xử lý các phụ thuộc của chúng tôi. Sau khi bạn làm theo các hướng dẫn tải xuống (và thêm vào biến môi trường đường dẫn của bạn), hãy cài đặt Laravel bằng lệnh:

$ composer global require laravel/installer

Sau khi cài đặt kết thúc, bạn có thể tạo ra một ứng dụng mới như thế này:

$ laravel new myapp

Đối với lệnh trên, bạn cần phải có ~/composer/vendor/bin trong $PATH. Nếu bạn không muốn giải quyết vấn đề đó, bạn cũng có thể tạo một dự án mới bằng Composer:

$ composer create-project --prefer-dist laravel/laravel myapp

Với cài đặt Laravel, bạn sẽ có thể khởi động máy chủ và kiểm tra xem mọi thứ có hoạt động không:

$ php artisan serve Laravel development server started: <http://127.0.0.1:8000>

Di chuyển và mô hình

Trước khi thực sự viết di chuyển đầu tiên của bạn, hãy đảm bảo bạn đã tạo cơ sở dữ liệu cho ứng dụng này và thêm thông tin đăng nhập vào .envtệp nằm trong thư mục gốc của dự án.

DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret

Bạn cũng có thể sử dụng Homestead, một hộp Vagrant được chế tạo đặc biệt cho Laravel, nhưng đó là một chút ngoài phạm vi của bài viết này.

Hãy bắt đầu với mô hình đầu tiên của chúng ta và di chuyển Điều khoản. Bài viết nên có một tiêu đề và một lĩnh vực cơ thể, cũng như một ngày sáng tạo. Laravel cung cấp một số lệnh thông qua công cụ dòng lệnh Artisan của Laravel giúp chúng ta bằng cách tạo các tệp và đặt chúng vào các thư mục chính xác. Để tạo mô hình Bài viết, chúng ta có thể chạy:

$ php artisan make:model Article -m

Tùy chọn -m là chữ viết tắt --migration và nó nói với Artisan để tạo ra một mô hình của chúng tôi. Đây là di chuyển được tạo:

<?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'); } }

Chúng ta hãy mổ xẻ điều này trong một giây:

  • Các phương thức up() và down() sẽ được chạy khi chúng ta di chuyển và khôi phục tương ứng;
  • $table->increments('id') thiết lập một số nguyên tăng tự động với tên id;
  • $table->timestamps() sẽ thiết lập dấu thời gian cho chúng ta created_at và updated_at, nhưng đừng lo về việc đặt mặc định, Laravel sẽ chăm sóc cập nhật các trường này khi cần.
  • Và cuối cùng, Schema::dropIfExists() tất nhiên, sẽ bỏ bảng nếu nó tồn tại.

Ngoài ra, hãy thêm hai dòng vào up()phương thức của chúng tôi :

public function up() { Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->text('body'); $table->timestamps(); }); }

Phương thức string() tạo ra một cột VARCHAR tương đương trong khi text() tạo ra một TEXT tương đương. Khi đã xong, hãy tiếp tục và di chuyển:

$ php artisan migrate

Bạn cũng có thể sử dụng tùy chọn --step ở đây và nó sẽ tách từng di chuyển thành lô riêng để bạn có thể cuộn chúng lại nếu cần.

Laravel ra khỏi hộp đi kèm với hai lần di chuyển, create_users_table và create_password_resets_table. Chúng tôi sẽ không sử dụng bảng password_resets, nhưng để users sẵn sàng cho chúng tôi sẽ hữu ích.

Bây giờ, hãy quay lại mô hình của chúng tôi và thêm các thuộc tính đó vào $fillable trường để chúng tôi có thể sử dụng chúng trong mô hình Article::create và Article::update mô hình của chúng tôi:

class Article extends Model { protected $fillable = ['title', 'body']; }

Các trường bên trong $fillable có thể được gán khối lượng bằng cách sử dụng các phương thức Eloquent create() và update(). Bạn cũng có thể sử dụng thuộc tính $guarded, để cho phép tất cả trừ một vài thuộc tính.

Cơ sở dữ liệu Seeding

Cơ sở dữ liệu seeding là quá trình điền vào cơ sở dữ liệu của chúng tôi với dữ liệu giả mà chúng ta có thể sử dụng để kiểm tra nó. Laravel đi kèm với Faker , một thư viện tuyệt vời để tạo ra định dạng chính xác của dữ liệu giả cho chúng tôi. Vì vậy, hãy tạo ra seeder đầu tiên của chúng tôi:

$ php artisan make:seeder ArticlesTableSeeder

Các seeder sẽ được đặt trong /database/seedsthư mục. Đây là giao diện sau khi chúng tôi thiết lập để tạo một vài bài viết:

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, ]); } } }

Vì vậy, hãy chạy lệnh seed:

$ php artisan db:seed --class=ArticlesTableSeeder

Hãy lặp lại quy trình để tạo seeder cho người dùng:

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('toptal'); User::create([ 'name' => 'Administrator', 'email' => 'admin@test.com', '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, ]); } } }

Chúng ta có thể làm cho nó dễ dàng hơn bằng cách thêm các seeder của chúng ta vào DatabaseSeederlớp chính bên trong database/seedsthư mục:

class DatabaseSeeder extends Seeder { public function run() { $this->call(ArticlesTableSeeder::class); $this->call(UsersTableSeeder::class); } }

Theo cách này, chúng ta có thể chạy đơn giản $ php artisan db:seedvà nó sẽ chạy tất cả các lớp được gọi trong run()phương thức.

Tuyến đường và bộ điều khiển

Hãy tạo các điểm cuối cơ bản cho ứng dụng của chúng tôi: tạo, truy xuất danh sách, truy xuất một điểm duy nhất, cập nhật và xóa. Trên routes/api.phptệp, chúng ta chỉ cần làm điều này:

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; })

Các tuyến đường bên trong api.phpsẽ được thêm tiền tố /api/và phần mềm trung gian điều tiết API sẽ được tự động áp dụng cho các tuyến đường này (nếu bạn muốn xóa tiền tố, bạn có thể chỉnh sửa RouteServiceProviderlớp trên /app/Providers/RouteServiceProvider.php).

Bây giờ hãy chuyển mã này sang Trình điều khiển riêng của nó:

$ php artisan make:controller ArticleController

ArticleControll.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; } }

Các routes/api.phptập tin:

Route::get('articles', 'ArticleController@index'); Route::get('articles/{id}', 'ArticleController@show'); Route::post('articles', 'ArticleController@store'); Route::put('articles/{id}', 'ArticleController@update'); Route::delete('articles/{id}', 'ArticleController@delete');

Chúng tôi có thể cải thiện các điểm cuối bằng cách sử dụng ràng buộc mô hình tuyến đường ngầm. Bằng cách này, Laravel sẽ đưa Articlecá thể vào các phương thức của chúng tôi và tự động trả về 404 nếu không tìm thấy. Chúng tôi sẽ phải thay đổi tệp tuyến đường và trên bộ điều khiển:

Route::get('articles', 'ArticleController@index'); Route::get('articles/{article}', 'ArticleController@show'); Route::post('articles', 'ArticleController@store'); Route::put('articles/{article}', 'ArticleController@update'); Route::delete('articles/{article}', 'ArticleController@delete'); 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); } }

Lưu ý về Mã trạng thái HTTP và Định dạng phản hồi

Chúng tôi cũng đã thêm response()->json()cuộc gọi đến các điểm cuối của chúng tôi. Điều này cho phép chúng tôi trả lại rõ ràng dữ liệu JSON cũng như gửi mã HTTP có thể được phân tích cú pháp bởi máy khách. Các mã phổ biến nhất bạn sẽ trả lại sẽ là:

  • 200: ĐƯỢC. Mã thành công tiêu chuẩn và tùy chọn mặc định.
  • 201: Đối tượng được tạo. Hữu ích cho các storehành động.
  • 204: Không có nội dung. Khi một hành động được thực hiện thành công, nhưng không có nội dung để trả về.
  • 206: Nội dung một phần. Hữu ích khi bạn phải trả về một danh sách tài nguyên được phân trang.
  • 400: Yêu cầu xấu. Tùy chọn tiêu chuẩn cho các yêu cầu không vượt qua xác nhận.
  • 401: Không được phép. Người dùng cần được xác thực.
  • 403: Cấm. Người dùng được xác thực, nhưng không có quyền để thực hiện một hành động.
  • 404: Không tìm thấy. Điều này sẽ được trả lại tự động bởi Laravel khi không tìm thấy tài nguyên.
  • 500: Lỗi máy chủ nội bộ. Lý tưởng nhất là bạn sẽ không trả lại một cách rõ ràng điều này, nhưng nếu có điều gì đó bất ngờ bị phá vỡ, đây là những gì người dùng của bạn sẽ nhận được.
  • 503: Dịch vụ Không sẵn có. Khá tự giải thích, nhưng cũng có một mã khác sẽ không được trả lại rõ ràng bởi ứng dụng.

Gửi phản hồi 404 chính xác

Nếu bạn đã cố gắng tìm nạp tài nguyên không tồn tại, bạn sẽ bị ném ngoại lệ và bạn sẽ nhận được toàn bộ stacktrace, như thế này:

Ngăn xếp NotFoundHttpException

Chúng tôi có thể khắc phục điều đó bằng cách chỉnh sửa lớp trình xử lý ngoại lệ của chúng tôi, được đặt trong app/Exceptions/Handler.php, để trả về phản hồi 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); }

Đây là một ví dụ về sự trở lại:

{ data: "Resource not found" }

Nếu bạn đang sử dụng Laravel để phục vụ các trang khác, bạn phải chỉnh sửa mã để hoạt động với Accepttiêu đề, nếu không, lỗi 404 từ các yêu cầu thông thường cũng sẽ trả về 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); }

Trong trường hợp này, các yêu cầu API sẽ cần tiêu đề Accept: application/json.

Xác thực

Có nhiều cách để triển khai Xác thực API trong Laravel (một trong số đó là Hộ chiếu , một cách tuyệt vời để triển khai OAuth2), nhưng trong bài viết này, chúng tôi sẽ thực hiện một cách tiếp cận rất đơn giản.

Để bắt đầu, chúng ta cần thêm một api_tokentrường vào usersbảng:

$ php artisan make:migration --table=users adds_api_token_to_users_table

Và sau đó thực hiện di chuyển:

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']); }); }

Sau đó, chỉ cần chạy di chuyển bằng cách sử dụng:

$ php artisan migrate

Tạo điểm cuối đăng ký

Chúng tôi sẽ sử dụng RegisterController(trong Auththư mục) để trả về phản hồi chính xác khi đăng ký. Laravel đi kèm với xác thực ngoài hộp, nhưng chúng ta vẫn cần phải điều chỉnh nó một chút để trả về phản hồi mà chúng ta muốn.

Bộ điều khiển sử dụng các đặc điểm RegistersUsersđể thực hiện đăng ký. Đây là cách nó hoạt động:

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()); }

Chúng tôi chỉ cần thực hiện các registered()phương pháp trong của chúng tôi RegisterController. Phương thức này nhận được $requestvà $user, vì vậy đó thực sự là tất cả những gì chúng ta muốn. Đây là cách phương thức sẽ trông giống như bên trong bộ điều khiển:

protected function registered(Request $request, $user) { $user->generateToken(); return response()->json(['data' => $user->toArray()], 201); }

Và chúng ta có thể liên kết nó trên tập tin tuyến đường:

Route::post(register, 'Auth\RegisterController@register);

Trong phần trên, chúng tôi đã sử dụng một phương thức trên mô hình Người dùng để tạo mã thông báo. Điều này hữu ích để chúng tôi chỉ có một cách duy nhất để tạo mã thông báo. Thêm phương thức sau vào mô hình Người dùng của bạn:

class User extends Authenticatable { ... public function generateToken() { $this->api_token = str_random(60); $this->save(); return $this->api_token; } }

Và đó là nó. Người dùng hiện đang đăng ký và nhờ xác nhận Laravel và ra khỏi xác thực hộp, name, email, password, và password_confirmationlĩnh vực được yêu cầu, và các thông tin phản hồi được xử lý tự động. Kiểm tra validator()phương thức bên trong RegisterControllerđể xem các quy tắc được thực hiện như thế nào.

Đây là những gì chúng ta nhận được khi đạt điểm cuối đó:

$ curl -X POST http://localhost:8000/api/register \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"name": "John", "email": "john.doe@toptal.com", "password": "toptal123", "password_confirmation": "toptal123"}' { "data": { "api_token":"0syHnl0Y9jOIfszq11EC2CBQwCfObmvscrZYo5o2ilZPnohvndH797nDNyAT", "created_at": "2017-06-20 21:17:15", "email": "john.doe@toptal.com", "id": 51, "name": "John", "updated_at": "2017-06-20 21:17:15" } }​

Tạo một điểm cuối đăng nhập

Giống như điểm cuối đăng ký, chúng tôi có thể chỉnh sửa LoginController(trong Auththư mục) để hỗ trợ xác thực API. Các loginphương pháp của AuthenticatesUsersđặc điểm có thể được ghi đè để hỗ trợ API của chúng tôi:

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); }

Và chúng ta có thể liên kết nó trên tập tin tuyến đường:

Route::post('login', 'Auth\LoginController@login');

Bây giờ, giả sử các máy gieo hạt đã được chạy, đây là những gì chúng tôi nhận được khi gửi POSTyêu cầu đến tuyến đường đó:

$ curl -X POST localhost:8000/api/login \ -H "Accept: application/json" \ -H "Content-type: application/json" \ -d "{\"email\": \"admin@test.com\", \"password\": \"toptal\" }" { "data": { "id":1, "name":"Administrator", "email":"admin@test.com", "created_at":"2017-04-25 01:05:34", "updated_at":"2017-04-25 02:50:40", "api_token":"Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw" } }

Để gửi mã thông báo trong yêu cầu, bạn có thể thực hiện bằng cách gửi một thuộc tính api_tokentrong tải trọng hoặc dưới dạng mã thông báo mang trong tiêu đề yêu cầu ở dạng Authorization: Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw.

Đăng xuất

Với chiến lược hiện tại của chúng tôi, nếu mã thông báo bị sai hoặc bị thiếu, người dùng sẽ nhận được phản hồi không được xác thực (chúng tôi sẽ triển khai trong phần tiếp theo). Vì vậy, đối với điểm cuối đăng xuất đơn giản, chúng tôi sẽ gửi mã thông báo và nó sẽ bị xóa trên cơ sở dữ liệu.

routes/api.php:

Route::post('logout', 'Auth\LoginController@logout');

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); }

Sử dụng chiến lược này, bất kỳ mã thông báo nào mà người dùng có sẽ không hợp lệ và API sẽ từ chối quyền truy cập (sử dụng phần mềm trung gian, như được giải thích trong phần tiếp theo). Điều này cần được phối hợp với giao diện người dùng để tránh người dùng vẫn đăng nhập mà không có quyền truy cập vào bất kỳ nội dung nào.

Sử dụng Middleware để hạn chế quyền truy cập

Với phần api_tokenđược tạo, chúng ta có thể chuyển đổi phần mềm trung gian xác thực trong tệp tuyến đường:

Route::middleware('auth:api') ->get('/user', function (Request $request) { return $request->user(); });

Chúng tôi có thể truy cập người dùng hiện tại bằng $request->user()phương thức hoặc thông qua mặt tiền 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

Và chúng tôi nhận được một kết quả như thế này:

Một Stacktrace không hợp lệ

Điều này là do chúng ta cần chỉnh sửa unauthenticatedphương thức hiện tại trên lớp Handler của chúng ta. Phiên bản hiện tại chỉ trả về JSON nếu yêu cầu có Accept: application/jsontiêu đề, vì vậy hãy thay đổi nó:

protected function unauthenticated($request, AuthenticationException $exception) { return response()->json(['error' => 'Unauthenticated'], 401); }

Với bản sửa lỗi đó, chúng ta có thể quay lại các điểm cuối của bài viết để bọc chúng trong auth:apiphần mềm trung gian. Chúng tôi có thể làm điều đó bằng cách sử dụng các nhóm tuyến đường:

Route::group(['middleware' => 'auth:api'], function() { Route::get('articles', 'ArticleController@index'); Route::get('articles/{article}', 'ArticleController@show'); Route::post('articles', 'ArticleController@store'); Route::put('articles/{article}', 'ArticleController@update'); Route::delete('articles/{article}', 'ArticleController@delete'); });

Bằng cách này, chúng tôi không phải đặt phần mềm trung gian cho mỗi tuyến. Nó không tiết kiệm nhiều thời gian ngay bây giờ, nhưng khi dự án phát triển, nó giúp giữ các tuyến đường KHÔ.

Kiểm tra các điểm cuối của chúng tôi

Laravel bao gồm tích hợp với PHPUnit ngoài hộp với một phpunit.xmlthiết lập đã được thiết lập. Khung này cũng cung cấp cho chúng tôi một số trợ giúp và các xác nhận bổ sung giúp cuộc sống của chúng tôi dễ dàng hơn nhiều, đặc biệt là để thử nghiệm API.

Có một số công cụ bên ngoài bạn có thể sử dụng để kiểm tra API của mình; tuy nhiên, kiểm tra bên trong Laravel là một giải pháp thay thế tốt hơn nhiều, chúng ta có thể có tất cả các lợi ích của việc kiểm tra cấu trúc và kết quả API trong khi vẫn giữ toàn quyền kiểm soát cơ sở dữ liệu. Ví dụ, đối với điểm cuối danh sách, chúng tôi có thể điều hành một vài nhà máy và khẳng định phản hồi có chứa các tài nguyên đó.

Để bắt đầu, chúng tôi cần điều chỉnh một vài cài đặt để sử dụng cơ sở dữ liệu SQLite trong bộ nhớ. Sử dụng điều đó sẽ làm cho các thử nghiệm của chúng tôi chạy nhanh như chớp, nhưng sự đánh đổi là một số lệnh di chuyển (chẳng hạn như các ràng buộc) sẽ không hoạt động đúng trong thiết lập cụ thể đó. Tôi khuyên bạn nên tránh xa SQLite khi kiểm tra khi bạn bắt đầu gặp lỗi di chuyển hoặc nếu bạn thích một bộ kiểm tra mạnh hơn thay vì chạy hiệu suất.

Chúng tôi cũng sẽ chạy di chuyển trước mỗi bài kiểm tra. Thiết lập này sẽ cho phép chúng tôi xây dựng cơ sở dữ liệu cho từng thử nghiệm và sau đó hủy nó, tránh mọi loại phụ thuộc giữa các thử nghiệm.

Trong config/database.phptệp của chúng tôi , chúng tôi sẽ cần thiết lập databasetrường trong sqlitecấu hình để :memory::

... 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], ... ]

Sau đó kích hoạt SQLite phpunit.xmlbằng cách thêm biến môi trường DB_CONNECTION:

<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>

Ngoài ra, tất cả những gì còn lại là cấu hình TestCaselớp cơ sở của chúng tôi để sử dụng di chuyển và khởi tạo cơ sở dữ liệu trước mỗi thử nghiệm. Để làm như vậy, chúng ta cần thêm DatabaseMigrationsđặc điểm và sau đó thêm một Artisancuộc gọi vào setUp()phương thức của chúng tôi . Đây là lớp sau những thay đổi:

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'); } }

Một điều cuối cùng mà tôi muốn làm là thêm lệnh kiểm tra vào composer.json:

"scripts": { "test" : [ "vendor/bin/phpunit" ], ... },

Lệnh kiểm tra sẽ có sẵn như thế này:

$ composer test

Thiết lập các nhà máy cho các thử nghiệm của chúng tôi

Các nhà máy sẽ cho phép chúng tôi nhanh chóng tạo các đối tượng với dữ liệu phù hợp để thử nghiệm. Chúng nằm trong database/factoriesthư mục. Laravel ra khỏi hộp với một nhà máy cho Userlớp, vì vậy hãy thêm một cái cho Articlelớp:

$factory->define(App\Article::class, function (Faker\Generator $faker) { return [ 'title' => $faker->sentence, 'body' => $faker->paragraph, ]; });

Các Faker thư viện đã được tiêm để giúp chúng tôi tạo ra định dạng đúng của dữ liệu ngẫu nhiên cho các mô hình của chúng tôi.

Thử nghiệm đầu tiên của chúng tôi

Chúng ta có thể sử dụng các phương thức khẳng định của Laravel để dễ dàng đạt điểm cuối và đánh giá phản ứng của nó. Hãy tạo thử nghiệm đầu tiên của chúng tôi, thử nghiệm đăng nhập, sử dụng lệnh sau:

$ php artisan make:test Feature/LoginTest

Và đây là bài kiểm tra của chúng tôi:

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' => 'testlogin@user.com', 'password' => bcrypt('toptal123'), ]); $payload = ['email' => 'testlogin@user.com', 'password' => 'toptal123']; $this->json('POST', 'api/login', $payload) ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'email', 'created_at', 'updated_at', 'api_token', ], ]); } }

Những phương pháp này kiểm tra một vài trường hợp đơn giản. Các json()phương pháp chạm endpoint và người kia khẳng định là khá tự giải thích. Một chi tiết về assertJson(): phương thức này chuyển đổi phản hồi thành một mảng tìm kiếm đối số, vì vậy thứ tự là quan trọng. Bạn có thể xâu chuỗi nhiều assertJson()cuộc gọi trong trường hợp đó.

Bây giờ, hãy tạo bài kiểm tra điểm cuối đăng ký và viết một cặp cho điểm cuối đó:

$ php artisan make:test RegisterTest class RegisterTest extends TestCase { public function testsRegistersSuccessfully() { $payload = [ 'name' => 'John', 'email' => 'john@toptal.com', 'password' => 'toptal123', 'password_confirmation' => 'toptal123', ]; $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' => 'john@toptal.com', 'password' => 'toptal123', ]; $this->json('post', '/api/register', $payload) ->assertStatus(422) ->assertJson([ 'password' => ['The password confirmation does not match.'], ]); } }

Và cuối cùng, điểm cuối đăng xuất:

$ php artisan make:test LogoutTest class LogoutTest extends TestCase { public function testUserIsLoggedOutProperly() { $user = factory(User::class)->create(['email' => 'user@test.com']); $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' => 'user@test.com']); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; // Simulating logout $user->api_token = null; $user->save(); $this->json('get', '/api/articles', [], $headers)->assertStatus(401); } }

Điều quan trọng cần lưu ý là, trong quá trình thử nghiệm, ứng dụng Laravel không được khởi tạo lại theo yêu cầu mới. Điều đó có nghĩa là khi chúng ta nhấn phần mềm trung gian xác thực, nó sẽ lưu người dùng hiện tại bên trong TokenGuard thể hiện để tránh đánh lại cơ sở dữ liệu. Một lựa chọn khôn ngoan, tuy nhiên, trong trường hợp này, điều đó có nghĩa là chúng ta phải chia bài kiểm tra đăng xuất thành hai, để tránh mọi vấn đề với người dùng được lưu trong bộ nhớ cache trước đó.

Việc kiểm tra các điểm cuối của Điều cũng rất đơn giản:

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'], ]); } }

Bước tiếp theo

Thats tất cả để có nó. Chắc chắn có cơ hội để cải thiện, bạn có thể triển khai OAuth2 với gói Passport , tích hợp lớp chuyển đổi và phân trang (tôi khuyên dùng Fractal), danh sách này có trên nhưng tôi muốn tìm hiểu những điều cơ bản về tạo và thử nghiệm API trong Laravel mà không cần gói bên ngoài.

Laravel chắc chắn đã cải thiện trải nghiệm của tôi với PHP và việc dễ dàng thử nghiệm với nó đã củng cố mối quan tâm của tôi đối với khung công tác. Nó không hoàn hảo, nhưng nó đủ linh hoạt để cho phép bạn giải quyết các vấn đề của nó.

Nếu bạn đang thiết kế API công khai, hãy xem 5 Quy tắc vàng cho Thiết kế API web tuyệt vời .

Từ khóa » Trong Laravel Các Phương Thức Post Put Patch Delete Cần Thêm Hidden Input Csrf_token để Làm Gì