Afficher la progression pour les scripts PHP de longue durée

j'ai un script PHP qui est susceptible de prendre au moins 10 secondes à exécuter. Je voudrais afficher l'état d'avancement de l'utilisateur.

dans la classe d'exécution, j'ai une propriété $progress qui est mise à jour avec la progression (en 1-100), et une méthode get_progress() (dont le but devrait être évident).

la question Est, comment mettre à jour l'élément <progress> sur le devant pour que l'utilisateur puisse le voir?

je pense que AJAX est la solution, mais je n'arrive pas à y voir clair. Je ne peux pas arriver à la même instance d'objet.

38
demandé sur arsho 2011-08-13 12:06:01

8 réponses

si votre tâche est de télécharger un jeu de données énorme ou de le traiter sur le serveur, tout en mettant à jour le progrès sur le serveur, vous devriez envisager d'aller avec une sorte d'architecture de travaux, où vous initiez le travail et le faire avec un autre script tournant sur le serveur (par exemple Mise à l'échelle / traitement des images etc). En cela vous faites une chose à la fois, formant ainsi un pipeline de tâches où il y a une entrée et une sortie finale traitée.

à chaque étape du pipeline l'état de la tâche est mis à jour à l'intérieur de la base de données qui peut alors être envoyé à l'utilisateur par n'importe quel mécanisme de poussée de serveur qui existe ces jours-ci. L'exécution d'un script unique qui gère les téléchargements et les mises à jour place la charge sur votre serveur et vous restreint (que faire si le navigateur ferme, et si une autre erreur se produit). Lorsque le processus est divisé en étapes, vous pouvez reprendre une tâche échouée à partir du point où elle a réussi en dernier.

il existe plusieurs façons de le faire. Mais le flux global du processus on dirait que c'est

enter image description here

la méthode suivante est ce que j'ai fait pour un projet personnel et ce script tenu bon pour le téléchargement et le traitement des milliers d'image de haute résolution à mon serveur qui ont ensuite été réduits en versions multiples et téléchargé sur amazon s3 tout en reconnaissant les objets à l'intérieur d'eux. (Mon code original était en python)

Étape 1:

initient le transport ou tâche

téléchargez D'abord votre contenu, puis renvoyez immédiatement un ID de transaction ou uuid pour cette transaction via une simple requête postale. Si vous faites plusieurs fichiers ou de plusieurs choses à la tâche, vous pouvez également gérer cette logique dans cette étape

Étape 2:

Faire le travail et de Retour, le progrès.

une fois que vous avez compris comment les transactions se produisent, vous pouvez alors utiliser n'importe quel côté du serveur push technologie pour envoyer des paquets de mise à jour. Je choisirais WebSocket ou le serveur a envoyé des événements selon le cas en retombant à un long sondage sur les navigateurs non pris en charge. Une méthode simple de SSE ressemblerait à ceci.

function TrackProgress(upload_id){

    var progress = document.getElementById(upload_id);
    var source = new EventSource('/status/task/' + upload_id );

    source.onmessage = function (event) {
        var data = getData(event); // your custom method to get data, i am just using json here
        progress.setAttribute('value', data.filesDone );
        progress.setAttribute('max', data.filesTotal );
        progress.setAttribute('min', 0);
    };
}

request.post("/me/photos",{
    files: files
}).then(function(data){
     return data.upload_id;
}).then(TrackProgress);

côté serveur, vous devrez créer quelque chose qui garde une trace des tâches, une architecture de tâches simple avec job_id et progress envoyé à la db suffira. Je vous laisserais l'horaire du travail et le routage aussi, mais après cela le le code conceptuel (pour L'ESS la plus simple qui suffira du code ci-dessus) est le suivant.

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
/* Other code to take care of how do you find how many files are left 
   this is really not required */
