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