みんなの「教えて(疑問・質問)」にみんなで「答える」Q&Aコミュニティ

こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

多次元配列のオーソドックスなファイル書き込みについて教えてください

PHP勉強中のAJAMAです。
下記にありますようなプログラムによって配列構造を持った変数をCSVファイルに書き込み、一行を1レコードとして管理をしたいと考えています。書き込み処理は、一意である変数の場合は、きっちりと書き込まれるのですが、配列変数の部分はarrayと書き込まれてしまいます。配列変数の中身を書き込むには、join(",",$xxxx)とすることで、すべてを同じ一行に書き込むことができました。しかしこれですと、すべてが、,区切りの二次元構造になってしまうので、これらのデータを読み込んで活用したい場合に、多次元構造を把握する処理をしなくてはいけないように見えるのですが、どうにもその仕組みを思考することができないので、模範的な手法を教えていただけないでしょうか。
serialize()も試してみましたが、知識がオブジェクト指向にまでいたっていないこともあり、うまくいきませんでした。(実行環境がPHP4だからかもしれません)
以 下、作成中のソースコードです。
                    ※$numから右が配列変数部
$lines=array("$recordID",$nickname,$date,$area,$num,$item,$size);
$lines=implode(",",$lines);
$lines=$lines."\n";
serialize($lines); ←試行して不成功だった加筆部分。
//CSVファイルに書き込み
$fp=fopen("report.csv","a");
flock($fp, LOCK_EX);
fputs($fp,$lines);
fclose($fp);

宜しくお願いいたします。

投稿日時 - 2008-08-04 18:23:57

QNo.4227623

困ってます

質問者が選んだベストアンサー

PHP4でも、serializeは問題なく使えるはずです。

fputs($fp, serialize($lines));

で書き込んで、

$lines = unserialize(fgets($fp));

で、読み込む。

PHP4の処理系、ないんで試してませんが。
PHP5では動きます。
この辺は、変わってないと思います。

投稿日時 - 2008-08-05 09:09:39

補足

まず、質問の中に『二次元』と書いてますが1レコードの範囲においては『一次元』かとおもいますので、訂正させていただきます。すみません。

言葉足らずになっているかと思いましたので、シリアライズを試した経緯も説明させていただきます。

serialize()の振る舞いを調べましたところ、『バイト文字列に変換する』『メソッド以外のオブジェクト、変数、配列が対象』とのことですので、理屈的に多次元の文字列を直列変換する(バイト文字列にする)ことで一行一レコードとして、変換できると考えたからです。

ご指導いただいたコードと私のコードの違いは、以下のとおりです。

  serialize($lines); 書き込みの前にシリアル化した。
     ↓
  fputs($fp, serialize($lines)); 書き込む寸前にシリアル化

現在、実行環境がないので、試していませんが、シリアル化するタイミングが悪いと理解してます。後ほどこれを試してみます。
unserialize(fgets($fp));については、どのような形態で展開されることになるのか分かりませんが、こちらも試してみます。

後、CSVをにこだわる理由ですが、サーバにDBがないのと、管理が用意というイメージを持っているからです。テキストファイルに格納する場合の理想的な方法は、これから勉強していきたいと考えてます。

投稿日時 - 2008-08-05 17:34:23

このQ&Aは役に立ちましたか?

3人が「このQ&Aが役に立った」と投票しています

回答(9)

ANo.9

ANo.2のANo.8です。
何度も失礼します。
インラインの疑問ですが、私にとってなんでもない処理だったので、説明するかどうしようか迷って、結局、答えないでしまいました。
私も気になっていたので答えさせていただきます。

 $b.=",".$v; //ここからの意味が分りません
これは、ループの中で次々に文字列を連結する処理です。
別の書き方をすると「$b=$b.",".$v;」となります。
$b に $b とカンマと $v を連結したものを転送しています。
「$b=$b.」を「$b.=」と略して記述でき、「$b」を1度しか書かない事でコーディングミスを防げます。

 $b=substr($b,1);
