フォームの送信後にPHPスクリプトをバックグラウンドで実行するにはどうすればよいですか?


104

問題
送信すると、送信された情報を処理してデータベースに挿入し、通知Webサイトに表示するための基本的なコードを実行するフォームがあります。さらに、これらの通知を電子メールおよびSMSメッセージで受信するようにサインアップした人々のリストがあります。このリストは今のところささいなことです(プッシュするのは約150)が、サブスクライバーのテーブル全体を循環して150通以上の電子メールを送信するには、1分以上かかる場合があります。(メールは大量メールポリシーのため、メールサーバーのシステム管理者からの要求に応じて個別に送信されます。)

この間、アラートを投稿した個人はフォームの最後のページに1分間ほど座って、通知が投稿されているという明確な補強はありません。これは他の潜在的な問題を引き起こし、私が考えている解決策はすべて理想的ではないと感じています。

  1. まず、投稿者はサーバーが遅れていると考えて、もう一度[送信]ボタンをクリックすると、スクリプトが最初から実行されるか、2回実行される可能性があります。JavaScriptを使用してボタンを無効にし、「Processing ...」のようなテキストを置き換えることでこれを解決できますが、スクリプトの実行中はユーザーがページにとどまるため、これは理想的とは言えません。(また、JavaScriptが無効になっている場合でも、この問題は依然として存在します。)

  2. 次に、ポスターはフォームを送信した後、途中でタブまたはブラウザを閉じる可能性があります。スクリプトは、ブラウザに書き戻すまでサーバー上で実行され続けますが、ユーザーが(スクリプトの実行中に)ドメイン内の任意のページを参照すると、スクリプトが終了するまでブラウザはページの読み込みを停止します。 。(これは、ブラウザーアプリケーション全体ではなく、ブラウザーのタブまたはウィンドウが閉じている場合にのみ発生します。)それでも、これは理想的とは言えません。

(可能)解決策
スクリプトの「メール」の部分を、通知が投稿された後に呼び出すことができる別のファイルに分割することにしました。通知が正常に投稿された後、これを確認ページに表示することを当初は考えていました。ただし、ユーザーはこのスクリプトが実行されていることを知らないため、異常は明らかではありません。このスクリプトは失敗しません。

しかし、このスクリプトをバックグラウンドプロセスとして実行できるとしたらどうでしょう。だから、私の質問はこれです:バックグラウンドサービスとしてトリガーするPHPスクリプトを実行して、ユーザーがフォームレベルで行ったこととは完全に独立して実行するにはどうすればよいですか?

編集:これ croned することはできません。フォームが送信された瞬間に実行する必要があります。これらは優先度の高い通知です。さらに、サーバーを実行しているシステム管理者は、cronが5分を超えて実行されることを許可していません。


cronジョブを開始し、リクエストが処理されていることなどを別のページでユーザーに警告します。
Evan Mulawski

1
定期的(毎日または毎時間)または送信時に実行しますか?私はあなたがphpを使うことができると思いますがexec()、私はこれを試したことはありません。
Simon、

2
私は単純に使用ajaxsleep()、ループ内に時間間隔を置いて挿入し(私の場合はforeachを使用)、バックグラウンドプロセスを実行します。それは私のサーバーで完全に動作します。他についてはわかりません。また、ループの前にignore_user_abort()set_time_limit()(計算された終了時間を使用して)を追加して、使用しているサーバーによってスクリプトが停止しないことを確認しています。私のテストによると、スクリプトはignore_user_abort()となしでタスクを完了していますset_time_limit()
Ari

私はあなたがすでに解決策を考え出したのを見ました。私はgearmanphpでバックグラウンドタスクを処理するために使用します。時間のかかる処理と重い負荷に遭遇した場合は、拡張するのに最適です。調べてみてください。
アンドレガルシア

[stackoverflow](stackoverflow.com/a/46617107/6417678)でこの回答をフォローする
Joshy Francis

回答:


89

で、いくつかの実験を行うexecshell_exec、私は完全に働いた解決策を発見しました!私は使用するshell_execことを選択したので、発生する(またはしない)すべての通知プロセスをログに記録できます。(shell_exec文字列として返され、これはを使用しexecて出力を変数に割り当て、書き込み先のファイルを開くよりも簡単でした。)

