web-dev-qa-db-fra.com

PHPExcel manque de 256, 512 et 1024 Mo de RAM

Je ne comprends pas La table XSLX fait environ 3 Mo de taille, mais 1024 Mo de RAM ne suffisent pas pour que PHPExcel puisse la charger en mémoire?

Je pourrais faire quelque chose d'horriblement faux ici:

function ReadXlsxTableIntoArray($theFilePath)
{
    require_once('PHPExcel/Classes/PHPExcel.php');
    $inputFileType = 'Excel2007';
    $objReader = PHPExcel_IOFactory::createReader($inputFileType);
    $objReader->setReadDataOnly(true);
    $objPHPExcel = $objReader->load($theFilePath);
    $rowIterator = $objPHPExcel->getActiveSheet()->getRowIterator();
    $arrayData = $arrayOriginalColumnNames = $arrayColumnNames = array();
    foreach($rowIterator as $row){
        $cellIterator = $row->getCellIterator();
        $cellIterator->setIterateOnlyExistingCells(false); // Loop all cells, even if it is not set
        if(1 == $row->getRowIndex ()) {
            foreach ($cellIterator as $cell) {
                $value = $cell->getCalculatedValue();
                $arrayOriginalColumnNames[] = $value;
                // let's remove the diacritique
                $value = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $value);
                // and white spaces
                $valueExploded = explode(' ', $value);
                $value = '';
                // capitalize the first letter of each Word
                foreach ($valueExploded as $Word) {
                    $value .= ucfirst($Word);
                }
                $arrayColumnNames[] = $value;
            }
            continue;
        } else {
            $rowIndex = $row->getRowIndex();
            reset($arrayColumnNames);
            foreach ($cellIterator as $cell) {
                $arrayData[$rowIndex][current($arrayColumnNames)] = $cell->getCalculatedValue();
                next($arrayColumnNames);
            }
        }
    }
    return array($arrayOriginalColumnNames, $arrayColumnNames, $arrayData);
}

La fonction ci-dessus lit les données d'un tableau Excel dans un tableau.

Aucune suggestion?

Au début, j’avais autorisé PHP à utiliser 256 Mo de RAM. Ce n'était pas suffisant. J'ai ensuite doublé le montant et ensuite essayé 1024MB. Il manque toujours de mémoire avec cette erreur:

Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 50331648 bytes) in D:\data\o\WebLibThirdParty\src\PHPExcel\Classes\PHPExcel\Reader\Excel2007.php on line 688

Fatal error (shutdown): Allowed memory size of 1073741824 bytes exhausted (tried to allocate 50331648 bytes) in D:\data\o\WebLibThirdParty\src\PHPExcel\Classes\PHPExcel\Reader\Excel2007.php on line 688
23
Richard Knop

Beaucoup de choses ont été écrites sur l'utilisation de la mémoire de PHPExcel sur le forum PHPExcel; Par conséquent, la lecture de certaines de ces discussions précédentes peut vous donner quelques idées. PHPExcel contient une représentation "en mémoire" d'une feuille de calcul et est sensible aux limitations de mémoire PHP.

La taille physique du fichier est en grande partie hors de propos. Il est bien plus important de savoir combien de cellules (rangées * colonnes sur chaque feuille de calcul) il contient.

La «règle empirique» que j'ai toujours utilisée est une moyenne d'environ 1k/cellule, donc un classeur de cellules 5M va nécessiter 5 Go de mémoire. Cependant, il existe plusieurs façons de réduire cette exigence. Celles-ci peuvent être combinées, en fonction des informations auxquelles vous avez besoin d'accéder dans votre classeur et de ce que vous voulez en faire.

Si vous avez plusieurs feuilles de calcul, mais n'avez pas besoin de toutes les charger, vous pouvez limiter les feuilles de calcul que le Reader va charger à l'aide de la méthode setLoadSheetsOnly () . Pour charger une seule feuille de calcul nommée:

$inputFileType = 'Excel5'; 
$inputFileName = './sampleData/example1.xls';
$sheetname = 'Data Sheet #2'; 
/**  Create a new Reader of the type defined in $inputFileType  **/
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
/**  Advise the Reader of which WorkSheets we want to load  **/ 
$objReader->setLoadSheetsOnly($sheetname); 
/**  Load $inputFileName to a PHPExcel Object  **/
$objPHPExcel = $objReader->load($inputFileName);

Ou vous pouvez spécifier plusieurs feuilles de calcul avec un appel à setLoadSheetsOnly () en transmettant un tableau de noms:

$inputFileType = 'Excel5'; 
$inputFileName = './sampleData/example1.xls';
$sheetnames = array('Data Sheet #1','Data Sheet #3'); 
/** Create a new Reader of the type defined in $inputFileType **/ 
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
/** Advise the Reader of which WorkSheets we want to load **/ 
$objReader->setLoadSheetsOnly($sheetnames); 
/**  Load $inputFileName to a PHPExcel Object  **/
$objPHPExcel = $objReader->load($inputFileName);