ループが終わった時には $b の内容が例えばこうなっています。
 ,A,B,C
これでは先頭のカンマが不要になるので substr で2文字目以降を残すようにしています。

 $v=open_array($v); //配列だったらopen_array処理をして、$vに代入
これは疑問ではありませんが、配列の中に配列があった場合は、自分自身の関数を呼び出す事で多次元配列の一次元化的な処理を目的としています。
簡単に言うと、さらに展開ですが、これがないと"Array"が返ってきてしまいます。
私が使用しているPHPのバージョンでは join が多次元を展開してくれなかったのと、インターネットでは検索していませんが、私が持っているPHP辞典では join の説明がなぜか書かれていないので、この処理が必要でした。

 $open_num=open_array($num);  //ここはopen_arrayを適用しているのが分ります。(そもそもこの置換えはどういうことなんでしょうか)
これがそもそも質問をよく読んでいなかったためにやってしまった無駄な回答です。
これは join のかわりのようにして作成した自作関数で、結局、ご要望のものではありませんでしたが、この処理が、配列を単純にカンマ区切りに展開するものです。
"Array"が出力される事が問題なのかと思って、"Array"ではなく、配列がカンマ区切りで出力されればいいのかなとの思いでした。

疑問点は以上で解消できたでしょうか。

投稿日時 - 2008-08-06 17:04:41

お礼

標準関数なくても作れてしまうのには尊敬してます。ご説明いただいたおかげで疑問をすっきりさせることもできました。また、自作関数に対して意欲的になれました。ほんとに有難うございました。

投稿日時 - 2008-08-06 18:53:36

ANo.8

ANo.2です。
すみません。
プログラムソースを見るとすぐに実行したくなるので、ご質問をよく読んでいませんでした。
私が作成した関数は join のただの真似事で、カンマ区切り専用でした。

ご丁寧な補足をいただき、そのご配慮に感謝します。
無駄な回答を読ませてしまって申し訳ありません。

私もあれからシリアライズ化とその出力まではテストしたのですが、読み込みの方はしておりません。
他のご回答からシリアライズ化がうまくいったようですので、私としては見守らせていただく事にしました。
逃げるようでいけませんが、もうひと息で完成のようですのでがんばってください。

投稿日時 - 2008-08-06 08:53:42

お礼

コードのインラインに示した疑問が残っていますが、自作関数の利用方法を垣間見ることが出来てとても勉強になりました。
自作関数についても勉強中で、自分がしたいことに直結しないサンプルコードをネットで色々見てみたものの、どうにも分からないことが多かったのですが、dellさんに実践的な活用方法教えていただいたことで、自作関数が少し分かった気がしました。また、激励のお言葉も頂いてホントに有難うございました。またお会いすることがありましたら宜しくお願いします。

投稿日時 - 2008-08-06 14:32:31

ANo.7

読み込んだ結果をunserializeすれば、元に戻ります。
$serializedLine = fgets($fp);
$lines = unserialize($serializedLine);

CSVが目的ではないんですよね?

投稿日時 - 2008-08-06 08:50:33

補足

はい。CSVが目的ではありません。
読み込みの際の制御を意識してのcsvです。

以下、ファイル読み込み処理です。
$Aid=$_GET['areaid']; //入力ページから渡される変数を取得します。

$handle=fopen(dirname(__FILE__)."/../report.csv","r");
while(($row=fgetcsv($handle,1024,","))!==FALSE){
if(strcmp($row[1],$Aid)==0){ //report.csvの内容を取得しつつ、GETで取得したaidと$row[1]の値を比較します。
$data[]=array('recordID'=>$row[0],'nickname'=>$row[1],'area'=>$row[2],'date'=>$row [3],'item'=>$size[4]); //列配列に取り込みます。
}
}
fclose($handle);

