Jak uruchomić skrypt PHP w tle po przesłaniu formularza?

Problem
Mam formularz, który po złożeniu uruchomi Podstawowy kod do przetworzenia przesłanych informacji i wstawi je do bazy danych w celu wyświetlenia na stronie zgłoszenia. Ponadto mam listę osób, które zarejestrowały się, aby otrzymywać te powiadomienia za pośrednictwem wiadomości e-mail i SMS. Ta lista jest trywialna jako moment (tylko pchanie około 150), jednak wystarczy, aby spowodować, że trwa ponad minutę, aby przejść przez całą tabelę subskrybentów i wysłać 150+ e-maile. (E-maile są wysyłane indywidualnie zgodnie z życzeniem administratorów systemu naszego serwera e-mail z powodu masowych zasad e-mail.)

W tym czasie osoba, która opublikowała alert, będzie siedzieć na ostatniej stronie formularza przez prawie minutę bez żadnego pozytywnego wzmocnienia, że ich powiadomienie jest publikowane. Prowadzi to do innych potencjalnych problemów, Wszystkie, które mają możliwe rozwiązania, które uważam za mniej niż idealne.

  1. Po pierwsze, plakat może pomyśl, że serwer jest opóźniony i kliknij przycisk "Wyślij" ponownie, powodując, że skrypt zaczyna się od nowa lub uruchamia się dwa razy. Mógłbym rozwiązać to za pomocą JavaScript, aby wyłączyć przycisk i zastąpić tekst, aby powiedzieć coś takiego jak " przetwarzanie...', jednak jest to mniej niż idealne, ponieważ użytkownik nadal będzie zatrzymany na stronie przez czas wykonywania skryptu. (Również, jeśli JavaScript jest wyłączony, ten problem nadal istnieje.)

  2. Po drugie, plakat może zamknąć kartę lub przeglądarkę przedwcześnie po przesłaniu formularza. Skrypt będzie działał na serwerze, dopóki nie spróbuje odpisać do przeglądarki, jednak jeśli użytkownik następnie przegląda dowolną stronę w naszej domenie (podczas gdy skrypt jest nadal uruchomiony), przeglądarka zawiesza ładowanie strony do momentu zakończenia skryptu. (Dzieje się tak tylko wtedy, gdy karta lub okno przeglądarki jest zamknięte, a nie cała aplikacja przeglądarki.) Mimo to nie jest to idealne rozwiązanie.

(Możliwe) Rozwiązanie
Ja postanowiłem, że chcę wyłamać część "e-mail" skryptu do osobnego pliku, który mogę wywołać po wysłaniu powiadomienia. Początkowo myślałem o umieszczeniu tego na stronie potwierdzenia po pomyślnym wysłaniu powiadomienia. Jednak użytkownik nie będzie wiedział, że ten skrypt jest uruchomiony i wszelkie anomalie nie będą dla niego widoczne; ten skrypt nie może zawieść.

Ale co jeśli mogę uruchomić ten skrypt jako proces w tle? Więc moje pytanie jest takie: jak mogę wykonać PHP skrypt uruchamiany jako usługa w tle i uruchamiany całkowicie niezależnie od tego, co użytkownik zrobił na poziomie formularza?

EDIT: This cannot be cron'ed. Musi działać natychmiast po przesłaniu formularza. Są to powiadomienia o wysokim priorytecie. Ponadto administratorzy systemu obsługujący nasze serwery nie zezwalają cronom na działanie częściej niż 5 minut.

Author: Michael Irigoyen, 2011-01-07

15 answers

Przeprowadzając eksperymenty z exec i shell_exec odkryłem rozwiązanie, które działało idealnie! Wybieram użycie shell_exec, aby móc rejestrować każdy proces powiadamiania, który się dzieje (lub nie). (shell_exec zwraca jako łańcuch znaków i było to łatwiejsze niż użycie exec, przypisanie wyjścia do zmiennej, a następnie otwarcie pliku do zapisu.)

Używam poniższej linii, aby wywołać skrypt e-mail:

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

Ważne jest zwrócenie uwagi na & na końcu polecenia (zgodnie ze wskazaniem out by @ netcoder). To polecenie UNIX uruchamia proces w tle.

Dodatkowe zmienne otoczone pojedynczymi cudzysłowami po ścieżce do skryptu są ustawione jako zmienne $_SERVER['argv'], które mogę wywołać w swoim skrypcie.

Skrypt e-mail następnie wysyła do mojego pliku dziennika za pomocą >> i wyświetli coś takiego:

[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)
 75
Author: Michael Irigoyen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-24 20:47:17

Na serwerach Linux / Unix, możesz wykonać zadanie w tle używając 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);

& jest tu najważniejszy. Skrypt będzie kontynuowany, nawet jeśli oryginalny skrypt został zakończony.

 16
Author: netcoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-07 17:19:50

PHP exec("php script.php") da radę.

Z instrukcji :

Jeśli program jest uruchamiany z tym funkcji, aby mogła być kontynuowana uruchomiony w tle, wyjście programu należy przekierować na plik lub inny strumień wyjściowy. Niepowodzenie aby to zrobić spowoduje, że PHP zawiesi się do wykonanie programu kończy się.

Więc jeśli przekierujesz wyjście do pliku dziennika (co jest dobrym pomysłem w każdym razie), Twój skrypt wywołujący nie zawiesi się, a Twój skrypt e-mail zostanie uruchomiony w bg.

 6