function sendStatusViaSSE($task_id){
    $status = getStatus($task_id);
    $json_payload = array('filesDone' => $status.files_done,
                          'filesTotal' => $status.files_total);
    echo 'data: ' . json_encode( $json_payload ) . '\n\n';
    ob_flush();
    flush();

    // End of the game
    if( $status.done ){
        die();
    }

}

while( True ){
     sendStatusViaSSE( $request.$task_id );
     sleep(4);
}

?>

un bon tutoriel sur SSE peut être trouvé ici http://html5doctor.com/server-sent-events/

et vous pouvez en savoir plus sur la mise à jour depuis le serveur sur cette question mise à jour depuis le serveur

ce qui précède était une explication conceptuelle, il ya d'autres façons d'atteindre mais c'était la solution qui a pris soin d'un assez énorme tâche dans mon cas.

37
répondu ShrekOverflow 2017-05-23 12:02:23

je vais mettre ceci ici comme une référence pour quiconque recherche - cela repose sur aucun javascript du tout..

<?php

/**
 * Quick and easy progress script
 * The script will slow iterate through an array and display progress as it goes.
 */

#First progress
$array1  = array(2, 4, 56, 3, 3);
$current = 0;

foreach ($array1 as $element) {
    $current++;
    outputProgress($current, count($array1));
}
echo "<br>";

#Second progress
$array2  = array(2, 4, 66, 54);
$current = 0;

foreach ($array2 as $element) {
    $current++;
    outputProgress($current, count($array2));
}

/**
 * Output span with progress.
 *
 * @param $current integer Current progress out of total
 * @param $total   integer Total steps required to complete
 */
function outputProgress($current, $total) {
    echo "<span style='position: absolute;z-index:$current;background:#FFF;'>" . round($current / $total * 100) . "% </span>";
    myFlush();
    sleep(1);
}

/**
 * Flush output buffer
 */
function myFlush() {
    echo(str_repeat(' ', 256));
    if (@ob_get_contents()) {
        @ob_end_flush();
    }
    flush();
}

?>
49
répondu l0ft13 2012-03-17 09:35:33

c'est un peu difficile, (FYI) le processus PHP et votre requête AJAX sont traités par thread séparé, donc vous ne pouvez pas obtenir la valeur $progress .

une solution rapide: vous pouvez écrire la valeur de progression à $_SESSION['some_progress'] chaque fois qu'il est mis à jour, puis votre demande AJAX peut obtenir la valeur de progression en accédant au $_SESSION['some_progress'] .

vous aurez besoin de JavaScript setInterval() ou setTimeout() pour continuer à appeler le gestionnaire AJAX, jusqu'à ce que vous obteniez le retour en tant que 100 .

il n'est pas la solution parfaite, mais sa rapide et facile.


comme vous ne pouvez pas utiliser la même session deux fois en même temps, utilisez plutôt une base de données. Écrivez l'État à une base de données et lire à partir de cela avec l'appel ajax intervalle.

24
répondu Jarod DY Law 2014-05-21 06:43:36

c'est une vieille question mais j'avais un besoin similaire. Je voulais lancer un script avec la commande php system() et afficher la sortie.

Je l'ai fait sans sondage.

pour la deuxième affaire Rikudoit devrait être quelque chose comme ceci:

JavaScript

document.getElementById("formatRaid").onclick=function(){
    var xhr = new XMLHttpRequest();     
    xhr.addEventListener("progress", function(evt) {
        var lines = evt.currentTarget.response.split("\n");
        if(lines.length)
           var progress = lines[lines.length-1];
        else
            var progress = 0;
        document.getElementById("progress").innerHTML = progress;
    }, false);
    xhr.open('POST', "getProgress.php", true);
    xhr.send();
}

PHP

