Wie kann ich ein PHP-Skript im Hintergrund ausführen, nachdem ein Formular gesendet wurde?


104

Problem
Ich habe ein Formular, das bei der Übermittlung Basiscode ausführt, um die übermittelten Informationen zu verarbeiten und in eine Datenbank einzufügen, um sie auf einer Benachrichtigungswebsite anzuzeigen. Außerdem habe ich eine Liste von Personen, die sich angemeldet haben, um diese Benachrichtigungen per E-Mail und SMS zu erhalten. Diese Liste ist im Moment trivial (nur etwa 150), es reicht jedoch aus, um zu veranlassen, dass es mehr als eine Minute dauert, um die gesamte Abonnententabelle zu durchlaufen und mehr als 150 E-Mails zu versenden. (Die E-Mails werden aufgrund von Massen-E-Mail-Richtlinien einzeln gesendet, wie von den Systemadministratoren unseres E-Mail-Servers angefordert.)

Während dieser Zeit bleibt die Person, die die Warnung veröffentlicht hat, fast eine Minute lang auf der letzten Seite des Formulars, ohne dass positiv bestätigt wird, dass ihre Benachrichtigung veröffentlicht wird. Dies führt zu anderen potenziellen Problemen, die alle mögliche Lösungen haben, die ich für weniger als ideal halte.

  1. Erstens könnte das Poster glauben, dass der Server hinterherhinkt, und erneut auf die Schaltfläche "Senden" klicken, wodurch das Skript neu gestartet oder zweimal ausgeführt wird. Ich könnte dies lösen, indem ich JavaScript verwende, um die Schaltfläche zu deaktivieren und den Text zu ersetzen, um so etwas wie "Verarbeitung ..." zu sagen. Dies ist jedoch nicht ideal, da der Benutzer für die Dauer der Skriptausführung immer noch auf der Seite hängen bleibt. (Wenn JavaScript deaktiviert ist, besteht dieses Problem weiterhin.)

  2. Zweitens schließt das Poster möglicherweise die Registerkarte oder den Browser vorzeitig, nachdem das Formular gesendet wurde. Das Skript wird auf dem Server so lange ausgeführt, bis es versucht, in den Browser zurückzuschreiben. Wenn der Benutzer dann jedoch zu einer beliebigen Seite in unserer Domäne navigiert (während das Skript noch ausgeführt wird), bleibt der Browser beim Laden der Seite hängen, bis das Skript beendet ist . (Dies geschieht nur, wenn eine Registerkarte oder ein Fenster des Browsers geschlossen ist und nicht die gesamte Browseranwendung.) Dies ist jedoch nicht ideal.

(Möglich) Lösung
Ich habe beschlossen, den "E-Mail" -Teil des Skripts in eine separate Datei aufzuteilen, die ich nach dem Posten der Benachrichtigung aufrufen kann. Ich dachte ursprünglich daran, dies auf die Bestätigungsseite zu setzen, nachdem die Benachrichtigung erfolgreich veröffentlicht wurde. Der Benutzer weiß jedoch nicht, dass dieses Skript ausgeführt wird, und Anomalien sind für ihn nicht erkennbar. Dieses Skript kann nicht fehlschlagen.

Aber was ist, wenn ich dieses Skript als Hintergrundprozess ausführen kann? Meine Frage lautet also: Wie kann ich ein PHP-Skript ausführen, um es als Hintergrunddienst auszulösen und völlig unabhängig davon auszuführen, was der Benutzer auf Formularebene getan hat?

EDIT: Dies kann nicht gekrönt werden. Es muss sofort ausgeführt werden, wenn das Formular gesendet wird. Dies sind Benachrichtigungen mit hoher Priorität. Darüber hinaus verbieten die Systemadministratoren, auf denen unsere Server ausgeführt werden, dass Cron häufiger als 5 Minuten ausgeführt wird.


Starten Sie einen Cron-Job und benachrichtigen Sie den Benutzer auf einer anderen Seite, dass seine Anfrage verarbeitet wird, oder so ähnlich.
Evan Mulawski

1
Möchten Sie es regelmäßig (jeden Tag oder jede Stunde) oder beim Einreichen ausführen? Ich denke, Sie könnten PHPs verwenden, exec()aber ich habe das nie versucht.
Simon

