تطوير واجهات REST API
بناء واجهة برمجة تطبيقات RESTful كاملة - الجزء 3
بناء واجهة برمجة تطبيقات RESTful كاملة - الجزء 3
في هذا الجزء الأخير، سنضيف اختبارًا شاملاً، وننشئ توثيق API تفاعلي باستخدام Swagger/OpenAPI، وننشر إلى الإنتاج، ونراجع جميع أفضل الممارسات التي تعلمناها خلال هذه الدورة.
اختبار واجهة برمجة التطبيقات باستخدام PHPUnit
الاختبار أمر بالغ الأهمية لموثوقية واجهة برمجة التطبيقات. لنكتب اختبارات شاملة:
tests/Feature/Api/AuthTest.php:
<?php
namespace Tests\Feature\Api;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AuthTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function user_can_register()
{
$response = $this->postJson('/api/v1/auth/register', [
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'password123',
'password_confirmation' => 'password123',
]);
$response->assertStatus(201)
->assertJsonStructure([
'access_token',
'token_type',
'expires_in',
'user' => ['id', 'name', 'email'],
]);
$this->assertDatabaseHas('users', [
'email' => 'john@example.com',
]);
}
/** @test */
public function user_cannot_register_with_invalid_data()
{
$response = $this->postJson('/api/v1/auth/register', [
'name' => '',
'email' => 'invalid-email',
'password' => 'short',
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['name', 'email', 'password']);
}
/** @test */
public function user_can_login_with_valid_credentials()
{
$user = User::factory()->create([
'email' => 'test@example.com',
'password' => bcrypt('password123'),
]);
$response = $this->postJson('/api/v1/auth/login', [
'email' => 'test@example.com',
'password' => 'password123',
]);
$response->assertStatus(200)
->assertJsonStructure([
'access_token',
'token_type',
'expires_in',
'user',
]);
}
/** @test */
public function user_cannot_login_with_invalid_credentials()
{
$response = $this->postJson('/api/v1/auth/login', [
'email' => 'test@example.com',
'password' => 'wrong-password',
]);
$response->assertStatus(401)
->assertJson(['message' => 'بيانات اعتماد غير صالحة']);
}
/** @test */
public function authenticated_user_can_get_profile()
{
$user = User::factory()->create();
$response = $this->actingAs($user, 'api')
->getJson('/api/v1/auth/me');
$response->assertStatus(200)
->assertJson([
'data' => [
'id' => $user->id,
'email' => $user->email,
],
]);
}
/** @test */
public function unauthenticated_user_cannot_access_protected_routes()
{
$response = $this->getJson('/api/v1/auth/me');
$response->assertStatus(401);
}
}
tests/Feature/Api/ProductTest.php:
<?php
namespace Tests\Feature\Api;
use App\Models\Category;
use App\Models\Product;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class ProductTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
Storage::fake('public');
}
/** @test */
public function guest_can_view_products()
{
Product::factory()->count(5)->create();
$response = $this->getJson('/api/v1/products');
$response->assertStatus(200)
->assertJsonStructure([
'data' => [
'*' => ['id', 'name', 'price', 'slug'],
],
'links',
'meta',
]);
}
/** @test */
public function guest_can_view_single_product()
{
$product = Product::factory()->create();
$response = $this->getJson("/api/v1/products/{$product->id}");
$response->assertStatus(200)
->assertJson([
'data' => [
'id' => $product->id,
'name' => $product->name,
],
]);
}
/** @test */
public function admin_can_create_product()
{
$admin = User::factory()->create(['role' => 'admin']);
$category = Category::factory()->create();
$response = $this->actingAs($admin, 'api')
->postJson('/api/v1/products', [
'category_id' => $category->id,
'name' => 'منتج تجريبي',
'slug' => 'test-product',
'description' => 'وصف تجريبي',
'price' => 99.99,
'sku' => 'TEST-001',
'stock_quantity' => 10,
]);
$response->assertStatus(201)
->assertJson([
'data' => ['name' => 'منتج تجريبي'],
]);
$this->assertDatabaseHas('products', [
'slug' => 'test-product',
]);
}
/** @test */
public function admin_can_upload_product_images()
{
$admin = User::factory()->create(['role' => 'admin']);
$category = Category::factory()->create();
$response = $this->actingAs($admin, 'api')
->postJson('/api/v1/products', [
'category_id' => $category->id,
'name' => 'منتج تجريبي',
'slug' => 'test-product',
'price' => 99.99,
'sku' => 'TEST-001',
'stock_quantity' => 10,
'images' => [
UploadedFile::fake()->image('product1.jpg'),
UploadedFile::fake()->image('product2.jpg'),
],
]);
$response->assertStatus(201);
$product = Product::where('slug', 'test-product')->first();
$this->assertCount(2, $product->images);
}
/** @test */
public function regular_user_cannot_create_product()
{
$user = User::factory()->create(['role' => 'user']);
$category = Category::factory()->create();
$response = $this->actingAs($user, 'api')
->postJson('/api/v1/products', [
'category_id' => $category->id,
'name' => 'منتج تجريبي',
'slug' => 'test-product',
'price' => 99.99,
'sku' => 'TEST-001',
'stock_quantity' => 10,
]);
$response->assertStatus(403);
}
/** @test */
public function products_can_be_filtered_by_category()
{
$category1 = Category::factory()->create();
$category2 = Category::factory()->create();
Product::factory()->count(3)->create(['category_id' => $category1->id]);
Product::factory()->count(2)->create(['category_id' => $category2->id]);
$response = $this->getJson("/api/v1/products?category_id={$category1->id}");
$response->assertStatus(200)
->assertJsonCount(3, 'data');
}
/** @test */
public function products_can_be_searched()
{
Product::factory()->create(['name' => 'لابتوب أزرق']);
Product::factory()->create(['name' => 'هاتف أحمر']);
Product::factory()->create(['name' => 'ماوس أزرق']);
$response = $this->getJson('/api/v1/products?search=أزرق');
$response->assertStatus(200)
->assertJsonCount(2, 'data');
}
}
أفضل ممارسات الاختبار:
- استخدم RefreshDatabase trait لإعادة تعيين قاعدة البيانات بين الاختبارات
- اختبر سيناريوهات النجاح والفشل
- اختبر المصادقة والتفويض
- اختبر قواعد التحقق
- اختبر الفلاتر والترتيب والترقيم
- استهدف تغطية كود تزيد عن 80%
توثيق واجهة برمجة التطبيقات باستخدام Swagger/OpenAPI
إنشاء توثيق تفاعلي لواجهة برمجة التطبيقات:
تثبيت L5-Swagger:
composer require darkaonline/l5-swagger
php artisan vendor:publish --provider="L5Swagger\L5SwaggerServiceProvider"
php artisan l5-swagger:generate
app/Http/Controllers/Api/Controller.php - تعليقات OpenAPI:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller as BaseController;
/**
* @OA\Info(
* title="واجهة برمجة تطبيقات التجارة الإلكترونية",
* version="1.0.0",
* description="واجهة برمجة تطبيقات RESTful لمنصة التجارة الإلكترونية",
* @OA\Contact(
* email="support@example.com"
* ),
* @OA\License(
* name="MIT",
* url="https://opensource.org/licenses/MIT"
* )
* )
*
* @OA\Server(
* url="http://localhost:8000",
* description="خادم التطوير المحلي"
* )
*
* @OA\Server(
* url="https://api.example.com",
* description="خادم الإنتاج"
* )
*
* @OA\SecurityScheme(
* securityScheme="bearerAuth",
* type="http",
* scheme="bearer",
* bearerFormat="JWT"
* )
*/
class Controller extends BaseController
{
//
}
الوصول إلى التوثيق: بعد الإنشاء، يمكنك الوصول إلى توثيق واجهة برمجة التطبيقات على
http://localhost:8000/api/documentation. يمكن لفريقك ومستهلكي واجهة برمجة التطبيقات استكشاف واختبار نقاط النهاية بشكل تفاعلي.
تحسين الأداء
تحسين الاستعلام:
<?php
// ❌ مشكلة N+1 Query
$products = Product::all();
foreach ($products as $product) {
echo $product->category->name; // استعلام الفئة لكل منتج
}
// ✅ التحميل المتلهف
$products = Product::with('category')->get();
foreach ($products as $product) {
echo $product->category->name; // لا توجد استعلامات إضافية
}
// ✅ التحميل المتلهف الكسول
$products = Product::all();
$products->load('category', 'images');
// ✅ عد النماذج ذات الصلة
$products = Product::withCount('reviews')->get();
foreach ($products as $product) {
echo $product->reviews_count; // لا استعلام
}
ذاكرة التخزين المؤقت للرد:
<?php
use Illuminate\Support\Facades\Cache;
public function getFeaturedProducts(): JsonResponse
{
$products = Cache::remember('featured_products', 3600, function () {
return Product::with(['category', 'images'])
->featured()
->active()
->limit(10)
->get();
});
return response()->json([
'data' => ProductResource::collection($products),
]);
}
// مسح ذاكرة التخزين المؤقت عند تحديث المنتجات
public function update(UpdateProductRequest $request, Product $product): ProductResource
{
$this->productRepository->update($product, $request->validated());
// مسح ذاكرات التخزين المؤقت ذات الصلة
Cache::forget('featured_products');
Cache::tags(['products'])->flush();
return new ProductResource($product->fresh());
}
ملخص أفضل ممارسات واجهة برمجة التطبيقات
قائمة التحقق الكاملة لأفضل ممارسات واجهة برمجة التطبيقات:
1. تصميم واجهة برمجة التطبيقات
- ✓ استخدم اتفاقيات RESTful (GET، POST، PUT، DELETE)
- ✓ قم بإصدار واجهة برمجة التطبيقات الخاصة بك (/api/v1/)
- ✓ استخدم الأسماء بصيغة الجمع للموارد (/products، وليس /product)
- ✓ استخدم رموز حالة HTTP المناسبة
- ✓ أرجع هياكل رد متسقة
- ✓ قم بتنفيذ HATEOAS لروابط التنقل
2. الأمان
- ✓ استخدم JWT أو OAuth2 للمصادقة
- ✓ قم بتنفيذ تحديد المعدل
- ✓ تحقق من جميع المدخلات
- ✓ قم بتعقيم المخرجات لمنع XSS
- ✓ استخدم HTTPS في الإنتاج
- ✓ قم بتنفيذ CORS بشكل صحيح
- ✓ قم بتجزئة كلمات المرور باستخدام bcrypt/argon2
- ✓ استخدم استعلامات معلمة (منع حقن SQL)
3. الأداء
- ✓ قم بتنفيذ الترقيم لنقاط نهاية القائمة
- ✓ استخدم التحميل المتلهف لتجنب استعلامات N+1
- ✓ قم بتخزين البيانات المخزنة بشكل متكرر مؤقتًا
- ✓ أضف فهارس قاعدة البيانات على المفاتيح الخارجية
- ✓ قم بتحسين الصور قبل التخزين
- ✓ استخدم وظائف قائمة الانتظار للعمليات الثقيلة
- ✓ قم بتمكين ضغط gzip
4. التوثيق
- ✓ استخدم OpenAPI/Swagger للوثائق التفاعلية
- ✓ وثق جميع نقاط النهاية بأمثلة
- ✓ قدم تعليمات المصادقة
- ✓ قم بتضمين أمثلة على رد الخطأ
- ✓ حافظ على تحديث التوثيق
5. الاختبار
- ✓ اكتب اختبارات الميزات لجميع نقاط النهاية
- ✓ اختبر المصادقة والتفويض
- ✓ اختبر قواعد التحقق
- ✓ اختبر معالجة الأخطاء
- ✓ استهدف تغطية كود تزيد عن 80%
- ✓ استخدم المصانع لبيانات الاختبار
6. معالجة الأخطاء
- ✓ أرجع رسائل خطأ ذات معنى
- ✓ استخدم تنسيق خطأ متسق
- ✓ سجل الأخطاء لتصحيح الأخطاء
- ✓ لا تكشف أبدًا عن البيانات الحساسة في الأخطاء
- ✓ تعامل مع الاستثناءات عالميًا
7. النشر
- ✓ استخدم متغيرات البيئة للتكوين
- ✓ قم بتمكين تسجيل أخطاء الإنتاج
- ✓ قم بإعداد المراقبة والتنبيهات
- ✓ قم بتنفيذ نقاط نهاية فحص الصحة
- ✓ استخدم CI/CD لعمليات النشر الآلية
- ✓ حافظ على تحديث التبعيات
مرجع سريع لأنماط واجهة برمجة التطبيقات الشائعة
التصفية:
GET /api/v1/products?category_id=5&is_active=true
الترتيب:
GET /api/v1/products?sort_by=price&sort_order=desc
الترقيم:
GET /api/v1/products?page=2&per_page=20
الرد:
{
"data": [...],
"links": {
"first": "...?page=1",
"last": "...?page=10",
"prev": "...?page=1",
"next": "...?page=3"
},
"meta": {
"current_page": 2,
"last_page": 10,
"per_page": 20,
"total": 200
}
}
البحث:
GET /api/v1/products?search=لابتوب
تحديد الحقول (مجموعات حقول متناثرة):
GET /api/v1/products?fields=id,name,price
تضمين العلاقات:
GET /api/v1/products?include=category,reviews
قائمة التحقق من الإنتاج: قبل النشر إلى الإنتاج، تأكد من: وضع التصحيح متوقف، جميع الأسرار في متغيرات البيئة، تسجيل الأخطاء مُكوَّن، HTTPS مُلزَم، تحديد المعدل مُمكَّن، النسخ الاحتياطية مجدولة، المراقبة نشطة، والتوثيق محدث.
تهانينا!
لقد أكملت دورة تطوير REST API! لقد تعلمت:
- ✓ مبادئ وتصميم واجهة برمجة تطبيقات RESTful
- ✓ طرق HTTP ورموز الحالة والرؤوس
- ✓ المصادقة باستخدام JWT
- ✓ التحقق ومعالجة الأخطاء
- ✓ الترقيم والتصفية والترتيب
- ✓ استراتيجيات إصدار واجهة برمجة التطبيقات
- ✓ تحديد المعدل والتقييد
- ✓ تحميل الملفات ومعالجة الوسائط
- ✓ استراتيجيات اختبار واجهة برمجة التطبيقات
- ✓ التوثيق باستخدام Swagger/OpenAPI
- ✓ تحسين الأداء
- ✓ أفضل ممارسات الأمان
- ✓ النشر وDevOps
- ✓ أنماط التصميم (المستودع، DTO، الإجراءات)
- ✓ بناء واجهة برمجة تطبيقات تجارة إلكترونية كاملة
الخطوات التالية
استمر في التعلم:
- ابنِ مشاريع واجهة برمجة تطبيقات خاصة بك للتدريب
- استكشف GraphQL كبديل لـ REST
- تعلم عن بوابات واجهة برمجة التطبيقات والخدمات الصغيرة
- ادرس واجهات برمجة التطبيقات الواقعية (Stripe، GitHub، Twitter)
- ساهم في مشاريع واجهة برمجة التطبيقات مفتوحة المصدر
- ابقَ على اطلاع بأحدث الاتجاهات وأفضل الممارسات لواجهة برمجة التطبيقات
شكرًا لك على إكمال هذه الدورة! لديك الآن المهارات لبناء واجهات برمجة تطبيقات RESTful احترافية وقابلة للتوسع. برمجة سعيدة!