Author: Simon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-07 15:13:13

A dlaczego nie wykonać żądania HTTP na skrypcie i nie zignorować odpowiedzi ?

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

Jeśli złożysz żądanie na skrypcie, musisz zadzwonić do serwera, który uruchomi go w tle i możesz (w swoim skrypcie głównym) pokazać komunikat informujący użytkownika, że skrypt jest uruchomiony.

 5
Author: Twister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-07 16:32:53

Co ty na to?

  1. Twój skrypt PHP, który zawiera formularz, zapisuje flagę lub jakąś wartość do bazy danych lub pliku.
  2. drugi skrypt PHP okresowo sprawdza tę wartość i jeśli została ustawiona, uruchamia skrypt poczty e-mail w sposób synchroniczny.

Ten drugi skrypt PHP powinien być ustawiony jako cron.

 4
Author: adarshr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-07 15:09:33

Jak wiem, nie można tego zrobić w łatwy sposób (patrz Fork exec etc (nie działa pod windows)), może być można odwrócić podejście, użyć tła przeglądarki publikując formularz w ajax, więc jeśli post nadal działa nie masz czasu oczekiwania.
To może pomóc, nawet jeśli trzeba zrobić jakieś długie opracowanie.

O wysyłaniu Poczty to zawsze sugerują użycie spoolera, może to być lokalny i szybki serwer smtp, który akceptuje twoje żądania i spolszczenie ich do prawdziwego MTA lub umieścić wszystko w DB, niż użyć cron, który szpuluje kolejkę.
Cron może być na innej maszynie wywołującej spooler jako zewnętrzny adres url:

* * * * * wget -O /dev/null http://www.example.com/spooler.php
 4
Author: Ivan Buttinoni,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-07 15:25:08

Prostszym sposobem uruchomienia skryptu PHP w tle jest

php script.php >/dev/null &

Skrypt będzie działał w tle, a strona szybciej dotrze do strony akcji.

 4
Author: Sandeep Dhanda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-19 20:16:07

Background cron job brzmi jak dobry pomysł na to.

Będziesz potrzebował dostępu ssh do komputera, aby uruchomić skrypt jako cron.

$ php scriptname.php żeby go uruchomić.

 3
Author: Ian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-07 15:08:29

Jeśli masz dostęp do serwera przez ssh i możesz uruchamiać własne skrypty, możesz stworzyć prosty serwer fifo używając php (chociaż będziesz musiał przekompilować php z posix wsparciem dla fork).

Serwer może być napisany we wszystkim, prawdopodobnie można to łatwo zrobić w Pythonie.

Lub najprostszym rozwiązaniem byłoby wysłanie HttpRequest i nie odczytanie danych zwrotnych, ale serwer może zniszczyć skrypt przed zakończeniem przetwarzania.

Przykładowy serwer :

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

Przykładowy klient:

<?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
Author: OneOfOne,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-07 16:26:13

Zakładając, że działasz na platformie *nix, użyj cron I pliku wykonywalnego php.

EDIT:

Istnieje spora liczba pytań pytających o" uruchamianie php bez crona " już na SO. Oto jeden:

Zaplanuj Skrypty bez użycia CRON

To powiedziawszy, powyższa odpowiedź exec () brzmi bardzo obiecująco:)

 2
Author: Spiny Norman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:17:39

Jeśli jesteś na Windows, badania proc_open lub popen...

Ale jeśli jesteśmy na tym samym serwerze "Linux" z cpanelem, to jest to właściwe podejście:

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

Nie używaj fork() lub curl Jeśli wątpisz, że sobie z nimi poradzisz, to tak jak nadużywanie serwera

Na koniec, w pliku script.php który jest wywołany powyżej, zwróć uwagę na to upewnij się, że napisałeś:

<?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
Author: i am ArbZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-09 20:16:32

Spośród wszystkich odpowiedzi, żadna nie uznała śmiesznie łatwej funkcji fastcgi_finish_request, która po wywołaniu usuwa wszystkie pozostałe dane wyjściowe do przeglądarki i zamyka sesję Fastcgi i połączenie HTTP, pozwalając jednocześnie skryptowi działać w tle.

Przykład:

<?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,
 2
Author: Danogentili,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-01 11:02:04

W moim przypadku mam 3 paramy, jedną z nich jest 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 &");
W moim procesie.php mam ten kod:
if (!isset($argv[1]) || !isset($argv[2]) || !isset($argv[3]))
{   
    die("Error.");
} 

$idCurso = $argv[1];
$idDestino = $argv[2];
$mensaje = $argv[3];
 0
Author: Hernaldo Gonzalez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-23 18:58:30

Dla background workera myślę, że powinieneś spróbować tej techniki pomoże to wywołać tyle stron, ile lubisz wszystkie strony będą działać naraz niezależnie bez czekania na każdą odpowiedź strony asynchroniczną.

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: Jeśli chcesz wysłać parametry url jako pętlę, postępuj zgodnie z tą odpowiedzią: https://stackoverflow.com/a/41225209/6295712

 0
Author: Hassan Saeed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:13

To działa dla mnie. tyr this

exec(“php asyn.php”.” > /dev/null 2>/dev/null &“);
 0
Author: Raj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-25 10:22:04