Si vous avez seulement besoin d'accéder à une partie d'une feuille de calcul, vous pouvez définir un filtre de lecture pour identifier uniquement les cellules que vous voulez réellement charger:

$inputFileType = 'Excel5'; 
$inputFileName = './sampleData/example1.xls';
$sheetname = 'Data Sheet #3'; 

/**  Define a Read Filter class implementing PHPExcel_Reader_IReadFilter  */ 
class MyReadFilter implements PHPExcel_Reader_IReadFilter {
    public function readCell($column, $row, $worksheetName = '') {
        //  Read rows 1 to 7 and columns A to E only 
        if ($row >= 1 && $row <= 7) {
           if (in_array($column,range('A','E'))) { 
              return true;
           }
        } 
        return false;
    }
}

/**  Create an Instance of our Read Filter  **/ 
$filterSubset = new MyReadFilter(); 
/** Create a new Reader of the type defined in $inputFileType **/ 
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
/**  Advise the Reader of which WorkSheets we want to load 
     It's more efficient to limit sheet loading in this manner rather than coding it into a Read Filter  **/ 
$objReader->setLoadSheetsOnly($sheetname); 
echo 'Loading Sheet using filter';
/**  Tell the Reader that we want to use the Read Filter that we've Instantiated  **/ 
$objReader->setReadFilter($filterSubset); 
/**  Load only the rows and columns that match our filter from $inputFileName to a PHPExcel Object  **/
$objPHPExcel = $objReader->load($inputFileName);

À l'aide de filtres de lecture, vous pouvez également lire un classeur en "morceaux", de sorte qu'un seul morceau soit résident en mémoire à la fois:

$inputFileType = 'Excel5'; 
$inputFileName = './sampleData/example2.xls';

/**  Define a Read Filter class implementing PHPExcel_Reader_IReadFilter  */ 
class chunkReadFilter implements PHPExcel_Reader_IReadFilter {
    private $_startRow = 0;
    private $_endRow = 0;

    /**  Set the list of rows that we want to read  */ 
    public function setRows($startRow, $chunkSize) { 
        $this->_startRow    = $startRow; 
        $this->_endRow      = $startRow + $chunkSize;
    } 

    public function readCell($column, $row, $worksheetName = '') {
        //  Only read the heading row, and the rows that are configured in $this->_startRow and $this->_endRow 
        if (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow)) { 
           return true;
        }
        return false;
    } 
}

/**  Create a new Reader of the type defined in $inputFileType  **/
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
/**  Define how many rows we want to read for each "chunk"  **/ 
$chunkSize = 20;
/**  Create a new Instance of our Read Filter  **/ 
$chunkFilter = new chunkReadFilter(); 
/**  Tell the Reader that we want to use the Read Filter that we've Instantiated  **/ 
$objReader->setReadFilter($chunkFilter); 

/**  Loop to read our worksheet in "chunk size" blocks  **/ 
/**  $startRow is set to 2 initially because we always read the headings in row #1  **/
for ($startRow = 2; $startRow <= 65536; $startRow += $chunkSize) { 
    /**  Tell the Read Filter, the limits on which rows we want to read this iteration  **/ 
    $chunkFilter->setRows($startRow,$chunkSize); 
    /**  Load only the rows that match our filter from $inputFileName to a PHPExcel Object  **/ 
    $objPHPExcel = $objReader->load($inputFileName); 
    //    Do some processing here 

    //    Free up some of the memory 
    $objPHPExcel->disconnectWorksheets(); 
    unset($objPHPExcel); 
}

Si vous n'avez pas besoin de charger les informations de formatage, mais uniquement les données de la feuille de calcul, la méthode setReadDataOnly () indiquera au lecteur de ne charger que les valeurs de cellule, en ignorant le formatage de cellule:

$inputFileType = 'Excel5';
$inputFileName = './sampleData/example1.xls';
/** Create a new Reader of the type defined in $inputFileType **/ 
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
/** Advise the Reader that we only want to load cell data, not formatting **/ 
$objReader->setReadDataOnly(true);
/**  Load $inputFileName to a PHPExcel Object  **/
$objPHPExcel = $objReader->load($inputFileName);

Utilisez la mise en cache de cellules. Il s'agit d'une méthode permettant de réduire la mémoire PHP requise pour chaque cellule, mais à un coût en rapidité. Cela fonctionne en stockant les objets de cellules dans un format compressé, ou en dehors de la mémoire de PHP (par exemple, disque, APC, memcache) ... mais plus vous économisez de la mémoire, plus vos scripts s'exécutent lentement. Cependant, vous pouvez réduire la mémoire requise par chaque cellule à environ 300 octets. Par conséquent, les cellules hypothétiques de 5 M auraient besoin d'environ 1,4 Go de mémoire PHP.

La mise en cache de cellules est décrite dans la section 4.2.1 de la documentation pour les développeurs.

MODIFIER

