以下のメソッドを使用して、ファイルの書き込みをます。

もしもこの処理時に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;
}

回答の条件
  • 1人5回まで
  • 登録:
  • 終了:2006/09/08 09:42:50
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

回答7件)

id:tadashi0805 No.1

回答回数287ベストアンサー獲得回数29

ポイント18pt

ディスク容量が不足すると、fwrite()もエラーを起こす可能性はあります。

なので、厳密な作りにするなら、fwrite()の戻り値がFALSEかどうかのチェックはいるでしょう。但し、その場合、今の処理でどこまで書き込めたのか、途中まで書き込めたものを元に戻すのかといったRollback処理も必要になるかと思います。

ディスク容量は事実上無制限(不足するようなこはまず起こらない)なら、そこまでの実装は不要かと。

id:hopefully

fwrite()の戻り値がFALSEとかRollback処理とかはどうしたらいいのでしょうか?

サンプルのプログラムなどわかりますか?

2006/09/05 11:39:20
id:tadashi0805 No.2

回答回数287ベストアンサー獲得回数29

ポイント17pt

fwrite()は、書き込み成功の長さをリターン値で返します。

なので、本当に厳密な処理を行う場合、

・文字列の長さとfwrite()リターン値が合っていればOK

・リターン値の方が短い場合、途中までは書けているので、書けた分を取り除いた文字列をもう一回リトライ

・リターン値がFALSE(この名前でそのまま値として使えます)の場合エラー

です。

http://us2.php.net/manual/ja/function.fwrite.php

また、Rollbackはfseek()関数で行えます。

fwrite()がいきなりFALSEリターンしたら不要ですが、上のリストの2番目(短いリターン)の後FALSEリターンした場合、

fseek($fno, -長さ, SEEK_END)

のようにして、途中まで書いた分を巻き戻してあげます。

http://us2.php.net/fseek

ただ、ここまで行うプログラムは、それほど多くはないと思います。

id:hopefully

そうすると以下の形でいいのでしょうか?

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;

}

2006/09/05 13:33:32
id:kurukuru-neko No.3

回答回数1844ベストアンサー獲得回数155

ポイント17pt

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

id:hopefully

すいません、よく意味がわからないのですけど?

2006/09/05 16:51:47
id:kurukuru-neko No.4

回答回数1844ベストアンサー獲得回数155

ポイント17pt

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

id:hopefully

出来ればソースのサンプルみたいのを

書いて欲しいのですが

可能でしょうか?

2006/09/05 23:34:18
id:elf No.5

回答回数76ベストアンサー獲得回数8

ポイント17pt

下記のようにすればファイルが壊れずに多重処理ができます.

  • ロックする("transaction.lock"とか適当なファイル名でロックファイルを作る)
  • $fpで今まで通りfopenする.
  • tmpnam()を使って一時ファイルを作成し,開く. see: http://php.net/tmpname
$tmpname = tmpnam( ...); $fp2 = fopen( $tmpname, "a"); // 常に新規ファイルなので"a"でいい
  • 作成するディレクトリはCSVFILE_CAMPと同じところにすること
  • $fp2に$fpの内容を全部コピー(fread&fwriteでいい)
    • 成功したら次に続ける
    • 失敗したら$fpと$fp2をfclose,$tmpnameをunlink()で削除,ロックファイルの削除して関連処理を終わる see: http://php.net/unlink
  • 新規データをid:hopefullyさんがやってるとおりに$fp2に追加(書き込み)します
    • 成功したら次に続ける
    • 失敗したら$fpと$fp2をfclose,$tmpnameをunlink()で削除,ロックファイルの削除して関連処理を終わる
  • $fp,$fp2を閉じる
  • rename()で一時ファイルをCSVFILE_CAMPに移動 see: http://php.net/rename
    • 常に新しいファイルになるので「ファイルの作成日」やファイルの所有者(作成者)が変わる可能性があります
  • ロックファイルを削除する

ようするに一時ファイルで一旦すべての作業をして本番ファイルにすげ替えます.

これでロック(と解除)に失敗,あるいはディスク障害でも起こらなければ多重に処理されてもファイルが壊れることはありません.

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
id:hopefully

ロックする("transaction.lock"とか適当なファイル名でロックファイルを作る)

がいまいちわかりません。

どうやって作成するのでしょうか?

2006/09/06 11:46:02
id:elf No.6

回答回数76ベストアンサー獲得回数8

ポイント17pt

ちなみに

また、Rollbackはfseek()関数で行えます。

fwrite()がいきなりFALSEリターンしたら不要ですが、上のリストの2番目(短いリターン)の後FALSEリターンした場合、

fseek($fno, -長さ, SEEK_END)

のようにして、途中まで書いた分を巻き戻してあげます。

http://us2.php.net/fseek

ただ、ここまで行うプログラムは、それほど多くはないと思います。

これは嘘です.

fseekは「ファイルのどこまで読み書きした」という内部的情報(CD

プレイヤーなどでいう今の再生時間みたいな物)を移動するだけです.

http://php.net/fseek

つまりファイルの書き出した内容は戻りません(中途半端に増えた物は増えたまま)

ファイルサイズを任意のサイズに編こするのはftruncate()です.

http://php.net/ftruncate

しかし,ftruncate()はその関数が実行されるまでファイルサイズは変更されないので,もし中途半端な状態でPHPが異常終了,あるいはPHPスクリプトでftruncate()前にreturnしたりするとやっぱり中途半端なファイルが残ってしまいます.

そもそも開発中などは処理を中断することが多いでしょうから中途半端なファイルで開発をしてしまう可能性が格段に高くなると思いますので全くお勧めできません.

#私の例に挙げた実装手順もエラー時に一時ファイルの削除をしなければ無駄なファイルができまくるデメリットはありますが,

それは単に削除すればいいわけですし,一番重要な「ファイルが壊れること」はありません.

id:hopefully

これじゃだめですよね?

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

2006/09/06 12:13:07
id:elf No.7

回答回数76ベストアンサー獲得回数8

ポイント17pt


ロックする("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;
}
  • $fp2経由で$tmpfnameを作成したのに削除したら無駄になってしまいます
  • returnしていいんですか?
  • ブロックを閉じてますが開いていません(parse errorになります)
if(!fwrite($fp2, $str_file)){
fclose($fno);
fclose($fp2);
unlink($tmpfname);
return -1;
}
  • fwriteに成功しても失敗してもfopenに成功しているならfcloseする必要があります
    • さきほど$fp2はfcloseする処理が入っていますね.注意してください
  • 同様に新しい内容を$fp2に追加したいのにunlinkで削除して無駄になってしまっています.っでreturnしていいんですか?
rename($fp2, "/test/list444.dat");

rename()は第1引数も第2引数も対象のファイル名もしくはディレクトリ名を指定します.

see http://php.net/rename

id:hopefully

すいません、

色々親切にありがたいのですが

自分のプログラムを修正してくれませんか?

理解できなくてすいません。

2006/09/06 14:58:18

コメントはまだありません

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません