web-dev-qa-db-fra.com

PHPExcel très lent - des pistes d'amélioration?

Je génère des rapports au format .xlsx avec PHPExcel. Les premiers tests avec de petits ensembles de données (des dizaines de lignes, 3 feuilles) étaient acceptables. Désormais, lorsque vous l'utilisez sur des données de production réelles contenant plus de 500 lignes sur chaque feuille, le processus devient incroyablement lent. Il faut 48 secondes pour générer un fichier et lors de l’exécution d’un rapport combinant plus d’informations, tout échoue avec Fatal error: Maximum execution time of 30 seconds exceeded in PHPExcel/Worksheet.php on line 1041. Parfois, il se trouve dans un autre fichier PHPExcel. Je doute donc que l'emplacement exact soit pertinent.

Idéalement, je voudrais accélérer si possible. Sinon, augmentez au moins la limite d'exécution de ce script.

Les seules suggestions que j'ai vues jusqu'à présent étaient de styliser des plages plutôt que des cellules individuelles. Malheureusement, je fais déjà mon style dans les gammes et c'est plutôt minimal aussi. D'autres suggestions?

40
SaltyNuts

Remplit-il la feuille de calcul? ou économiser? que tu trouves trop lent?

Comment remplissez-vous la feuille de calcul avec les données?

  • L'utilisation de la méthode fromArray() est plus efficace que le remplissage de chaque cellule, en particulier si vous utilisez le classeur de valeurs avancées pour définir automatiquement les types de données de cellules.
  • Si vous définissez des valeurs pour chaque cellule d'une feuille à l'aide de 

    $objPHPExcel->getActiveSheet()->setCellValue('A1',$x);
    $objPHPExcel->getActiveSheet()->setCellValue('B1',$y);
    

    utilisation

    $sheet = $objPHPExcel->getActiveSheet();
    $sheet->setCellValue('A1',$x);
    $sheet->setCellValue('B1',$y);
    

    de sorte que vous n'accédez qu'une fois à la méthode getActiveSheet(); ou que vous tiriez parti de l'interface fluide pour définir plusieurs cellules avec un seul appel à $objPHPExcel->getActiveSheet()

    $objPHPExcel->getActiveSheet()->setCellValue('A1',$x)
                                  ->setCellValue('B1',$y);
    

Vous avez commenté l'application de styles à des plages de cellules: 

  • Vous avez également la possibilité d'utiliser applyFromArray() pour définir une grande variété de paramètres de style en une fois.
  • C'est beaucoup plus efficace si vous pouvez appliquer des styles à une colonne ou à une ligne plutôt qu'à une plage.

Si vous utilisez des formules dans votre classeur, enregistrez:

  • Utilisation 

    $objWriter->setPreCalculateFormulas(false)
    

    désactiver le calcul des formules au sein même de PHPExcel.

Ce ne sont là que quelques astuces pour améliorer les performances. De plus, de nombreux autres suggestions ont été faites dans les discussions du forum. Ils n’aideront pas forcément tous, mais votre classeur spécifique ne vous donnera pas d’absolus absolus, mais vous devriez pouvoir améliorer cette vitesse lente. Même le petit bloc-notes que j'utilise pour le développement peut écrire un fichier Excel 2007 de 3 feuilles de calcul, 20 colonnes, 2 000 lignes plus rapidement que votre serveur de production.

MODIFIER

S'il était possible d'améliorer simplement la vitesse de PHPExcel elle-même, je l'aurais déjà fait depuis longtemps. En l'état actuel, je teste constamment les performances pour voir comment améliorer sa vitesse. Si vous voulez des vitesses plus rapides que PHPExcel lui-même, il existe une liste de bibliothèques alternatives ici .

55
Mark Baker

J'ai rencontré ce problème aussi. Je pensais que je mettrais mes deux sous dans la mesure où cette question suscite tant d'opinions.

Définition des valeurs de cellule

