مقدمة إلى رفع الملفات في واجهات برمجة التطبيقات
يتطلب التعامل مع رفع الملفات عبر واجهات برمجة تطبيقات REST اهتمامًا خاصًا لأن الملفات عبارة عن بيانات ثنائية يجب نقلها بشكل مختلف عن حمولات JSON النموذجية. يغطي هذا الدرس بيانات النموذج متعدد الأجزاء، والتحقق من الصحة، واستراتيجيات التخزين، وإرجاع عناوين URL للملفات القابلة للوصول إلى العملاء.
المفهوم الأساسي: تستخدم عمليات رفع الملفات نوع المحتوى multipart/form-data بدلاً من application/json، مما يسمح بنقل بيانات الملف الثنائي جنبًا إلى جنب مع حقول النموذج الأخرى.
فهم بيانات النموذج متعدد الأجزاء
عند رفع الملفات عبر واجهة برمجة التطبيقات، يستخدم الطلب ترميز multipart/form-data. يسمح هذا التنسيق بنقل أنواع بيانات متعددة (حقول نصية وملفات) في طلب واحد:
POST /api/posts HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Authorization: Bearer your-token-here
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="title"
منشور مدونتي
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="content"
هذا هو محتوى المنشور
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="image"; filename="photo.jpg"
Content-Type: image/jpeg
[بيانات الملف الثنائي هنا]
------WebKitFormBoundary7MA4YWxkTrZu0gW--
متحكم رفع الملفات الأساسي
إليك متحكم Laravel بسيط يتعامل مع رفع الملفات:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class FileUploadController extends Controller
{
public function upload(Request $request)
{
// التحقق من صحة الملف المرفوع
$request->validate([
\047file\047 => \047required|file|max:10240\047, // 10MB كحد أقصى
]);
// تخزين الملف
$path = $request->file(\047file\047)->store(\047uploads\047, \047public\047);
// إرجاع عنوان URL للملف
return response()->json([
\047success\047 => true,
\047message\047 => \047تم رفع الملف بنجاح\047,
\047data\047 => [
\047path\047 => $path,
\047url\047 => Storage::url($path),
\047size\047 => $request->file(\047file\047)->getSize(),
\047mime_type\047 => $request->file(\047file\047)->getMimeType(),
],
], 201);
}
}
التحقق الشامل من الملفات
التحقق الصحيح أمر بالغ الأهمية للأمان وتجربة المستخدم:
<?php
public function uploadImage(Request $request)
{
$validator = Validator::make($request->all(), [
\047image\047 => [
\047required\047,
\047file\047,
\047image\047, // يجب أن تكون صورة
\047mimes:jpeg,png,jpg,gif,webp\047, // التنسيقات المسموحة
\047max:5120\047, // 5MB كحد أقصى
\047dimensions:min_width=100,min_height=100,max_width=4000,max_height=4000\047,
],
\047title\047 => \047sometimes|string|max:255\047,
\047alt_text\047 => \047sometimes|string|max:255\047,
]);
if ($validator->fails()) {
return response()->json([
\047success\047 => false,
\047message\047 => \047فشل التحقق من الصحة\047,
\047errors\047 => $validator->errors(),
], 422);
}
// معالجة الرفع
$image = $request->file(\047image\047);
// أمان إضافي: التحقق من محتوى الملف الفعلي
$imageInfo = getimagesize($image->getPathname());
if ($imageInfo === false) {
return response()->json([
\047success\047 => false,
\047message\047 => \047ملف صورة غير صالح\047,
], 422);
}
// التخزين باسم ملف مخصص
$filename = time() . \047_\047 . uniqid() . \047.\047 . $image->extension();
$path = $image->storeAs(\047images\047, $filename, \047public\047);
return response()->json([
\047success\047 => true,
\047data\047 => [
\047url\047 => Storage::url($path),
\047path\047 => $path,
\047size\047 => $image->getSize(),
\047width\047 => $imageInfo[0],
\047height\047 => $imageInfo[1],
],
], 201);
}
تحذير أمني: لا تثق أبدًا في نوع MIME المقدم من العميل أو امتداد الملف وحده. تحقق دائمًا من محتوى الملف الفعلي لمنع التحميلات الضارة. استخدم getimagesize() للصور والتحقق المناسب لأنواع الملفات الأخرى.
خيارات وإعدادات التخزين
يدعم Laravel برامج تشغيل تخزين متعددة. قم بتكوينها في config/filesystems.php:
<?php
return [
\047default\047 => env(\047FILESYSTEM_DISK\047, \047local\047),
\047disks\047 => [
// التخزين المحلي
\047local\047 => [
\047driver\047 => \047local\047,
\047root\047 => storage_path(\047app\047),
],
// التخزين العام (يمكن الوصول إليه عبر الويب)
\047public\047 => [
\047driver\047 => \047local\047,
\047root\047 => storage_path(\047app/public\047),
\047url\047 => env(\047APP_URL\047).\047/storage\047,
\047visibility\047 => \047public\047,
],
// Amazon S3
\047s3\047 => [
\047driver\047 => \047s3\047,
\047key\047 => env(\047AWS_ACCESS_KEY_ID\047),
\047secret\047 => env(\047AWS_SECRET_ACCESS_KEY\047),
\047region\047 => env(\047AWS_DEFAULT_REGION\047),
\047bucket\047 => env(\047AWS_BUCKET\047),
\047url\047 => env(\047AWS_URL\047),
],
],
];
استخدام التخزين السحابي (Amazon S3)
لتطبيقات الإنتاج، يوصى بالتخزين السحابي:
<?php
// التثبيت: composer require league/flysystem-aws-s3-v3
public function uploadToS3(Request $request)
{
$request->validate([
\047file\047 => \047required|file|max:10240\047,
]);
$file = $request->file(\047file\047);
// إنشاء اسم ملف فريد
$filename = \047uploads/\047 . date(\047Y/m/d\047) . \047/\047 . uniqid() . \047.\047 . $file->extension();
// الرفع إلى S3 مع الرؤية العامة
$path = Storage::disk(\047s3\047)->putFileAs(
\047\047,
$file,
$filename,
\047public\047
);
// الحصول على عنوان URL العام
$url = Storage::disk(\047s3\047)->url($path);
return response()->json([
\047success\047 => true,
\047data\047 => [
\047url\047 => $url,
\047path\047 => $path,
\047size\047 => $file->getSize(),
],
], 201);
}
رفع ملفات متعددة
التعامل مع ملفات متعددة في طلب واحد:
<?php
public function uploadMultiple(Request $request)
{
$request->validate([
\047files\047 => \047required|array|max:10\047,
\047files.*\047 => \047required|file|mimes:jpeg,png,pdf,doc,docx|max:5120\047,
]);
$uploadedFiles = [];
foreach ($request->file(\047files\047) as $file) {
$path = $file->store(\047uploads\047, \047public\047);
$uploadedFiles[] = [
\047original_name\047 => $file->getClientOriginalName(),
\047path\047 => $path,
\047url\047 => Storage::url($path),
\047size\047 => $file->getSize(),
\047mime_type\047 => $file->getMimeType(),
];
}
return response()->json([
\047success\047 => true,
\047message\047 => count($uploadedFiles) . \047 تم رفع الملفات بنجاح\047,
\047data\047 => $uploadedFiles,
], 201);
}
معالجة الصور والصور المصغرة
إنشاء صور مصغرة أو معالجة الصور باستخدام مكتبة Intervention Image:
<?php
// التثبيت: composer require intervention/image
use Intervention\Image\Facades\Image;
public function uploadWithThumbnail(Request $request)
{
$request->validate([
\047image\047 => \047required|image|max:10240\047,
]);
$image = $request->file(\047image\047);
$filename = time() . \047_\047 . uniqid() . \047.\047 . $image->extension();
// تخزين النسخة الأصلية
$originalPath = $image->storeAs(\047images/original\047, $filename, \047public\047);
// إنشاء صورة مصغرة
$thumbnailImage = Image::make($image)
->fit(300, 300)
->encode($image->extension(), 90);
Storage::disk(\047public\047)->put(
\047images/thumbnails/\047 . $filename,
$thumbnailImage
);
// إنشاء حجم متوسط
$mediumImage = Image::make($image)
->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})
->encode($image->extension(), 90);
Storage::disk(\047public\047)->put(
\047images/medium/\047 . $filename,
$mediumImage
);
return response()->json([
\047success\047 => true,
\047data\047 => [
\047original\047 => Storage::url($originalPath),
\047medium\047 => Storage::url(\047images/medium/\047 . $filename),
\047thumbnail\047 => Storage::url(\047images/thumbnails/\047 . $filename),
],
], 201);
}
نصيحة للأداء: عالج الصور بشكل غير متزامن باستخدام قوائم الانتظار للحصول على أوقات استجابة أفضل لواجهة برمجة التطبيقات. قم بتخزين النسخة الأصلية على الفور وأنشئ الصور المصغرة في الخلفية.
رفع الملفات المجزأة للملفات الكبيرة
للملفات الكبيرة جدًا، قم بتطبيق عمليات الرفع المجزأة:
<?php
public function uploadChunk(Request $request)
{
$request->validate([
\047chunk\047 => \047required|file\047,
\047chunk_number\047 => \047required|integer|min:0\047,
\047total_chunks\047 => \047required|integer|min:1\047,
\047file_name\047 => \047required|string\047,
\047upload_id\047 => \047required|string\047,
]);
$uploadId = $request->upload_id;
$chunkNumber = $request->chunk_number;
$totalChunks = $request->total_chunks;
$fileName = $request->file_name;
// تخزين الجزء مؤقتًا
$chunkPath = \047chunks/\047 . $uploadId . \047/chunk_\047 . $chunkNumber;
Storage::put($chunkPath, file_get_contents($request->file(\047chunk\047)));
// التحقق من رفع جميع الأجزاء
$uploadedChunks = count(Storage::files(\047chunks/\047 . $uploadId));
if ($uploadedChunks === $totalChunks) {
// دمج جميع الأجزاء
$finalPath = \047uploads/\047 . $fileName;
$finalFile = fopen(Storage::path($finalPath), \047wb\047);
for ($i = 0; $i < $totalChunks; $i++) {
$chunkContent = Storage::get(\047chunks/\047 . $uploadId . \047/chunk_\047 . $i);
fwrite($finalFile, $chunkContent);
}
fclose($finalFile);
// تنظيف الأجزاء
Storage::deleteDirectory(\047chunks/\047 . $uploadId);
return response()->json([
\047success\047 => true,
\047message\047 => \047اكتمل رفع الملف\047,
\047data\047 => [
\047url\047 => Storage::url($finalPath),
\047path\047 => $finalPath,
],
], 201);
}
return response()->json([
\047success\047 => true,
\047message\047 => \047تم رفع الجزء\047,
\047data\047 => [
\047uploaded_chunks\047 => $uploadedChunks,
\047total_chunks\047 => $totalChunks,
\047progress\047 => round(($uploadedChunks / $totalChunks) * 100, 2),
],
], 200);
}
نقطة نهاية تنزيل الملف
توفير تنزيلات ملفات آمنة من خلال واجهة برمجة التطبيقات الخاصة بك:
<?php
public function download($id)
{
$file = File::findOrFail($id);
// التحقق من التفويض
if (!auth()->user()->can(\047download\047, $file)) {
return response()->json([
\047success\047 => false,
\047message\047 => \047غير مصرح\047,
], 403);
}
// التحقق من وجود الملف
if (!Storage::disk(\047private\047)->exists($file->path)) {
return response()->json([
\047success\047 => false,
\047message\047 => \047الملف غير موجود\047,
], 404);
}
// تسجيل التنزيل
$file->increment(\047download_count\047);
// إرجاع الملف
return response()->download(
Storage::disk(\047private\047)->path($file->path),
$file->original_name
);
}
عناوين URL المؤقتة/الموقعة
إنشاء عناوين URL مؤقتة للوصول الآمن للملفات:
<?php
public function getTemporaryUrl($id)
{
$file = File::findOrFail($id);
// التحقق من التفويض
if (!auth()->user()->can(\047view\047, $file)) {
return response()->json([
\047success\047 => false,
\047message\047 => \047غير مصرح\047,
], 403);
}
// إنشاء عنوان URL مؤقت (صالح لمدة 30 دقيقة)
$url = Storage::disk(\047s3\047)->temporaryUrl(
$file->path,
now()->addMinutes(30)
);
return response()->json([
\047success\047 => true,
\047data\047 => [
\047url\047 => $url,
\047expires_at\047 => now()->addMinutes(30)->toIso8601String(),
],
]);
}
تمرين: بناء واجهة برمجة تطبيقات رفع الملفات
أنشئ نظام رفع ملفات كاملاً:
- أنشئ نقطة نهاية تقبل رفع الصور مع التحقق من الصحة
- أنشئ ثلاثة أحجام: مصغرة (150×150)، متوسطة (عرض 800 بكسل)، وأصلية
- قم بتخزين الملفات على Amazon S3 أو التخزين المحلي
- أرجع عناوين URL لجميع الأحجام الثلاثة
- أنشئ نقطة نهاية لسرد جميع الملفات المرفوعة مع الترقيم
- طبّق حذف الملفات مع فحوصات التفويض
- أضف مولد عنوان URL مؤقت للوصول الآمن للملفات
أفضل الممارسات لرفع الملفات
- تحقق بدقة: تحقق من نوع الملف والحجم والمحتوى، وليس الامتداد فقط
- حدد أحجام الملفات: حدد حدودًا معقولة بناءً على احتياجات تطبيقك
- استخدم أسماء فريدة: أنشئ أسماء ملفات فريدة لمنع التصادمات والكتابة فوقها
- عقّم أسماء الملفات: احذف أو استبدل الأحرف الخطرة في أسماء الملفات المقدمة من المستخدم
- التخزين الخاص: احتفظ بالملفات الحساسة خارج الدليل العام
- فحص البرامج الضارة: استخدم فحص مكافحة الفيروسات للملفات المرفوعة من المستخدم في الإنتاج
- حدد حدود الرفع: قم بتكوين الحد الأقصى لأحجام الرفع في كل من PHP وخادم الويب
- استخدم CDN: قدم الملفات الثابتة من خلال CDN للحصول على أداء أفضل
- طبّق الحصص: حدد إجمالي التخزين لكل مستخدم لمنع إساءة الاستخدام
- نظّف: احذف بانتظام الملفات اليتيمة أو المؤقتة
اعتبار الإنتاج: للتطبيقات ذات حركة المرور العالية، ضع في اعتبارك استخدام خدمة معالجة ملفات مخصصة أو نظام قائمة انتظار للتعامل مع عمليات الرفع بشكل غير متزامن. هذا يمنع حظر واجهة برمجة التطبيقات الخاصة بك ويوفر قابلية توسع أفضل.