次の行を使用して電子メールスクリプトを呼び出しています。

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

注意することが重要である&(@netcoderによって尖ったアウトなど)コマンドの最後に。このUNIXコマンドは、プロセスをバックグラウンドで実行します。

スクリプトへのパスの後に一重引用符で囲まれた追加の変数は$_SERVER['argv']、スクリプト内で呼び出すことができる変数として設定されています。

次に、電子メールスクリプトはを使用してログファイルに出力し、>>次のように出力します。

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

ありがとう。これは私の命を救った。
リアムベイリー

$post_idPHPスクリプトのパスの後には何ですか?
Perocat 14

@Perocatは、スクリプトに送信する変数です。この場合、呼び出されるスクリプトのパラメーターとなるIDを送信しています。
2014年

なるほど、これは完璧に仕事をして疑うstackoverflow.com/questions/2212635/...
symcbean

1
エスケープするための保留中の編集提案があり$post_idます。私はむしろそれを直接数値にキャストしたいと思い(int) $post_idます:。(どちらが良いかを決めるために注意を喚起します。)
Al.G.

19

Linux / Unixサーバーでは、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);

&ここで重要なのビットであること。元のスクリプトが終了しても、スクリプトは続行されます。


これproc_closeは、タスクが完了したときに呼び出さない場合に機能します。proc_closeスクリプトが約15〜25秒ハングします。パイプ情報を使用してログを保持する必要があるため、書き込み先のファイルを開き、それを閉じてから、proc接続を閉じる必要があります。したがって、残念ながら、この解決策は私にはうまくいきません。
Michael Irigoyen、2011年

3
もちろん、あなたは電話をしませんproc_close、それがポイントです!そして、はい、パイプを使用してファイルにパイプできますproc_open。更新された回答を参照してください。
ネットコーダー、2011年

@netcoder:結果をファイルに保存したくない場合はどうすればよいですか。stdoutで必要な変更は何ですか?
naggarwal11

9

すべての回答の中で、とんでもないほど簡単なfastcgi_finish_request関数は考慮されていません。呼び出された場合、残りのすべての出力をブラウザーにフラッシュし、FastcgiセッションとHTTP接続を閉じながら、スクリプトをバックグラウンドで実行します。

例:

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

これはまさに私が探していたものです。shell_execでは、投稿されたデータを実行されたスクリプトに何らかの形で転送する必要があります。これは、私の場合、それ自体時間がかかるタスクであり、事態も非常に複雑にします。
Aurangzeb

7

PHP exec("php script.php")はそれを行うことができます。

マニュアルから:

この関数を使用してプログラムを起動した場合、プログラムをバックグラウンドで実行し続けるには、プログラムの出力をファイルまたは別の出力ストリームにリダイレクトする必要があります。そうしないと、プログラムの実行が終了するまでPHPがハングします。

したがって、出力をログファイルにリダイレクトする場合(とにかく良いアイデアです)、呼び出しスクリプトはハングせず、電子メールスクリプトはbgで実行されます。


1
おかげで、これはうまくいきませんでした。スクリプトは、2番目のファイルを処理している間も「ハング」します。
Michael Irigoyen、2011年

コマンドphp.net/manual/en/function.exec.php#80582を見てください。これはSafe_modeが有効になっているためと思われます。UNIXでの回避策があります。
Simon

残念ながら、セーフモードはインストールで有効になっていません。それがまだぶら下がっているのは奇妙です。
Michael Irigoyen、2011年

6

そして、スクリプトでHTTPリクエストを作成し、応答を無視しないのはなぜですか。

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

スクリプトでリクエストを行う場合、ウェブサーバーを呼び出す必要があります。ウェブサーバーはバックグラウンドで実行され、(メインスクリプトで)スクリプトが実行されていることをユーザーに知らせるメッセージを表示できます。


4

これはどう?

  1. フォームを保持するPHPスクリプトは、フラグまたは何らかの値をデータベースまたはファイルに保存します。
  2. 2番目のPHPスクリプトはこの値を定期的にポーリングし、設定されている場合は、同期的な方法で電子メールスクリプトをトリガーします。