つまり、表形態をとったCSVファイルの列方向の値を走査して、ソートや抽出をするために、カンマ区切りが必要と考えていましたので、CSVとしてます。

そして、serializeを使うとすると、値が一行一列に収まってしまったので、読み込みの際の制御ができないのではないかと早合点して、Line[0]に一塊になっていることを伝えてしまい、混乱を招いてすみませんでした。

教えていただきました、
$serializedLine = fgets($fp);
$lines = unserialize($serializedLine);
をループで取り出すを仕組みを試してみます。

投稿日時 - 2008-08-06 13:28:08

お礼

最終的にunserializeまで実行することが出来ましたのでお知らせをして尾お礼の言葉とさせていただきます。
masaさん、シリアライズを使った時とそうでない時の2つの方法を教えていただいてとても助かりました。有難うございました。
今はシリアル化を使った場合で開発することで進めてます。
出力部分で新たに課題がでてしまいました。各変数のダンプを見ますと、すべての値がarray型となっているところに問題が隠されているのでないかと思ってますので、少し考えてみます。ご指導有難うございました。

以下、ソースです。
//入力ページから渡される変数を取得します。
$Aid=$_GET['areaid'];

//ファイルを開きます。
$handle=fopen(dirname(__FILE__)."/../report.csv","r");

//report.csvの内容を取得しつつ、GETで取得したaidと$row[1]の値を比較します。
while(($serialrow=fgets($handle,1024,","))!==FALSE){
if(strcmp($row[1],$Aid)==0){

//列行列に取り込みます。
$data[]=array('recordID'=>$row[0],'nickname'=>$row[1],'area'=>$row[2],'date'=>$row [3],'item'=>$row[4],'$size'=>$row[5]); 
}
}
fclose($handle);

//列行列を変数に代入します。
foreach($data as $key=>$row){
$recordID[$key] =$row['recordID'];
$nickname[$key]=$row['nickname'];
$area[$key]=$row['area'];
$date[$key]=$row['date'];
$no[$key]=$row['no'];
$item[$key]=$row['item'];
$size[$key]=$row['size'];
}
//10項目ごとに値を表示します。
(ここからは何故だろうか、出力に至ってませんが、がんばります。)
$P=$_GET["p"];
for($i=$P*10;$i<$P*10+10;$i++){
echo <<<EOF
<tr>
<td> $recordID[$i] </td>
<td> $nickname[$i] </td>
<td> $area </td>
<td> $date[$i] </td>
<td> $$no[$i] </td>
<td> $size[$i] </td>
</tr>
EOF;

投稿日時 - 2008-08-06 21:10:03

ANo.6

申し訳ないですが、何か根本的におかしいのでは?
serializeは、ごく普通の関数ですので動かないわけないんですが。

CSVの$line[0]って何ですか?

$lines=array("$recordID",$nickname,$date,$area,$num,$item,$size);
$serializedLine = seriarize($lines);
echo $serializedLine;

を実行してみてください。
問題なく、シリアライズされるはずです。
まさか、シリアライズの前にimplodeなんて、実行してませんよね。

投稿日時 - 2008-08-06 05:12:01

補足

次のようなお馬鹿なことをしていました。大変失礼しました。
$serializedLine = seriarize($lines);
$lines=array("$recordID",$nickname,$date,$area,$num,$item,$size);
これを修正しましたところ、シリアル化とファイル書き込みに成功しました!ただし、カンマ区切りファイルのフィールド0番目列にすべての変数がが格納されてしまいます。
データを取り出す場合については後日試してみます。

投稿日時 - 2008-08-06 06:21:40

ANo.5

serializeは、引数を変えません。
関数の返り値が、シリアライズした結果の文字列です。

$serializedLines = serialize($lines);

とでもして、$serializedLinesの方を書き込みます。
この時、$linesは元のままです。

投稿日時 - 2008-08-05 18:00:13

ANo.4

近頃、PHP書いてないんで、ちょっと好奇心が・・・
csv っていうのは、仕様でしょうか?
もし、値の復元でしたら、serialize、unserializeで何の問題もありませんが。

元々csvの行は1次元ですから、多次元を表すのはそのままでは無理ですね。ただ、配列の範囲を明確にすれば、可能でしょう。

こんなのどうでしょう?

<?php
function array2string($source) {
  if(is_array($source)) {
    $result = '(';
    return 'BeginArray,' . implode(',', array_map('array2string', $source)) . ',EndArray';
  } else {
    return $source;
  }
}

$fp = fopen('report.csv', 'w');
fputs($fp, array2string(array('x', 'y', 'z', array(1,2,3),array('a','b',array('c','d')),array(7,8,9))));
fclose($fp);
?>

結果は、

BeginArray,x,y,z,BeginArray,1,2,3,EndArray,BeginArray,a,b,BeginArray,c,d,EndArray,EndArray,BeginArray,7,8,9,EndArray,EndArray

になります。BeginArray、EndArrayは配列の開始・終了を意味します。何でも結構です。

で、読み込むほうです。

<?php
function string2array($array, &$i) {
  if($array[$i] == 'BeginArray') {
    $result = array();
    $i++;
    while($array[$i] != 'EndArray') {
      $result[] = string2Array($array, $i);
    }
    $i++;
    return $result;
  } else {
    return $array[$i++];
  }
}

$fp = fopen('report.csv', 'r');
var_dump(string2array(explode(',',fgets($fp)), $i = 0));
fclose($fp);
?>

これで、配列は元に戻ります。

投稿日時 - 2008-08-05 13:41:02

補足

masa6272さん、沢山のアイデアを出して下さり有難うございます。
こちらは、シリアル化関数を使わない場合ですね。
#最もシンプルに実現できると期待したのですが残念です。o(~~;)o
dellOKさんのご指導内容と似て非なる感じと受け止めてます。
前にも質問してますが、ファイルへの書き込み前の状態は、変数配列(ex $num)になっています(セッション変数で持ちまわしているからです)。
ですから、明示的に「array('x', 'y', 'z', array(1,2,3),array('a','b',array('c','d')),array(7,8,9))));」となっていません。
このようにするためにはどうしたらよいでしょうか?
print_rは出力言語で、配列内部を表示できますが、これと同様な処理を変数に出力できる関数や言語をご存知でしたら教えていただけないものでしょうか?もし存在すれば、$num ⇒ array(1,2,3)という形に変換できるのではないかと思いました。
度々で恐縮ですが宜しくお願いいたします。

投稿日時 - 2008-08-06 01:48:56

ANo.3

serializeは、PHPの値(配列でも、クラスでも)を文字列に変換します。
serializeを含むプログラムとその実行結果です。結果に改行が入っていますが、実際には改行のない文字列です。

<?php
echo serialize(array(array(1,2,3),array('a','b','c','d'),array(7,8,9)));
?>
^Z
a:3:{i:0;a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}i:1;a:4:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";i:3;s:1:"d";}i:2;a:3:{i:0;i:7;i:1;i:8;i:2;i:9;}}

ここで、a:3 は、要素数3の配列、i、sは整数、文字列の意味です。

unserializeは、この文字列から配列なり、何なり任意の値を復元します。
クラスの場合、unserializeの前にクラス定義が読み込まれている必要があると言う制限はありますが。

投稿日時 - 2008-08-05 12:01:55

補足

ご指導いただいたように、以下の変更によってそれぞれserializeを試してみました。

(変更1)fputs($fp, serialize($lines));
  ⇒CSVのline[0]の部分にすべてのデータが平文で格納されました。
   データはjoinを使った時と同じカンマ区切りにはなっています。
(変更2)$serializedLines = serialize($lines);
  ⇒CSVのline[0]の部分にすべてのデータが平文で格納され、
   配列変数の部分はarrayという文字で格納されてしまいました。

