Image for Upload dużych plików w Zend Framework 2 z wykorzystaniem Plupload

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 (wink)

Zainteresował Cię ten artykuł?

Oferujemy profesjonalne wsparcie programistów w technologii Web.
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

Clutch Recognizes GOGOmedia as a 2022 Development Leader in Poland

GOGOmedia is a multidisciplinary team with vast experience in the digital technology space. We deliver… Read More