Upload via l'extension APC de PHP

Il est possible avec des scripts réalisés en Flash de créer un système d'upload avec une barre de progression, mais certaines personnes seront très probablement réticentes à utiliser du Flash et laisseront donc tomber la progression de l'upload sur leur formulaire. Heureusement, il existe une solution nommée APC.

L'avantage principal d'APC est de pouvoir fonctionner assez facilement sur n'importe quel navigateur Web car il n'y a que du Javascript et un peu de PHP, ainsi on évite certains problèmes de compatibilité qu'il peut y avoir parfois entre un navigateur et Flash (actuellement, il y a des problèmes entre Firefox 3 et Flash 10).

Présentation d'APC

Vous vous dites sûrement : "Youpi ! On va pouvoir faire une barre de progression sans Flash !"
Malheureusement, il faut savoir que tout n'est pas rose avec APC... En effet, pour commencer il y a beaucoup moins de fonctionnalités qu'avec SWFUpload mais ce n'est pas vraiment un problème si vous voulez seulement faire une barre de progression.

Le deuxième facteur gênant est, lui, autrement plus problématique et ce pour une raison bien précise : APC n'est codé ni en Javascript, ni en Flash, mais est en réalité une extension pour PHP ! Ce qui signifie que pour utiliser APC sur votre site web il vous faudra un hébergeur ayant au minimum la version 5.2.0 de PHP et surtout il faut qu'il ait activé l'extension APC, ce qui est vraiment très rare pour le moment. Bref, ce n'est pas gagné pour pouvoir s'en servir librement...

En revanche, vous pourrez toujours faire des tests en local car cela fonctionne tout aussi bien qu'en ligne (bien que j'ai fréquemment relevé des temps de réponse plus élevé qu'avec mon hébergeur web, un comble !).

D'ailleurs, nous allons justement voir dans la partie suivante comment installer APC ou bien comment vérifier si votre hébergeur l'a activé ou non.

Installer APC ou vérifier si il est activé

Installer APC sur son ordinateur :

Comme beaucoup d'entre vous n'ont probablement pas l'extension APC d'active chez leur hébergeur web, je vais donc tout d'abord traiter de l'activation de cette dernière sur votre ordinateur.

Tout d'abord, vérifiez la version de PHP qui est installée sur votre PC, celle-ci doit être supérieure ou égale à la version 5.2.0.

Une fois cette vérification effectuée, il vous faudra aller dans le fichier de configuration de PHP (nommé "php.ini"). Pour ceux qui utilisent Wamp, vous avez juste à aller dans ce menu pour y accéder.

Il vous faudra tout d'abord initialiser l'extension APC, pour cela entrez donc l'information suivante : extension=php_apc.dll

Une fois l'extension initialisée il faut lui définir ses paramètres principaux, pour cela allez tout en bas du fichier de configuration et entrez le code suivant :

[APC]
apc.enabled = 1
apc.rfc1867 = On
apc.shm_size = 64M
apc.max_file_size = 100M

Ceci est une configuration basique de l'extension, elle permet un fonctionnement de base sans aucune réelle optimisation car ce n'est pas vraiment notre but dans l'immédiat. Toutefois, pour ceux qui voudrait en savoir plus sur sa configuration alors c'est par ici .

Détaillons maintenant ce que l'on vient de faire :

  • apc.enabled = 1 permet d'activer l'extension. Bien qu'elle ait été initialisée plus haut, il faut aussi l'activer par la suite.

  • apc.rfc1867 = On permet (en gros) d'activer le suivi de l'upload des fichiers. J'en reparlerai plus tard.

  • apc.shm_size = 64M défini la taille du cache d'APC, par défaut il est à 30Mo mais là on va faire un brin d'optimisation et mettre cette valeur à 64Mo.

  • apc.max_file_size = 100M est la constante qui défini la taille maximale d'un fichier à mettre en cache, si cette valeur est dépassée alors l'upload n'aura pas lieu. Si j'ai mis une telle valeur (100 Mo quand même) c'est parce que la copie d'un fichier est très rapide en local, vous pouvez même augmenter si ça va toujours trop vite (mais il vous faudra de gros fichiers, prenez une distribution Linux si vous n'avez rien d'autre sous la main ^^ ).

    Autre chose, il vous faudra aussi modifier la valeur de la constante upload_max_filesize et y mettre la même valeur (ou plus) que celle de apc.max_file_size si vous voulez que l'upload se déroule correctement.

    Une dernière chose : Les utilisateurs de Linux pourront éviter l'envoi de gros fichiers en utilisant Trickle. Celui-ci permet la limitation de la bande passante par le biais d'une simple commande comme ci-dessous :
    trickle -u 20 -d 500 firefox -profilemanager --no-remote

    Ainsi, cette commande permet de limiter l'upload à 20Ko/s et le download à 500Ko/s pour l'application Firefox. Les options "-profilemanager --no-remote", servent à empêcher l'ouverture de la fenêtre dans une instance existante de Firefox (ce qui a pour effet d'annuler la commande trickle). Ce sera quand même plus simple pour faire vos tests :) .

