إطار Laravel

تصميم REST API باستخدام Laravel

18 دقيقة الدرس 36 من 45

تصميم REST API باستخدام Laravel

يوفر Laravel أدوات شاملة لبناء واجهات برمجية قوية وآمنة وقابلة للتطوير تتبع معايير REST. في هذا الدرس، سنستكشف اصطلاحات تصميم API، والمصادقة، والإصدارات، وتحديد المعدل، وأفضل الممارسات لإنشاء واجهات برمجية جاهزة للإنتاج.

اصطلاحات RESTful

يتبع REST (نقل الحالة التمثيلية) اصطلاحات محددة لتصميم API. يجعل Laravel من السهل تطبيق هذه المعايير:

<!-- طرق HTTP وأغراضها --> GET /api/users # عرض جميع المستخدمين (index) GET /api/users/{id} # عرض مستخدم واحد (show) POST /api/users # إنشاء مستخدم جديد (store) PUT /api/users/{id} # تحديث مستخدم كامل (update) PATCH /api/users/{id} # تحديث جزئي للمستخدم (update) DELETE /api/users/{id} # حذف مستخدم (destroy) <!-- الموارد المتداخلة --> GET /api/users/{id}/posts # الحصول على منشورات المستخدم POST /api/users/{id}/posts # إنشاء منشور للمستخدم DELETE /api/posts/{id} # حذف منشور محدد
أفضل ممارسات REST:
  • استخدم الأسماء للموارد (وليس الأفعال)
  • استخدم الأسماء بصيغة الجمع للمجموعات (/users وليس /user)
  • استخدم طرق HTTP لتحديد الإجراءات
  • أرجع رموز حالة HTTP المناسبة
  • قم بإصدار API الخاص بك من البداية

إعداد مسارات API

يوفر Laravel ملف routes/api.php مخصص لمسارات API. تحتوي هذه المسارات تلقائياً على بادئة /api وهي بدون حالة:

// routes/api.php use App\Http\Controllers\Api\UserController; use App\Http\Controllers\Api\PostController; use Illuminate\Support\Facades\Route; // مسارات API العامة Route::get('/status', function () { return response()->json([ 'status' => 'online', 'version' => '1.0.0', 'timestamp' => now()->toIso8601String() ]); }); // مسارات API المحمية Route::middleware(['auth:sanctum'])->group(function () { // موارد المستخدم Route::apiResource('users', UserController::class); // الموارد المتداخلة Route::apiResource('users.posts', PostController::class) ->shallow(); // استخدام التوجيه الضحل للموارد المتداخلة // نقاط النهاية المخصصة Route::post('/users/{user}/follow', [UserController::class, 'follow']); Route::delete('/users/{user}/unfollow', [UserController::class, 'unfollow']); });
apiResource مقابل resource: تنشئ طريقة apiResource() مسارات بدون طرق create و edit، حيث لا تقدم واجهات API عادة نماذج HTML. تتضمن فقط: index, store, show, update, destroy.

متحكمات API

أنشئ متحكمات خاصة بـ API مع تنسيق استجابة مناسب ومعالجة أخطاء:

// app/Http/Controllers/Api/UserController.php namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Http\Resources\UserResource; use App\Models\User; use Illuminate\Http\Request; use Illuminate\Http\Response; class UserController extends Controller { /** * عرض قائمة المستخدمين. */ public function index(Request $request) { $users = User::query() ->when($request->search, function ($query, $search) { $query->where('name', 'like', "%{$search}%"); }) ->paginate($request->per_page ?? 15); return UserResource::collection($users); } /** * تخزين مستخدم جديد. */ public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users', 'password' => 'required|min:8|confirmed' ]); $validated['password'] = bcrypt($validated['password']); $user = User::create($validated); return new UserResource($user); } /** * عرض مستخدم محدد. */ public function show(User $user) { return new UserResource($user->load(['posts', 'followers'])); } /** * تحديث مستخدم محدد. */ public function update(Request $request, User $user) { $this->authorize('update', $user); $validated = $request->validate([ 'name' => 'sometimes|string|max:255', 'email' => 'sometimes|email|unique:users,email,' . $user->id, 'bio' => 'nullable|string|max:1000' ]); $user->update($validated); return new UserResource($user); } /** * حذف مستخدم محدد. */ public function destroy(User $user) { $this->authorize('delete', $user); $user->delete(); return response()->json([ 'message' => 'تم حذف المستخدم بنجاح' ], Response::HTTP_OK); } }

