عند رفع الملفات في لارافل، قد تواجه مشاكل بسبب محدودية الذاكرة أو محدودية رفع الملفات في إعدادات السيرفر أو إعدادات php.
فهذا حل سهل لهذه المشكلة.
- تنزيل مكتبة laravel-chunk-upload.
composer require pion/laravel-chunk-upload
- ومن متطلباتها استخدام مكتبة رفع Front End وأنا جربت مكتبة Dropzone.
ويمكن تنزيلها عير هذا الأمر
npm install --save dropzone
- ولا تنس تفعيل JavaScript لكي تعمل المكتبة المستخدمة.
npm run dev
- في ملف ال blade
<form class="dropzone" id="my-form"></form>
- في ملف الroutes وليكن
web.php
Route::post('/file-upload', [FileController::class, 'upload']);
- في ملف
app.js
import Dropzone from 'dropzone'
import 'dropzone/dist/dropzone.css'
;(function () {
// this to work after vite app.js been loaded
const token = document
.querySelector('meta[name="csrf-token"]')
.getAttribute('content')
let myDropzone = new Dropzone('#my-form', {
url: '/file-upload', // هذا الرابط للرفع في web.php
method: 'post',
chunking: true,
chunkSize: 2097152, // 2MB
headers: {
'X-CSRF-TOKEN': token,
},
})
})()
في FileController
وهي مأخوذة من هذا المثال وفيه تفصيل كذلك في تسمية الملفات والمجلدات.. ولم أذكرها هنا اختصارا.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Events\ProcessUploadedFile;
use Pion\Laravel\ChunkUpload\Receiver\FileReceiver;
use Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException;
use Illuminate\Http\UploadedFile;
use Pion\Laravel\ChunkUpload\Handler\HandlerFactory;
class FileController extends Controller
{
public function upload(Request $request, FileReceiver $receiver)
{
// create the file receiver
$receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request));
// check if the upload is success, throw exception or return response you need
if ($receiver->isUploaded() === false) {
throw new UploadMissingFileException();
}
// receive the file
$save = $receiver->receive();
// check if the upload has finished (in chunk mode it will send smaller files)
if ($save->isFinished()) {
// save the file and return any response you need, current example uses `move` function. If you are
// not using move, you need to manually delete the file by unlink($save->getFile()->getPathname())
$filepath = $this->saveFile($save->getFile());
ProcessUploadedFile::dispatch($filepath);
}
}
protected function saveFile(UploadedFile $file)
{
$fileName = $this->createFilename($file);
$finalPath = storage_path("app/upload/");
$file->move($finalPath, $fileName);
return $finalPath . $fileName;
}
protected function createFilename(UploadedFile $file)
{
return $file->getClientOriginalName();
}
}
وهنا مثال افتراضي لما قد يحتويه الملف وليكن يحتوي email
و username
لنقم بتقسيم رفع المحتوى عن طريق jobs
/batch queues
، واستخدمنا كذلك php generators
عن طريق الكلمة المفتاحية yield
.
<?php
namespace App\Jobs;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Bus;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class ProcessUploadedFile implements ShouldQueue
{
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public string $filepath)
{
//
}
public function handle(): void
{
$fileBath = $this->filepath;
$generateRow = function ($row) {
return [
'email' => $row[1],
'username' => $row[2],
];
};
$batch = [];
foreach ($this->chunkFile($fileBath, $generateRow, 1000) as $chunk) {
$batch[] = new ProcessUploadedChunk($chunk);
}
Bus::batch($batch)
->allowFailures()
->catch(function ($batch, $exception) {
logger($batch->id . " failed! Exception: {$exception}");
})
->then(function ($batch) {
logger($batch->id . ' has completed successfully with no errors');
})
->finally(function ($batch) {
logger('finally ' . $batch->id . ' has completed');
})
->dispatch();
}
public function chunkFile(string $path, callable $generator, int $chunkSize)
{
$file = fopen($path, 'r');
$data = [];
for ($i = 1; ($row = fgetcsv($file, null, ',')) !== false; $i++) {
$data[] = $generator($row);
if ($i % $chunkSize === 0) {
yield $data;
$data = [];
}
}
if (!empty($data)) {
yield $data;
}
fclose($file);
}
}
وفي ملف ProcessUploadedChunk
وليكن رفع البيانات إلى User Model
بحسب مثالنا الافتراضي.
<?php
namespace App\Jobs;
use App\Models\User;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessUploadedChunk implements ShouldQueue
{
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public array $chunk)
{
//
}
/**
* Execute the job.
*/
public function handle(): void
{
User::insert($this->chunk);
}
}
وأود شكر الصديق العزيز مصطفى هموني على تعليمي لهذه المعلومات وإنما جمعتها في هذه التدوينة لإفادة الغير وكي تكون مرجعا للعودة إليها.