التعامل مع 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 عند شرح رفع الملفات.

جدول المحتويات

  1. مراجعة سريعة لعمليات CRUD
  2. إضافة بيانات جديدة باستخدام POST
  3. إعداد Mass Assignment و$fillable
  4. إنشاء دالة store
  5. إرجاع البيانات بعد الإنشاء
  6. التحقق من البيانات باستخدام Validation
  7. إنشاء Form Request
  8. استخدام validated بدل all
  9. التعامل مع Validation Errors
  10. تعديل البيانات باستخدام PUT وPATCH
  11. الفرق بين PUT وPATCH
  12. حذف البيانات باستخدام DELETE
  13. استخدام 204 No Content
  14. استخدام Route::apiResource
  15. إنشاء API Resource Controller
  16. رفع الملفات والصور عبر API
  17. التحقق من الملفات المرفوعة
  18. تخزين الصورة
  19. إتاحة الملفات المخزنة للعامة
  20. رفع الملفات باستخدام Postman
  21. مثال BrandController متكامل
  22. HTTP Status Codes المستخدمة
  23. ملاحظات أمنية مهمة
  24. ملخص الدرس
  25. الخلاصة

مراجعة سريعة لعمليات CRUD

تعرفنا في الجزء السابق على أن REST APIs تعتمد بصورة كبيرة على HTTP Methods لتحديد نوع العملية المطلوبة.

أشهر العمليات هي:

HTTP MethodCRUDالعملية
GETReadجلب البيانات
POSTCreateإنشاء بيانات جديدة
PUT / PATCHUpdateتعديل البيانات
DELETEDeleteحذف البيانات

في الجزء الأول استخدمنا 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/10

PATCH

يُستخدم عادةً عندما نريد تحديث جزء من Resource فقط.

PATCH /api/brands/10

Laravel 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 الخاصة بالعمليات التالية:

MethodURIController Method
GET/brandsindex
POST/brandsstore
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

ويتم حفظ هذا المسار في قاعدة البيانات.

إتاحة الملفات المخزنة للعامة

إذا كنا نستخدم:

public

Disk لتخزين الصور التي يجب الوصول إليها من الويب، فقد نحتاج إلى إنشاء 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 FoundResource المطلوب غير موجود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.