Laravel المتقدم
معالجة الملفات المتقدمة ومكتبات الوسائط
معالجة الملفات المتقدمة ومكتبات الوسائط
تتطلب إدارة الملفات والوسائط في تطبيقات Laravel أكثر من عمليات تحميل الملفات الأساسية. في هذا الدرس، سنستكشف حزمة Spatie Media Library القوية، وتقنيات معالجة الصور المتقدمة، وتحويلات الملفات، والتكامل مع التخزين السحابي، وعناوين URL الموقعة، وقدرات البث لبناء أنظمة إدارة وسائط قوية.
تثبيت Spatie Media Library
Spatie Media Library هي حزمة غنية بالميزات لربط الملفات بنماذج Eloquent.
# تثبيت الحزمة
composer require "spatie/laravel-medialibrary:^11.0"
# نشر الترحيل والتكوين
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-config"
# تشغيل الترحيلات
php artisan migrate
# اختياري: تثبيت تبعيات معالجة الصور
composer require "spatie/image"
# لمعالجة الصور المتقدمة، قم بتثبيت Imagick أو GD
# Ubuntu/Debian: sudo apt-get install php-imagick
# أو: sudo apt-get install php-gd
ملاحظة: تتطلب Spatie Media Library إما امتداد Imagick أو GD لـ PHP لمعالجة الصور. يوفر Imagick أداءً أفضل ومزيداً من الميزات.
الاستخدام الأساسي لمكتبة الوسائط
أضف قدرات الوسائط إلى نماذجك باستخدام واجهة HasMedia وسمة InteractsWithMedia.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Product extends Model implements HasMedia
{
use InteractsWithMedia;
// تسجيل مجموعات الوسائط
public function registerMediaCollections(): void
{
$this->addMediaCollection('images')
->useDisk('public');
$this->addMediaCollection('documents')
->acceptsMimeTypes(['application/pdf', 'application/msword'])
->useDisk('s3');
// مجموعة ملف واحد
$this->addMediaCollection('thumbnail')
->singleFile()
->useDisk('public');
}
// تسجيل تحويلات الوسائط (الصور المصغرة، الإصدارات المحسّنة)
public function registerMediaConversions(Media $media = null): void
{
$this->addMediaConversion('thumb')
->width(300)
->height(300)
->sharpen(10)
->performOnCollections('images');
$this->addMediaConversion('detail')
->width(1200)
->height(800)
->format('webp')
->quality(90)
->performOnCollections('images');
$this->addMediaConversion('preview')
->width(600)
->height(400)
->nonQueued() // إنشاء فوراً
->performOnCollections('images');
}
}
// تحميل الملفات في المتحكم
class ProductController extends Controller
{
public function store(Request $request)
{
$product = Product::create($request->validated());
// تحميل ملف واحد
if ($request->hasFile('thumbnail')) {
$product->addMediaFromRequest('thumbnail')
->toMediaCollection('thumbnail');
}
// تحميل ملفات متعددة
if ($request->hasFile('images')) {
foreach ($request->file('images') as $image) {
$product->addMedia($image)
->withCustomProperties(['order' => $loop->index])
->toMediaCollection('images');
}
}
return redirect()->route('products.show', $product);
}
public function show(Product $product)
{
// الحصول على أول عنصر وسائط
$thumbnail = $product->getFirstMediaUrl('thumbnail');
// الحصول على جميع عناصر الوسائط
$images = $product->getMedia('images');
// الحصول على عنوان URL تحويل محدد
$thumbUrl = $product->getFirstMediaUrl('images', 'thumb');
$detailUrl = $product->getFirstMediaUrl('images', 'detail');
return view('products.show', compact('product', 'thumbnail', 'images'));
}
}
معالجة الصور المتقدمة
قم بتطبيق تحويلات صور متطورة باستخدام Spatie Image والمعالجات المخصصة.
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\Image\Manipulations;
class Post extends Model implements HasMedia
{
use InteractsWithMedia;
public function registerMediaConversions(Media $media = null): void
{
// تغيير الحجم الأساسي
$this->addMediaConversion('thumb')
->width(200)
->height(200)
->sharpen(10);
// طرق الملاءمة
$this->addMediaConversion('square')
->fit(Manipulations::FIT_CROP, 500, 500)
->background('#ffffff');
$this->addMediaConversion('contain')
->fit(Manipulations::FIT_CONTAIN, 800, 600)
->background('#f5f5f5');
// تحويل التنسيق
$this->addMediaConversion('webp')
->format('webp')
->quality(85);
// علامة مائية
$this->addMediaConversion('watermarked')
->width(1200)
->watermark(public_path('images/watermark.png'))
->watermarkPosition('bottom-right')
->watermarkPadding(10, 10);
// معالجات متعددة
$this->addMediaConversion('optimized')
->width(1920)
->height(1080)
->fit(Manipulations::FIT_MAX, 1920, 1080)
->format('webp')
->quality(90)
->optimize()
->sharpen(5);
// الصور المستجيبة
$this->addMediaConversion('responsive')
->withResponsiveImages()
->format('webp');
// معالجة مخصصة باستخدام callback
$this->addMediaConversion('custom')
->manipulate(function (Image $image) {
return $image->greyscale()
->blur(10)
->brightness(50);
});
}
}
// معالج صور مخصص
use Spatie\Image\Image;
use Spatie\Image\Manipulations;
class ImageManipulator
{
public static function createCollage(array $imagePaths, string $outputPath)
{
$image = Image::load($imagePaths[0])
->fit(Manipulations::FIT_CROP, 500, 500);
// أضف المزيد من الصور جنباً إلى جنب
foreach (array_slice($imagePaths, 1) as $path) {
$nextImage = Image::load($path)
->fit(Manipulations::FIT_CROP, 500, 500);
// دمج الصور (يتطلب تنفيذاً مخصصاً)
}
$image->save($outputPath);
}
public static function addTextOverlay(string $imagePath, string $text, string $outputPath)
{
$image = Image::load($imagePath);
// هذا يتطلب Imagick
$image->manipulate(function ($imagick) use ($text) {
$draw = new \ImagickDraw();
$draw->setFontSize(50);
$draw->setFillColor('white');
$draw->setGravity(\Imagick::GRAVITY_CENTER);
$imagick->annotateImage($draw, 0, 0, 0, $text);
return $imagick;
});
$image->save($outputPath);
}
}
نصيحة: استخدم طريقة
withResponsiveImages() لإنشاء أحجام متعددة تلقائياً للصور المستجيبة. هذا ينشئ مجموعات صور متوافقة مع srcset يمكن للمتصفحات الاختيار منها بناءً على حجم منفذ العرض.
التكامل مع التخزين السحابي
قم بتخزين ملفات الوسائط على الخدمات السحابية مثل S3 أو Cloudinary أو DigitalOcean Spaces.
// التكوين في config/filesystems.php
'disks' => [
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'visibility' => 'public',
],
'cloudinary' => [
'driver' => 'cloudinary',
'cloud_name' => env('CLOUDINARY_CLOUD_NAME'),
'api_key' => env('CLOUDINARY_API_KEY'),
'api_secret' => env('CLOUDINARY_API_SECRET'),
],
'spaces' => [
'driver' => 's3',
'key' => env('DO_SPACES_KEY'),
'secret' => env('DO_SPACES_SECRET'),
'endpoint' => env('DO_SPACES_ENDPOINT'),
'region' => env('DO_SPACES_REGION'),
'bucket' => env('DO_SPACES_BUCKET'),
],
],
// استخدام أقراص مختلفة لمجموعات مختلفة
class Video extends Model implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
// تخزين الصور المصغرة محلياً
$this->addMediaCollection('thumbnails')
->useDisk('public');
// تخزين الفيديوهات على S3
$this->addMediaCollection('videos')
->useDisk('s3');
// تخزين الفيديوهات المحولة على Cloudinary
$this->addMediaCollection('transcoded')
->useDisk('cloudinary');
}
}
// التحميل المباشر إلى S3
use Illuminate\Support\Facades\Storage;
public function uploadToS3(Request $request)
{
$path = $request->file('video')->store('videos', 's3');
// الحصول على عنوان URL العام
$url = Storage::disk('s3')->url($path);
Video::create([
'title' => $request->title,
'path' => $path,
'url' => $url,
]);
}
تحذير: قم دائماً بالتحقق من صحة تحميلات الملفات على جانب الخادم. تحقق من أنواع الملفات والأحجام واستخدم فحص الفيروسات للملفات التي يحملها المستخدم. لا تثق أبداً في التحقق من جانب العميل وحده.
عناوين URL الموقعة للملفات الخاصة
أنشئ عناوين URL آمنة ومؤقتة للوصول إلى الملفات الخاصة دون الكشف عن روابط تنزيل دائمة.
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
// إنشاء عنوان URL موقع مؤقت (ينتهي في 30 دقيقة)
$url = Storage::disk('s3')->temporaryUrl(
'private/document.pdf',
now()->addMinutes(30)
);
// عنوان URL موقع مخصص للتنزيلات المحمية
class DocumentController extends Controller
{
public function show(Document $document)
{
// التحقق من الأذونات
if (!auth()->user()->canView($document)) {
abort(403);
}
// إنشاء عنوان URL موقع
$signedUrl = URL::temporarySignedRoute(
'documents.download',
now()->addHours(1),
['document' => $document->id]
);
return view('documents.show', [
'document' => $document,
'downloadUrl' => $signedUrl,
]);
}
public function download(Request $request, Document $document)
{
// التحقق من عنوان URL الموقع
if (!$request->hasValidSignature()) {
abort(401, 'رابط تنزيل غير صالح أو منتهي الصلاحية');
}
// التحقق من الأذونات مرة أخرى
if (!auth()->user()->canView($document)) {
abort(403);
}
return Storage::disk('s3')->download($document->path, $document->filename);
}
}
// في routes/web.php
Route::get('/documents/{document}/download', [DocumentController::class, 'download'])
->name('documents.download')
->middleware('signed');
// مكتبة الوسائط مع عناوين URL موقعة
class ProtectedDocument extends Model implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
$this->addMediaCollection('files')
->useDisk('s3-private');
}
public function getTemporaryUrl($expiresAt = null)
{
$expiresAt = $expiresAt ?? now()->addHour();
return $this->getFirstMedia('files')
->getTemporaryUrl($expiresAt);
}
}
بث الملفات وطلبات النطاق
نفذ بث ملفات فعال للملفات الكبيرة والفيديوهات والصوت مع دعم طلبات النطاق.
use Symfony\Component\HttpFoundation\StreamedResponse;
class VideoStreamController extends Controller
{
public function stream(Video $video)
{
// التحقق من التفويض
if (!auth()->user()->canWatch($video)) {
abort(403);
}
$path = $video->getFirstMedia('videos')->getPath();
$size = filesize($path);
$start = 0;
$end = $size - 1;
// معالجة طلب النطاق للبحث
if (request()->header('Range')) {
$range = request()->header('Range');
list(, $range) = explode('=', $range, 2);
if (strpos($range, ',') !== false) {
return response('Requested Range Not Satisfiable', 416)
->header('Content-Range', "bytes */{$size}");
}
if ($range == '-') {
$start = $size - substr($range, 1);
} else {
$range = explode('-', $range);
$start = $range[0];
$end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $end;
}
$start = max($start, 0);
$end = min($end, $size - 1);
$length = $end - $start + 1;
return response()->stream(function () use ($path, $start, $length) {
$stream = fopen($path, 'rb');
fseek($stream, $start);
echo fread($stream, $length);
fclose($stream);
}, 206, [
'Content-Type' => 'video/mp4',
'Content-Length' => $length,
'Content-Range' => "bytes {$start}-{$end}/{$size}",
'Accept-Ranges' => 'bytes',
]);
}
// بث الملف الكامل
return response()->stream(function () use ($path) {
$stream = fopen($path, 'rb');
fpassthru($stream);
fclose($stream);
}, 200, [
'Content-Type' => 'video/mp4',
'Content-Length' => $size,
'Accept-Ranges' => 'bytes',
]);
}
// البث التكيفي لـ HLS
public function streamHls(Video $video)
{
$playlist = $video->getFirstMedia('playlists')->getPath();
return response()->file($playlist, [
'Content-Type' => 'application/vnd.apple.mpegurl',
]);
}
}
تمرين 1: ابنِ نظام معرض منتجات باستخدام Spatie Media Library. نفذ التحميل بالسحب والإفلات، والإنشاء التلقائي للصور المصغرة بأحجام متعددة (صغير، متوسط، كبير)، والقدرة على إعادة ترتيب الصور. قم بتضمين حذف الصور وعارض lightbox.
تمرين 2: أنشئ نظام إدارة مستندات مع ضوابط الوصول. قم بتخزين الملفات على S3، وأنشئ عناوين URL موقعة مؤقتة تنتهي صلاحيتها بعد ساعة واحدة، وتتبع التنزيلات، ونفذ فحص الفيروسات باستخدام ClamAV أو خدمة سحابية قبل السماح بالتنزيلات.
تمرين 3: ابنِ منصة بث فيديو مع دعم مستويات جودة متعددة. نفذ تحميل الفيديو مع التحويل التلقائي إلى 360p و 720p و 1080p. أنشئ معاينات صور مصغرة في طوابع زمنية متعددة وادعم طلبات النطاق للبحث أثناء التشغيل.