PHPExcel и большие файлы

в 16:39, , рубрики: php, метки:

PHPExcel — отличная библиотека с огромным функционалом по работе с форматами xls, xlsx. Можно считывать, записывать, менять форматирование, задавать формулы, а из xlsx можно и картинки вытаскивать.

На хабре уже был пост про эту библиотеку - Универсальное чтение ячеек в PHPExcel. Я остановлюсь только лишь на главном минусе PHPExcel — вечно памяти не хватает, все время сыпятся ошибки «Fatal error: Out of memory». Этот пост о том, как это обойти.

Чтение файла

Для чтения большого файла (~25 000 строк) я использовал комплексное решение.

Во-первых, считываем файл не целиком, а по несколько строк. Это делается так:

import_xls.php


<?php
require_once 'path/to/PHPExcel/IOFactory.php';

class chunkReadFilter implements PHPExcel_Reader_IReadFilter
{
private $_startRow = 0;
private $_endRow = 0;

public function setRows($startRow, $chunkSize) {
$this->_startRow = $startRow;
$this->_endRow = $startRow + $chunkSize;
}

public function readCell($column, $row, $worksheetName = '') {
if (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow)) {
return true;
}
return false;
}
}

session_start();

if ($_SESSION['startRow']) $startRow = $_SESSION['startRow'];
else $startRow = 13;

$inputFileType = 'Excel5';
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$chunkSize = 20;
$chunkFilter = new chunkReadFilter();

while ($startRow <= 65000) {
$chunkFilter->setRows($startRow,$chunkSize);
$objReader->setReadFilter($chunkFilter);
$objReader->setReadDataOnly(true);
$objPHPExcel = $objReader->load($fileName);
//Что-то с этими строками делаем
$startRow += $chunkSize;
$_SESSION['startRow'] = $startRow;

unset($objReader);

unset($objPHPExcel);

}

echo "The End";
unset($_SESSION['startRow']);
?>

Собственно класс chunkReadFilter — это то, что нам нужно. Устанавливаем его в качестве фильтра для чтения файла, и файл будет загружаться не целиком, а лишь определенное количество строк.

Во-вторых, кроме него еще используем такую полезную опцию как ReadDataOnly. Как следует из названия, она позволяет не загружать форматирование документа, высвобождая место для данных.

И, в-третьих, конечно же используем unset(). Это так же поможет высвободить память.

Но помимо нехватки памяти возникает другая проблема. На большинстве shared-хостингов у php-скриптов помимо ограничения на использование памяти, еще стоит ограничение на время выполнения. И крайне вероятно, что этого времени хватать не будет. Лично я обошел эту проблему при помощи сессий и повторяющихся ajax запросов. В коде представленном выше вы уже увидели использование сессий и завершающее «The End».

А вот код клиентской части.

import_xls.html

<html>

<head>

<title>Импорт прайс-листа</title>
<script src="/media/js/jquery.js" type="text/javascript"></script>
<script src="/media/js/import-xls.js" type="text/javascript"></script>
</head>
<body>

<h1>Импорт прайс-листа</h1>
Подождите завершения импорта, не закрывайте данную страницу!
<div id="progress-bar">
</div>
<div id="content">
</div>

</body>

</html>

import-xls.js


function repeat_import() {
$.get("import_xls.php", function(data){
$("#progress-bar").append("I");
if (data == "The End") {
$("#content").html("<h2>Импорт завершен!</h2>");
}
else {
$("#content").html(data);
repeat_import();
}
});
}

$(function (){
/*$(document).ajaxError(function (e, jqxhr, settings, exception) {
error_message = jqxhr.status;
alert(error_message);
});*/
repeat_import();
});

Т.е. мы отправляем ajax-запрос нашему скрипту import_xls.php, ждем ответа, и, если ответ нас не устраивает, посылаем новый ajax-запрос. Встречал еще в сети решение без использования AJAX — при помощи редиректа в самом php. Обработка файла делится на малое количество строк и после этого вставляется код:

header ("Location: import_xls.php");).

Но лично мне больше нравится решение с AJAX, потому что тут можно легко и просто добавить прогресс-бар и какие-нибудь другие рюшечки и плюшечки. Кстати, внимательный читатель заметил, что в моем коде простейший прогресс-бар уже реализован. Дополнительный немаловажный момент: в решение с AJAX не нужно заранее знать сколько строк скрипт сможет обработать за один проход.

Запись файла

Для записи файла в формате xls величиной так же около 25 000 строк крайне полезно использовать следующий код:

$cacheMethod = PHPExcel_CachedObjectStorageFactory::cache_to_phpTemp;
$cacheSettings = array( 'memoryCacheSize ' => '256MB');
PHPExcel_Settings::setCacheStorageMethod($cacheMethod, $cacheSettings);

Можно еще поиграться с методами кеширования. Помимо кеширования во временной директории php еще поддерживается memcache:

$cacheMethod = PHPExcel_CachedObjectStorageFactory::cache_to_memcache; $cacheSettings = array( 'memcacheServer' => 'localhost', 'memcachePort' => 11211, 'cacheTime' => 600 );

А так же cache_to_discISAM.

Автор: MParshin

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js