もしもこの処理時にreturn -1を他に入れた
方がいい場所などはありますか?
知っている方がいましたら教えて下さい。
これで、大体、2000件/日にあるデータを
1ヶ月間list.csvに溜め込みたいです。
同時に書き込む場合などもあると思うのですが
大丈夫でしょうか?
サーバはlinuxっでPHP4以上で処理をします。
function test_wirte($data){
define("CSVFILE_CAMP","/test/list.csv");
$file_csv = CSVFILE_CAMP;
//ファイルオープン
if(!($fno = fopen("$file_csv","a+"))){
return -1;
}
// 排他的ロックを行う
if (flock($fno, LOCK_EX)) {
$bbbb = $data['bbbb'];
$aaaa = $data['aaaa'];
//現在日付の取得
$strDate = date("y-m-d H:i:s");
$str_file = "";
$str_file .= "$strDate";
$str_file .= "\t";
$str_file .= "$bbbb";
$str_file .= "\t";
$str_file .= "$aaaa";
$str_file .= "\t";
$str_file .= "\n";
//ファイル書き込み
fwrite($fno, $str_file);
//ファイルアンロック
// flock($fno,LOCK_UN);
//バッファ関係の不具合対応
fflush($fno);
}else{
return -1;
}
//ファイルクローズ
fclose($fno);
return 0;
}
ディスク容量が不足すると、fwrite()もエラーを起こす可能性はあります。
なので、厳密な作りにするなら、fwrite()の戻り値がFALSEかどうかのチェックはいるでしょう。但し、その場合、今の処理でどこまで書き込めたのか、途中まで書き込めたものを元に戻すのかといったRollback処理も必要になるかと思います。
ディスク容量は事実上無制限(不足するようなこはまず起こらない)なら、そこまでの実装は不要かと。
fwrite()は、書き込み成功の長さをリターン値で返します。
なので、本当に厳密な処理を行う場合、
・文字列の長さとfwrite()リターン値が合っていればOK
・リターン値の方が短い場合、途中までは書けているので、書けた分を取り除いた文字列をもう一回リトライ
・リターン値がFALSE(この名前でそのまま値として使えます)の場合エラー
です。
http://us2.php.net/manual/ja/function.fwrite.php
また、Rollbackはfseek()関数で行えます。
fwrite()がいきなりFALSEリターンしたら不要ですが、上のリストの2番目(短いリターン)の後FALSEリターンした場合、
fseek($fno, -長さ, SEEK_END)
のようにして、途中まで書いた分を巻き戻してあげます。
ただ、ここまで行うプログラムは、それほど多くはないと思います。
そうすると以下の形でいいのでしょうか?
function test_wirte($data){
define("CSVFILE_CAMP","/test/list.csv");
$file_csv = CSVFILE_CAMP;
//ファイルオープン
if(!($fno = fopen("$file_csv","a+"))){
return -1;
}
// 排他的ロックを行う
if (flock($fno, LOCK_EX)) {
$bbbb = $data['bbbb'];
$aaaa = $data['aaaa'];
//現在日付の取得
$strDate = date("y-m-d H:i:s");
$str_file = "";
$str_file .= "$strDate";
$str_file .= "\t";
$str_file .= "$bbbb";
$str_file .= "\t";
$str_file .= "$aaaa";
$str_file .= "\t";
$str_file .= "\n";
//ファイル書き込み
//サーバの容量不足による、書き込めない場合のエラー
if (!fwrite($fno, $str_file)) {
return -1;
}else{
//最大10バイトまで取得
$counter = fgets($fno,10);
$counter++;
fseek($fno, 0);
fputs($fno, $counter); //fwrite($fno, $counter);でもOK
}
//ファイルアンロック
// flock($fno,LOCK_UN);
//バッファ関係の不具合対応
fflush($fno);
}else{
//ロックが出来ない場合のエラー
return -1;
}
//ファイルクローズ
fclose($fno);
return 0;
}
1. fopen
NG return -1
2. flock(LOCK_EX)
NG リトライを数回してもNGなら
fclose ;return -1
3. ftell( 現在位置 ) or fstat
(ファイルサイズを得る)
NG flock(LOCK_UN); fclose ; return -1;
4. fwrite
NG 書き込み 0バイト以下:
flock(LOCK_UN); fclose ; return -1
書き込み 1バイト以上:
ftruncate ; flock(LOCK_UN);
fclose; return -1
4. fflush
NG ftruncate ; flock(LOCK_UN);
fclose; return -1
5. flock(LOCK_UN)
NG 無視
6. fclose
NG 無視.
7. return 0
すいません、よく意味がわからないのですけど?
PHPの処理のファイル入出力部分と、リターンの
NGケースの処理のみ処理内容にしました。
補足します。
(一部訂正)
=============
#. NNNNN
NG
==============
# : function test_writeの呼び出すファイル関係
の関数を 抜き出し 順番に1.~8.
NNNN: 呼び出す関数
NG: NGの時に処理すべき内容
1. fopen
NG return -1
2. flock(LOCK_EX)
NG リトライしてもNGなら
fclose ;return -1
※:エラー処理が不十分
3. ftell( 現在位置 ) or fstat
(ファイルサイズを得る)
NG flock(LOCK_UN); fclose ; return -1;
※:現在この処理はない
4. fwrite
NG 書き込み 0バイト以下:
flock(LOCK_UN); fclose ; return -1
※:エラー処理が不足
NG 書き込み 1バイト以上で全部かけていない:
(ファイルサイズを切り詰める)
ftruncate ; flock(LOCK_UN);
fclose; return -1
※:エラー処理が不足
5. fflush
(ファイルサイズを切り詰める)
NG ftruncate ; flock(LOCK_UN);
fclose; return -1
※:エラー処理が不足,
fwriteはバッファリングするので
実際の書き込みはfflush行われエラーになる可能性
がある。
6. flock(LOCK_UN)
NG 無視
7. fclose
NG 無視.
8. return 0
出来ればソースのサンプルみたいのを
書いて欲しいのですが
可能でしょうか?
下記のようにすればファイルが壊れずに多重処理ができます.
$tmpname = tmpnam( ...); $fp2 = fopen( $tmpname, "a"); // 常に新規ファイルなので"a"でいい
ようするに一時ファイルで一旦すべての作業をして本番ファイルにすげ替えます.
これでロック(と解除)に失敗,あるいはディスク障害でも起こらなければ多重に処理されてもファイルが壊れることはありません.
tempnam()は一時ファイルを適当な名前で自動で作成しますが,自動で削除はしてくれないので自分で明示的に消してください.
ちなみに
$str_file = ""; $str_file .= "$strDate"; $str_file .= "\t"; $str_file .= "$bbbb"; $str_file .= "\t"; $str_file .= "$aaaa"; $str_file .= "\t"; $str_file .= "\n";
ですが,下記のように記述するともう少し可読性が上がるでしょう。
$body = array(); $body[] = $strDate; $body[] = $bbbb; $body[] = $aaaa; $body[] = "\n"; $str_file = implode( "\t", $body); // http://php.net/implode
ロックする("transaction.lock"とか適当なファイル名でロックファイルを作る)
がいまいちわかりません。
どうやって作成するのでしょうか?
ちなみに
また、Rollbackはfseek()関数で行えます。 fwrite()がいきなりFALSEリターンしたら不要ですが、上のリストの2番目(短いリターン)の後FALSEリターンした場合、 fseek($fno, -長さ, SEEK_END) のようにして、途中まで書いた分を巻き戻してあげます。 http://us2.php.net/fseek ただ、ここまで行うプログラムは、それほど多くはないと思います。
これは嘘です.
fseekは「ファイルのどこまで読み書きした」という内部的情報(CD
プレイヤーなどでいう今の再生時間みたいな物)を移動するだけです.
つまりファイルの書き出した内容は戻りません(中途半端に増えた物は増えたまま)
ファイルサイズを任意のサイズに編こするのはftruncate()です.
しかし,ftruncate()はその関数が実行されるまでファイルサイズは変更されないので,もし中途半端な状態でPHPが異常終了,あるいはPHPスクリプトでftruncate()前にreturnしたりするとやっぱり中途半端なファイルが残ってしまいます.
そもそも開発中などは処理を中断することが多いでしょうから中途半端なファイルで開発をしてしまう可能性が格段に高くなると思いますので全くお勧めできません.
#私の例に挙げた実装手順もエラー時に一時ファイルの削除をしなければ無駄なファイルができまくるデメリットはありますが,
それは単に削除すればいいわけですし,一番重要な「ファイルが壊れること」はありません.
これじゃだめですよね?
define("CSVFILE_CAMP","","/test/list.csv");
$file_csv = CSVFILE_CAMP;
//ファイルオープン
if(!($fno = fopen("$file_csv","a+"))){
return -1;
}
$tmpfname = tempnam (/test/, "FOO");
$fp2 = fopen( $tmpname, "a");
while ($data = fread($fno, 1024))
if(!fwrite($fp2, $data)){
fclose($fno);
fclose($fp2);
unlink($tmpfname);
return -1;
}
$body = array();
$body[] = $strDate;
$body[] = $key1;
$body[] = $final_point;
$body[] = "\n";
$str_file = implode( "\t", $body);
if(!fwrite($fp2, $str_file)){
fclose($fno);
fclose($fp2);
unlink($tmpfname);
return -1;
}
fclose($fno);
fclose($fp2);
rename($fp2, "/test/list444.dat");
ロックする("transaction.lock"とか適当なファイル名でロックファイルを作る)
がいまいちわかりません。
どうやって作成するのでしょうか?
個人的にはディレクトリでやる方が好きなのでそちらのサンプルでロック関数とロック解除関数を作ってみました.
// ロック用ディレクトリがそもそも作成できなかったらどうしようもない話なので // ロック用がまず作れることを別途確認しておいてください define( "LOCK_DIR", "ロックファイルを作るディレクトリ"); define( "LOCK_NAME", "a_transaction.lock"); /** * ロックする * * @return boolean ロックに成功したらTRUEが,失敗したらFALSEが返る */ function app_lock() { $is_locked = FALSE; $lock_retry = 10; // 10回までリトライする $lock_wait = 5; // ロックできなかったら5秒待つ for ( $i = 0; $i < $lock_retry; $i++) { $is_locked = @mkdir( LOCK_DIR."/".LOCK_NAME); if ( $is_locked != TRUE) { // ロックディレクトリが作れたのですぐ終わる break; } else { // ロックディレクトリが作れなかった sleep(5); // とりあえず次のリトライまで5秒待ってみる } } return $is_locked; } /** * ロック解除する * */ function app_unlock() { @rmdir( LOCK_DIR."/".LOCK_NAME); }
app_lock()は最高10回x5秒ロックを試みます.
上記でロックできればapp_lock()はTRUEを返します.
それでもロックできなければ必要分挑戦(あるいは永遠にapp_lock()を実行)するのか,
処理を中断(放棄)するのかはid:hopefullyさん次第で考えてください.
このままだと開発中はロックディレクトリが残ってデッドロックしてしまう可能性があるので,
本来はexpireの処理を追加するのですが,それは考えてみてください.
大体は処理に必要十分な時間(nn秒)を想定し,それ以上ロックされていたら(ディレクトリ作成からnn秒以上過ぎていたら)ロックディレクトリを削除みたいな処理を作ります.
あるいはregister_shutdown_function()で自分がロックディレクトリを作成していたら終了時には削除するような実装をします.
see: http://php.net/register_shutdown_function
で,id:hopefullyさんの実装についてですが,
if(!($fno = fopen("$file_csv","a+"))){
読み込むだけ(書き込みは$fp2)なので"r"で十分です
fclose($fp2); unlink($tmpfname); return -1; }
if(!fwrite($fp2, $str_file)){ fclose($fno); fclose($fp2); unlink($tmpfname); return -1; }
rename($fp2, "/test/list444.dat");
rename()は第1引数も第2引数も対象のファイル名もしくはディレクトリ名を指定します.
すいません、
色々親切にありがたいのですが
自分のプログラムを修正してくれませんか?
理解できなくてすいません。
fwrite()の戻り値がFALSEとかRollback処理とかはどうしたらいいのでしょうか?
サンプルのプログラムなどわかりますか?