[PHP][COM] ScreenUpdating et PHP, l'amour impossible ?

pickwick

XLDnaute Nouveau
Bonjour,

J'ai quelques soucis de performance avec ma génération de fichier Excel via PHP. Le remplissage d'un fichier utilise mon processeur à 100% pendant une dizaine de secondes alors que je dois avoir une trentaine de cellules à remplir à tout casser. C'est d'autant plus gênant que tout ça va être déployé sur un serveur, monoproc tant qu'à faire ^^. Du coup si aucune page web n'est disponible pendant 10 secondes à chaque fois qu'un utilisateur voudra générer son fichier, ça risque d'être problématique.

Je passe par un objet COM "Excel.application" à partir duquel j'appelle les fonctions classiques.

Mon problème est que je ne trouve pas le moyen de forcer Excel a ne pas mettre à jour l'écran. (J'ai l'impression que c'est ça qui ralentit l'exécution)

Je fais bien "$excel->Screenupdating = False;" au début de mon traitement mais y'a rien à faire, j'ai droit au remplissage des cellules une par une :'(

J'ai cru lire quelque part que Application.Screenupdating revenait à True dès qu'Excel reprenait la main après une fonction. Le problème c'est que j'ai pas de fonction au sens "Excel" du terme, j'ai l'impression qu'il considère chaque opération comme une fonction indépendante et réactive donc automatiquement la mise à jour de l'écran à chaque opération.

Quelqu'un saurait comment forcer Excel à ne pas afficher les changements ?
A moins que ça soit pas l'affichage qui soit responsable de la lenteur... (je suis ouvert à toute proposition sur le sujet.)
 

tototiti2008

XLDnaute Barbatruc
Re :
PHP:
[COM] ScreenUpdating et PHP, l'amour impossible ?[/b]

Bonjour pickwick,

Euh pour moi, le php, c'est un peu chinois... donc je ne peux que te proposer du vba, à toi de l'adapter en php.

Essaye peut-être :
Application.visible = false 'au début
et 
Application.visible = true 'à la fin

Pour rire :en php ça donnera peut-être :
"$excel->Visible = False;"
 

pickwick

XLDnaute Nouveau
Re :
PHP:
[COM] ScreenUpdating et PHP, l'amour impossible ?[/b]

Merci tototiti de te jeter comme ça sur mes posts, ça fait plaisir. ;)

Application.visible ça fonctionne, c'est même comme ça par défaut quand on passe par un objet COM en PHP et ça change pas le temps de traitement :D

C'est justement en rajoutant un $excel->Visible = True; pour voir ce qui pouvait prendre autant de temps que je me suis aperçu qu'il remplissait les cellules au fur et à mesure et surtout pas très vite ^^

Une autre idée ? :D
 

pickwick

XLDnaute Nouveau
Re :
PHP:
[COM] ScreenUpdating et PHP, l'amour impossible ?[/b]

Voila le code :



[PHP]//======================================================================================
// Création du document Excel
//======================================================================================

// Création de l'objet COM
$excel = new COM("Excel.application" ) or die("Impossible d'instancier l'objet COM");

//$excel->Visible = true;
$excel->ScreenUpdating = False;


// Ouverture du fichier
$classeur = $excel->Workbooks->Open($nom_fichier) or die("Impossible d'ouvrir le RMA vierge");

	
// Selection de la feuille à lire
$feuille = $classeur->Worksheets($nom_feuille);


