فهم متحكمات الموارد في Laravel
توفر متحكمات الموارد طريقة ملائمة للتعامل مع عمليات CRUD (الإنشاء، القراءة، التحديث، الحذف) في واجهة برمجة التطبيقات الخاصة بك. تتبع متحكمات موارد Laravel اصطلاحات RESTful وتقلل بشكل كبير من الكود المتكرر.
ما هو متحكم الموارد؟
متحكم الموارد هو متحكم يتعامل مع جميع عمليات CRUD النموذجية لمورد معين. بدلاً من تعريف مسارات وطرق متحكم فردية، يوفر Laravel نهجاً موحداً.
ملاحظة: تتبع متحكمات الموارد اصطلاحات تسمية RESTful، مما يجعل واجهة برمجة التطبيقات الخاصة بك متسقة ويمكن التنبؤ بها للمستهلكين.
إنشاء متحكم موارد
قم بتوليد متحكم موارد باستخدام Artisan:
php artisan make:controller Api/PostController --api --resource
يقوم العلم --api بإنشاء متحكم بدون طرق create() و edit()، والتي تستخدم عادةً لعرض النماذج في تطبيقات الويب ولكنها غير ضرورية لواجهات برمجة التطبيقات.
طرق متحكم الموارد
يتضمن متحكم الموارد سبع طرق قياسية:
| الطريقة |
فعل HTTP |
URI |
الإجراء |
index() |
GET |
/posts |
عرض جميع المنشورات |
store() |
POST |
/posts |
إنشاء منشور جديد |
show() |
GET |
/posts/{post} |
عرض منشور محدد |
update() |
PUT/PATCH |
/posts/{post} |
تحديث منشور محدد |
destroy() |
DELETE |
/posts/{post} |
حذف منشور محدد |
تسجيل مسارات الموارد
سجل جميع مسارات الموارد بسطر واحد في routes/api.php:
use App\Http\Controllers\Api\PostController;
Route::apiResource('posts', PostController::class);
ينشئ هذا السطر الواحد جميع المسارات الخمسة تلقائياً. يمكنك التحقق منها باستخدام:
php artisan route:list --name=posts
تنفيذ طريقة Index
تسترجع طريقة index() وتعيد مجموعة من الموارد:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
/**
* عرض قائمة المنشورات
*/
public function index()
{
$posts = Post::with('author')
->latest()
->paginate(15);
return response()->json([
'success' => true,
'data' => $posts->items(),
'pagination' => [
'total' => $posts->total(),
'per_page' => $posts->perPage(),
'current_page' => $posts->currentPage(),
'last_page' => $posts->lastPage(),
]
]);
}
}
نصيحة: قم دائماً بترقيم المجموعات الكبيرة في واجهة برمجة التطبيقات لتحسين الأداء وتقليل أحجام الاستجابة.
تنفيذ طريقة Store
تقوم طريقة store() بإنشاء مورد جديد:
/**
* تخزين منشور جديد
*/
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
'category_id' => 'required|exists:categories,id',
'tags' => 'nullable|array',
'tags.*' => 'string|max:50',
]);
$post = Post::create([
'title' => $validated['title'],
'content' => $validated['content'],
'category_id' => $validated['category_id'],
'user_id' => auth()->id(),
'published_at' => now(),
]);
if (isset($validated['tags'])) {
$post->tags()->attach($validated['tags']);
}
return response()->json([
'success' => true,
'message' => 'تم إنشاء المنشور بنجاح',
'data' => $post->load('author', 'category', 'tags')
], 201);
}
ملاحظة: أعد رمز حالة HTTP 201 (تم الإنشاء) عند إنشاء مورد جديد بنجاح.
تنفيذ طريقة Show
تسترجع طريقة show() مورداً واحداً:
/**
* عرض المنشور المحدد
*/
public function show(Post $post)
{
// تحميل العلاقات
$post->load('author', 'category', 'tags', 'comments.user');
return response()->json([
'success' => true,
'data' => $post
]);
}
لاحظ كيف نستخدم ربط نموذج المسار من خلال تحديد نوع نموذج Post. يقوم Laravel تلقائياً بالاستعلام عن قاعدة البيانات ويعيد خطأ 404 إذا لم يكن المنشور موجوداً.
نصيحة: استخدم ربط نموذج المسار للتعامل تلقائياً مع استرجاع النموذج واستجابات 404.
تنفيذ طريقة Update
تقوم طريقة update() بتعديل مورد موجود:
/**
* تحديث المنشور المحدد
*/
public function update(Request $request, Post $post)
{
// التحقق من التفويض
if ($post->user_id !== auth()->id()) {
return response()->json([
'success' => false,
'message' => 'غير مصرح لك بتحديث هذا المنشور'
], 403);
}
$validated = $request->validate([
'title' => 'sometimes|required|string|max:255',
'content' => 'sometimes|required|string',
'category_id' => 'sometimes|required|exists:categories,id',
'tags' => 'nullable|array',
'tags.*' => 'string|max:50',
'published' => 'sometimes|boolean',
]);
$post->update($validated);
if (isset($validated['tags'])) {
$post->tags()->sync($validated['tags']);
}
return response()->json([
'success' => true,
'message' => 'تم تحديث المنشور بنجاح',
'data' => $post->load('author', 'category', 'tags')
]);
}
ملاحظة: استخدم sometimes في قواعد التحقق لطلبات PATCH للسماح بالتحديثات الجزئية.
تنفيذ طريقة Destroy
تحذف طريقة destroy() مورداً:
/**
* إزالة المنشور المحدد
*/
public function destroy(Post $post)
{
// التحقق من التفويض
if ($post->user_id !== auth()->id()) {
return response()->json([
'success' => false,
'message' => 'غير مصرح لك بحذف هذا المنشور'
], 403);
}
// حذف ناعم أو حذف قسري
$post->delete();
return response()->json([
'success' => true,
'message' => 'تم حذف المنشور بنجاح'
], 200);
}
تحذير: قم دائماً بتنفيذ فحوصات التفويض المناسبة قبل السماح بعمليات الحذف.
تخصيص مسارات الموارد
يمكنك تحديد الطرق المتاحة:
// طرق index و show فقط
Route::apiResource('posts', PostController::class)
->only(['index', 'show']);
// جميع الطرق باستثناء destroy
Route::apiResource('posts', PostController::class)
->except(['destroy']);
متحكمات الموارد المتداخلة
تعامل مع الموارد المتداخلة (مثل تعليقات المنشور):
// routes/api.php
Route::apiResource('posts.comments', CommentController::class);
// app/Http/Controllers/Api/CommentController.php
public function index(Post $post)
{
$comments = $post->comments()
->with('user')
->latest()
->paginate(20);
return response()->json([
'success' => true,
'data' => $comments->items(),
'pagination' => [
'total' => $comments->total(),
'current_page' => $comments->currentPage(),
]
]);
}
public function store(Request $request, Post $post)
{
$validated = $request->validate([
'content' => 'required|string|max:1000',
]);
$comment = $post->comments()->create([
'content' => $validated['content'],
'user_id' => auth()->id(),
]);
return response()->json([
'success' => true,
'message' => 'تمت إضافة التعليق بنجاح',
'data' => $comment->load('user')
], 201);
}
التداخل الضحل
للموارد المتداخلة بعمق، استخدم التداخل الضحل للحفاظ على عناوين URL قابلة للإدارة:
Route::apiResource('posts.comments', CommentController::class)
->shallow();
// هذا يولد:
// GET /posts/{post}/comments - index
// POST /posts/{post}/comments - store
// GET /comments/{comment} - show
// PUT /comments/{comment} - update
// DELETE /comments/{comment} - destroy
التصفية والفرز المتقدمة
عزز طريقة index بالتصفية والفرز:
public function index(Request $request)
{
$query = Post::with('author', 'category');
// التصفية حسب الفئة
if ($request->has('category')) {
$query->where('category_id', $request->category);
}
// التصفية حسب المؤلف
if ($request->has('author')) {
$query->where('user_id', $request->author);
}
// البحث حسب العنوان أو المحتوى
if ($request->has('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('title', 'like', "%{$search}%")
->orWhere('content', 'like', "%{$search}%");
});
}
// الفرز
$sortBy = $request->get('sort_by', 'created_at');
$sortOrder = $request->get('sort_order', 'desc');
$query->orderBy($sortBy, $sortOrder);
$posts = $query->paginate(
$request->get('per_page', 15)
);
return response()->json([
'success' => true,
'data' => $posts->items(),
'pagination' => [
'total' => $posts->total(),
'per_page' => $posts->perPage(),
'current_page' => $posts->currentPage(),
'last_page' => $posts->lastPage(),
]
]);
}
تمرين:
- أنشئ متحكم موارد لنموذج "Product" مع جميع عمليات CRUD
- نفذ التصفية حسب نطاق السعر والفئة في طريقة index
- أضف فحوصات التفويض لضمان أن مالكي المنتجات فقط يمكنهم تحديث/حذف منتجاتهم
- أنشئ مورداً متداخلاً لمراجعات المنتج (
/products/{product}/reviews)
- اختبر جميع نقاط النهاية باستخدام Postman أو curl
أفضل الممارسات
- استخدم ربط نموذج المسار: حقن نماذج مثيلة تلقائياً بدلاً من الاستعلام اليدوي
- أعد رموز حالة مناسبة: 200 للنجاح، 201 للإنشاء، 204 للحذف، 404 لغير موجود
- نفذ التفويض: تحقق دائماً من أذونات المستخدم قبل السماح بالعمليات
- حمل العلاقات بشكل مسبق: استخدم
with() لمنع مشاكل استعلام N+1
- رقم المجموعات: لا تعيد أبداً مجموعات غير محدودة في الإنتاج
- تحقق من المدخلات: تحقق دائماً من البيانات الواردة قبل المعالجة
- استخدم المعاملات: اربط العمليات المعقدة في معاملات قاعدة البيانات
الخلاصة
توفر متحكمات الموارد نهجاً موحداً متوافقاً مع RESTful لبناء واجهات برمجة التطبيقات في Laravel. من خلال اتباع الاصطلاحات وأفضل الممارسات، تقوم بإنشاء واجهات برمجة تطبيقات يمكن التنبؤ بها وسهلة الصيانة ويسهل على المستهلكين فهمها واستخدامها. في الدرس التالي، سنستكشف موارد API لتحويل بيانات النموذج إلى استجابات JSON.