Comment puis-je exécuter un script PHP en arrière-plan après la soumission d'un formulaire?


Answers

Sur les serveurs Linux / Unix, vous pouvez exécuter un travail en arrière-plan en utilisant proc_open :

$descriptorspec = array(
   array('pipe', 'r'),               // stdin
   array('file', 'myfile.txt', 'a'), // stdout
   array('pipe', 'w'),               // stderr
);

$proc = proc_open('php email_script.php &', $descriptorspec, $pipes);

Le & étant le bit important ici. Le script continuera même si le script d'origine est terminé.

Question

Problème
J'ai un formulaire qui, une fois soumis, exécutera le code de base pour traiter l'information soumise et l'insérer dans une base de données pour l'affichage sur un site Web de notification. En outre, j'ai une liste de personnes qui se sont inscrites pour recevoir ces notifications par e-mail et SMS. Cette liste est triviale comme le moment (seulement environ 150), mais il suffit de faire en sorte qu'il faut plus d'une minute pour parcourir l'ensemble du tableau des abonnés et envoyer plus de 150 e-mails. (Les e-mails sont envoyés individuellement, comme demandé par les administrateurs système de notre serveur de messagerie en raison de règles de messagerie massives.)

Pendant ce temps, la personne qui a posté l'alerte sera assise sur la dernière page du formulaire pendant presque une minute sans aucun renforcement positif que sa notification est affichée. Cela conduit à d'autres problèmes potentiels, tous ceux qui ont des solutions possibles qui me semblent moins qu'idéaux.

  1. Tout d'abord, l'affiche pourrait penser que le serveur est à la traîne et cliquer à nouveau sur le bouton "Soumettre", provoquant le redémarrage du script ou son exécution à deux reprises. Je pourrais résoudre cela en utilisant JavaScript pour désactiver le bouton et remplacer le texte pour dire quelque chose comme «Traitement ...», mais ce n'est pas idéal parce que l'utilisateur sera toujours coincé sur la page pour la durée de l'exécution du script. (De plus, si JavaScript est désactivé, ce problème persiste.)

  2. Deuxièmement, l'affiche peut fermer prématurément l'onglet ou le navigateur après l'envoi du formulaire. Le script continue à fonctionner sur le serveur jusqu'à ce qu'il essaye de réécrire dans le navigateur, cependant si l'utilisateur navigue sur n'importe quelle page de notre domaine (pendant que le script est encore en cours), le navigateur se bloque jusqu'à ce que le script se termine . (Cela se produit uniquement lorsqu'un onglet ou une fenêtre du navigateur est fermé et non l'intégralité de l'application du navigateur.) Cela reste néanmoins loin d'être idéal.

(Solution possible
J'ai décidé de sortir la partie "email" du script dans un fichier distinct que je peux appeler après que la notification a été postée. J'ai d'abord pensé mettre cela sur la page de confirmation après que la notification a été postée avec succès. Cependant, l'utilisateur ne saura pas que ce script est en cours d'exécution et aucune anomalie ne lui sera apparente; Ce script ne peut pas échouer.

Mais, et si je peux exécuter ce script en tant que processus d'arrière-plan? Donc, ma question est la suivante: Comment puis-je exécuter un script PHP à déclencher en tant que service d'arrière-plan et exécuter complètement indépendant de ce que l'utilisateur a fait au niveau du formulaire?

EDIT: Cela ne peut pas être croné. Il doit s'exécuter à l'instant où le formulaire est soumis. Ce sont des notifications hautement prioritaires. De plus, les administrateurs système qui exécutent nos serveurs interdisent l'exécution de crons plus de 5 minutes.




De toutes les réponses, aucun ne considérait la fonction fastcgi_finish_request ridiculement facile, qui, lorsqu'elle est appelée, vide toutes les sorties restantes vers le navigateur et ferme la session Fastcgi et la connexion HTTP, tout en laissant le script s'exécuter en arrière-plan.

Un exemple:

<?php
header('Content-Type: application/json');
echo json_encode(['ok' => true]);
fastcgi_finish_request(); // The user is now disconnected from the script

// do stuff with received data,



Si vous pouvez accéder au serveur via ssh et que vous pouvez exécuter vos propres scripts, vous pouvez créer un simple serveur fifo utilisant php (bien que vous deviez recompiler php avec le support posix pour fork ).

Le serveur peut être écrit dans n'importe quoi, vous pouvez facilement le faire en python.

Ou la solution la plus simple serait d'envoyer un http://php.net/manual/en/function.httprequest-send.php et ne pas lire les données de retour, mais le serveur pourrait détruire le script avant qu'il ne finisse le traitement.

Exemple de serveur:

<?php
define('FIFO_PATH', '/home/user/input.queue');
define('FORK_COUNT', 10);

if(file_exists(FIFO_PATH)) {
    die(FIFO_PATH . ' exists, please delete it and try again.' . "\n");
}

if(!file_exists(FIFO_PATH) && !posix_mkfifo(FIFO_PATH, 0666)){
    die('Couldn\'t create the listening fifo.' . "\n");
}

$pids = array();
$fp = fopen(FIFO_PATH, 'r+');
for($i = 0; $i < FORK_COUNT; ++$i) {
    $pids[$i] = pcntl_fork();
    if(!$pids[$i]) {
        echo "process(" . posix_getpid() . ", id=$i)\n";
        while(true) {
            $line = chop(fgets($fp));
            if($line == 'quit' || $line === false) break;
            echo "processing (" . posix_getpid() . ", id=$i) :: $line\n";
        //  $data = json_decode($line);
        //  processData($data);
        }
        exit();
    }
}
fclose($fp);
foreach($pids as $pid){
    pcntl_waitpid($pid, $status);
}
unlink(FIFO_PATH);
?>

Exemple de client:

<?php
define('FIFO_PATH', '/home/user/input.queue');
if(!file_exists(FIFO_PATH)) {
    die(FIFO_PATH . ' doesn\'t exist, please make sure the fifo server is running.' . "\n");
}

function postToQueue($data) {
    $fp = fopen(FIFO_PATH, 'w+');
    stream_set_blocking($fp, false); //don't block
    $data = json_encode($data) . "\n";
    if(fwrite($fp, $data) != strlen($data)) {
        echo "Couldn't the server might be dead or there's a bug somewhere\n";
    }
    fclose($fp);
}
$i = 1000;
while(--$i) {
    postToQueue(array('xx'=>21, 'yy' => array(1,2,3)));
}
?>



En supposant que vous utilisez une plate-forme * nix, utilisez cron et l'exécutable php .

MODIFIER:

Il y a un certain nombre de questions demandant "running php without cron" sur SO déjà. Voici un:

Planifier des scripts sans utiliser CRON

Cela dit, la réponse d'exec () ci-dessus semble très prometteuse :)




Que dis-tu de ça?

  1. Votre script PHP qui contient le formulaire enregistre un indicateur ou une valeur dans une base de données ou un fichier.
  2. Un deuxième script PHP interroge périodiquement cette valeur et, si elle a été définie, déclenche le script Email de manière synchrone.

Ce second script PHP devrait être configuré pour fonctionner en tant que cron.




Pour l'arrière-plan, je pense que vous devriez essayer cette technique, il vous aidera à appeler autant de pages que vous voulez que toutes les pages seront exécutées en même temps indépendamment sans attendre que chaque réponse de la page soit asynchrone.

form_action_page.php

     <?php

    post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue");
    //post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue2");
    //post_async("http://localhost/projectname/otherpage.php", "Keywordname=anyValue");
    //call as many as pages you like all pages will run at once //independently without waiting for each page response as asynchronous.

  //your form db insertion or other code goes here do what ever you want //above code will work as background job this line will direct hit before //above lines response     
                ?>
                <?php

                /*
                 * Executes a PHP page asynchronously so the current page does not have to wait for it to     finish running.
                 *  
                 */
                function post_async($url,$params)
                {

                    $post_string = $params;

                    $parts=parse_url($url);

                    $fp = fsockopen($parts['host'],
                        isset($parts['port'])?$parts['port']:80,
                        $errno, $errstr, 30);

                    $out = "GET ".$parts['path']."?$post_string"." HTTP/1.1\r\n";//you can use POST instead of GET if you like
                    $out.= "Host: ".$parts['host']."\r\n";
                    $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
                    $out.= "Content-Length: ".strlen($post_string)."\r\n";
                    $out.= "Connection: Close\r\n\r\n";
                    fwrite($fp, $out);
                    fclose($fp);
                }
                ?>

testpage.php

    <?
    echo $_REQUEST["Keywordname"];//case1 Output > testValue
//here do your background operations it will not halt main page
    ?>

PS: si vous voulez envoyer des paramètres d'URL en boucle, suivez cette réponse: https://.com/a/41225209/6295712




Et pourquoi ne pas faire une requête HTTP sur le script et ignorer la réponse?

http://php.net/manual/en/function.httprequest-send.php

Si vous faites votre demande sur le script, vous devez appeler votre serveur Web pour l'exécuter en arrière-plan et vous pouvez (dans votre script principal) afficher un message indiquant à l'utilisateur que le script est en cours d'exécution.




Links