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