この2番目のPHPスクリプトは、cronとして実行するように設定する必要があります。


ありがとうございます。元の質問を更新しました。この場合、Cronはオプションではありません。
Michael Irigoyen、2011年

4

私はあなたがこれを簡単な方法で行うことができないことを知っているので(fork execなど(windowsでは機能しない)を参照)、アプローチを逆にでき、ブラウザの背景をajaxに投稿するブラウザの背景を使用できるので、投稿がまだ待ち時間がない仕事。
これは、長い入念な作業が必要な場合でも役立ちます。

メールの送信については常にスプーラーを使用することをお勧めします。キューをスプールするcronを使用するよりも、リクエストを受け入れてそれらを実際のMTAにスプールするか、すべてをDBに入れるローカル&クイックSMTPサーバーの場合があります。
cronは、スプーラーを外部URLとして呼び出す別のマシン上にある可能性があります。

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

3

バックグラウンドcronジョブは、このための良いアイデアのように思えます。

スクリプトをcronとして実行するには、マシンへのsshアクセスが必要です。

$ php scriptname.php それを実行します。


1
sshは必要ありません。多くのホストはsshを許可していませんが、cronをサポートしています。
Teson、2011年

ありがとうございます。元の質問を更新しました。この場合、Cronはオプションではありません。
Michael Irigoyen、2011年

3

ssh経由でサーバーにアクセスでき、独自のスクリプトを実行できる場合は、phpを使用して単純なfifoサーバーを作成できます(ただし、をposixサポートするようにphpを再コンパイルする必要がありますfork)。

サーバーは本当に何でも書くことができ、おそらくPythonで簡単に実行できます。

または、最も簡単な解決策は、HttpRequestを送信して戻りデータを読み取らないことですが、サーバーは処理が完了する前にスクリプトを破棄する可能性があります。

サーバーの例:

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

クライアントの例:

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

PHPスクリプトをバックグラウンドで実行する最も簡単な方法は、

php script.php >/dev/null &

スクリプトはバックグラウンドで実行され、ページもアクションページに速く到達します。


2

* nixプラットフォームで実行していると仮定してcronphp実行可能ファイルを使用します。

編集:

SOで「cronを使用せずにphpを実行する」ことを求めるかなりの質問があります。ここに一つあります:

CRONを使用せずにスクリプトをスケジュールする

とはいえ、上記のexec()の答えは非常に有望に聞こえます:)


ありがとうございます。元の質問を更新しました。この場合、Cronはオプションではありません。
Michael Irigoyen、2011年

2

Windowsを使用している場合は、リサーチproc_openまたはpopen...

しかし、cpanelを実行している同じサーバー「Linux」上にいる場合、これは正しいアプローチです。

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

使用しないでくださいfork()またはcurlあなたがそれらを扱うことができる疑うならば、それは自分のサーバーを乱用ようなものです

最後に、script.php上記で呼び出されたファイルで、これを書き留めて、次のことを確認してください。

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

?>

私の携帯電話を使用して後で編集するのは申し訳ありません。実際、exec()スクリプトを作成するよりもはるかに困難です。Lol;)
私はArbZです。2014年

2

バックグラウンドワーカーの場合、このテクニックを試してみるべきだと思います。非同期で各ページの応答を待たずに、すべてのページが同時に独立して実行されるのと同じ数のページを呼び出すのに役立ちます。

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:URLパラメータをループとして送信する場合は、次の回答に従ってくださいhttps : //stackoverflow.com/a/41225209/6295712


0

私の場合、3つのパラメーターがあり、そのうちの1つは文字列です(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 &");

私のProcess.phpには、次のコードがあります。

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

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

0

これは私の作品です。これを

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

投稿値がある場合はどうなりますか?フォームから、私はそれをajaxとserialize()関数を介して送信し ます。たとえば、私はindex.php フォームを持っており、ajaxを介してindex2.phpに値を送信しています。おかげで
ハリドJA 2018

0

Amphpを使用して、並列および非同期でジョブを実行します。

ライブラリをインストールする

composer require amphp/parallel-functions

コードサンプル

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

あなたのユースケースでは、以下のようなことができます

<?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);
}));
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.