2
Ich verwende ajaxund füge einfach sleep()mit Zeitintervall in die Schleife ein (in meinem Fall foreach), um den Hintergrundprozess auszuführen. Es funktioniert perfekt in meinem Server. Ich bin mir nicht sicher über die anderen. Ich füge außerdem ignore_user_abort()und set_time_limit()(mit berechneter Endzeit) vor der Schleife hinzu, um sicherzustellen, dass das Skript nicht von dem Server gestoppt wird, den ich verwende, auch nach meinem Test, wenn das Skript die Aufgabe ohne ignore_user_abort()und ausführt set_time_limit().
Ari

Ich habe gesehen, dass Sie bereits eine Lösung gefunden haben. Ich benutze gearmanmit PHP, um Hintergrundaufgaben zu erledigen. Es ist perfekt zu skalieren, wenn Sie auf große Zeitverarbeitung und hohe Lasten stoßen. Sie sollten einen Blick darauf werfen.
Andre Garcia

Folgen Sie dieser Antwort auf [stackoverflow] ( stackoverflow.com/a/46617107/6417678 )
Joshy Francis

Antworten:


89

Doing einige Experimente mit execund shell_execich habe eine Lösung entdeckt , die perfekt funktioniert! Ich entscheide mich für die Verwendung, shell_execdamit ich jeden Benachrichtigungsprozess protokollieren kann, der stattfindet (oder nicht). ( shell_execGibt als Zeichenfolge zurück und dies war einfacher als die Verwendung exec, die Ausgabe einer Variablen zuzuweisen und dann eine Datei zum Schreiben zu öffnen.)

Ich verwende die folgende Zeile, um das E-Mail-Skript aufzurufen:

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

Es ist wichtig, das &am Ende des Befehls zu beachten (wie von @netcoder hervorgehoben). Dieser UNIX-Befehl führt einen Prozess im Hintergrund aus.

Die zusätzlichen Variablen in einfachen Anführungszeichen nach dem Pfad zum Skript werden als $_SERVER['argv']Variablen festgelegt, die ich in meinem Skript aufrufen kann.

Das E-Mail-Skript gibt dann mithilfe von Folgendes in meine Protokolldatei aus >>und gibt Folgendes aus:

[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)

Vielen Dank. Das hat mir das Leben gerettet.
Liam Bailey

Was ist das $post_idnach dem Pfad des PHP-Skripts?
Perocat

@Perocat ist eine Variable, die Sie an das Skript senden. In diesem Fall wird eine ID gesendet, die ein Parameter für das aufgerufene Skript ist.
Chique

Ich bezweifle, dass dies perfekt funktioniert, siehe stackoverflow.com/questions/2212635/…
symcbean

1
Es gibt einen ausstehenden Bearbeitungsvorschlag, um zu entkommen $post_id. Ich würde es lieber direkt auf eine Zahl übertragen : (int) $post_id. (
Fordern Sie

19

Auf Linux / Unix-Servern können Sie einen Job im Hintergrund mit proc_open ausführen :

$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);

Das &Sein ist hier das Wichtigste. Das Skript wird auch dann fortgesetzt, wenn das ursprüngliche Skript beendet wurde.


Dies funktioniert, wenn ich nicht anrufe, proc_closewenn die Aufgabe abgeschlossen ist. proc_closeLässt das Skript etwa 15 bis 25 Sekunden hängen. Ich müsste ein Protokoll mit den Pipe-Informationen führen, also muss ich eine Datei öffnen, in die geschrieben werden soll, sie schließen und dann die Proc-Verbindung schließen. Leider funktioniert diese Lösung bei mir nicht.
Michael Irigoyen

3
Natürlich rufst du nicht an proc_close, das ist der Punkt! Und ja, Sie können mit zu einer Datei leiten proc_open. Siehe aktualisierte Antwort.
Netcoder

@netcoder: Was ist, wenn ich das Ergebnis in keiner Datei speichern möchte? Welche Änderungen sind in stdout erforderlich?
Naggarwal11

9

