التعامل مع API في Laravel – الجزء الثاني: إضافة وتعديل وحذف البيانات والتحقق ورفع الملفات
تعرفنا في الجزء الأول من سلسلة Laravel API على مفهوم RESTful API، وكيفية إنشاء API Routes، وجلب البيانات باستخدام Eloquent وRoute Model Binding، وتنظيم JSON Responses باستخدام Laravel API Resources.
في هذا الجزء سننتقل من قراءة البيانات إلى تنفيذ بقية عمليات CRUD، حيث سنتعلم كيفية إضافة بيانات جديدة، والتحقق من البيانات باستخدام Validation، وتعديل السجلات وحذفها، بالإضافة إلى رفع الملفات والصور من خلال REST API.
سنستخدم في الأمثلة Resource باسم Brand يحتوي على حقل name، ثم سنضيف إليه حقل photo عند شرح رفع الملفات.
جدول المحتويات
- مراجعة سريعة لعمليات CRUD
- إضافة بيانات جديدة باستخدام POST
- إعداد Mass Assignment و$fillable
- إنشاء دالة store
- إرجاع البيانات بعد الإنشاء
- التحقق من البيانات باستخدام Validation
- إنشاء Form Request
- استخدام validated بدل all
- التعامل مع Validation Errors
- تعديل البيانات باستخدام PUT وPATCH
- الفرق بين PUT وPATCH
- حذف البيانات باستخدام DELETE
- استخدام 204 No Content
- استخدام Route::apiResource
- إنشاء API Resource Controller
- رفع الملفات والصور عبر API
- التحقق من الملفات المرفوعة
- تخزين الصورة
- إتاحة الملفات المخزنة للعامة
- رفع الملفات باستخدام Postman
- مثال BrandController متكامل
- HTTP Status Codes المستخدمة
- ملاحظات أمنية مهمة
- ملخص الدرس
- الخلاصة
مراجعة سريعة لعمليات CRUD
تعرفنا في الجزء السابق على أن REST APIs تعتمد بصورة كبيرة على HTTP Methods لتحديد نوع العملية المطلوبة.
أشهر العمليات هي:
| HTTP Method | CRUD | العملية |
|---|---|---|
| GET | Read | جلب البيانات |
| POST | Create | إنشاء بيانات جديدة |
| PUT / PATCH | Update | تعديل البيانات |
| DELETE | Delete | حذف البيانات |
في الجزء الأول استخدمنا GET، وسنقوم الآن بتنفيذ بقية العمليات.
إضافة بيانات جديدة باستخدام POST
لإنشاء Brand جديد نحتاج إلى إرسال Request من نوع:
POSTإلى:
/api/brandsنضيف Route داخل:
routes/api.phpبالشكل التالي:
use App\Http\Controllers\BrandController;
use Illuminate\Support\Facades\Route;
Route::post('/brands', [BrandController::class, 'store']);لاحظ استخدام الاسم بصيغة الجمع:
brandsحتى تكون Endpoints متناسقة:
GET /api/brands
POST /api/brands
GET /api/brands/{brand}
PUT /api/brands/{brand}
DELETE /api/brands/{brand}إعداد Mass Assignment و$fillable
إذا أردنا استخدام:
Brand::create(...)فيجب الانتباه إلى Mass Assignment.
داخل:
app/Models/Brand.phpيمكن تحديد الحقول المسموح بتمريرها جماعيًا:
protected $fillable = [
'name',
];بهذا نحدد أن حقل:
nameمسموح باستخدامه في عمليات Mass Assignment.
إنشاء دالة store
يمكن في أبسط صورة كتابة:
public function store(Request $request)
{
$brand = Brand::create([
'name' => $request->name,
]);
return $brand;
}لكن هذه الطريقة ما زالت تفتقد خطوة أساسية:
Validation.
لا ينبغي أن نثق مباشرة بالبيانات القادمة من Client ونقوم بحفظها قبل التحقق منها.
إرجاع البيانات بعد إنشاء Brand
في الجزء الأول أنشأنا:
BrandResourceلذلك بدل إعادة Eloquent Model مباشرة يمكن إعادة Resource:
return new BrandResource($brand);مثلًا:
public function store(BrandStoreRequest $request)
{
$brand = Brand::create($request->validated());
return new BrandResource($brand);
}قد تكون الاستجابة:
{
"data": {
"id": 21,
"name": "Apple",
"created_at": "2026-08-30T10:30:00.000000Z"
}
}وعند إنشاء Resource جديد بنجاح يكون HTTP Status Code المناسب عادةً:
201 Createdالتحقق من البيانات باستخدام Validation
أي بيانات قادمة من Client يجب التعامل معها باعتبارها بيانات غير موثوقة حتى يتم التحقق منها.
لنفترض أن Brand يحتاج إلى:
nameونريد أن يكون:
- مطلوبًا.
- نصًا.
- لا يتجاوز 255 حرفًا.
- غير مكرر.
يمكن تنفيذ Validation مباشرة داخل Controller، لكن عندما تبدأ القواعد بالزيادة يصبح من الأفضل فصلها باستخدام Form Request.
إنشاء Form Request
ننفذ:
php artisan make:request BrandStoreRequestسيقوم Laravel بإنشاء:
app/Http/Requests/BrandStoreRequest.phpيمكن كتابة:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class BrandStoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'name' => [
'required',
'string',
'max:255',
'unique:brands,name',
],
];
}
}الدالة:
authorize()مسؤولة عن تحديد ما إذا كان المستخدم الحالي مخولًا لتنفيذ هذا الطلب.
أما:
rules()فتحتوي على قواعد Validation الخاصة بالبيانات.
استخدام validated بدل all
بعد استخدام Form Request يمكننا الحصول على البيانات التي اجتازت Validation باستخدام:
$request->validated()فتصبح دالة store:
public function store(BrandStoreRequest $request)
{
$brand = Brand::create(
$request->validated()
);
return new BrandResource($brand);
}وهذه أفضل من تمرير جميع البيانات القادمة من Request باستخدام:
$request->all()لأن validated() تعيد البيانات التي خضعت لقواعد Validation.
يوفر Laravel أيضًا:
$request->safe()والذي يمكن استخدامه عند الحاجة للحصول على جزء من البيانات التي تم التحقق منها.
مثلًا:
$request->safe()->only([
'name',
]);التعامل مع Validation Errors في API
إذا أرسل Client Request بدون:
nameفسيفشل Validation.
عندما يتوقع الطلب JSON سيعيد Laravel استجابة JSON تحتوي على تفاصيل الخطأ.
مثلًا:
{
"message": "The name field is required.",
"errors": {
"name": [
"The name field is required."
]
}
}مع HTTP Status Code:
422 Unprocessable Contentوهذا مهم جدًا لمطور تطبيق الهاتف أو Frontend، حيث يستطيع قراءة:
errorsوعرض رسالة Validation المناسبة للمستخدم.
وأثناء اختبار API يُنصح بإرسال:
Accept: application/jsonتعديل البيانات باستخدام PUT وPATCH
لتعديل Brand موجود يمكن استخدام:
PUT /api/brands/{brand}أو:
PATCH /api/brands/{brand}يمكن تعريف Route:
Route::put('/brands/{brand}', [BrandController::class, 'update']);وسنستخدم Route Model Binding للحصول على Brand تلقائيًا:
public function update(
BrandUpdateRequest $request,
Brand $brand
) {
$brand->update(
$request->validated()
);
return new BrandResource($brand);
}إذا كان Brand غير موجود، يقوم Route Model Binding بإرجاع:
404 Not Foundتلقائيًا.
ما الفرق بين PUT وPATCH؟
يستخدم الاثنان لتحديث Resource، لكن من الناحية الدلالية يوجد فرق بينهما.
PUT
يُستخدم عادةً عندما يمثل Request تحديثًا أو استبدالًا كاملًا لحالة Resource.
PUT /api/brands/10PATCH
يُستخدم عادةً عندما نريد تحديث جزء من Resource فقط.
PATCH /api/brands/10Laravel Resource Routes تدعم الاثنين في عملية:
updateوعند استخدام Postman يمكن اختيار:
PUTأو:
PATCHمباشرة من قائمة HTTP Methods.
لذلك لا تحتاج إلى استخدام:
_method=PUTعند إرسال PUT حقيقي من Postman.
Method Spoofing مثل _method يكون مفيدًا بصورة أساسية عندما يكون Client مثل HTML Form غير قادر على إرسال PUT أو PATCH أو DELETE مباشرة.
حذف البيانات باستخدام DELETE
لحذف Brand نستخدم:
DELETE /api/brands/{brand}ونعرف Route:
Route::delete('/brands/{brand}', [
BrandController::class,
'destroy'
]);داخل Controller:
public function destroy(Brand $brand)
{
$brand->delete();
return response()->noContent();
}مرة أخرى يقوم Route Model Binding بجلب Brand المطلوب تلقائيًا.
استخدام 204 No Content بعد الحذف
بعد نجاح عملية DELETE ليس من الضروري دائمًا إعادة بيانات داخل Response.
يمكن استخدام:
return response()->noContent();والذي يعيد:
204 No Contentوهذا يعني أن العملية تمت بنجاح، لكن Response لا يحتوي على Body.
مثلًا:
DELETE /api/brands/20
HTTP/1.1 204 No Contentوهذا Status Code مناسب جدًا لعملية حذف ناجحة لا نحتاج بعدها إلى إعادة Resource.
استخدام Route::apiResource
حتى الآن يمكن أن يكون لدينا Routes مثل:
Route::get('/brands', [BrandController::class, 'index']);
Route::post('/brands', [BrandController::class, 'store']);
Route::get('/brands/{brand}', [BrandController::class, 'show']);
Route::put('/brands/{brand}', [BrandController::class, 'update']);
Route::delete('/brands/{brand}', [BrandController::class, 'destroy']);Laravel يوفر طريقة مختصرة لإنشاء RESTful Routes الخاصة بالـAPI:
Route::apiResource(
'brands',
BrandController::class
);سيقوم Laravel بإنشاء Routes الخاصة بالعمليات التالية:
| Method | URI | Controller Method |
|---|---|---|
| GET | /brands | index |
| POST | /brands | store |
| GET | /brands/{brand} | show |
| PUT / PATCH | /brands/{brand} | update |
| DELETE | /brands/{brand} | destroy |
بعكس:
Route::resource()فإن:
Route::apiResource()لا ينشئ Routes الخاصة بعرض HTML Forms:
create
editلأن API لا يحتاج عادةً إلى صفحات Form من Laravel.
إنشاء API Resource Controller
يمكن أيضًا إنشاء Controller مخصص لعمليات API باستخدام:
php artisan make:controller BrandController --apiوسيقوم Laravel بإنشاء Controller يحتوي على الدوال المناسبة للـAPI:
index()
store()
show()
update()
destroy()بدون:
create()
edit()لأن هاتين الدالتين تستخدمان عادةً لعرض HTML Forms في تطبيقات الويب التقليدية.
رفع الملفات والصور عبر Laravel API
يمكن للـREST API استقبال الملفات مثل الصور والمستندات بالإضافة إلى البيانات النصية.
لنفترض أننا نريد إضافة صورة لكل Brand.
سيكون لدينا حقل:
photoداخل قاعدة البيانات.
كما نضيفه إلى الحقول المسموح بها داخل Brand Model:
protected $fillable = [
'name',
'photo',
];التحقق من الملفات المرفوعة
لا ينبغي تخزين أي ملف يرسله Client قبل التحقق منه.
يمكن إضافة Validation للصورة داخل BrandStoreRequest:
public function rules(): array
{
return [
'name' => [
'required',
'string',
'max:255',
'unique:brands,name',
],
'photo' => [
'nullable',
'image',
'mimes:jpg,jpeg,png,webp',
'max:2048',
],
];
}بهذه القواعد تكون الصورة اختيارية، لكن إذا تم إرسالها فيجب أن تكون صورة من الأنواع المسموحة وألا يتجاوز حجمها الحد المحدد.
القيمة:
2048تمثل الحد الأقصى للحجم بالكيلوبايت في هذه القاعدة، أي حوالي 2 MB.
تخزين الصورة
يمكن استخدام Laravel Filesystem بدل إنشاء اسم الملف ومساره يدويًا.
مثلًا:
public function store(BrandStoreRequest $request)
{
$data = $request->validated();
if ($request->hasFile('photo')) {
$data['photo'] = $request
->file('photo')
->store('brands', 'public');
}
$brand = Brand::create($data);
return new BrandResource($brand);
}السطر:
$request->file('photo')يحصل على الملف المرفوع.
أما:
->store('brands', 'public')فيقوم بتخزين الملف داخل مجلد:
brandsعلى Disk باسم:
publicويقوم Laravel بإنشاء اسم ملف فريد تلقائيًا وإرجاع المسار الذي تم تخزينه.
مثلًا:
brands/AbCdEf123456.jpgويتم حفظ هذا المسار في قاعدة البيانات.
إتاحة الملفات المخزنة للعامة
إذا كنا نستخدم:
publicDisk لتخزين الصور التي يجب الوصول إليها من الويب، فقد نحتاج إلى إنشاء Symbolic Link:
php artisan storage:linkوبذلك يتم ربط Public Storage بمجلد يمكن الوصول إليه من الويب وفق إعدادات Laravel Filesystem.
يمكن بعد ذلك بناء URL للصورة عند الحاجة باستخدام الأدوات التي يوفرها Laravel Filesystem بدل تخزين URL كامل داخل قاعدة البيانات.
من الأفضل عادةً تخزين:
brands/AbCdEf123456.jpgفي قاعدة البيانات بدل تخزين Domain كامل، لأن Domain أو Storage Provider قد يتغير لاحقًا.
رفع الملفات باستخدام Postman
عند إرسال صورة مع بيانات أخرى من Postman نستخدم عادةً:
Body
|
v
form-dataثم نضيف:
name Text Apple
photo File brand.jpgويجب تغيير نوع حقل:
photoمن:
Textإلى:
Fileثم اختيار الصورة من الجهاز.
Postman يقوم عندها بإرسال الطلب باستخدام:
multipart/form-dataبالصيغة المناسبة للملفات.
مثال BrandController متكامل
بعد جمع ما تعلمناه في الجزأين الأول والثاني، يمكن أن يكون Controller قريبًا من الشكل التالي:
<?php
namespace App\Http\Controllers;
use App\Http\Requests\BrandStoreRequest;
use App\Http\Requests\BrandUpdateRequest;
use App\Http\Resources\BrandResource;
use App\Models\Brand;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Http\Response;
class BrandController extends Controller
{
public function index(): AnonymousResourceCollection
{
return BrandResource::collection(
Brand::all()
);
}
public function store(
BrandStoreRequest $request
): BrandResource {
$data = $request->validated();
if ($request->hasFile('photo')) {
$data['photo'] = $request
->file('photo')
->store('brands', 'public');
}
$brand = Brand::create($data);
return new BrandResource($brand);
}
public function show(
Brand $brand
): BrandResource {
return new BrandResource($brand);
}
public function update(
BrandUpdateRequest $request,
Brand $brand
): BrandResource {
$data = $request->validated();
if ($request->hasFile('photo')) {
$data['photo'] = $request
->file('photo')
->store('brands', 'public');
}
$brand->update($data);
return new BrandResource($brand);
}
public function destroy(
Brand $brand
): Response {
$brand->delete();
return response()->noContent();
}
}ويمكن تعريف جميع Routes باستخدام:
Route::apiResource(
'brands',
BrandController::class
);HTTP Status Codes المستخدمة في CRUD API
من المهم أن يعرف مطور Backend ومطور Frontend معنى Status Codes التي يعيدها API.
| Status Code | المعنى | مثال |
|---|---|---|
| 200 OK | تم تنفيذ الطلب بنجاح | عرض أو تعديل البيانات |
| 201 Created | تم إنشاء Resource جديد | إضافة Brand |
| 204 No Content | نجحت العملية ولا يوجد Response Body | حذف Brand |
| 404 Not Found | Resource المطلوب غير موجود | Brand ID غير موجود |
| 422 Unprocessable Content | فشل Validation | حقل name غير موجود أو غير صالح |
ملاحظات أمنية مهمة عند استقبال البيانات والملفات
لا تستخدم جميع بيانات Request بدون حاجة
بدل:
Brand::create($request->all());يفضل في هذا السيناريو:
Brand::create($request->validated());وبذلك تكون البيانات التي تصل إلى Model مرتبطة بقواعد Validation التي حددناها.
تحقق من الملفات قبل تخزينها
لا تعتمد على اسم الملف أو Extension المرسل من المستخدم وحده.
استخدم Validation المناسبة للملف وحدد:
- نوع الملف.
- الحجم الأقصى.
- هل الملف مطلوب أم اختياري.
لا تستخدم اسم الملف الأصلي مباشرة
استخدام:
store()يسمح لـLaravel بإنشاء اسم فريد للملف بدل الاعتماد مباشرة على الاسم الذي أرسله المستخدم.
لا تجعل كل الملفات Public
إذا كان الملف خاصًا مثل:
- وثيقة هوية.
- فاتورة خاصة.
- عقد.
- ملف مستخدم حساس.
فلا ينبغي تخزينه تلقائيًا على Public Disk.
استخدم Storage خاصًا مع Authorization مناسب للوصول إليه.
تحقق من Authorization وليس Validation فقط
Validation يجيب عن سؤال:
هل البيانات صحيحة؟
أما Authorization فيجيب عن سؤال مختلف:
هل هذا المستخدم مسموح له أصلًا بتنفيذ هذه العملية؟
وسنتعامل مع Authentication وAuthorization بصورة أوسع في الأجزاء المتقدمة من السلسلة.
ملخص الدرس
| العنصر | الاستخدام |
|---|---|
| POST | إنشاء Resource جديد |
| PUT | تحديث Resource |
| PATCH | تحديث Resource أو جزء منه |
| DELETE | حذف Resource |
| $fillable | تحديد الحقول المسموح بها في Mass Assignment |
| Form Request | فصل Validation وAuthorization الخاصة بالطلب |
| validated() | الحصول على البيانات التي اجتازت Validation |
| 422 | فشل Validation |
| 201 | تم إنشاء Resource جديد |
| 204 | نجحت العملية بدون Response Body |
| Route Model Binding | جلب Model تلقائيًا من Route Parameter |
| Route::apiResource() | إنشاء RESTful API Routes دفعة واحدة |
| hasFile() | التحقق من وجود ملف في Request |
| file() | الحصول على الملف المرفوع |
| store() | تخزين الملف باستخدام Laravel Filesystem |
| storage:link | إنشاء رابط للملفات العامة عند استخدام Public Disk |
الخلاصة
في الجزء الثاني من سلسلة Laravel API أكملنا العمليات الأساسية التي بدأناها في الجزء الأول، وأصبح لدينا الآن API قادر على تنفيذ عمليات CRUD كاملة.
استخدمنا:
POST /api/brandsلإضافة Brand جديد، و:
PUT /api/brands/{brand}
PATCH /api/brands/{brand}لتعديل البيانات، و:
DELETE /api/brands/{brand}لحذف البيانات.
كما تعرفنا على أهمية Validation واستخدام Form Requests لفصل قواعد التحقق عن Controller، واستخدمنا:
$request->validated()للحصول على البيانات التي اجتازت Validation بدل تمرير جميع مدخلات Request مباشرة إلى Model.
ثم استخدمنا:
Route::apiResource()لاختصار RESTful Routes الخاصة بالـCRUD في تعريف واحد.
وأخيرًا تعلمنا كيفية استقبال الصور والملفات من Client والتحقق منها وتخزينها باستخدام Laravel Filesystem، بالإضافة إلى إرسال الملفات من Postman باستخدام multipart/form-data.
بعد الجزأين الأول والثاني أصبح لدينا الأساس اللازم لبناء REST API حقيقي في Laravel: نستطيع جلب البيانات، وإنشاءها، والتحقق منها، وتعديلها، وحذفها، وإرجاعها باستخدام API Resources، بالإضافة إلى استقبال الملفات.
الخطوة التالية في بناء API احترافي هي الانتقال إلى مواضيع مثل تنظيم Responses بصورة أعمق، Pagination، معالجة الأخطاء، Authentication، Authorization وحماية Endpoints.
السلام عليكم ورحمة الله تعالى وبركاته اولا: جزاك الله خير لما تقدمه من علم نافع باذن الله ثانيا: مجرد ما قمت بعمل BrandStoreRequest صار يظهر لي خطأ في الحفظ وعندما تجاوزتها للتعديل كمان هنالك خطا مع العلم اني اتبعت الخطوات خطوة بخطوة.