CSVファイルの中身
太郎,男,22
花子,女,21
桃子,女,23
次郎,男,25
のように項目は「名前」「性別」「年齢」とします。
個人毎のデータの追加、削除は出来るのですが、
名前からデータを検索し、性別、年齢を変更したいのですが
どのように考えれば実現できるのでしょうか?
自分なりに色々と考えたのですが、もっとスマートでプログラマ的な考え方
ありましたらアドバスをお願いします。
素人のため、ソースの再利用や簡素化が出来ればと考えています。
<?php
$array = file("csv.csv");
$fp = fopen("csv.csv", "w");
flock($fp, LOCK_EX);
$c = count($array);
$i = 0;
while($i < $c){
$array[$i] = trim($array[$i]);
list($name[$i],$age[$i],$sex[$i]) = split("\,",$array[$i]);
$i++;
}
$key = array_search("花子", $name);
$sex[$key] = 20;
$reg = implode("\n", $array);
fputs($fp, "$reg\n");
flock($fp, LOCK_UN);
fclose($fp);
?>
花子の年齢を21から20にしてCSVファイルの上書きを行いたいです。
自分なりに思いついたソースが上記です。
CSV形式ファイルを読み込む関数として、fgetcsv関数があります。使い方は「PHPでCSVファイルを読み込む」を参考にしてください。
一方、書き込む関数としては、fputcsv関数があります。
読み込んだデータの構造ですが、項目名を連想配列にして、
$row['name'][$num] = 名前 $row['sex'][$num] = 性別 $row['age'][$num] = 年齢
にすれば、項目が増えたときにも変数名を増やす必要がなく楽です。($numは行番号)
#1のコメント:
私のサンプルスクリプトではなぜうまく動作しないのでしょうか?
変更した配列 $sex の値を $array に反映していないからです。
※設定された回答回数の上限になりました。さらにフォローが必要でしたら、コメント欄を開けていただくか、回答回数を増やしてください。
$sex[$key] = 20;
の部分で$arrayまで上書きされていると勘違いしていました。
pahooさんのおかげで見えていない部分が見えてきました。ありがとうございます。
一度連想配列で考えてみます。
ファイルのロックをするためにこうしているのでしょうがファイルのロックを無視すると
と明確にした方が解りやすいと思います
<?php $array = file("csv.csv"); $c = count($array); $i = 0; while($i < $c) { $array[$i] = trim($array[$i]); list($name[$i],$sex[$i],$age[$i]) = split("\,",$array[$i]); $i++; } $key = array_search("花子", $name); if ($key !== FALSE) { $age[$key] = 20; $array[$key] = implode(',',array($name[$key],$sex[$key],$age[$key])); } $fp = fopen("csv.csv", "w"); $reg = implode("\n", $array); fputs($fp, "$reg\n"); fclose($fp);
配列の全て要素にアクセスする場合はforやwhileよりもforeachが便利です
trim()で$arrayの各要素の最後の\nは取ってません
splitはexplodeに替えています
<?php list($name, $age, $sex) = array(array(),array(),array()); foreach ($array as $row) list($name[],$sex[],$age[]) = explode(",",trim($row));
php5ならファイルへの書き出しはfile_put_contents()が便利です
$arrayの各要素の最後に\nが付加されていると仮定
file_put_contents("csv.csv",$array)
以上をふまえて書き直すと下記のようになります
<?php $array = file("csv.csv"); list($name, $sex, $age) = array(array(),array(),array()); foreach ($array as $row) list($name[],$sex[],$age[]) = explode(",",trim($row)); $key = array_search("花子", $name); if ($key !== FALSE) { $age["$key"] = 20; $array["$key"] = implode(',',array($name["$key"],$sex["$key"],$age["$key"]))."\n"; } file_put_contents("csv.csv",$array);
該当する部分の年齢だけを変更するだけならわざわざ$name,$age,$sexを配列に展開する必要もないように思います
<?php $array = file("csv.csv"); foreach ($array as $i=>$row) { list($name,$sex,$age) = explode(",",trim($row)); if ($name == '花子') { $age = 20; $array[$i] = implode(',',array($name,$sex,$age))."\n"; break; } } file_put_contents("csv.csv",$array);
ソースの再利用を考えるならforeachの部分を関数にしておけば再利用できるかもしれません
csv_lookup($array, $name, $col, $value)
$arrayの各要素は,で区切られたcsv形式で1列目が$nameと同じ行を見付け、$col列の値を$valueで置き換えるとします
<?php function csv_lookup($array, $name, $col, $value) { foreach ($array as $i=>$row) { $data = explode(",",trim($row)); if ($data[0] == $name) { $data["$col"] = $value; $array["$i"] = implode(',',$data)."\n"; break; } } return $array; } file_put_contents("csv.csv",csv_lookup(file("csv.csv"), '花子', 2, 20));
"csv.csv"ファイルをfile()で配列にし
csv_lookup()で花子の年齢(2列目)を20に代えた配列を
file_put_contents()で"csv.csv"ファイルに書き出す
となります。
CSV形式と考えず平坦な文字列と考え正規表現を使って該当する行を入手する方法です
preg_splitでPREG_SPLIT_DELIM_CAPTUREとすることで該当する部分が一つだけだと三つの要素が返ります
該当する以前の部分と、該当した部分と、該当した以降の部分です
検索文字として非英数字を使っているのでUTF-8にしてuのパターン修飾子を付けています。
また複数行のm(PCRE_MULTILINE)も付加してみました
<?php function csv_lookup($file, $name, $col, $value) { $splt = preg_split("/^(".$name.".*)$/mu",$file, -1, PREG_SPLIT_DELIM_CAPTURE); if (2 < count($splt)) { $data = explode(',',$splt[1]); $data["$col"] = $value; $splt[1] = implode(',',$data); } return implode('',$splt); } file_put_contents("csv.csv",csv_lookup(file_get_contents("csv.csv"), '花子', 2, 20));
ここにもあるようにシビアに考えると結構やっかいです。ただこのCSV形式のデータ操作でそれほどの機能が必要なのでしょうか。
また必要だとしても、CSV形式のデータファイルにアクセスする以前にアクセス制限して排他制御する方がいいように思います。(ファイルをロックするのではなくファイルを操作する方を排他する)
回答ありがとうございます。
fgetcsvに関してはバグがあるとの事でしたので利用を控えておりました。
連想配列で考えるのも1つなんですね。
私のサンプルスクリプトではなぜうまく動作しないのでしょうか?