Bagaimana cara menjalankan skrip PHP di latar belakang setelah formulir dikirimkan?

104

Masalah
saya memiliki formulir yang, ketika dikirimkan, akan menjalankan kode dasar untuk memproses informasi yang dikirimkan dan memasukkannya ke dalam database untuk ditampilkan di situs web notifikasi. Selain itu, saya memiliki daftar orang yang telah mendaftar untuk menerima pemberitahuan ini melalui email dan pesan SMS. Daftar ini sepele saat ini (hanya mendorong sekitar 150), namun cukup untuk menyebabkan perlu lebih dari satu menit untuk menelusuri seluruh tabel pelanggan dan mengirim 150+ email. (Email dikirim secara individual seperti yang diminta oleh administrator sistem server email kami karena kebijakan email massal.)

Selama waktu ini, individu yang mengeposkan lansiran akan duduk di halaman terakhir formulir selama hampir satu menit tanpa penguatan positif bahwa pemberitahuan mereka sedang diposkan. Hal ini menyebabkan masalah potensial lainnya, semua itu memiliki solusi yang mungkin saya rasa kurang ideal.

  1. Pertama, pengepos mungkin mengira server sedang lamban dan mengklik tombol 'Kirim' lagi, menyebabkan skrip dimulai kembali atau dijalankan dua kali. Saya bisa menyelesaikan ini dengan menggunakan JavaScript untuk menonaktifkan tombol dan mengganti teks untuk mengatakan sesuatu seperti 'Memproses ...', namun ini kurang dari ideal karena pengguna masih akan terjebak di halaman selama eksekusi skrip. (Selain itu, jika JavaScript dinonaktifkan, masalah ini masih ada.)

  2. Kedua, poster mungkin menutup tab atau browser sebelum waktunya setelah mengirimkan formulir. Skrip akan terus berjalan di server hingga mencoba menulis kembali ke browser, namun jika pengguna kemudian menjelajahi halaman mana pun dalam domain kami (saat skrip masih berjalan), browser berhenti memuat halaman sampai skrip selesai . (Ini hanya terjadi ketika tab atau jendela browser ditutup dan bukan seluruh aplikasi browser.) Tetap saja, ini kurang dari ideal.

(Kemungkinan) Solusi
Saya telah memutuskan untuk membagi bagian "email" dari skrip menjadi file terpisah yang dapat saya panggil setelah pemberitahuan dikirim. Saya awalnya berpikir untuk meletakkan ini di halaman konfirmasi setelah pemberitahuan berhasil diposting. Namun, pengguna tidak akan tahu skrip ini sedang berjalan dan semua anomali tidak akan terlihat oleh mereka; Skrip ini tidak boleh gagal.

Tetapi, bagaimana jika saya dapat menjalankan skrip ini sebagai proses latar belakang? Jadi, pertanyaan saya adalah ini: Bagaimana saya dapat menjalankan skrip PHP untuk memicu sebagai layanan latar belakang dan berjalan sepenuhnya terlepas dari apa yang telah dilakukan pengguna di tingkat formulir?

EDIT: Ini tidak dapat di-cron'ed. Ini harus berjalan segera setelah formulir dikirimkan. Ini adalah pemberitahuan dengan prioritas tinggi. Selain itu, administrator sistem yang menjalankan server kami melarang crons berjalan lebih dari 5 menit.

Michael Irigoyen
sumber
Mulai tugas cron dan beri tahu pengguna di halaman lain bahwa permintaan mereka sedang diproses, atau semacamnya.
Evan Mulawski
1
Apakah Anda ingin menjalankannya secara teratur (setiap hari atau setiap jam) atau di submit? Saya kira Anda bisa menggunakan php exec()tetapi saya tidak pernah mencoba ini.
Simon
2
Saya hanya menggunakan ajaxdan memasukkan sleep()dengan interval waktu di dalam loop (saya menggunakan foreach dalam kasus saya) untuk menjalankan proses latar belakang. Ia bekerja dengan sempurna di server saya. Tidak yakin mengenai yang lain. Saya juga menambahkan ignore_user_abort()dan set_time_limit()(dengan waktu akhir yang dihitung) sebelum loop untuk memastikan bahwa skrip tidak akan dihentikan oleh server yang saya gunakan, bahkan menurut pengujian saya, skrip menyelesaikan tugas tanpa ignore_user_abort()dan set_time_limit().
Ari
Saya melihat Anda sudah menemukan solusi. Saya menggunakan gearmanphp untuk menangani tugas latar belakang. Ini sempurna untuk menskalakan jika Anda menemukan pemrosesan waktu yang lama dan beban berat. Anda harus memeriksanya.
Andre Garcia
Ikuti jawaban ini di [stackoverflow] ( stackoverflow.com/a/46617107/6417678 )
Joshy Francis

Jawaban:

89

Melakukan beberapa eksperimen dengan execdan shell_execsaya telah menemukan solusi yang bekerja dengan sempurna! Saya memilih untuk menggunakan shell_execsehingga saya dapat mencatat setiap proses pemberitahuan yang terjadi (atau tidak). ( shell_execdikembalikan sebagai string dan ini lebih mudah daripada menggunakan exec, menetapkan keluaran ke variabel dan kemudian membuka file untuk menulis.)

