Upload dużych plików w Zend Framework 2 z wykorzystaniem Plupload
Czas czytania:
Na pewno każdy programista PHP spotkał się z problemem uploadu dużych plików na serwer (taki 1,2 3GB). Jak zatem ominąć ograniczenia serwera max_upload czy post_max_size? Pomocny w takich przypadkach okazuje się plugin plupload z opcją dzielenia pliku na części podczas jego przesyłania. Jego zasada działania jest prosta – wysyłany plik jest najpierw porcjowany na mniejsze (akceptowalne przez serwer) paczki i dopiero po kolei wysyłany na serwer.
Przykład wdrożenia uploadu dużych plików
Pobieramy plugin pludpload ze strony producenta http://www.plupload.com/download/
Dodajemy do layotutu wymagane pliki:
->prependFile($this->basePath() . '/js/plupload/i18n/'.ModelLang::getLang().'.js')
->prependFile($this->basePath() . '/js/plupload/jquery.ui.plupload/jquery.ui.plupload.min.js')
->prependFile($this->basePath() . '/js/plupload/plupload.full.min.js')
Następnie wdrażamy obsługę uploadu po stronie javascriptu
initPlupload: function () { $('.plUploadPlugin').each(function () { var box = $(this); var type = box.attr('data-type'); box.find('.plProgress').hide(); var uploader = new plupload.Uploader({ url: $('#' + box.attr('data-start')).attr('data-url'), chunk_size: '1Mb', // maksymalny rozmiar części na jakie zostanie podzielony plik max_retries: 3, // Liczba prób unique_names: true, multi_selection: false, dragdrop: true, drop_element: box.attr('data-drop'), browse_button: box.attr('data-button'), }); uploader.setOption('filters', [ {title: "Image files", extensions: "jpg,jpeg,gif,png"} ]); uploader.init(); uploader.bind('Init', function (up, params) { box.find('.plProgress').html(0 + "%"); $('#' + box.attr('data-drop')).on('dragenter', function () { $(this).addClass('drop-file-active'); }); $('#' + box.attr('data-drop')).on('dragleave drop', function () { $(this).removeClass('drop-file-active'); }); }); uploader.bind('FilesAdded', function (up, files) { box.find('.plClearFile, .extraHtml').hide(); box.find('.plProgress').show(); $('#' + box.attr('data-status')).removeClass('afterUpload').addClass('beforeUpload'); var html = ''; plupload.each(files, function (file) { html = '
' + file.name + ' (' + plupload.formatSize(file.size) + ') '; }); document.getElementById(box.attr('data-filelist')).innerHTML = html; $('#' + box.attr('data-status')).removeClass('beforeUpload').addClass('afterUpload'); uploader.start(); }); uploader.bind('UploadProgress', function (up, file) { box.find('.imagePlaceholder').addClass('progress').find('img').remove(); Loading('show', box.find('.imagePlaceholder')); $('#' + box.attr('data-status')).removeClass('beforeUpload').addClass('progressUpload'); box.find('.plProgress').html(file.percent + "%"); }); uploader.bind('Error', function (up, err) { document.getElementById(box.attr('data-console')).innerHTML = "nError #" + err.code + ": " + err.message; }); uploader.bind('FileUploaded', function (up, file, response) { var obj = jQuery.parseJSON(response.response); if (obj.error && obj.error.code != 200) { box.parent().find('.error').removeClass('hide'); return; } $('#' + box.attr('data-status')).removeClass('progressUpload').removeClass('beforeUpload').addClass('afterUpload'); var size = box.attr('data-size'); Loading('show', box.find('.imagePlaceholder')); if (size && obj.urls) { box.find('.imagePlaceholder').find('.img').remove(); var url = obj.urls[size]; } else { var url = obj.url; } box.find('.imagePlaceholder').find('img').remove(); var img = $(''); box.find('.imagePlaceholder').append(img).css("width", "auto"); img.load(function () { Loading('hide', box.find('.imagePlaceholder')); box.find('.imagePlaceholder').removeClass('progress'); }); box.find('.plClearFile, .extraHtml').removeClass('hide'); box.find('.plClearFile, .extraHtml').show(); box.find('.plProgress').hide(); }); }) },
Kolejnym etapem jest dzielenie pliku na części oraz zapis całego pliku na serwerze.
W kontrolerze tworzymy akcję, na którą przekierowany jest upload w pliku js.
<pre class="wp-block-preformatted prettyprint" >use TesrServiceManagerStaticServiceManager; use ModelFileFile; use ModelFileFileBuilder; use ModelFileFileQuery; use ZendHttpPhpEnvironmentResponse; use ZendHttpResponseStream; use ZendMvcMvcEvent; use ZendSerializerAdapterJson; use ZendViewModelJsonModel; use ZendViewModelViewModel; use ZendHttpHeaders; public function pluploadAction() {
$config = $this->getServiceLocator()->get( 'Config' );
header( "Expires: Mon, 26 Jul 1997 05:00:00 GMT" );
header( "Last-Modified: " . gmdate( "D, d M Y H:i:s" ) . " GMT" );
header( "Cache-Control: no-store, no-cache, must-revalidate" );
header( "Cache-Control: post-check=0, pre-check=0", false );
header( "Pragma: no-cache" );
@set_time_limit( 300 );
$fileType = $this->getRequest()->getQuery( 'type', false );
$fileName = md5( $_REQUEST["name"] . $this->getRequest()->getQuery( 'box_id', uniqid() ) );
$fileShortPath = substr( $fileName, 0, 2 ) . DIRECTORY_SEPARATOR . substr( $fileName, 2, 2 );
$targetDir = $config['file']['uploadPath'] . $fileShortPath;
$cleanupTargetDir = true;
$maxFileAge = 5 * 3600;
$tmp = $config['file']['uploadPath'];
foreach ( explode( DIRECTORY_SEPARATOR, $fileShortPath ) as $dir ) {
$tmp .= DIRECTORY_SEPARATOR . $dir;
if ( ! is_dir( $tmp ) ) {
@mkdir( $tmp, 0777, true );
system( "chmod 777 " . $tmp );
}
}
if ( isset( $_REQUEST["name"] ) ) {
$fileName = $_REQUEST["name"];
} elseif ( ! empty( $_FILES ) ) {
$fileName = $_FILES["file"]["name"];
} else {
$fileName = uniqid( "file_" );
}
$fileName = MedtubeLibUrl::slug( $fileName );
$filePath = $targetDir . DIRECTORY_SEPARATOR . $fileName;
$chunk = isset( $_REQUEST["chunk"] ) ? intval( $_REQUEST["chunk"] ) : 0;
$chunks = isset( $_REQUEST["chunks"] ) ? intval( $_REQUEST["chunks"] ) : 0;
if ( $cleanupTargetDir ) {
if ( ! is_dir( $targetDir ) || ! $dir = opendir( $targetDir ) ) {
die( '{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}' );
}
while ( ( $file = readdir( $dir ) ) !== false ) {
$tmpfilePath = $targetDir . DIRECTORY_SEPARATOR . $file;
if ( $tmpfilePath == "{$filePath}.part" ) {
continue;
}
if ( preg_match( '/.part$/', $file ) && ( filemtime( $tmpfilePath ) < time() - $maxFileAge ) ) {
@unlink( $tmpfilePath );
}
}
closedir( $dir );
}
if ( ! $out = @fopen( "{$filePath}.part", $chunks ? "ab" : "wb" ) ) {
$resp = [
"jsonrpc" => "2.0",
"error" => [ "code" => 102, "message" => "Failed to open output stream." ],
"id" => "id"
];
return new JsonModel( $resp );
}
if ( ! empty( $_FILES ) ) {
if ( $_FILES["file"]["error"] || ! is_uploaded_file( $_FILES["file"]["tmp_name"] ) ) {
$resp = [
"jsonrpc" => "2.0",
"error" => [ "code" => 103, "message" => "Failed to move uploaded file." ],
"id" => "id"
];
return new JsonModel( $resp );
}
if ( ! $in = @fopen( $_FILES["file"]["tmp_name"], "rb" ) ) {
$resp = [
"jsonrpc" => "2.0",
"error" => [ "code" => 101, "message" => "Failed to open input stream." ],
"id" => "id"
];
return new JsonModel( $resp );
}
} else {
if ( ! $in = @fopen( "php://input", "rb" ) ) {
$resp = [
"jsonrpc" => "2.0",
"error" => [ "code" => 101, "message" => "Failed to open input stream." ],
"id" => "id"
];
return new JsonModel( $resp );
}
}
while ( $buff = fread( $in, 4096 ) ) {
fwrite( $out, $buff );
}
@fclose( $out );
@fclose( $in );
if ( ! $chunks || $chunk == $chunks - 1 ) {
rename( "{$filePath}.part", $filePath );
if ( filesize( $filePath ) == 0 ) {
$resp = [
"jsonrpc" => "2.0",
"error" => [ "code" => 104, "message" => "Failed to upload." ],
"id" => "id"
];
return new JsonModel( $resp );
}
try {
$file = new File();
$builder = new FileBuilder( $file );
$builder->buildFile( $fileShortPath, $fileName );
$file = $builder->getFile();
} catch ( Exception $e ) {
$resp = [
"jsonrpc" => "2.0",
"error" => [ "code" => 104, "message" => "Failed to convert." ],
"id" => "id"
];
return new JsonModel( $resp );
}
try {
$file->getBlobImage( [ 'maxSize' => 100 ] );
} catch ( Exception $e ) {
$resp = [
"jsonrpc" => "2.0",
"error" => [ "code" => 104, "message" => "Failed to convert." ],
"id" => "id"
];
return new JsonModel( $resp );
}
$url = $this->url()->fromRoute( 'image', [ 'id' => $file->getId(), 'size' => 150 ] );
return new JsonModel( [ "jsonrpc" => "2.0", "result" => null, "id" => $file->getId(), 'url' => $url);
}
return new JsonModel( [ "jsonrpc" => "2.0", "result" => null, "id" => "id" ] );
} </pre >
Teraz możemy wgrywać na serwer nawet wielo-gigabajtowe pliki, nadzorując progress uploadu. Bardziej zaawansowanym rozwiązaniem jest jeszcze wznawianie przerwanego uploadu (np. na wypadek zerwanego połączenia). Wymaga on jednak odrobinę więcej pracy
Zainteresował Cię ten artykuł?
Może Cię również zainteresować:
5 rzeczy, na które warto zwrócić uwagę, wybierając dedykowany system klasy ERP, WMS lub LMS
Tworzenie dedykowanych aplikacji web’owych (dostępnych przez przeglądarkę WWW z poziomu komputera, tabletu czy telefonu) jest… Read More
Warsztaty Discovery – 5 powodów dla których warto je przeprowadzić
Post pochodzi bezpośrednio z naszych oficjalnych kanałów na Social Media. W dynamicznym… Read More
Optymalizacja eCommerce vs. Zewnętrzny Dyrektor Technologiczny
🛠️ Studium przypadku 🛠️Post pochodzi bezpośrednio z naszych oficjalnych kanałów na Social… Read More