<?php 
header('Content-Type: application/octet-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.

// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
ini_set('zlib.output_compression', false);
// Implicitly flush the buffer(s)
ini_set('implicit_flush', true);
ob_implicit_flush(true);
// Clear, and turn off output buffering
while (ob_get_level() > 0) {
    // Get the curent level
    $level = ob_get_level();
    // End the buffering
    ob_end_clean();
    // If the current level has not changed, abort
    if (ob_get_level() == $level) break;
}   

while($progress < 100) {
    // STUFF TO DO...
    echo '\n' . $progress;
}
?>
12
répondu Ramon La Pietra 2015-07-30 12:32:33

les Solutions sont :

  1. Ajax d'interrogation Sur le côté serveur de stocker les progrès quelque part et ensuite utiliser un appel ajax pour récupérer les progrès à intervalles réguliers.

  2. Server sent events - une fonctionnalité html5 qui permet de générer des événements dom à partir de la sortie envoyer par le serveur. C'est la meilleure solution pour un tel cas, mais IE 10 ne le soutient pas.

  3. Script / Iframe streaming-utilisez une iframe pour diffuser la sortie du script à long terme qui produirait des balises de script comme intervalles qui peuvent générer une certaine réaction dans le navigateur.

7
répondu Silver Moon 2013-03-07 06:22:22

avez-vous envisagé d'extraire javascript et d'utiliser un stream flush? Il ressemblerait à quelque chose comme ceci

echo '<script type="text/javascript> update_progress('.($progress->get_progress()).');</script>';
flush();

cette sortie est envoyée immédiatement au navigateur en raison de la chasse d'eau. Faites-le périodiquement à partir du scénario à long terme et il devrait fonctionner magnifiquement.

4
répondu Michael Petrov 2011-08-13 15:47:33

ici je voulais juste ajouter 2 préoccupations en plus de ce que @Jarod Law a écrit au-dessus de https://stackoverflow.com/a/7049321/2012407

très simple et efficace en effet. J'ai trafiqué et utilisé :) Donc mes 2 préoccupations sont:

  1. plutôt que d'utiliser setInterval() ou setTimeout() utilisez un appel récursif dans le callback comme:

    function trackProgress()
    {
        $.getJSON(window.location, 'ajaxTrackProgress=1', function(response)
        {
            var progress = response.progress;
            $('#progress').text(progress);
    
            if (progress < 100) trackProgress();// You can add a delay here if you want it is not risky then.
        });
    }
    

    comme les appels sont asynchrones peut revenir indésirable en ordonner autrement.

  2. économiser dans $_SESSION['some_progress'] est intelligent et vous pouvez, pas besoin de stockage de base de données. Ce dont vous avez réellement besoin est de permettre aux deux scripts d'être appelés simultanément et de ne pas être mis en file d'attente par PHP. Donc ce dont vous avez le plus besoin c'est session_write_close() ! J'ai posté un exemple de démonstration très simple ici: https://stackoverflow.com/a/38334673/2012407

4
répondu antoni 2017-05-23 12:17:57

fréquemment dans les applications web, nous pouvons avoir une requête vers le système de bout en bout qui peut déclencher un processus de longue durée tel que la recherche énorme quantité de données ou un processus de base de données de longue durée. Ensuite, la page Web avant peut être suspendue et attendre que le processus soit terminé. Au cours de ce processus, si l'on peut fournir à l'utilisateur des informations sur l'avancement de la fin du processus, il peut améliorer l'expérience utilisateur. Malheureusement, dans les applications web, cela ne semble pas une tâche facile parce que les langages de script web ne prennent pas en charge le multithreading et HTTP est apatride. Nous pouvons maintenant avoir AJAX pour simuler le processus en temps réel.

en gros, nous avons besoin de trois fichiers pour traiter la demande. Le premier est le script qui exécute le travail réel de longue durée et il a besoin d'une variable de session pour stocker la progression. Le second script est le script d'état qui va faire écho à la variable session dans le script de travail en cours d'exécution. Le dernier est le script AJAX côté client qui peut Poller le script de statut fréquemment.

pour les détails de la mise en œuvre, vous pouvez vous référer à PHP pour obtenir la progression du processus de longue durée dynamiquement

0
répondu PixelsTech 2014-11-22 03:44:53