آموزش restful api در لاراول
لاراول یک فریمورک PHP است که با در نظر گرفتن حداکثر بهره وری توسط توسعه دهندگان توسعه یافته است. این فریم ورک که توسط تیلور اتول نوشته و نگهداری میشود، طوری طراحی شده که ساختار کلی برنامه نیازی به کار چندانی نداشته باشد. در این میان توسعه دهنده بیشتر به جزئیات برنامه میپردازد و این امر در زمان توسعهدهنده صرفهجویی میکند.
هدف این چارچوب کمک به تکامل وب است و در حال حاضر چندین ویژگی و ایده جدید را در دنیای توسعه وب مانند صف های شغلی، احراز هویت API خارج از جعبه، ارتباطات بلادرنگ، و موارد دیگر از آن استفاده میکنند. در مقاله آموزش restful api در لاراول، راههایی را که میتوان با استفاده از لاراول یک API قوی ساخت و آزمایش کرد، بررسی خواهیم کرد. ما از Laravel 5.4 استفاده خواهیم کرد و همه کدها برای مرجع در GitHub در دسترس هستند.
با ظهور فریمورکهای توسعه موبایل و جاوا اسکریپت، استفاده از یک API RESTful بهترین گزینه برای ایجاد یک رابط واحد بین دادههای شما و مشتری شما است.
API های RESTful
ابتدا باید بفهمیم که دقیقا یک RESTful API چیست و چرا در نظر گرفته می شود. به زبان بسیار ساده REST مخفف REpresentational State Transfer است و یک سبک معماری برای ارتباط شبکه بین برنامهها است که برای تعامل به یک پروتکل بدون حالت (معمولا HTTP) متکی است.
متدهای HTTP
در API های RESTful، از متدهای HTTP به عنوان عملیات استفاده می کنیم و نقاط پایانی منابعی هستند که بر اساس آنها عمل می شود. ما از این متدهای HTTP بر اساس معانی آن ها استفاده خواهیم کرد که در زیر آورده شده اند:
- GET: retrieve resources
- POST: create resources
- PUT: update resources
- DELETE: delete resources
متدهای http در restful api
متد PUT در مقابل POST
API های RESTful بسیار مورد مناقشه هستند و نظرات زیادی در مورد اینکه آیا بهتر است با POST، PATCH یا PUT به روز شود یا اینکه عمل ایجاد به فعل PUT واگذار شود، وجود دارد. در این مقاله از PUT برای عمل بهروزرسانی استفاده میکنیم، زیرا طبق HTTP RFC، PUT به معنای ایجاد/بهروزرسانی یک منبع در یک مکان خاص است. یکی دیگر از نیازهای فعل PUT، idempotence است، که در این مورد اساساً به این معنی است که می توانید آن درخواست را 1، 2 یا 1000 بار ارسال کنید و نتیجه یکسان خواهد بود:
منابع
در این مقاله آموزش restful api در لاراول منابع کاربران و مقالات هدف عملیات ما هستند و آن ها نقاط پایانی خود را دار ند.
/articles
/users
ما می توانیم منابعی را در بیش از یک مدل داده (یا اصلاً در پایگاه داده ارائه نشده است) و مدل هایی کاملاً خارج از محدودیت برای کاربر داشته باشیم. در پایان، ما باید تصمیم بگیری که چگونه منابع و مدلها را به گونهای طراحی کنیم که متناسب با برنامه ما باشد.
نکاتی در مورد سازگاری REST
بزرگترین مزیت استفاده از مجموعه ای از قراردادها مانند REST این است که مصرف و توسعه API ما بسیار آسان تر خواهد بود. طراحی برخی از نقاط پایانی بسیار ساده هستند و در نتیجه، استفاده و نگهداری API ما بسیار آسانتر خواهد بود.
آموزش کار با rest در لاراول و نحوه راه اندازی آن
مانند همه فریمورکهای مدرن PHP، برای نصب و مدیریت وابستگیهایمان به Composer نیاز داریم. ، کامپوزر یک ابزار کامندلاین است که در آن با استفاده از یکسری دستورات از پیش تعریف شده میتوان به مدیریت منابع خارجی پرداخت.
بعد از اینکه دستورالعمل های دانلود را اجرا کردیم و آن به متغیر محیط مسیر خود اضافه کردیم، لاراول را با استفاده از دستور نصب میکنیم:
$ composer global require laravel/installer
پس از اتمام نصب، می توانیم یک برنامه جدید مانند زیر بسازیم:
$ laravel new myapp
برای دستور بالا، باید ~/composer/vendor/bin را در $PATH خود داشته باشیم. همچنین میتوانیم با استفاده از Composer یک پروژه جدید ایجاد کنیم:
$ composer create-project --prefer-dist laravel/laravel myapp
با نصب لاراول، باید بتوانیم سرور را راه اندازی کنیم و آزمایش کنیم که آیا سرور ما کار میکند یا خیر:
$ php artisan serve
سرور توسعه لاراول راه اندازی شد و در آدرس زیر در دسترس است: http://127.0.0.1:8000. وقتی localhost:8000 را در مرورگر خود باز می کنیم، باید این صفحه نمونه را ببینیم.
میگریشن و مدل ها در آموزش restful api در لاراول
Migration یک مفهوم بسیار مفید برای مدیریت انواع تغییرات در پایگاه داده است. با استفاده از Migration می توانید به راحتی پایگاه داده جدید بسازید و یا پایگاه داده موجود را به روز رسانی و حتی حذف کنید.
قبل از اینکه واقعاً اولین کد میگریشن خود را بنویسیم، باید مطمئن شویم که یک پایگاه داده برای این برنامه ایجاد کرده ایم و اعتبار آن را به فایل env واقع در ریشه پروژه اضافه کنیم.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
بیایید با اولین مدل و میگریشن خود مقاله شروع کنیم. مقاله باید دارای عنوان و قسمت متن و همچنین تاریخ ایجاد باشد. لاراول چندین دستور را از طریق Artisan – ابزار خط فرمان لاراول، ارائه می دهد که با تولید فایل ها و قرار دادن آنها در پوشه های صحیح به ما کمک می کند این کار را انجام دهیم. برای ایجاد مدل 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');
}
}
تشریح کد بالا از آموزش restful api در لاراول:
:up() & down()- به ترتیب زمانی اجرا می شوند که ما میگریشن و rollback انجام دهیم.
$table->increments(‘id’)- یک عدد صحیح افزایش خودکار را با نام id تنظیم می کند.
$table->timestamps() – مهرهای زمانی را برای ما تنظیم میکند—created_at و updated_at، اما نگران تنظیم پیشفرض نباشید، لاراول مراقب است که این فیلدها را در صورت نیاز بهروزرسانی کند.
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() یک TEXTequivalent ایجاد می کند. حال با انجام این کار، باید جلو برویم و میگریشن کنیم:
لاراول پیکربندی خارج از جعبه با دو میگریشن ارائه می شود، create_users_table و create_password_resets_table. ما از جدول password_resets استفاده نخواهیم کرد، اما آماده کردن جدول create_users_table برای ما خواهد بود.
حالا بیایید به مدل خود برگردیم و آن ویژگی ها را به فیلد fillable اضافه کنیم تا بتوانیم از آنها در مدل های Article::create و Article::update خود استفاده کنیم: این کار در آموزش restful api در لاراول به صورت زیر قابل انجام است.
class Article extends Model
{
protected $fillable = ['title', 'body'];
نکته: فیلدهای داخل ویژگی $fillable را میتوان با استفاده از متدهای ()create() و update() Eloquent اختصاص داد. همچنین میتوانید از ویژگی $guarded برای اجازه دادن به همه ویژگیها به جز چند مورد استفاده کنید.
بذریابی پایگاه داده یا پرکردن پایگاه داده با داده های آزمایشی
بذریابی پایگاه داده فرآیند پر کردن پایگاه داده ما با داده های ساختگی است که می توانیم از آنها برای آزمایش استفاده کنیم. لاراول با Faker عرضه میشود، یک کتابخانه عالی برای ایجاد فرمت صحیح دادههای ساختگی برای ما. پس بیایید اولین Seed خود را ایجاد کنیم:
$ php artisan make:seeder ArticlesTableSeeder
Seedها در فهرست /database/seeds قرار خواهند گرفت. در این ما Seed را برای چند مقاله تنظیم میکنیم:
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
بیایید این فرآیند را برای ایجاد یک Seed کاربران تکرار کنیم:
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,
]);
}
}
}
میتوانیم با افزودن seders خود به کلاس اصلی DatabaseSeeder در داخل پوشه database/seeds کار را آسانتر کنیم:
class DatabaseSeeder extends Seeder
class DatabaseSeeder extends Seeder
{
public function run()
{
$this->call(ArticlesTableSeeder::class);
$this->call(UsersTableSeeder::class);
}
}
به این ترتیب، ما می توانیم به سادگی php artisan db:seed را اجرا کنیم و تمام کلاس های فراخوانی شده را در متد run اجرا کنیم.
مسیرها و کنترلرها یا Routes and Controllers
در این قسمت از آموزش api لاراول، حال نوبت این است که نقاط پایانی اصلی را برای برنامه خود ایجاد کنیم: ایجاد، بازیابی لیست، بازیابی یک مورد، به روز رسانی و حذف همه این ها را میتوانیم به سادگی در فایل 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/ پیشوند می شوند و میان افزار throttling API به طور خودکار روی این مسیرها اعمال می شود (اگر می خواهید پیشوند را حذف کنید، می توانید کلاس RouteServiceProvider را در /app/Providers/RouteServiceProvider.php ویرایش کنید).
حالا بیایید این کد را به 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', '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');
ما می توانیم با استفاده از اتصال مدل مسیر ضمنی، نقاط پایانی را بهبود ببخشیم. به این ترتیب، لاراول نمونه Article را در متدهای ما تزریق می کند و اگر 404 پیدا نشد، به طور خودکار آن را برمی گرداند. ما باید در فایل مسیرها و کنترلر تغییراتی ایجاد کنیم:
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);
}
}
نکته ای در مورد کدهای وضعیت HTTP و فرمت پاسخ
ما پاسخ:
response()->json()
را به نقاط پایانی خود اضافه کرده ایم. این به ما امکان میدهد صریحاً دادههای JSON را برگردانیم و همچنین یک کد HTTP ارسال کنیم که توسط مشتری قابل تجزیه است. رایج ترین کدهایی که برمی گرداند عبارتند از:
- 200: کد موفقیت استاندارد و گزینه پیش فرض.
- 201: شی ایجاد شد.
- 204: بدون محتوا. زمانی که یک عمل با موفقیت اجرا شد، اما محتوایی برای بازگشت وجود ندارد.
- 206: مطالب جزئی. زمانی مفید است که باید فهرست صفحه بندی شده منابع را برگردانید.
- 400: درخواست بد. گزینه استاندارد برای درخواست هایی که موفق به تایید اعتبار نمی شوند.
- 401: غیر مجاز. کاربر باید احراز هویت شود.
- 403: ممنوع. کاربر احراز هویت شده است، اما مجوز انجام یک عمل را ندارد.
- 404 پیدا نشد. هنگامی که منبع پیدا نشد، این به طور خودکار توسط لاراول برگردانده می شود.
- 500: خطای سرور داخلی.
- 503 : خدمات در دسترس نیست.
ارسال یک پاسخ صحیح 404 سر لاورال
اگر سعی کردید یک منبع ناموجود را واکشی کنید، یک استثنا برای شما ایجاد می شود و کل stacktrace را دریافت خواهید کرد، مانند این:
میتوانیم با ویرایش کلاس کنترلکننده استثنای خود، واقع در 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"
}
اگر از لاراول برای سرویس دادن به صفحات دیگر استفاده می کنید، باید کد را برای کار با سربرگ 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 نیاز دارند.
احراز هویت
راههای زیادی برای پیادهسازی API Authentication در لاراول وجود دارد (یکی از آنها Passport است، روشی عالی برای پیادهسازی OAuth2)، اما در این مقاله آموزش restful api در لاراول، ما یک رویکرد بسیار سادهتر را در پیش خواهیم گرفت.
برای شروع، باید یک فیلد 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) برای برگرداندن پاسخ صحیح هنگام ثبت نام استفاده خواهیم کرد. لاراول با احراز هویت خارج از جعبه ارائه می شود، اما ما باید آن را کمی تغییر دهیم تا پاسخ مورد نظرمان را برگردانیم.
کنترل کننده از ویژگی RegistersUsers برای پیاده سازی ثبت استفاده می کند. در اینجا نحوه کار آن آمده است:
public function register(Request $request)
{
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
$this->guard()->login($user);
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
ما فقط باید متد ()registered را در RegisterController خود پیاده سازی کنیم. متد request $ و user $ را دریافت می کند، بنابراین این واقعاً تمام چیزی است که ما می خواهیم. در اینجا روش باید در داخل کنترلر به نظر برسد.
protected function registered(Request $request, $user)
{
$user->generateToken();
return response()->json(['data' => $user->toArray()], 201);
}
میتوانیم آن را در فایل مسیرها پیوند دهیم:
Route::post('register', 'Auth\RegisterController@register');
در بخش بالا، از روشی در مدل User برای تولید توکن استفاده کردیم. این روش زمانی مفید است که ما فقط یک راه واحد برای تولید توکن ها داریم. روش زیر را به مدل کاربر خود اضافه کنید:
class User extends Authenticatable
{
...
public function generateToken()
{
$this->api_token = str_random(60);
$this->save();
return $this->api_token;
}
}
کاربر اکنون ثبت نام شده است و به لطف اعتبار لاراول و احراز هویت خارج از جعبه، فیلدهای نام، ایمیل، رمز عبور و تایید_گذرواژه مورد نیاز است و بازخورد به صورت خودکار انجام می شود. متد ()validator را در داخل RegisterController بررسی میکنیم تا ببینیم قوانین چگونه پیادهسازی میشوند.
$ 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"
}
}
ایجاد نقطه پایانی ورود یا Login Endpoint
درست مانند نقطه پایانی ثبت نام، ما می توانیم LoginController (در پوشه Auth) را ویرایش کنیم تا از احراز هویت API خود پشتیبانی کنیم. متد login ویژگی 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\LoginController@login');
حال، با فرض اینکه seeder ها اجرا شده اند، وقتی درخواست POST را به آن مسیر ارسال می کنیم، این چیزی است که به دست می آوریم:
$ 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"
}
}
برای ارسال توکن در یک درخواست، می توانیم این کار را با ارسال یک ویژگی api_token در payload یا به عنوان یک توکن حامل در هدرهای درخواست به شکل زیر انجام دهیم.
Authorization: Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw
خروج از سیستم یا Logging Out
با استراتژی فعلی ما، اگر توکن اشتباه یا گم شده باشد، کاربر باید یک پاسخ تایید نشده دریافت کند. بنابراین برای یک نقطه پایانی خروج ساده، توکن را ارسال می کنیم و به این ترتیب در پایگاه داده حذف می شود.
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);
}
با استفاده از این استراتژی، هر توکنی که کاربر داشته باشد نامعتبر خواهد بود و API دسترسی را رد می کند (با استفاده از میان افزارها، همانطور که در بخش بعدی توضیح داده شد). این باید با front-end هماهنگ شود تا از ورود کاربر بدون دسترسی به هیچ محتوایی جلوگیری شود.
استفاده از Middlewares برای محدود کردن دسترسی
با ایجاد api_token، میتوانیم میانافزار احراز هویت را در فایل routes تغییر دهیم:
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 خود ویرایش کنیم. نسخه فعلی فقط در صورتی JSON را برمیگرداند که درخواست دارای هدر Accept: application/json باشد، بنابراین اجازه دهید آن را تغییر دهیم:
protected function unauthenticated($request, AuthenticationException $exception)
{
return response()->json(['error' => 'Unauthenticated'], 401);
}
با رفع این مشکل، میتوانیم به نقاط پایانی مقاله برگردیم تا آنها را در میانافزار auth:api قرار دهیم. ما می توانیم این کار را در این آموزش restful api در لاراول با استفاده از گروه های مسیر انجام دهیم:
protected function unauthenticated($request, AuthenticationException $exception)
{
return response()->json(['error' => 'Unauthenticated'], 401);
}
به این ترتیب ما مجبور نیستیم میان افزار را برای هر یک از مسیرها تنظیم کنیم.
تست نقاط پایانی در آموزش restful api در لاراول
لاراول شامل ادغام با PHPUnit خارج از جعبه با phpunit.xml است که قبلاً راه اندازی شده است. این چارچوب همچنین چندین کمک کننده و ابزار اضافی را در اختیار ما قرار می دهد که کار ما را بسیار آسان تر می کند، به خصوص برای آزمایش API ها.
تعدادی ابزار خارجی وجود دارد که می توانیم برای آزمایش API خود از آنها استفاده کنیم. با این حال، آزمایش در لاراول جایگزین بسیار بهتری است و ما میتوانیم تمام مزایای آزمایش ساختار و نتایج API را داشته باشیم و در عین حال کنترل کامل پایگاه داده را حفظ کنیم.
برای شروع، ما باید چند تنظیمات را برای استفاده از پایگاه داده SQLite در حافظه تغییر دهیم. استفاده از آن باعث میشود که تستهای ما به سرعت اجرا شوند، اما نتیجه این است که برخی از دستورات میگریشن (مثلاً محدودیتها) در آن تنظیمات خاص به درستی کار نمیکنند.
ما قبل از هر آزمون نیز میگریشن ها را اجرا خواهیم کرد. این تنظیمات به ما این امکان را می دهد که برای هر تست پایگاه داده بسازیم و سپس آن را از بین ببریم و از هر نوع وابستگی بین تست ها اجتناب کنیم.
در فایل config/database.php ما باید فیلد پایگاه داده را در پیکربندی sqlite به صورت :memory تنظیم کنیم:
...
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
...
]
سپس SQLite را در phpunit.xml با افزودن متغیر محیطی 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>
با حذف این موضوع، تنها چیزی که باقی میماند این است که کلاس TestCase پایه خود را پیکربندی کنیم تا از میگریشنها استفاده کرده و پایگاه داده را قبل از هر تست بکار ببریم. برای انجام این کار در آموزش restful api در لاراول ، باید ویژگی DatabaseMigrations را اضافه کنیم و سپس یک فراخوان Artisan را به متد setUp() خود اضافه کنیم. کلاس بعد از تغییرات به شکل زیر است:
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');
}
}
آخرین کاری که باید انجام دهیم این است که دستور test را به composer.json اضافه کنیم:
"scripts": {
"test" : [
"vendor/bin/phpunit"
],
...
},
دستور تست به صورت زیر در دسترس خواهد بود:
$ composer test
راه اندازی Factories برای آزمایش های ما
Factories به ما این امکان را می دهند که به سرعت اشیایی با داده های مناسب برای آزمایش ایجاد کنیم. آنها در پوشه database/factories قرار دارند. لاراول با یک Factories برای کلاس User از جعبه خارج می شود. بنابراین بیایید یکی برای کلاس Article اضافه کنیم:
$factory->define(App\Article::class, function (Faker\Generator $faker) {
return [
'title' => $faker->sentence,
'body' => $faker->paragraph,
];
});
کتابخانه Faker قبلاً تزریق شده است تا به ما کمک کند قالب صحیح داده های تصادفی را برای مدل های خود ایجاد کنیم.
اولین تست های ما
ما برای اولین تست ورود میتوانیم از روشهای ادعایی لاراول برای رسیدن به نقطه پایانی و ارزیابی پاسخ آن استفاده کنیم. بیایید اولین آزمایش خود، تست ورود، را با استفاده از دستور زیر ایجاد کنیم:
$ php artisan make:test Feature/LoginTest
و این هم تست ما در آموزش restful api در لاراول:
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',
],
]);
}
}
این روش ها چند مورد ساده را آزمایش می کنند. متد json به نقطه پایانی برخورد می کند. assertJson روش پاسخ را به آرایه ای که آرگومان را جستجو می کند، تبدیل می کند، بنابراین ترتیب در آن مهم است.
اکنون، بیایید آزمون نقطه پایانی ثبت نام را ایجاد کنیم:
$ 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.'],
]);
}
}
و در آخر، آزمایش نقطه پایان خروج از سیستم در آموزش api در لاراول :
$ 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);
}
}
توجه به این نکته مهم است که در طول آزمایش، برنامه لاراول مجدداً در یک درخواست جدید نمونه سازی نمی شود. به این معنی که وقتی میان افزار احراز هویت را می زنیم، کاربر فعلی را در داخل نمونه TokenGuard ذخیره می کند تا از درخواست مجدد به پایگاه داده جلوگیری کند.
آزمایش نقاط پایانی :
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'],
]);
}
}
منبع:
https://www.toptal.com/laravel/restful-laravel-api-tutorial