تطوير واجهات REST API

موارد API والتحويلات

18 دقيقة الدرس 7 من 35

فهم موارد API في Laravel

توفر موارد API طبقة تحويل بين نماذج Eloquent الخاصة بك واستجابات JSON المرسلة إلى مستهلكي واجهة برمجة التطبيقات. تمنحك تحكماً دقيقاً في السمات المعروضة وكيفية تنسيقها.

لماذا نستخدم موارد API؟

بدون موارد API، قد تعيد بيانات نموذج خام تعرض حقولاً حساسة أو لها تنسيق غير متسق. تحل الموارد هذه المشاكل من خلال:

  • التحكم في سمات النموذج المعروضة في الاستجابات
  • تحويل تنسيق البيانات بشكل متسق عبر واجهة برمجة التطبيقات
  • إضافة خصائص محسوبة إلى الاستجابات
  • إنشاء منطق تحويل قابل لإعادة الاستخدام
  • فصل تمثيل البيانات عن منطق الأعمال
ملاحظة: موارد API مهمة بشكل خاص للحفاظ على التوافق مع الإصدارات السابقة عندما يتطور مخطط قاعدة البيانات الخاصة بك.

إنشاء مورد أساسي

قم بتوليد مورد باستخدام Artisan:

php artisan make:resource PostResource

ينشئ هذا ملفاً في app/Http/Resources/PostResource.php:

<?php namespace App\Http\Resources; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; class PostResource extends JsonResource { public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, 'slug' => $this->slug, 'excerpt' => $this->excerpt, 'content' => $this->content, 'published_at' => $this->published_at?->format('Y-m-d H:i:s'), 'created_at' => $this->created_at->format('Y-m-d H:i:s'), 'updated_at' => $this->updated_at->format('Y-m-d H:i:s'), ]; } }

استخدام الموارد في المتحكمات

أعد مورداً واحداً من متحكمك:

use App\Http\Resources\PostResource; use App\Models\Post; public function show(Post $post) { return new PostResource($post); }

يقوم Laravel تلقائياً بتغليف المورد في مفتاح data:

{ "data": { "id": 1, "title": "مقدمة إلى واجهات برمجة التطبيقات في Laravel", "slug": "introduction-to-laravel-apis", "excerpt": "تعلم كيفية بناء واجهات برمجة تطبيقات RESTful...", "content": "محتوى المقال الكامل...", "published_at": "2024-01-15 10:30:00", "created_at": "2024-01-10 08:00:00", "updated_at": "2024-01-15 10:30:00" } }

مجموعات الموارد

للمجموعات من الموارد، استخدم طريقة collection:

public function index() { $posts = Post::with('author', 'category') ->latest() ->paginate(15); return PostResource::collection($posts); }

أو أنشئ فئة مجموعة مخصصة لمزيد من التحكم:

php artisan make:resource PostCollection
<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class PostCollection extends ResourceCollection { public function toArray(Request $request): array { return [ 'data' => $this->collection, 'meta' => [ 'total' => $this->total(), 'per_page' => $this->perPage(), 'current_page' => $this->currentPage(), ], ]; } }
نصيحة: تمنحك فئات المجموعة المخصصة تحكماً كاملاً في بيانات التصفح والمعلومات على مستوى المجموعة.

السمات الشرطية

قم بتضمين السمات بشكل شرطي بناءً على المنطق:

public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, 'content' => $this->content, // قم بتضمين حالة المسودة فقط إذا كان المستخدم مصادقاً 'is_draft' => $this->when( $request->user(), $this->is_draft ), // أظهر edit_url فقط إذا كان المستخدم يملك المنشور 'edit_url' => $this->when( $request->user()?->id === $this->user_id, route('posts.edit', $this->id) ), ]; }

دمج السمات الشرطية

استخدم mergeWhen() لإضافة سمات متعددة بشكل شرطي:

public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, // دمج حقول المسؤول فقط إذا كان المستخدم مسؤولاً $this->mergeWhen($request->user()?->is_admin, [ 'internal_notes' => $this->internal_notes, 'moderation_status' => $this->moderation_status, 'revision_count' => $this->revisions->count(), ]), ]; }
ملاحظة: mergeWhen() مثالي لرؤية السمات على أساس الدور.

العلاقات المتداخلة

قم بتضمين الموارد ذات الصلة مع التحويل المناسب:

public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, // تحويل العلاقات المتداخلة 'author' => new UserResource($this->whenLoaded('author')), 'category' => new CategoryResource($this->whenLoaded('category')), 'tags' => TagResource::collection($this->whenLoaded('tags')), // الأعداد بدون تحميل العلاقات 'comments_count' => $this->whenCounted('comments'), 'average_rating' => $this->whenAggregated('ratings', 'rating', 'avg'), ]; }
تحذير: استخدم دائماً whenLoaded() لمنع مشاكل استعلام N+1.