Voilà pour la configuration d'APC sur votre ordinateur. Vérifions maintenant si votre hébergeur a activé l'extension ou non.

Vérifier l'activation d'APC chez son hébergeur :

La vérification de l'initialisation d'une extension est très simple, il vous suffit de créer un fichier PHP y mettre la ligne suivante <?php phpinfo(); ?> et ensuite exécuter le fichier chez votre hébergeur web.

Si l'extension est activée vous devriez voir apparaître ceci à un moment :

Image utilisateur

Pensez bien à vérifier que l'extension est active en regardant la première ligne, celle-ci doit indiquer enabled comme sur l'image ci-dessus. Autre chose, la constante apc.rfc1867 doit être à On ou bien vous ne pourrez vous servir de la principale fonctionnalité d'APC (autrement dit, la progression de l'upload d'un fichier).

Si tout est bon, notez quelque part les valeurs des constantes suivantes car nous en aurons besoin par la suite :

  • apc.max_file_size

  • apc.rfc1867_name

  • apc.rfc1867_prefix

Voilà, c'est tout pour la vérification de l'activation d'APC.

Un upload simple

Le formulaire :

Bien, nous allons maintenant pouvoir passer à la pratique. Créons tout d'abord un formulaire pour l'upload de notre fichier :

<div>
<p>
<form enctype="multipart/form-data" method="post" action="" target="uploadFrame">
<input type="hidden" id="keyFile" name="APC_UPLOAD_PROGRESS" value="<?php echo uniqid(); ?>" />
<input type="file" name="fileToUpload" /><br />
<input type="submit" value="Uploader" />
</form>
</p>
</div>
<iframe id="uploadFrame" name="uploadFrame" src="#" style="display:none"></iframe>

Comme vous pouvez le constater, le formulaire opère exactement de la même façon qu'avec un simple upload par Iframe. Il y a seulement une ligne supplémentaire qui est celle-ci :

<input type="hidden" id="keyFile" name="APC_UPLOAD_PROGRESS" value="<?php echo uniqid(); ?>" />

Le rôle de cette ligne est de permettre à APC d'identifier le fichier dont vous souhaitez connaître les informations d'upload, il s'agit là d'une clé rfc1867 (rappelez-vous, je vous avais demandé vérifier que la constante apc.rfc1867 était à On et bien c'était pour permettre l'identification du fichier uploadé).
Son utilisation est simple, on crée un input de type "hidden" et on défini ses attributs. L'attribut "name" doit être de la même valeur que celle de la constante apc.rfc1867_name que je vous avais demandé de relever plus haut (par défaut il s'agit de APC_UPLOAD_PROGRESS). Quant à l'attribut "value", on va faire simple et lui attribuer un id unique grâce à la fonction PHP <?php uniqid(); ?> .

Cette valeur que nous lui avons attribué servira donc à "indexer" le fichier lorsqu'il sera en cours d'upload, ainsi on pourra récupérer les informations le concernant grâce à une requête Ajax.

Alors le formulaire c'est bien, mais si on affiche pas les informations sur la progression de notre upload c'est pas génial :-° ... Voici donc le code HTML tout bête pour cela, insérez-le où vous le souhaitez :

<div>
<p>
<strong>Nom du fichier</strong> : <span id="fileName"><em>Aucun fichier chargé</em></span><br />
<strong>Progression</strong> : <span id="progress"><em>Aucun fichier chargé</em></span>
</p>
</div>
La vérification de l'upload :

Voyons maintenant la requête PHP pour la progression de l'upload ! Celle-ci sera exécutée par le biais d'un script Ajax, créez donc un fichier nommé "verifUpload.php" et insérez-y le code suivant :

<?php
header('Content-type:text/plain;charset=utf-8');
if(isset($_POST['keyFile'])) {
$fileInformation = apc_fetch('upload_'.$_POST['keyFile']);
echo json_encode($fileInformation);
}
exit;
?>