if(!empty($feuille)){

	//===================================================================================
	//Deprotection du classeur et de la feuille
	//===================================================================================
	$excel->run("UnProtectionWbk");
	$excel->run("Unprotectionn");

	//===================================================================================
	// Suppression des feuilles inutiles
	//===================================================================================

	//Suppression des boites de dialogue de confirmation
	//( nécessaire pour supprimer les feuilles )
	$excel->DisplayAlerts = false;

	//Suppression des feuilles Excel
	for($i=0;$i<sizeof($liste_feuilles_a_supprimer);$i++){
		$classeur->Worksheets($liste_feuilles_a_supprimer[$i])->Delete();
	}
		
	//Restauration des boites de dialogue de confirmation
	$excel->DisplayAlerts = true;
		


	//====================================================================================
	// Cellule A1 (si sa valeur reste nulle, on aura la boite de dialogue de saisie du matricule
	//====================================================================================

	$coords = "A1";
	$feuille->Range($coords)->value = $a1;


	//====================================================================================
	// Matricule
	//====================================================================================

	//On remplit chaque case avec un chiffre du matricule de la ressource (6 chiffres)
	for($i=1;$i<7;$i++){
		$feuille->Range("SalarieC$i")->value = substr($matricule,$i-1,1);
	}

	//====================================================================================
	// Modalité
	//====================================================================================

	//Ajout de la modalité de la ressource
	$feuille->Range("Modalite")->value = $modalite;


	//====================================================================================
	// Mois et année
	//====================================================================================

	//Mois
	$mois_a_afficher = str_pad($mois,2,"0",STR_PAD_LEFT); //Ajout d'un zéro au début si besoin

	$feuille->Range("MoisC1")->value = substr($mois_a_afficher,0,1);
	$feuille->Range("MoisC2")->value = substr($mois_a_afficher,1,1);

	//année
	$feuille->Range("AnneeC1")->value = substr($annee,0,1);
	$feuille->Range("AnneeC2")->value = substr($annee,1,1);
	$feuille->Range("AnneeC3")->value = substr($annee,2,1);
	$feuille->Range("AnneeC4")->value = substr($annee,3,1);
		

	//====================================================================================
	// Nom et prenom
	//====================================================================================

	$feuille->Range("Nom")->value = $nom_salarie;
	$feuille->Range("Prenom")->value = $prenom_salarie;


	//====================================================================================
	// DO et cost-center
	//====================================================================================

	$feuille->Range($cellule_do)->value = $do;
	$feuille->Range($cellule_costcenter)->value = $costcenter;
	
	//====================================================================================//
	//  Modification des cellules de sous-totaux (suppression du msgbox si charge >10)	  //
	//====================================================================================//
	//																					  //
	// On doit modifier la formule des cellules qui calculent les totaux journaliers pour //
	// éviter l'affichage d'un popup bloquant sur le serveur lorsque la charge journalière// 
	// est supérieure à un jour.														  //
	// Pour ce faire, on modifie une première cellule pour qu'elle se contente de calculer// 
	// la somme, puis on copie sa formule dans autres cellules de la ligne (en utilisant  //
	// la fonction "recopier vers la droite" d'Excel.									  //
	//====================================================================================//
	
	//Selection de la première cellule
	$cellule_premier_jour = $feuille->Range($premiere_colonne_charge.$ligne_totaux);
	
	//Définition de la formule pour le premier jour
	$cellule_premier_jour->FormulaLocal="=SI(SOMME(".$premiere_colonne_charge.$premiere_ligne_charge.":".$premiere_colonne_charge.$derniere_ligne_charge.")=0;\"\";SOMME(".$premiere_colonne_charge.$premiere_ligne_charge.":".$premiere_colonne_charge.$derniere_ligne_charge."))";
	
	
	//Selection de la zone à modifier
	$zone_de_destination = $feuille->Range($premiere_colonne_charge.$ligne_totaux.":".$derniere_colonne_charge.$ligne_totaux);
	
	//Copie de la formule pour les autres jours du mois (les indices de colonnes sont incrémentés automatiquement)
	$cellule_premier_jour->AutoFill($zone_de_destination);
	
	
	//====================================================================================
	//  Remplissage des charges
	//====================================================================================

	// Suppression des alertes
	$excel->DisplayAlerts = false;
	$excel->Interactive = false;
	
	

	// Requête de sélection des données

	$requete_donnees = "SELECT
	date,
	sum(charge) AS charge,
	projets.nom AS nom,
	projets.codecontrat AS numero,
	ordredetravail.profil AS profil

	FROM  imputations
	LEFT  JOIN ordredetravail
	ON imputations.idordredetravail = ordredetravail.idot
	LEFT  JOIN demande
	ON ordredetravail.iddemande = demande.iddemande
	LEFT  JOIN projets ON projets.idprojet = demande.idprojet

	WHERE
	ordredetravail.ressource =  '$login'
	AND imputations.annee = '$annee'
	AND imputations.mois = '$mois'
	AND charge <> 0

	GROUP BY
	projets.nom,
	profil,
	date";

	$result = mysql_db_query(BDD,$requete_donnees,$base);

	//Initialisation des valeurs en cours
	$projet_en_cours = "";
	$profil_en_cours = "";
	$numero_en_cours = "";
	$numero_premiere_ligne = $feuille->Range("A$premiere_ligne_charge")->row;
	$numero_derniere_ligne = $feuille->Range("A$derniere_ligne_charge")->row;
	$ligne = $numero_premiere_ligne;

	//Parcours du resultset
	while($enr = mysql_fetch_array($result)){
		
		if(($enr['nom'] != $projet_en_cours) || ($enr['numero'] != $numero_en_cours) || ($enr['profil'] != $profil_en_cours)){
		// Si on change de projet, profil ou code projet, on doit remplir une autre ligne
		
		
			// Il y aura a priori des charges à saisir
			$remplir = true;

			// Mise à jour du projet en cours
			$projet_en_cours = $enr['nom'];
			$numero_en_cours = $enr['numero'];
			$profil_en_cours = $enr['profil'];
				
			// Selection de la première ligne du classeur correspondant à des charges
			$ligne = $numero_premiere_ligne;
				
			// Recupération de la reference analytique de l'enregisrement en cours
			// Le numéro de projet est de la forme "012-AB12345678", 
			// la référence analytique est la partie précédant le tiret "-"
			$temp = explode("-",$enr['numero']);
			$ref_en_cours = $temp[0];
				
			
			if($ref_en_cours != ""){
			// Si la référence analytique a été définie
					
				while( (!meme_ref_ana($ligne,$ref_en_cours)) && ($ligne != $numero_derniere_ligne)){
				// Tant que la reference n'est pas la bonne (et qu'on reste dans le tableau), on regarde la ligne d'en dessous
					$ligne++;
					$ligne++; // On double l'incrémentation car les lignes sont fusionnées 2 par 2.
				}
					
				if($ligne != $numero_derniere_ligne){
				// Si on est toujours dans le tableau (ce qui veut dire qu'on a trouvé une ligne avec la bonne reference)
					
					// Recupération du numéro de la colonne des totaux
					$num_colonne_total = $feuille->Range($colonne_total_mensuel."1")->column;
					
					while(($feuille->cells($ligne,$num_colonne_total)->value != "")&&(meme_ref_ana($ligne,$ref_en_cours))){
					// Tant que la référence de la ligne correspond et que la ligne n'est pas vide, on regarde la ligne d'en dessous
						$ligne++;
						$ligne++; // On double l'incrémentation car les lignes sont fusionnées 2 par 2.
					}
					
					
					if(($feuille->cells($ligne,$num_colonne_total)->value == "")&&(meme_ref_ana($ligne,$ref_en_cours))){
					// Si le total de charges mensuel est vide (ce qui veut dire que la ligne est libre) et qu'on est toujours sur la même ref ana.
						
						if(in_array($ref_en_cours,$liste_ref_a_saisir)){
						//Si le nom du projet de cette référence analytique doit être précisé
						
							//Numéro de la colonne qui contient le libellé du projet
							$num_colonne_projet = $feuille->Range($colonne_nom_projet."1")->column;
						
						
							//Affichage du nom du projet
							
							if($enr['profil'] != ""){
								//Si un profil a été défini, on l'ajoute entre parenthèses au libellé du projet
								$libelle_a_inserer = $enr['nom']." (".$enr['profil'].")";
							}else{
								//Sinon on affiche simplement le nom du projet
								$libelle_a_inserer = $enr['nom'];
							}
							//Remplissage du libellé du projet
							$feuille->cells($ligne,$num_colonne_projet)->value = $libelle_a_inserer;
						
						}
						
							
						//Affichage du numero de projet
						$new_numero_projet = str_replace("-","",$enr['numero']); //Suppression du tiret de séparation entre la ref ana et le reste du numéro
						$num_colonne_ref = $feuille->Range($premiere_colonne_ref_ana."1")->column;
							
						for($k=0;$k<strlen($new_numero_projet);$k++){
						// On remplit chaque case avec un caractère du numéro de projet
							$feuille->cells($ligne,$num_colonne_ref+$k)->value = substr($new_numero_projet,$k,1);
						}
							
					}else{
						//On a parcouru toutes les lignes de cette référence analytique, aucune n'est libre
						$remplir = false;
						ajouterCommentaire("Ref ".$ref_en_cours.": pas assez de lignes");
						
					}

				}else{
					//On a parcouru tout le tableau sans trouver sans trouver cette référence analytique
					$remplir = false;
					ajouterCommentaire("Ref $ref_en_cours inconnue pour le projet $projet_en_cours");
					
				}
					
			}else{
				//Ce projet n'a pas de référence analytique
				$remplir = false;
				ajouterCommentaire("Pas de ref pour le projet $projet_en_cours");
				
			}
				
		}

		
		//REMPLISSAGE DES CHARGES
		
		if($remplir){
		// S'il n'y a pas eu d'erreur
				
			//On force le typage du jour en entier (pour enlever l'éventuel 0 du début)
			$jour_du_mois = 0+substr($enr['date'],0,2);
				
			//numéro de la première colonne de charge
			$prem_colonne = $feuille->Range($premiere_colonne_charge."1")->column;
			
			//Calcul de la colonne qui doit être renseignée
			$colonne_a_renseigner = $prem_colonne + $jour_du_mois - 1;
				
			//Calcul de la charge à insérer (on compte en dixièmes de jour)
			$charge_a_inserer = 10*$enr['charge'];
			
			//Remplissage
			$feuille->Cells($ligne,$colonne_a_renseigner)->value = $charge_a_inserer;
					
		}
	}

	
	//====================================================================================
	// Re-protection de la feuille et du classeur
	//====================================================================================

	$excel->run("Protectionn");
	$excel->run("ProtectionWbK");

}