Saya menggunakan baris berikut untuk menjalankan skrip email:

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

Penting untuk memperhatikan &di akhir perintah (seperti yang ditunjukkan oleh @netcoder). Perintah UNIX ini menjalankan proses di latar belakang.

Variabel ekstra yang diapit tanda kutip tunggal setelah jalur ke skrip ditetapkan sebagai $_SERVER['argv']variabel yang dapat saya panggil dalam skrip saya.

Skrip email kemudian menampilkan file log saya menggunakan >>dan akan menampilkan sesuatu seperti ini:

[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)
Michael Irigoyen
sumber
Terima kasih. Ini menyelamatkan hidup saya.
Liam Bailey
Apa itu $post_idsetelah jalur skrip php?
Perocat
@Perocat itu adalah variabel yang Anda kirim ke skrip. Dalam hal ini mengirimkan ID yang akan menjadi parameter ke skrip yang dipanggil.
Chique
Saya ragu ini berfungsi dengan baik, lihat stackoverflow.com/questions/2212635/…
symcbean
1
Ada saran edit yang tertunda untuk keluar $post_id; Saya lebih suka dilemparkan langsung ke nomor: (int) $post_id. (Memanggil perhatian Anda untuk memutuskan mana yang lebih baik.)
Al.G.
19

Di server Linux / Unix, Anda dapat menjalankan pekerjaan di latar belakang dengan menggunakan 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);

The &menjadi sedikit penting di sini. Skrip akan terus berlanjut meskipun skrip asli telah berakhir.

netcoder
sumber
Ini berfungsi ketika saya tidak menelepon proc_closeketika tugas telah selesai. proc_closemembuat skrip hang sekitar 15 hingga 25 detik. Saya perlu menyimpan log menggunakan informasi pipa, jadi saya harus membuka file yang akan ditulis, menutupnya, dan kemudian menutup koneksi proc. Jadi, sayangnya, solusi ini tidak berhasil untuk saya.
Michael Irigoyen
3
Tentu saja Anda tidak menelepon proc_close, itulah intinya! Dan ya, Anda bisa menyalurkan ke file dengan proc_open. Lihat jawaban yang diperbarui.
netcoder
@netcoder: bagaimana jika saya tidak ingin menyimpan hasil di file apa pun. perubahan apa yang diperlukan di stdout?
naggarwal11
9

Dari semua jawaban, tidak ada yang menganggap fungsi fastcgi_finish_request yang sangat mudah , yang ketika dipanggil, membuang semua output yang tersisa ke browser dan menutup sesi Fastcgi dan koneksi HTTP, sambil membiarkan skrip berjalan di latar belakang.

Sebuah contoh:

<?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,
Danogentili
sumber
inilah yang saya cari, di shell_exec saya harus entah bagaimana mentransfer data yang diposting ke skrip yang dieksekusi yang dengan sendirinya merupakan tugas yang memakan waktu dalam kasus saya dan juga membuat segalanya menjadi sangat rumit.
Aurangzeb
7

PHP exec("php script.php")bisa melakukannya.

Dari Manual :

Jika sebuah program dimulai dengan fungsi ini, agar dapat terus berjalan di latar belakang, keluaran dari program harus diarahkan ke file atau aliran keluaran lain. Gagal melakukannya akan menyebabkan PHP hang hingga eksekusi program berakhir.

Jadi jika Anda mengarahkan output ke file log (bagaimanapun juga ide yang bagus), skrip pemanggil Anda tidak akan hang dan skrip email Anda akan berjalan di bg.

Simon
sumber
1
Terima kasih, tapi ini tidak berhasil. Skrip masih "hang" saat memproses file kedua.
Michael Irigoyen
Lihat perintah php.net/manual/en/function.exec.php#80582 Sepertinya ini karena Safe_mode diaktifkan. Ada solusi di bawah unix.
Simon
Sayangnya, mode aman tidak diaktifkan pada instalasi kami. Aneh kalau masih menggantung.
Michael Irigoyen
6

Dan mengapa tidak membuat Permintaan HTTP pada skrip dan mengabaikan responsnya?

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

Jika Anda membuat permintaan pada skrip, Anda perlu memanggil server web Anda akan menjalankannya di latar belakang dan Anda dapat (di skrip utama Anda) menampilkan pesan yang memberi tahu pengguna bahwa skrip sedang berjalan.

Twister
sumber
4

Bagaimana dengan ini?

  1. Skrip PHP Anda yang menyimpan formulir menyimpan bendera atau beberapa nilai ke dalam database atau file.
  2. Skrip PHP kedua mengumpulkan nilai ini secara berkala dan jika sudah disetel, ini memicu skrip Email secara sinkron.

Skrip PHP kedua ini harus diatur untuk dijalankan sebagai cron.

adarshr
sumber
Terima kasih, tetapi saya telah memperbarui pertanyaan asli. Cron bukanlah pilihan dalam kasus ini.
Michael Irigoyen
4

