web-dev-qa-db-fra.com

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

Problème
J'ai un formulaire qui, une fois soumis, exécutera un code de base pour traiter les informations soumises et les insérer dans une base de données pour les afficher sur un site Web de notification. De plus, j’ai une liste de personnes qui se sont inscrites pour recevoir ces notifications par courrier électronique et par SMS message. Cette liste est triviale à l’instant (ne pousser que 150 personnes environ), mais cela suffit car il faut plus d’une minute pour parcourir l’ensemble du tableau des abonnés et envoyer plus de 150 courriers électroniques (les courriers électroniques sont envoyés individuellement, à la demande des administrateurs système de notre serveur de messagerie en raison de la politique de messagerie en masse).

Pendant ce temps, la personne qui a posté l'alerte reste sur la dernière page du formulaire pendant près d'une minute sans aucun élément positif indiquant que sa notification est en train d'être affichée. Cela conduit à d'autres problèmes potentiels, tous ceux qui ont des solutions possibles que je pense ne sont pas idéales.

  1. Tout d'abord, l'affiche pourrait penser que le serveur est à la traîne et cliquer à nouveau sur le bouton "Soumettre", ce qui entraînera le script à recommencer ou à exécuter deux fois. Je pourrais résoudre ce problème en utilisant JavaScript pour désactiver le bouton et remplacer le texte pour dire quelque chose comme "Traitement en cours ...". Toutefois, ce n'est pas idéal car l'utilisateur restera bloqué sur la page pendant toute la durée de l'exécution du script. (En outre, si JavaScript est désactivé, ce problème persiste.)

  2. Deuxièmement, l'affiche pourrait fermer l'onglet ou le navigateur prématurément après avoir soumis le formulaire. Le script continuera à fonctionner sur le serveur jusqu'à ce qu'il tente d'écrire sur le navigateur. Toutefois, si l'utilisateur navigue ensuite vers une page de notre domaine (tant que le script est en cours d'exécution), le navigateur se bloque pendant le chargement de la page jusqu'à la fin du script. . (Cela ne se produit que lorsqu'un onglet ou une fenêtre du navigateur est fermé et non l'application complète du navigateur.) Cela reste néanmoins loin d'être idéal.

Solution possible
J'ai décidé de séparer la partie "courrier électronique" du script dans un fichier séparé que je peux appeler après l'envoi de la notification. Au départ, j'avais pensé mettre ceci sur la page de confirmation une fois que la notification avait é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 que se passe-t-il si je peux exécuter ce script en tant que processus d'arrière-plan? Ma question est donc la suivante: comment puis-je exécuter un script PHP à déclencher en tant que service d’arrière-plan et s’exécutant de manière totalement indépendante de ce que l’utilisateur a fait au niveau du formulaire?

EDIT: Ceci ne peut pas être corrigé. Il doit être exécuté au moment où le formulaire est soumis. Ce sont des notifications hautement prioritaires. De plus, les administrateurs système qui exécutent nos serveurs interdisent aux crons de fonctionner plus souvent que 5 minutes.

97
Michael Irigoyen

Faire des expériences avec exec et Shell_exec J'ai découvert une solution qui fonctionnait parfaitement! J'ai choisi d'utiliser Shell_exec afin que je puisse consigner chaque processus de notification qui se produit (ou non). (Shell_exec retourne sous forme de chaîne, ce qui était plus facile que d’utiliser exec, assigner la sortie à une variable puis ouvrir un fichier dans lequel écrire.)

J'utilise la ligne suivante pour appeler le script de messagerie:

Shell_exec("/path/to/php /path/to/send_notifications.php '".$post_id."' 'alert' >> /path/to/alert_log/paging.log &");

Il est important de noter le & à la fin de la commande (comme indiqué par @netcoder). Cette commande UNIX exécute un processus en arrière-plan.

Les variables supplémentaires entourées de guillemets simples après le chemin du script sont définies comme suit: $_SERVER['argv'] variables que je peux appeler dans mon script.

Le script de messagerie envoie ensuite dans mon fichier journal à l'aide de la commande >> et affichera quelque chose comme ceci:

[2011-01-07 11:01:26] Alert Notifications Sent for http://alerts.illinoisstate.edu/2049 (SCRIPT: 38.71 seconds)
[2011-01-07 11:01:34] CRITICAL ERROR: Alert Notifications NOT sent for http://alerts.illinoisstate.edu/2049 (SCRIPT: 23.12 seconds)
82
Michael Irigoyen

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 original est terminé.

17
netcoder

PHP exec("php script.php") peut le faire.

Du Manuel :

Si un programme est démarré avec cette fonction, pour qu'il continue de fonctionner en arrière-plan, la sortie du programme doit être redirigée vers un fichier ou un autre flux de sortie. Faute de quoi, PHP sera suspendu jusqu'à la fin de l'exécution du programme.

Donc, si vous redirigez la sortie vers un fichier journal (ce qui est une bonne idée de toute façon), votre script d'appel ne sera pas bloqué et votre script de messagerie s'exécutera en bg.

7
Simon

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 et l'exécuter en arrière-plan. Vous pouvez (dans votre script principal) afficher un message indiquant à l'utilisateur que le script est en cours d'exécution.

6
Twister

De toutes les réponses, aucune n'a considéré la fonction ridiculement facile fastcgi_finish_request , qui, lorsqu'elle est appelée, vide toutes les sorties restantes dans 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,
6
Danogentili

Comme je sais que vous ne pouvez pas le faire facilement (voir fork exec etc (ne fonctionne pas sous Windows)), vous pouvez peut-être inverser l'approche, utilisez l'arrière-plan du navigateur affichant le formulaire en ajax, donc si le message reste travaille tu n'as aucun temps d'attente.
Cela peut aider même si vous devez élaborer longuement.

À propos de l’envoi de courrier, il est toujours recommandé d’utiliser un spouleur. Il peut s’agir d’un serveur smtp local et rapide qui accepte vos demandes et les diffuse vers le vrai MTA ou met tout cela dans une base de données, puis utilise un cron spoulant le spool. queue.
Le cron peut se trouver sur une autre machine appelant le spouleur en tant qu’URL externe:

* * * * * wget -O /dev/null http://www.example.com/spooler.php
4
Ivan Buttinoni

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 cette valeur périodiquement et, si elle a été définie, il déclenche le script Email de manière synchrone.

Ce second script PHP doit être configuré pour s'exécuter en tant que cron.

4
adarshr

La manière la plus simple d’exécuter un script PHP en arrière-plan est la suivante:

php script.php >/dev/null &

Le script s'exécutera en arrière-plan et la page atteindra également la page d'action plus rapidement.

4
Sandeep Dhanda

Le travail de fond cron semble être une bonne idée pour cela.

Vous aurez besoin d’un accès ssh à la machine pour exécuter le script en tant que cron.

$ php scriptname.php pour l'exécuter.

3
Ian

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

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

Ou bien la solution la plus simple serait d'envoyer un HttpRequest et de ne pas lire les données de retour, mais le serveur pourrait détruire le script avant la fin du 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)));
}
?>
3
OneOfOne