serializeを使うことで、多次元配列を直列に変換し、1行1レコードにできるとワクワクしていたのですが、配列構造は維持してくれない結果となってしまいました。
だから、新たにNo3のご回答をいただいたと理解してますが、そういうことでしょうか?また、No3のご指導では、配列構造が維持できる仕組みがあるように見えますのでこれを試して見たいと考えています。
早速と行きたい所ですが、

array("$recordID",$nickname,$date,$area,$num,$item,$size);
で保持されたデータを

serialize(array(array(1,2,3),array('a','b','c','d'),array(7,8,9)));
に置き換えるには、何か方法がありますでしょうか?

次のこのように考え、実験しましたが、warnningとなってしまいます。
array(1,2,3)は$numと同意
array('a','b','c','d')は$itemと同意
array(7,8,9)は$sizeと同意
と考えて、
$line=array("$recordID",$nickname,$date,$area,serialize(array(($num),($item),($size)));
これで実行しましたら、警告となってしまいましたのでどうして良いか分からなくなってしまいました。

本題からそれて申し訳ありませんが、ここが分からないと実験ができないので宜しくお願いいたします。

投稿日時 - 2008-08-06 00:25:57

ANo.2

serializeの事はよくわかりませんが、それをする前にすでに"Array"が出力されているために不成功になったのだと思います。

簡単な配列展開処理を作成してみました。

<?php
//追加部分開始
function open_array($a){
foreach ($a as $v){
$b.=",".$v;
}
$b=substr($b,1);
return $b;
}
$open_num=open_array($num);
$open_item=open_array($item);
$open_size=open_array($size);
//追加部分終了
//$num、$item、$sizeを$open_~に変更
$lines=array("$recordID",$nickname,$date,$area,$open_num,$open_item,$open_size);
$lines=implode(",",$lines);
$lines=$lines."\n";
serialize($lines);
//CSVファイルに書き込み
$fp=fopen("report.csv","a");
flock($fp, LOCK_EX);
fputs($fp,$lines);
fclose($fp);
?>

$numなどの配列が2次元以上であれば、展開処理を以下のようにしてください。
function open_array($a){
foreach ($a as $v){
if (is_array($v)){
$v=open_array($v);
}
$b.=",".$v;
}
$b=substr($b,1);
return $b;
}

投稿日時 - 2008-08-05 09:38:51

補足

ファンクションはまだ、使いこなせていませんが、がんばって理解しようとしますが、分らない部分がありますのでもう少し教えてください。
<?php
//追加部分開始
function open_array($a){ //引数を得る
foreach ($a as $v){   //何項目あるか分らないが、得た値をありったけ$vに置きなさいと命令する
$b.=",".$v; //ここからの意味が分りません
}
$b=substr($b,1);
return $b; //最終的に$bに値が格納され、returnでどこかに返すのだなあと分ります。
}
$open_num=open_array($num);  //ここはopen_arrayを適用しているのが分ります。(そもそもこの置換えはどういうことなんでしょうか)
$open_item=open_array($item);
$open_size=open_array($size);
//追加部分終了
---------------------------------
function open_array($a){
foreach ($a as $v){
if (is_array($v)){ //$vが配列なのか確かめる。
$v=open_array($v); //配列だったらopen_array処理をして、$vに代入
}
$b.=",".$v; //やはりここから分りません。
}
$b=substr($b,1);
return $b;
}

また、ファイルを読み込んで格納データを活用する際には、1行1レコードを順番に読み込んで配列変数に格納することになるのですが、その場合、$numより左の項目もカンマ切りで連なって取り出します。そしてこの1行の中から$numより右の部分のそれぞれを引数として、このファンクションに渡して展開するという理解でよいでしょうか?

投稿日時 - 2008-08-05 18:26:07

あなたにオススメの質問