Seperti yang saya tahu Anda tidak dapat melakukan ini dengan cara yang mudah (lihat fork exec dll (tidak berfungsi di bawah windows)), mungkin Anda dapat membalikkan pendekatan, gunakan latar belakang browser untuk memposting formulir di ajax, jadi jika posting masih bekerja, Anda tidak perlu menunggu waktu.
Ini dapat membantu bahkan jika Anda harus melakukan elaborasi yang panjang.

Tentang pengiriman email selalu disarankan untuk menggunakan spooler, mungkin server smtp lokal & cepat yang menerima permintaan Anda dan spool mereka ke MTA yang sebenarnya atau meletakkan semuanya di DB, daripada menggunakan cron yang spool antrian.
Cron mungkin berada di komputer lain yang memanggil spooler sebagai url eksternal:

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

Pekerjaan cron latar belakang terdengar seperti ide bagus untuk ini.

Anda memerlukan akses ssh ke mesin untuk menjalankan skrip sebagai cron.

$ php scriptname.php untuk menjalankannya.

Ian
sumber
1
tidak perlu ssh. Banyak host yang melarang ssh, tetapi memiliki dukungan cron.
Teson
Terima kasih, tetapi saya telah memperbarui pertanyaan asli. Cron bukanlah pilihan dalam kasus ini.
Michael Irigoyen
3

Jika Anda dapat mengakses server melalui ssh dan dapat menjalankan skrip Anda sendiri, Anda dapat membuat server fifo sederhana menggunakan php (meskipun Anda harus mengkompilasi ulang php dengan posixdukungan untuk fork).

Server dapat ditulis dalam apa saja, Anda mungkin dapat dengan mudah melakukannya dengan python.

Atau solusi paling sederhana akan mengirimkan HttpRequest dan tidak membaca data yang dikembalikan tetapi server mungkin menghancurkan skrip sebelum selesai diproses.

Contoh server:

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

Contoh klien:

<?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)));
}
?>
OneOfOne
sumber
3

Cara paling sederhana untuk menjalankan skrip PHP di latar belakang adalah

php script.php >/dev/null &

Script akan berjalan di background dan halaman juga akan mencapai halaman tindakan lebih cepat.

Sandeep Dhanda
sumber
2

Dengan asumsi Anda berjalan pada platform * nix, gunakan crondan phpeksekusi.

EDIT:

Ada cukup banyak pertanyaan yang menanyakan untuk "menjalankan php tanpa cron" di SO. Ini dia:

Jadwalkan skrip tanpa menggunakan CRON

Konon, jawaban exec () di atas terdengar sangat menjanjikan :)

Norman berduri
sumber
Terima kasih, tetapi saya telah memperbarui pertanyaan asli. Cron bukanlah pilihan dalam kasus ini.
Michael Irigoyen
2

Jika Anda menggunakan Windows, teliti proc_openatau popen...

Tetapi jika kita berada di server yang sama "Linux" yang menjalankan cpanel maka ini adalah pendekatan yang tepat:

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

Jangan gunakan fork()atau curljika Anda ragu Anda dapat menanganinya, itu seperti menyalahgunakan server Anda

Terakhir, pada script.phpfile yang disebut di atas, perhatikan ini pastikan Anda menulis:

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

?>
saya ArbZ
sumber
Maaf untuk mengeditnya selanjutnya karena saya menggunakan ponsel saya dan memang itu jauh lebih sulit daripada menulis skrip exec (). Lol;)
saya ArbZ
2

untuk pekerja latar belakang saya pikir Anda harus mencoba teknik ini, ini akan membantu untuk memanggil sebanyak halaman yang Anda suka semua halaman akan berjalan sekaligus secara independen tanpa menunggu respons setiap halaman sebagai asynchronous.

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: jika Anda ingin mengirim parameter url sebagai loop, ikuti jawaban ini: https://stackoverflow.com/a/41225209/6295712

Hassan Saeed
sumber
0

Dalam kasus saya, saya memiliki 3 params, salah satunya adalah 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 &");

Di Process.php saya, saya memiliki kode ini:

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

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

Ini berhasil untuk saya. tyr ini

exec(“php asyn.php”.” > /dev/null 2>/dev/null &“);
Raj
sumber
bagaimana jika saya memiliki nilai posting? dari formulir dan saya mengirimkannya melalui ajax dan serialize()fungsi. sebagai contoh, saya memiliki index.php formulir dan saya mengirim nilai melalui ajax ke index2.php Saya ingin melakukan pengiriman data di index2.php di latar belakang apa yang Anda sarankan? terima kasih
khalid JA
0

Gunakan Amphp untuk menjalankan pekerjaan secara paralel & asinkron.

Instal perpustakaan

composer require amphp/parallel-functions

Contoh kode

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

Untuk kasus penggunaan Anda, Anda dapat melakukan sesuatu seperti di bawah ini

<?php

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

$responses = wait(parallelMap([
    '[email protected]',
    '[email protected]',
    '[email protected]',
], function ($to) {
    return send_mail($to);
}));
SkyRar
sumber