Von allen Antworten berücksichtigte keine die lächerlich einfache Funktion fastcgi_finish_request , die beim Aufruf alle verbleibenden Ausgaben in den Browser löscht und die Fastcgi-Sitzung und die HTTP-Verbindung schließt, während das Skript im Hintergrund ausgeführt wird.

Ein Beispiel:

<?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,

Genau das habe ich gesucht. In shell_exec muss ich die veröffentlichten Daten irgendwie in ein ausgeführtes Skript übertragen, was in meinem Fall selbst eine zeitaufwändige Aufgabe ist und die Dinge auch sehr kompliziert macht.
Aurangzeb

7

PHP exec("php script.php")kann es schaffen.

Aus dem Handbuch :

Wenn ein Programm mit dieser Funktion gestartet wird, muss die Ausgabe des Programms in eine Datei oder einen anderen Ausgabestream umgeleitet werden, damit es im Hintergrund weiter ausgeführt werden kann. Andernfalls bleibt PHP hängen, bis die Ausführung des Programms beendet ist.

Wenn Sie also die Ausgabe in eine Protokolldatei umleiten (was sowieso eine gute Idee ist), bleibt Ihr aufrufendes Skript nicht hängen und Ihr E-Mail-Skript wird in bg ausgeführt.


1
Danke, aber das hat nicht funktioniert. Das Skript "hängt" noch, während es die zweite Datei verarbeitet.
Michael Irigoyen

Schauen Sie sich die Empfehlung php.net/manual/en/function.exec.php#80582 an. Dies scheint darauf zurückzuführen zu sein, dass Safe_mode aktiviert ist. Unter Unix gibt es eine Problemumgehung.
Simon

Leider ist der abgesicherte Modus bei unserer Installation nicht aktiviert. Es ist seltsam, dass es immer noch hängt.
Michael Irigoyen

6

Und warum nicht eine HTTP-Anfrage für das Skript stellen und die Antwort ignorieren?

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

Wenn Sie Ihre Anfrage für das Skript stellen, das Sie aufrufen müssen, wird Ihr Webserver im Hintergrund ausgeführt, und Sie können (in Ihrem Hauptskript) eine Meldung anzeigen, die dem Benutzer mitteilt, dass das Skript ausgeführt wird.


4

Wie wäre es damit?

  1. Ihr PHP-Skript, das das Formular enthält, speichert ein Flag oder einen Wert in einer Datenbank oder Datei.
  2. Ein zweites PHP-Skript fragt regelmäßig nach diesem Wert. Wenn dieser Wert festgelegt wurde, wird das E-Mail-Skript synchron ausgelöst.

Dieses zweite PHP-Skript sollte so eingestellt sein, dass es als Cron ausgeführt wird.


Vielen Dank, aber ich habe die ursprüngliche Frage aktualisiert. Cron's sind in diesem Fall keine Option.
Michael Irigoyen

4

Wie ich weiß, können Sie dies nicht auf einfache Weise tun (siehe Fork Exec usw. (funktioniert nicht unter Windows)). Möglicherweise können Sie den Ansatz umkehren und den Hintergrund des Browsers verwenden, der das Formular in Ajax veröffentlicht Arbeit hast du keine Wartezeit.
Dies kann auch dann hilfreich sein, wenn Sie einige lange Ausarbeitungen vornehmen müssen.

Beim Senden von E-Mails wird immer empfohlen, einen Spooler zu verwenden. Möglicherweise handelt es sich um einen lokalen und schnellen SMTP-Server, der Ihre Anforderungen akzeptiert und diese an den echten MTA weiterleitet oder alle in eine Datenbank stellt, als einen Cron, der die Warteschlange spoolt.
Der Cron befindet sich möglicherweise auf einem anderen Computer, der den Spooler als externe URL aufruft:

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

3

Hintergrund Cron Job klingt nach einer guten Idee dafür.

Sie benötigen SSH-Zugriff auf den Computer, um das Skript als Cron auszuführen.

$ php scriptname.php um es auszuführen.


1
keine Notwendigkeit für ssh. Viele Hosts verbieten ssh, haben aber Cron-Unterstützung.
Teson

Vielen Dank, aber ich habe die ursprüngliche Frage aktualisiert. Cron's sind in diesem Fall keine Option.
Michael Irigoyen