موارد API

توفر موارد API طبقة تحويل بين نماذج Eloquent واستجابات JSON:

// إنشاء مورد: php artisan make:resource UserResource // app/Http/Resources/UserResource.php namespace App\Http\Resources; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; class UserResource extends JsonResource { /** * تحويل المورد إلى مصفوفة. */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'bio' => $this->bio, 'avatar_url' => $this->avatar_url, 'created_at' => $this->created_at->toIso8601String(), 'updated_at' => $this->updated_at->toIso8601String(), // السمات الشرطية 'email_verified' => $this->when($this->email_verified_at, true), 'phone' => $this->when($request->user()?->isAdmin(), $this->phone), // العلاقات 'posts' => PostResource::collection($this->whenLoaded('posts')), 'followers_count' => $this->when($this->relationLoaded('followers'), fn() => $this->followers->count() ), // السمات المحسوبة 'is_following' => $this->when( $request->user(), fn() => $request->user()->isFollowing($this->resource) ), // الروابط 'links' => [ 'self' => route('api.users.show', $this->id), 'posts' => route('api.users.posts.index', $this->id) ] ]; } /** * الحصول على بيانات إضافية لاستجابة المورد. */ public function with(Request $request): array { return [ 'meta' => [ 'version' => '1.0.0', 'timestamp' => now()->toIso8601String() ] ]; } }

إصدارات API

نفذ إصدارات API للسماح بالتوافق العكسي والانتقالات السلسة:

// الطريقة 1: إصدار مسار URL (موصى به) // routes/api.php Route::prefix('v1')->group(function () { Route::apiResource('users', Api\V1\UserController::class); Route::apiResource('posts', Api\V1\PostController::class); }); Route::prefix('v2')->group(function () { Route::apiResource('users', Api\V2\UserController::class); Route::apiResource('posts', Api\V2\PostController::class); }); // URLs: /api/v1/users, /api/v2/users // الطريقة 2: إصدار الرأس // app/Http/Middleware/ApiVersion.php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; class ApiVersion { public function handle(Request $request, Closure $next, string $version) { $acceptedVersion = $request->header('Accept-Version', '1.0'); if ($acceptedVersion !== $version) { return response()->json([ 'error' => 'عدم تطابق إصدار API', 'requested' => $acceptedVersion, 'supported' => ['1.0', '2.0'] ], 406); } return $next($request); } } // تسجيل البرنامج الوسيط Route::middleware(['api.version:2.0'])->group(function () { // مسارات V2 }); // الطريقة 3: التفاوض على المحتوى // Accept: application/vnd.myapp.v2+json
أفضل ممارسات الإصدار:
  • استخدم إصدار مسار URL للبساطة والوضوح
  • قم بزيادة الإصدارات فقط للتغييرات الكبيرة
  • حافظ على الإصدارات القديمة لفترة إهمال
  • وثق الاختلافات بين الإصدارات بوضوح
  • استخدم الإصدار الدلالي (major.minor.patch)

تحديد المعدل

احمِ API الخاص بك من الإساءة باستخدام تحديد المعدل المدمج في Laravel:

// app/Providers/RouteServiceProvider.php use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Http\Request; use Illuminate\Support\Facades\RateLimiter; public function boot(): void { // تكوين محددات المعدل RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); }); // حدود المستخدم المصادق عليه RateLimiter::for('api-authenticated', function (Request $request) { return $request->user() ? Limit::perMinute(120)->by($request->user()->id) : Limit::perMinute(20)->by($request->ip()); }); // حدود المستوى المميز RateLimiter::for('api-premium', function (Request $request) { if ($request->user()?->isPremium()) { return Limit::perMinute(1000)->by($request->user()->id); } return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); }); // حدود متعددة RateLimiter::for('api-strict', function (Request $request) { return [ Limit::perMinute(10), // 10 في الدقيقة Limit::perHour(100), // 100 في الساعة Limit::perDay(1000), // 1000 في اليوم ]; }); // استجابة مخصصة عند تجاوز الحد RateLimiter::for('api-custom', function (Request $request) { return Limit::perMinute(60) ->by($request->user()?->id ?: $request->ip()) ->response(function (Request $request, array $headers) { return response()->json([ 'error' => 'تم تجاوز حد المعدل', 'message' => 'طلبات كثيرة جداً. يرجى التباطؤ.', 'retry_after' => $headers['Retry-After'] ], 429, $headers); }); }); } // تطبيق على المسارات // routes/api.php Route::middleware(['throttle:api-premium'])->group(function () { Route::apiResource('users', UserController::class); }); // تطبيق على نقاط نهاية محددة Route::post('/expensive-operation', [ApiController::class, 'process']) ->middleware('throttle:5,1'); // 5 طلبات في الدقيقة
رؤوس حد المعدل: يضيف Laravel تلقائياً رؤوس X-RateLimit-Limit و X-RateLimit-Remaining و Retry-After إلى الاستجابات المحددة المعدل، مما يسهل على عملاء API التعامل مع الحدود بسلاسة.

تنسيق الاستجابة

أنشئ استجابات API متسقة وموحدة عبر تطبيقك:

// app/Traits/ApiResponse.php namespace App\Traits; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; trait ApiResponse { /** * استجابة النجاح */ protected function success($data = null, string $message = null, int $code = Response::HTTP_OK): JsonResponse { $response = [ 'success' => true, 'message' => $message, 'data' => $data, ]; return response()->json(array_filter($response), $code); } /** * استجابة الخطأ */ protected function error(string $message, int $code = Response::HTTP_BAD_REQUEST, $errors = null): JsonResponse { $response = [ 'success' => false, 'message' => $message, 'errors' => $errors, ]; return response()->json(array_filter($response), $code); } /** * استجابة خطأ التحقق */ protected function validationError($errors, string $message = 'فشل التحقق'): JsonResponse { return response()->json([ 'success' => false, 'message' => $message, 'errors' => $errors ], Response::HTTP_UNPROCESSABLE_ENTITY); } /** * استجابة غير موجود */ protected function notFound(string $message = 'المورد غير موجود'): JsonResponse { return $this->error($message, Response::HTTP_NOT_FOUND); } /** * استجابة غير مصرح */ protected function unauthorized(string $message = 'غير مصرح'): JsonResponse { return $this->error($message, Response::HTTP_UNAUTHORIZED); } /** * استجابة مقسمة إلى صفحات */ protected function paginated($data, string $message = null): JsonResponse { return response()->json([ 'success' => true, 'message' => $message, 'data' => $data->items(), 'meta' => [ 'current_page' => $data->currentPage(), 'last_page' => $data->lastPage(), 'per_page' => $data->perPage(), 'total' => $data->total(), 'from' => $data->firstItem(), 'to' => $data->lastItem(), ], 'links' => [ 'first' => $data->url(1), 'last' => $data->url($data->lastPage()), 'prev' => $data->previousPageUrl(), 'next' => $data->nextPageUrl(), ] ]); } } // الاستخدام في المتحكم class UserController extends Controller { use ApiResponse; public function store(Request $request) { $validated = $request->validate([...]); $user = User::create($validated); return $this->success( new UserResource($user), 'تم إنشاء المستخدم بنجاح', Response::HTTP_CREATED ); } public function index() { $users = User::paginate(15); return $this->paginated($users, 'تم استرجاع المستخدمين بنجاح'); } }

تكوين CORS

قم بتكوين مشاركة الموارد عبر الأصول للسماح بالوصول إلى API الخاص بك من نطاقات مختلفة:

// config/cors.php return [ 'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['*'], 'allowed_origins' => [ 'https://example.com', 'https://app.example.com', ], // للتطوير فقط 'allowed_origins_patterns' => [ '/^https?:\/\/localhost(:\d+)?$/', ], 'allowed_headers' => ['*'], 'exposed_headers' => [ 'X-RateLimit-Limit', 'X-RateLimit-Remaining', ], 'max_age' => 0, 'supports_credentials' => true, ]; // إضافة برنامج CORS الوسيط لمسارات api // app/Http/Kernel.php protected $middlewareGroups = [ 'api' => [ \Illuminate\Routing\Middleware\SubstituteBindings::class, \App\Http\Middleware\HandleCors::class, // أضف هذا ], ]; // أو استخدم معالجة CORS المدمجة في Laravel // تأكد من أن \Fruitcake\Cors\HandleCors موجود في $middleware
أمان CORS: لا تستخدم أبداً 'allowed_origins' => ['*'] في الإنتاج! حدد دائماً النطاقات الدقيقة. استخدم 'supports_credentials' => true فقط عند الضرورة، حيث يتطلب أصولاً محددة (وليس أحرف بدل).

توثيق API

وثق API الخاص بك باستخدام أدوات Laravel المدمجة أو الحزم الشائعة:

// تثبيت Scribe لتوثيق API composer require --dev knuckleswtf/scribe // إنشاء التوثيق php artisan scribe:generate // إضافة docblocks للمتحكمات /** * سرد جميع المستخدمين * * يعيد قائمة مقسمة إلى صفحات من المستخدمين مع تصفية بحث اختيارية. * * @group إدارة المستخدم * * @queryParam search string تصفية المستخدمين حسب الاسم. مثال: john * @queryParam per_page int عدد العناصر في الصفحة. مثال: 20 * * @response { * "data": [ * { * "id": 1, * "name": "John Doe", * "email": "john@example.com" * } * ], * "links": {...}, * "meta": {...} * } */ public function index(Request $request) { // ... } // بديل: OpenAPI/Swagger composer require darkaonline/l5-swagger // إنشاء توثيق Swagger php artisan l5-swagger:generate // إضافة تعليقات Swagger التوضيحية /** * @OA\Get( * path="/api/users", * summary="الحصول على قائمة المستخدمين", * tags={"Users"}, * @OA\Parameter( * name="page", * in="query", * description="رقم الصفحة", * required=false, * @OA\Schema(type="integer") * ), * @OA\Response( * response=200, * description="عملية ناجحة" * ) * ) */

التمرين 1: بناء مورد API كامل

أنشئ Post API بالمتطلبات التالية:

  1. أنشئ PostController مع جميع عمليات CRUD
  2. أنشئ PostResource مع تحويل البيانات المناسب
  3. نفذ التصفية حسب الفئة والمؤلف
  4. أضف تحديد المعدل (60 طلباً في الدقيقة)
  5. قم بتضمين التحقق المناسب لـ store/update
  6. أرجع استجابات JSON موحدة
  7. أضف ترقيم الصفحات مع معلومات meta

التمرين 2: تنفيذ إصدارات API

أنشئ نسختين من User API:

  1. V1: يعيد المستخدم مع id, name, email
  2. V2: يعيد المستخدم مع uuid (بدلاً من id), full_name (بدلاً من name), email, created_at
  3. قم بإعداد المسارات لكلا الإصدارين
  4. أنشئ متحكمات منفصلة أو استخدم اكتشاف الإصدار
  5. وثق الاختلافات بين الإصدارات

التمرين 3: تحديد معدل متقدم

نفذ تحديد معدل متدرج:

  1. المستخدمون المجانيون: 30 طلباً في الدقيقة
  2. المستخدمون المميزون: 120 طلباً في الدقيقة
  3. المستخدمون الإداريون: لا حدود
  4. المستخدمون المجهولون: 10 طلبات في الدقيقة حسب IP
  5. أرجع رسائل خطأ مخصصة مع معلومات الترقية
  6. أضف رأس X-RateLimit-Tier مخصص

الخلاصة

في هذا الدرس، تعلمت كيفية تصميم وبناء واجهات برمجية RESTful باستخدام Laravel. استكشفت اصطلاحات توجيه API، ومتحكمات الموارد والتحويلات، واستراتيجيات الإصدار، وتحديد المعدل، وتكوين CORS، وتوثيق API. تمكنك هذه المهارات من إنشاء واجهات برمجية احترافية وقابلة للتطوير تتبع أفضل ممارسات الصناعة.

في الدرس التالي، سنغوص في Policies & Gates في Laravel للتحكم الدقيق في التفويض.