//========================================================================================
//Sauvegarde et fermeture d'Excel
//========================================================================================

//Définition du nom du fichier
$nom_fichier_genere = $chemin . "RMA_genere_$matricule.xls";


// Supression du fichier temporaire s'il existe déjà
if(file_exists($nom_fichier_genere)){
	unlink($nom_fichier_genere);
}


$classeur->SaveAs($nom_fichier_genere);

$excel->Workbooks->Close();
	
$excel->Quit();


//========================================================================================
//Telechargement du fichier
//========================================================================================

//Ajout des headers HTTP manquants
header("Content-Disposition: attachment; filename=\"Activité Mensuelle $login $mois_a_afficher-$annee.xls\"");
header("Content-Length: " .(string)(filesize($nom_fichier_genere)));

//Lecture des fichiers
$fh = fopen($nom_fichier_genere,"rb");
fpassthru($fh);
fclose($fh);

//========================================================================================
//Suppression du fichier temporaire du serveur après le téléchargement
//========================================================================================
unlink($nom_fichier_genere);

//****************************************************************************************
// 								FIN DU TRAITEMENT
//****************************************************************************************

Chaque enregistrement de ma requête correspond à une charge de travail pour un jour donné sur un projet donné.

La première étape (après les remplissages unitaires comme le nom, prénom etc...) c'est de trouver une ligne pour l'enregistrement. chaque ligne est pré-remplie avec une "référence analytique" (3 caractères) qui correspond au début du numéro de projet donc je parcours les lignes à la recherche de la bonne réf ana.
Une fois que j'ai ma réf ana, faut que je voie si la ligne est libre (plusieurs projets peuvent avoir la même réf ana). Pour ce faire, je regarde s'il y a quelque chose dans la cellule qui contient le total de la ligne (pour éviter de me taper un FOR sur toutes les cellules). Si la ligne est vide, je choisis cette ligne et j'en change pas tant que le projet est le même.