En regardant votre code, vous utilisez les itérateurs, qui ne sont pas particulièrement efficaces, et vous créez un tableau de données de cellules. Vous voudrez peut-être examiner la méthode toArray (), qui est déjà intégrée à PHPExcel, et le fait pour vous. Jetez également un coup d'œil à cette discussion récente sur SO à propos de la nouvelle méthode variant rangeToArray () pour créer un tableau associatif de données de ligne.

71
Mark Baker

J'ai eu le même problème de mémoire avec PHPExcel et en fait avec toutes les autres bibliothèques. Comme l'explique Mark Baker, la lecture des données en morceaux pourrait permettre de résoudre le problème (la mise en cache fonctionne également), mais il est apparu que le problème de mémoire était devenu un problème de temps. Le temps de lecture et d’écriture étant exponentiel, il ne s’agissait donc pas d’un bon calcul pour les grandes feuilles de calcul.

PHPExcel et d'autres ne sont pas conçus pour gérer des fichiers volumineux. J'ai donc créé une bibliothèque qui résout ce problème. Vous pouvez le vérifier ici: https://github.com/box/spout

J'espère que cela pourra aider!

9
Adrien

Lorsque vous travaillez avec PHPExcel, vous pouvez prendre beaucoup de mesures pour économiser moins de mémoire. Je vous recommande de prendre les mesures suivantes pour optimiser l'utilisation de la mémoire avant de modifier la limite de mémoire de votre serveur dans Apache.

/* Use the setReadDataOnly(true);*/
    $objReader->setReadDataOnly(true);

/*Load only Specific Sheets*/
    $objReader->setLoadSheetsOnly( array("1", "6", "6-1", "6-2", "6-3", "6-4", "6-5", "6-6", "6-7", "6-8") );

/*Free memory when you are done with a file*/
$objPHPExcel->disconnectWorksheets();
   unset($objPHPExcel);

Évitez d’utiliser de très gros fichiers Exel, n’oubliez pas que c’est la taille du fichier qui ralentit le processus et le bloque.

Évitez d’utiliser getCalculatedValue (); fonctionner lors de la lecture des cellules.

5
pancy1

Vous pouvez essayer PHP Excel http://ilia.ws/archives/237-PHP-Excel-Extension-0.9.1.html C'est une extension C pour PHP et son très rapide. (Utilise également moins de mémoire que les implémentations PHP)

2
osm

Il suffit de republier mon message d'un autre fil. Il décrit différentes approches de génération ou d’édition de feuilles de calcul Excel au niveau du serveur, qui doivent être prises en compte. Pour une grande quantité de données, je ne recommanderais pas d'outils comme PHPExcel ou ApachePOI (pour Java) en raison de leurs besoins en mémoire. Il existe un autre moyen très pratique (bien que peut-être un peu fastidieux) d’injecter des données dans des feuilles de calcul. La génération ou la mise à jour côté serveur des feuilles de calcul Excel peut être réalisée, ce qui permet une édition XML simple. Vous pouvez avoir une feuille de calcul XLSX sur le serveur et chaque fois que des données sont collectées à partir de dB, vous la décompressez à l’aide de php. Ensuite, vous accédez à des fichiers XML spécifiques contenant le contenu de feuilles de calcul devant être injectées et insérant des données manuellement. Ensuite, vous compressez le dossier de la feuille de calcul afin de le distribuer en tant que fichier XLS standard. L'ensemble du processus est assez rapide et fiable. De toute évidence, il existe peu de problèmes et problèmes liés à l'organisation interne du fichier XLSX/Open XML (par exemple, Excel a tendance à stocker toutes les chaînes dans un tableau séparé et à utiliser des références à ce tableau dans des fichiers de feuille de calcul). Mais lorsque vous n'injectez que des données telles que des nombres et des chaînes, ce n'est pas si difficile. Si quelqu'un est intéressé, je peux fournir du code.

1
bazinac

Dans mon cas, phpexcel a toujours parcouru 19999 lignes. peu importe, combien de lignes ont été réellement remplies. 100 lignes de données se sont toujours retrouvées avec une erreur de mémoire.

Peut-être devez-vous simplement vérifier, si les cellules de la ligne en cours sont vides, puis "continuer" ou casser la boucle, cela itère les lignes.

1
Robert

J'ai rencontré ce problème et malheureusement aucune des solutions suggérées ne pourrait m'aider. J'ai besoin des fonctionnalités fournies par PHPExcel (formules, style conditionnel, etc.), de sorte que l'utilisation d'une autre bibliothèque n'était pas une option.

Ce que j'ai finalement fait a été d'écrire chaque feuille de calcul dans un fichier individuel (temporaire), puis de combiner ces fichiers séparés avec un logiciel spécial que j'ai écrit. Cela a réduit ma consommation de mémoire de> 512 Mo à moins de 100 Mo. Voir https://github.com/infostreams/Excel-merge si vous rencontrez le même problème.

1
Edward