La variable $_POST['keyFile'] contient la clé rfc1867 du fichier dont on veut connaître les informations d'upload, on vérifie donc qu'elle est bien initialisée. Une fois cette vérification faite, on appelle la fonction apc_fetch() en spécifiant en argument la concaténation entre la valeur de la constante apc.rfc1867_prefix et la variable $_POST['keyFile'].

La fonction apc_fetch() retournera alors un tableau contenant diverses informations concernant le fichier dont vous voulez suivre l'upload. Mais il y a un problème : le tableau retourné est en PHP, or on veut analyser ce code par le biais de Javascript, il nous faut donc encoder le tableau avec la fonction json_encode() et renvoyer le tout au Javascript. Quand ce dernier aura réceptionné les informations, il n'y aura plus qu'à utiliser la fonction eval() pour créer le tableau en Javascript.

Concernant les différents valeurs de ce tableau, je vous en parlerai plus bas.

Le code Javascript :

Bien, maintenant que nous avons notre formulaire et notre fichier PHP de prêts nous allons pouvoir passer au code Javascript. Pour vérifier l'état de la progression, il va nous falloir faire appel au fichier "verifUpload.php" par le biais de l'objet XMLHttpRequest.

Tout d'abord, il nous faut une fonction d'initialisation de ce dernier que vous nommerez getXHR(), je vous laisse faire, vous êtes normalement capable de la réaliser sans problème.

Il nous faut maintenant une fonction faisant appelle au fichier "verifUpload.php" et traitant les informations reçues :

function verifUpload() {
xhr = getXHR();
if(xhr && xhr.readyState != 0) {
xhr.abort();
}
var keyFile = document.getElementById('keyFile').value;
xhr.open('POST', 'verifUpload.php', true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send('keyFile='+ keyFile);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4) {
if(xhr.responseText != 'false') {
var response = eval('('+xhr.responseText+')');
document.getElementById('fileName').innerHTML = response.filename;
document.getElementById('progress').innerHTML =
Math.round(response.current / response.total * 100) + '%';
if(response.done != 1) {
verifUpload();
}
} else {
verifUpload();
}
}
};
}

Voyons plus en détail ce code : Tout d'abord, on récupère la clé rfc1867 contenue dans le input caché de notre formulaire puis on l'envoi par la méthode POST au fichier "verifUpload.php", celui-ci renvoi par la suite le tableau associé à ce fichier. À ce moment, c'est cette partie du code qui prend le relai :

if(xhr.responseText != 'false') {
var response = eval('('+xhr.responseText+')');
document.getElementById('fileName').innerHTML = response.filename;
document.getElementById('progress').innerHTML =
Math.round(response.current / response.total * 100) + '%';
if(response.done != 1) {
verifUpload();
}
} else {
verifUpload();
}

Tout d'abord, vu que l'on reçoit un tableau écrit en Javascript, il nous faut "l'émuler" par le biais de la fonction eval(). Vous constaterez que j'ai mis des parenthèses autour, c'est pour corriger un bug qui fait que l'émulation échoue sans elles ;) .

Ensuite, vu que l'on a notre tableau, il ne nous reste plus qu'à afficher les valeurs que l'on souhaite dans notre page HTML. Je ne pense pas qu'il y ait besoin de vous expliquer quoi que ce soit à ce niveau là si ce n'est qu'à un moment vous pouvez voir un calcul, il ne s'agit en fait que d'un simple calcul de pourcentage.

Et enfin, le reste du code permet de vérifier si l'upload est terminé ou non : tant que done est différent de la valeur 1 alors l'upload n'est pas terminé, il nous faut donc continuer à exécuter notre fonction afin de continuer à recevoir les informations sur l'upload.

Autre chose, je ne vous ai toujours pas donné les valeurs que contient le tableau que vous recevez par le biais de PHP, voici donc une liste des informations que vous pouvez récupérer :

  • filename : Le nom du fichier complet qui a été uploadé.

  • name : La valeur de l'attribut name de l'input qui contenait le chemin du fichier.

  • total : La taille totale du fichier à uploader en octets.

  • current : Poids uploadé en octets.

  • done : Précise si l'upload est terminé ou non : "1" pour oui, "0" pour non.

Il y a aussi 3 valeurs supplémentaires qui s'ajoutent lorsque done est à 1 :

  • temp_filename : Le chemin temporaire vers le fichier uploadé.

  • rate : Vitesse d'upload en octets par seconde (cette valeur n'est introduite qu'à partir de la version 3.1 d'APC, ce qui correspond normalement (et dans une configuration standard) à la version 5.2.5 de PHP).

  • cancel_upload : Spécifie si une erreur est apparue pendant l'upload, une valeur à "0" signifie que tout s'est bien déroulé. Si la valeur est différente de "0" alors je vous laisse vous référer à cette page pour en connaître la raison.