إضافة خصائص محسوبة

أضف سمات ديناميكية محسوبة في وقت التشغيل:

public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, 'content' => $this->content, // خصائص محسوبة 'reading_time' => $this->calculateReadingTime(), 'word_count' => str_word_count(strip_tags($this->content)), 'is_new' => $this->created_at->isToday(), 'url' => route('posts.show', $this->slug), 'image_url' => $this->image ? asset('storage/' . $this->image) : null, 'views_formatted' => number_format($this->views), 'published_human' => $this->published_at?->diffForHumans(), ]; }

تخصيص غلاف المورد

بشكل افتراضي، يتم تغليف الموارد في مفتاح data. قم بالتخصيص أو التعطيل:

// غلاف مخصص في فئة المورد public static $wrap = 'post'; // تعطيل الغلاف عالمياً في AppServiceProvider JsonResource::withoutWrapping();

إضافة بيانات وصفية إلى المجموعات

أضف معلومات إضافية جنباً إلى جنب مع بيانات المجموعة:

public function index() { $posts = Post::latest()->paginate(15); return PostResource::collection($posts) ->additional([ 'meta' => [ 'api_version' => 'v1', 'timestamp' => now()->toIso8601String(), 'total_posts' => Post::count(), ] ]); }

العلاقات الشرطية

حمل العلاقات بناءً على معاملات الاستعلام:

public function index(Request $request) { $query = Post::query(); $includes = $request->input('include', ''); $allowedIncludes = ['author', 'category', 'tags']; $requestedIncludes = array_filter( explode(',', $includes), fn($inc) => in_array($inc, $allowedIncludes) ); if (!empty($requestedIncludes)) { $query->with($requestedIncludes); } return PostResource::collection($query->paginate(15)); }
نصيحة: السماح للعملاء بطلب علاقات محددة يقلل من الجلب الزائد.

بيانات المحور في العلاقات

قم بتضمين بيانات جدول المحور في العلاقات المتعددة:

public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, 'tags' => $this->whenLoaded('tags', function() { return $this->tags->map(function($tag) { return [ 'id' => $tag->id, 'name' => $tag->name, 'assigned_at' => $tag->pivot->created_at, ]; }); }), ]; }

تخصيص الاستجابة

قم بتخصيص الاستجابة بالكامل باستخدام طريقة with():

class PostResource extends JsonResource { public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, ]; } public function with(Request $request): array { return [ 'success' => true, 'message' => 'تم استرجاع المنشور بنجاح', ]; } }

الموارد متعددة الأشكال

تعامل مع العلاقات متعددة الأشكال بأنواع موارد شرطية:

public function toArray(Request $request): array { return [ 'id' => $this->id, 'content' => $this->content, 'commentable' => $this->whenLoaded('commentable', function() { return match($this->commentable_type) { 'App\\Models\\Post' => new PostResource($this->commentable), 'App\\Models\\Video' => new VideoResource($this->commentable), default => null, }; }), ]; }

تعديل استجابة المورد

عدل رؤوس استجابة HTTP قبل الإرسال:

public function withResponse(Request $request, JsonResponse $response): void { $response->header('X-Resource-Type', 'Post'); $response->setMaxAge(3600); $response->setPublic(); }
تمرين:
  1. أنشئ UserResource يخفي البريد الإلكتروني ما لم يشاهد المستخدم ملفه الشخصي
  2. أنشئ ProductResource مع نسبة_الخصم المحسوبة
  3. ابنِ CommentCollection مع بيانات إحصائية وصفية
  4. نفذ تحميل العلاقة الشرطية عبر معامل ?include=
  5. أضف بيانات محور إلى علاقة متعددة إلى متعددة

أفضل الممارسات

  • استخدم whenLoaded(): امنع استعلامات N+1 بالتحميل الشرطي
  • أخفِ البيانات الحساسة: لا تعرض كلمات المرور أو الرموز أبداً
  • تنسيق متسق: نسق التواريخ والأرقام بشكل موحد
  • خصائص محسوبة: أضف حقولاً مفيدة مثل عناوين URL والتواريخ البشرية
  • موارد الإصدارات: أنشئ موارد منفصلة لإصدارات API
  • اختبر التحويلات: تحقق من بنية JSON المتوقعة

الخلاصة

توفر موارد API تحكماً قوياً في تحويل الاستجابة. تخفي تفاصيل التنفيذ، وتضيف خصائص محسوبة، وتنشئ استجابات API متسقة ويمكن التنبؤ بها. بعد ذلك، سنستكشف التحقق من الطلبات لضمان سلامة البيانات.