En espérant que ça te parle ^^
 

pickwick

XLDnaute Nouveau
Re :
PHP:
[COM] ScreenUpdating et PHP, l'amour impossible ?[/b]

J'oubliais : Le fait d'utiliser le processeur du serveur pendant 10 secondes ne bloque pas les autres requêtes finalement, c'est juste si j'ouvre une autre page depuis le même poste et le même navigateur que ça coince. Du coup ça devient moins critique donc te casse pas la tête pour trouver, au pire le temps de traitement sera juste un peu long.

Je me suis rendu compte de ça en baissant la priorité du processus EXCEL.EXE en faisant utiliser le WMI par PHP : même en priorité minimale ça bloquait donc le pb venait pas de là.
 

tototiti2008

XLDnaute Barbatruc
Re :
PHP:
[COM] ScreenUpdating et PHP, l'amour impossible ?[/b]

Bonjour pickwick,
 
Et le traitement a lieu sur beaucoup de feuilles ? sur beaucoup de classeurs ? et combien de temps prend-il ?
 
Comme ça, à première vue, je ne vois pas d'optimisation enorme à réaliser... Je pense que l'un des traitements les plus longs est peut-être la mise en place d'une formule (sous-entend souvent un recalcul) et éventuellement la requête MySQL. si tu veux éviter le recalcul, tu peux le désactiver pendant le traitement (si tu n'as pas besoin de récupérer de résultat en cours de route). Il faudra quand même le réactiver à la fin car c'est enregistré avec le classeur.
 

pickwick

XLDnaute Nouveau
Re :
PHP:
[COM] ScreenUpdating et PHP, l'amour impossible ?[/b]

Le traitement a lieu sur une seule feuille d'un seul classeur et prend une dizaine de secondes. Après c'est vrai qu'à chaque fois que je modifie une cellule de charge, y'a trois sommes qui sont recalculées, c'est p-e de là que vient le souci.
j'ai un sous total de la ligne, un sous total de la colonne et un total des sous-totaux. Mais si je désactive la calcul de ces sommes, ça m'oblige à parcourir toute la ligne quand je veux savoir si elle a déjà été utilisée plutôt que de juste regarder si le total est non nul ^^. Ça me parait juste fou qu'Excel prenne autant de temps : si je modifie une valeur à la main dans le fichier, la mise à jour des cellules est instantanée, ça prend pas trois plombes. :mad:
 

Discussions similaires

Réponses
26
Affichages
682
Réponses
5
Affichages
461

Statistiques des forums

Discussions
312 913
Messages
2 093 535
Membres
105 753
dernier inscrit
besnard