Si vous utilisez Windows, recherchez proc_open Ou popen...

Mais si nous sommes sur le même serveur "Linux" exécutant cpanel, c’est la bonne approche:

#!/usr/bin/php 
<?php
$pid = Shell_exec("Nohup Nice php -f            
'path/to/your/script.php' /dev/null 2>&1 & echo $!");
While(exec("ps $pid"))
{ //you can also have a streamer here like fprintf,        
 // or fgets
}
?>

N'utilisez pas fork() ou curl si vous doutez de pouvoir les gérer, c'est comme si vous abusiez de votre serveur.

Enfin, sur le fichier script.php Qui s’appelle ci-dessus, prenez note de ceci, assurez-vous d’écrire:

<?php
ignore_user_abort(TRUE);
set_time_limit(0);
ob_start();
// <-- really optional but this is pure php

//Code to be tested on background

ob_flush(); flush(); 
//this two do the output process if you need some.        
//then to make all the logic possible


str_repeat(" ",1500); 
//.for progress bars or loading images

sleep(2); //standard limit

?>
2
i am ArbZ

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

MODIFIER:

Il y a beaucoup de questions demandant "d'exécuter php sans cron" sur SO déjà. En voici une:

Programmer des scripts sans utiliser CRON

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

2
Spiny Norman

pour les travailleurs d’arrière-plan, je pense que vous devriez essayer cette technique, cela vous aidera d’appeler autant de pages que vous aimez, toutes les pages s’exécutant en même temps indépendamment sans attendre la réponse de chaque page de manière 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 les paramètres d’URL sous forme de boucle, suivez la réponse suivante: https://stackoverflow.com/a/41225209/6295712

2
Hassan Saeed

Dans mon cas, j'ai 3 paramètres, dont l'un est string (mensaje):

exec("C:\wamp\bin\php\php5.5.12\php.exe C:/test/N/trunk/api/v1/Process.php $idTest2 $idTest3 \"$mensaje\" >> c:/log.log &");

Dans mon Process.php j'ai ce code:

if (!isset($argv[1]) || !isset($argv[2]) || !isset($argv[3]))
{   
    die("Error.");
} 

$idCurso = $argv[1];
$idDestino = $argv[2];
$mensaje = $argv[3];
0
Hernaldo Gonzalez

Ceci fonctionne pour moi. tyr cette

exec(“php asyn.php”.” > /dev/null 2>/dev/null &“);
0
Raj