web-dev-qa-db-ja.com

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

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

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

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

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

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

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

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

97

execおよびShell_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)
82

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

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

17
netcoder

PHP exec("php script.php")でできます。

手動 から:

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

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

7
Simon

そして、なぜスクリプトでHTTPリクエストを作成し、レスポンスを無視しないのですか?

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

スクリプトでリクエストを行う場合、Webサーバーを呼び出してバックグラウンドで実行する必要があり、(メインスクリプトで)スクリプトが実行中であることをユーザーに伝えるメッセージを表示できます。

6
Twister

すべての答えの中で、とんでもないほど簡単な 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,
6
Danogentili

私はあなたがこれを簡単な方法で行うことはできないことを知っています(fork execなどを参照してください(Windowsでは動作しません))、アプローチを逆にして、ajaxでフォームを投稿するブラウザの背景を使用することができますあなたは待ち時間がない仕事をします。
これは、長い時間をかけなければならない場合でも役立ちます。

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

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

これはどう?

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

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

4
adarshr

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

php script.php >/dev/null &

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

4
Sandeep Dhanda

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

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

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

3
Ian

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

サーバーは本当に何でも書くことができます。おそらく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
OneOfOne

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

?>
2
i am ArbZ

* nixプラットフォームで実行している場合、cronおよびphp実行可能ファイルを使用します。

編集:

SOで「cronを使用せずにphpを実行する」ことを要求する質問がかなりあります。以下がその1つです。

CRONを使用しないスクリプトのスケジュール

とは言っても、上記のexec()の答えは非常に有望です:)

2
Spiny Norman

バックグラウンドワーカーの場合、この手法を試してみてください。各ページの応答を非同期として待機することなく、すべてのページが独立して実行されるページをできるだけ多く呼び出すのに役立ちます。

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

2
Hassan Saeed

私の場合、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 &“);
0
Raj