3

Wenn Sie über ssh auf den Server zugreifen und Ihre eigenen Skripte ausführen können, können Sie mit PHP einen einfachen FIFO-Server erstellen (obwohl Sie PHP mit posixUnterstützung für neu kompilieren müssen fork).

Der Server kann wirklich in alles geschrieben werden, Sie können es wahrscheinlich leicht in Python tun.

Oder die einfachste Lösung wäre, eine HttpRequest zu senden und die Rückgabedaten nicht zu lesen, aber der Server könnte das Skript zerstören, bevor die Verarbeitung abgeschlossen ist.

Beispielserver:

<?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);
?>

Beispielclient:

<?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

Die einfachere Möglichkeit, ein PHP-Skript im Hintergrund auszuführen, ist

php script.php >/dev/null &

Das Skript wird im Hintergrund ausgeführt und die Seite erreicht die Aktionsseite auch schneller.


2

Angenommen, Sie laufen auf einer * nix-Plattform, verwenden Sie cronund die phpausführbare Datei.

BEARBEITEN:

Es gibt bereits eine ganze Reihe von Fragen zu "PHP ohne Cron ausführen" auf SO. Hier ist eine:

Planen Sie Skripte ohne CRON

Trotzdem klingt die Antwort von exec () oben sehr vielversprechend :)


Vielen Dank, aber ich habe die ursprüngliche Frage aktualisiert. Cron's sind in diesem Fall keine Option.
Michael Irigoyen

2

Wenn Sie unter Windows arbeiten, recherchieren Sie proc_openoder popen...

Wenn wir uns jedoch auf demselben Server "Linux" befinden, auf dem cpanel ausgeführt wird, ist dies der richtige Ansatz:

#!/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
}
?>

Verwenden Sie es nicht fork()oder curlwenn Sie Zweifel haben, dass Sie damit umgehen können, ist es wie der Missbrauch Ihres Servers

Schließlich auf der script.phpDatei , die oben genannt wird, zur Kenntnis nehmen diese stellen Sie sicher , Sie schreibt:

<?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

?>

Es tut mir leid, dass ich es später bearbeitet habe, weil ich mein Handy benutze, und es ist in der Tat viel schwieriger als ein exec () - Skript zu schreiben. Lol;)
Ich bin ArbZ

2

Für Hintergrundarbeiter denke ich, dass Sie diese Technik ausprobieren sollten. Es wird hilfreich sein, so viele Seiten aufzurufen, wie Sie möchten. Alle Seiten werden unabhängig voneinander gleichzeitig ausgeführt, ohne auf jede Seitenantwort als asynchron zu warten.

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: Wenn Sie URL-Parameter als Schleife senden möchten, folgen Sie dieser Antwort: https://stackoverflow.com/a/41225209/6295712


0

In meinem Fall habe ich 3 Parameter, einer davon ist 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 &");

In meiner Process.php habe ich diesen Code:

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

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

0

Das funktioniert bei mir. Tyr dies

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

Was ist, wenn ich Post-Werte habe? von einem Formular und ich sende es über Ajax und serialize()Funktion. Ich habe zum Beispiel ein index.php Formular und sende einen Wert über Ajax an index2.php. Ich möchte das Senden von Daten in index2.php im Hintergrund durchführen. Was schlagen Sie vor? danke
khalid JA

0

Verwenden Sie Amphp , um Jobs parallel und asynchron auszuführen.

Installieren Sie die Bibliothek

composer require amphp/parallel-functions

Codebeispiel

<?php

require "vendor/autoload.php";

use Amp\Promise;
use Amp\ParallelFunctions;


echo 'started</br>';

$promises[1] = ParallelFunctions\parallel(function (){
    // Send Email
})();

$promises[2] = ParallelFunctions\parallel(function (){
    // Send SMS
})();


Promise\wait(Promise\all($promises));

echo 'finished';

Für Ihren Anwendungsfall können Sie Folgendes tun

<?php

use function Amp\ParallelFunctions\parallelMap;
use function Amp\Promise\wait;

$responses = wait(parallelMap([
    'a@example.com',
    'b@example.com',
    'c@example.com',
], function ($to) {
    return send_mail($to);
}));
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.