Bien, il ne nous reste qu'une seule et dernière petite chose mais ceci s'effectue au niveau de notre formulaire : il nous faut lancer la fonction verifUpload() dès que celui-ci est soumis. On ajoute donc onsubmit="verifUpload();" aux attributs de notre formulaire et celui-ci est maintenant prêt !

Essayer le code
(Je n'ai que très légèrement modifié le code pour la présentation, vous devriez normalement vous y retrouver)

Voilà pour un upload simple. Je pense que cela vous aura suffit pour vous faire une idée sur le sujet, si vous voulez maintenant aller plus loin lisez la partie ci-dessous qui traite le multi-upload (elle est incomplète actuellement et j'en explique les raisons ;) ).

Concernant l'utilisation de eval() :

Alors oui, j'ai utilisé eval()... dans une fonction qui tourne en boucle en plus : OUTRAGE !!
Alors pour ceux qui comprennent pourquoi je dis ça c'est bien, pour les autres c'est par ici .

Bref, si j'utilise cette fonction c'est pour faire court, mon but n'est pas de vous embrouiller avec un système de parsage XML qui permettrait de récupérer les informations sans eval() mais plutôt de vous montrer comment faire un upload avec APC. Donc si vous êtes motivé (ou même si vous ne l'êtes pas), je vous encourage à éviter l'utilisation de eval().

Introduction au multi-upload

Voyons donc le multi-upload, dans cette partie mon but sera de vous faire prendre conscience d'un problème et de vous introduire à la façon de le résoudre, autrement dit, on ne va pas coder (ne fuyez pas §).

Bref, prenez donc le code que je vous ai donné dans la partie ci-dessus, ajoutez-y un deuxième input file puis essayez d'uploader deux fichiers en même temps .

Voyez-vous le problème ? Le pourcentage de chaque upload est additionné à l'autre, en clair APC ne fait aucune distinction entre les deux, si ce n'est qu'il affiche le nom du fichier en cours d'upload. C'est un problème dans le sens que tous les uploads sont dépendants entre eux, ainsi on ne peut pas dire "je veux annuler tel upload" ou bien les repositionner.

La solution ? Oui, il y en a une (même si je n'ai pas encore réussi à l'appliquer :-° ) : Uploader chaque fichier dans un formulaire séparé ainsi on pourra bénéficier des informations individuelles de chaque fichier. Pour cela, il nous faudra créer un script permettant l'ajout de fichier ainsi que leur formulaire attitré puis effectuer un premier upload, ensuite effectuer le second, le troisième, etc...

L'affichage :

Le principe du code est assez simple à comprendre : On affiche un input file, dès que sa valeur est modifiée alors on le cache puis on ajoute son nom à la liste des fichiers à uploader avec un bouton de suppression, enfin il suffit de créer un nouvel input file vide afin que l'utilisateur puisse ajouter un nouveau fichier. Quand l'utilisateur cliquera sur le bouton de suppression cela aura pour effet de supprimer le nom du fichier dans la liste ainsi que l'input file qui lui était associé. Vous avez compris ? Si ce n'est pas le cas, voici un exemple de ce que je vous raconte .

L'upload :

C'est là que ça se complique. Le but est de parcourir les formulaires et effectuer les uploads les uns à la suite des autres, pour cela il nous faudra repérer le premier fichier et l'uploader, une fois cette opération effectuée, il faut passer au deuxième et ainsi de suite. Cela ne pose pas de réel problème car il ne s'agit que de parcourir le DOM et lancer les uploads au bon moment. Le véritable problème vient au niveau du suivi des fichiers : dans mes tests j'obtiens le suivi du fichier une fois que l'upload est terminé, pratique :-° . Autrement dit, ça m'affiche bien 100% d'upload une fois que celui-ci est terminé mais avant, rien...

APC étant une extension d'une stabilité encore assez douteuse, il se peut que ça vienne de là mais il ne faut pas non plus exclure les probables fautes dans mon code.

Voilà tout pour cette introduction, je suis bien navré de ne pouvoir vous en fournir plus mais mon cerveau sature un peu là donc il vous faudra sûrement attendre encore un peu...

Ce chapitre est maintenant terminé, j'espère que vous nous ferez de beaux formulaires avec APC (enfin si vous avez un hébergeur compatible >_ ).