Au lieu de définir la valeur pour chaque cellule individuellement, utilisez la méthode fromArray(). Pris et modifié depuis le wiki .

$arrayData = array(
array(NULL, 2010, 2011, 2012),
array('Q1',   12,   15,   21),
array('Q2',   56,   73,   86),
array('Q3',   52,   61,   69),
array('Q4',   30,   32,    0),
);

$as = $objPHPExcel->getActiveSheet();

$as->fromArray(
    $arrayData,  // The data to set
    NULL,        // Array values with this value will not be set
    'C3'         // Top left coordinate of the worksheet range where
                 //    we want to set these values (default is A1)
);

Cellules de coiffage

Statique

Il est également plus rapide d'appliquer les styles pour une plage que de définir le style pour chaque cellule individuellement (en remarquant un motif ??).

$default_style = array(
    'font' => array(
        'name' => 'Verdana',
        'color' => array('rgb' => '000000'),
        'size' => 11
    ),
    'alignment' => array(
        'horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
        'vertical' => \PHPExcel_Style_Alignment::VERTICAL_CENTER
    ),
    'borders' => array(
        'allborders' => array(
            'style' => \PHPExcel_Style_Border::BORDER_THIN,
            'color' => array('rgb' => 'AAAAAA')
        )
    )
);

// Apply default style to whole sheet
$as->getDefaultStyle()->applyFromArray($default_style);

$titles = array(
    'Name',
    'Number',
    'Address',
    'Telephone'
);

$title_style = array(
    'font' => array(
        'bold' => true
    ),
    'fill' => array(
        'type' => \PHPExcel_Style_Fill::FILL_SOLID,
        'startcolor' => array('rgb' => '5CACEE')
    ),
    'alignment' => array(
        'wrap' => true
    )
);

$as->fromArray($titles, null, 'A1'); // Add titles

$last_col = $as->getHighestColumn(); // Get last column, as a letter

// Apply title style to titles
$as->getStyle('A1:'.$last_col.'1')->applyFromArray($title_style);

Dynamiquement

J'utilise PHPExcel pour vérifier les données figurant dans le tableur avec les données actuelles de la base de données. Comme chaque cellule est cochée individuellement, j'ai placé les styles dans un tableau (null pour aucun style) et j'ai utilisé la boucle ci-dessous pour obtenir la plage de cellules à laquelle appliquer le style.

/*
 * $row is previously set in a loop iterating through each 
 *     row from the DB, which is equal to a spreadsheet row.
 * $styles = array(0 => 'error', 1 => 'error', 2 => null, 3 => 'changed', ...);
 */
$start = $end = $style = null;
foreach ($styles as $col => $s) {
    if (!$style && !$s) continue;
    if ($style === $s) {
        $end = $col;
    } else {
        if ($style) {
            $array = null;
            switch ($style) {
                case 'changed':
                    $array = $this->changed_style;
                    break;
                case 'error':
                    $array = $this->error_style;
                    break;
                case 'ignored':
                    $array = $this->ignored_style;
                    break;
            }
            if ($array) { 
                $start = \PHPExcel_Cell::stringFromColumnIndex($start);
                $end = \PHPExcel_Cell::stringFromColumnIndex($end);
                $as->getStyle($start.$row.':'.$end.$row)->applyFromArray($array);
            }
        }
        $start = $end = $col;
        $style = $s;
    }
} 
13
GreeKatrina

Je rencontrais le même problème - j'avais environ 450 lignes et 11 colonnes de données que j'essayais d'écrire, et j'ai continué à courir après le délai d'expiration de 30 secondes. J'ai pu réduire le temps d'exécution à 2 secondes ou moins en ajoutant toutes mes nouvelles lignes en bloc, puis en définissant le contenu de la cellule après coup. En d'autres termes, j'insère 450 lignes dans un seul appel à insertNewRowBefore (), puis effectue une boucle et définit le contenu de ces lignes ultérieurement.

Ainsi:

$num_rows = count($output_rows);
$last_row = $sheet->getHighestRow();
$row = $last_row + 1;
$sheet->insertNewRowBefore($row, $num_rows);
// Now add all of the rows to the spreadsheet
foreach($output_rows as $line) {
    $i = 0;
    foreach($line as $val) {
        // Do your setCellValue() or setCellValueByColumnAndRow() here
        $i++;
    }
    $row++;
}
5
JaredC

Je ne suis en aucun cas un expert dans l'utilisation de PHPExcel, mais le format OfficeOpenXML (le format des fichiers * .xlsx) est lui-même un groupe de fichiers XML emballés dans une archive Zip avec l'extension * .xlsx. Si vous appréciez vos performances et savez quel type de données vous allez transmettre, c'est peut-être une meilleure idée de construire son propre générateur XLSX, réduit aux fonctions les plus importantes, en effectuant peut-être des calculs sur la couche de base de données, etc. au lieu d’analyser l’ensemble du document.

Pour ce faire, vous pouvez commencer par analyser les fichiers générés à l'aide de jeux de données plus petits (en modifiant l'extension * .xlsx en * .Zip, en le décompressant et en parcourant le contenu des fichiers individuels). De cette façon, vous pourrez déterminer ce dont vous avez réellement besoin et le générer vous-même (en créant des fichiers XML appropriés et en les plaçant dans une archive Zip, puis en les renommant avec l'extension * .xlsx).

Il existe également une spécification de OfficeOpenXML , qui est volumineux (quelques milliers de pages). Je ne propose donc pas de le lire à moins que vous ne le souhaitiez vraiment. La création de fichiers correspondant à la manière dont ils ont été générés par PHPExcel devrait suffire.

La solution mentionnée ci-dessus n'inclut aucun conseil relatif à PHPExcel, car je n'en suis pas un expert. Cependant, je me suis déjà intéressé au processus de normalisation OOXML et je serais heureux si la connaissance de cette norme pouvait vous aider à résoudre votre problème.

2
Tadeck

Pour une exportation XLSX avec les colonnes a-amj (~ 800) et seulement ~ 50 lignes, je me suis également heurté à la limite de 30 secondes. Pour tester mon programme, j'ai limité le nombre de lignes traitées à 7, ce qui a fonctionné en 25 secondes.

  1. en passant de $ objPHPExcel-> getActiveSheet () à $ sheet (premier conseil), le temps passé sur un nombre limité de lignes est passé de 25 à 26 s.

  2. Ce qui m'a vraiment aidé, c'est de remplacer tous mes getHighestDataColumn () par une simple variable $ column_nr incrémentée en PHP. Je suis passé de 26 à 7 secondes. 

Après cela, j'ai pu traiter les 50 lignes en 11 secondes.

1
Cas Tuyn

Dans mon cas, j'ai augmenté les performances en changeant la méthode de mise en cache dans la mémoire gzip cache_in_memory_gzip

$cm = \PHPExcel_CachedObjectStorageFactory::cache_in_memory_gzip;
\PHPExcel_Settings::setCacheStorageMethod($cm);
0
Farid Movsumov

J'ai eu exactement le même problème. Vous avez un fichier CSV à 5 000 lignes et 32 ​​colonnes qui a pris un temps fou à traiter. Il s’avère que presque tout le temps passé à "traiter" est en fait le codage de caractères qui est configuré pour tout coder en UTF8 par défaut. Donc, si vous allez dans votre fichier config\Excel.php et faites défiler jusqu’à encoder, définissez-le simplement comme suit:

/*
|--------------------------------------------------------------------------
| Import encoding
|--------------------------------------------------------------------------
*/
    'encoding' => array(

        'input'  => '',
        'output' => ''

    ),

Avec cela seul - le fichier mentionné ci-dessus prend environ 8 secondes à traiter. Vous voudrez peut-être avertir votre client de bien enregistrer le fichier CSV.

0
Skipp