スレッド表示 | フラット表示〕 全トピック 920 件中 274 番目 次≫ ≪前

CSVデータを20件づつ取り出す

created: 2006-02-15 19:30 | modified: 2006-02-18 23:21 | reply: 14

[2928] CSVデータを20件づつ取り出す

user: 江戸川 | created: 2006-02-15 19:30
いつもお世話になります。

よくデータベースと連動したサイトで、最新20件が表示されその前の20件、さらにその前の20件・・・を表示するためのリンクが貼ってあるページがありますが、これをCSVデータ+PHPでやりたいのです。

単にCSVの上の行から20件抜き出して表示するだけなら簡単ですが、次の20件を表示するページを自動的に作成し、そこへのリンク(1)を作る、さらに次のページを作りそのリンク(2)・・・と該当データ件数にあわせて自動的に処理させるにはどうしたら良いのでしょうか?

とりあえず引っかかっているポイントとしては、それらのページを別々のPHPファイルとして作る必要があるのか、あるいはリンクをクリックした時にある変数をPHPに渡すことによって、希望する順位のデータを表示させるのか、と言うことです。

概念的な質問になってしまいましたが、ヒントや指針で結構ですので教えていただけると助かります。
reply: 2930 2931 返信 編集 削除

[2930] ページング

user: ぱぴよん | created: 2006-02-16 10:38
江戸川さんこんにちは、ぱぴよんと申します。


レコード数が多いものを表示するときに江戸川さんがおっしゃられるように
数件だけ表示し、前のページや次のページといったようにページングするものを
よく見かけます。


> あるいはリンクをクリックした時にある変数をPHPに渡すことによって、希望する順位のデータを表示させるのか

一般的にはこちらの方法になると思います。
ページングされているページではGETの値としてページ数や
レコードのインデックスを渡してやるものが多いです。

例えばこちらのBBSでは
/php/bbs/index.php?mode=tree&page=1
という風になっていますね。
これはURLのパラメータ(GET)に「page」という変数を持たせて、
この値により表示すべきページ数を取得し、PHPで該当データを表示するプログラムを組んでいます。


該当ページのデータは該当ページ以前の不要なデータを
スキップすることで取得します。

例)
<?php
/*********
* 初期化
*/
$page = 1; // ページ数
$row = 10; //表示限度件数
$count = 0; // 表示した件数
$data = array(...); // データ

/********
* ページ数の取得
*/
if(isset($_GET['page'])) $page = (int)$_GET['page'];

/********
* データの表示
*/
for($i = 0; $i < count($data); $i++) {
// 該当ページ以前のデータをスキップする
if($row * ($page - 1)) continue;

/* $data[$i]を表示 */
count++;

// 表示件数が表示限度件数に達したとき
if($count == $row) break;
}
?>


もしくは次のような方法もあります。
こちらではスキップ処理をするのではなく、該当ページの先頭インデックスの
ポインタを指定し、そこから表示限度件数まで表示したり、
データが存在しなければ表示を終わる
といったことをしています。

処理の速さでいうと上記の例のようにスキップをする手間が省けた分こちらの方が速いと思います。
  // 表示するデータの先頭インデックス
// ※配列の添え字が0から始まる場合
//  もし添え字が1から始まる場合は+1する必要がある
$idx = $row * ($page - 1);

/********
* データの表示
*/
// データが存在するか、もしくは $row 件表示されたか
// ※$count++ は比較をした後で+1されます
while((isset($data[$idx])) || ($count++ == $row)) {
/* $data[$idx]を表示 */
idx++;
}
Parent: 2928  reply: 2932 2934 返信 編集 削除

[2932] 訂正です。

user: ぱぴよん | created: 2006-02-16 11:48
> while((isset($data[$idx])) || ($count++ == $row)) {

while((! isset($data[$idx])) && ($count++ < $row)) {
Parent: 2930  返信 編集 削除

[2934] ありがとうございます。

user: 江戸川 | created: 2006-02-16 14:50
ぱぴよんさん、こんにちは。江戸川と申します。

お陰さまで、大分道筋が見えて参りました。

先ず一番目のポイントとして、URLの後ろの?以降の文字列は気になっていたのですが、GET変数を格納できるんですね。そうなるとかなり便利です。
私はこれは、クッキーが使えない人のために、セッション変数を格納しておくものだと思っていました。

ちなみに、mode=treeとあるのはGET変数を出すと言う意味なんでしょうか?また、これをPOSTで出すとしたら、どうすれば良いのでしょう?(もっとも、ここではセキュリティー的にGETでもPOSTでも、あまり関係ないかもしれませんが)

次に該当データの表示で、要らないものをスキップする方法ですが、この例では先ずは全てのデータを2次元配列化するのが前提なのでしょうか? あるいは、とりあえず全行fgetcsvしてしまって、該当データだけ表示するでもいけそうですね。

次のポインタを指定する方法では、予めCSVの各行の頭に通し番号を振っておくのか前提でしょうか?ファイルポインタと言う意味でしたら、これを好きな場所に置く方法があるのか無いのかすら、私は判りません。
Parent: 2930  reply: 2935 返信 編集 削除

[2935] Re: ありがとうございます。

user: ぱぴよん | created: 2006-02-16 16:48
> ちなみに、mode=treeとあるのはGET変数を出すと言う意味なんでしょうか?

違います。
modeというのもpageと同じように一つの変数です。
このmodeによりユーザがどの処理を行おうとしているのかを判断しているのです。
・tree ⇒ BBSの投稿内容をツリー表示する
・new  ⇒ BBSに投稿をする
など

「GET変数を出す」というのをどういった意味で言われているのかが分かりません^^;
GETは目で見えてしまいます。
エンコードをしたりしてセキュリティを強化される方もおられるようです。

これについてはどういった内容を扱っているのかによると思います。
それに合わせたセキュリティ対策になります。
セキュリティレベルを上げればその分ユーザの使い勝手は悪くなります。
セキュリティレベルを下げればその分セキュリティホールが生まれます。


> また、これをPOSTで出すとしたら、どうすれば良いのでしょう?(もっとも、ここではセキュリティー的にGETでもPOSTでも、あまり関係ないかもしれませんが)

POSTで渡そうとするとURLの後ろに変数をつけて渡すことはできません。
hidden(inputタグ)に値を入れておき、リンクが押されればサブミットする
といった形になるのではないでしょうか。

<script type="javascript">
<!--
function jump_url() {
document.forms['フォーム名'].method = 'post';
document.forms['フォーム名'].submit();
}
//-->
</script>
<a href="javascript:void(0);" onClick="jump_url();">リンク</a>





サンプルではイメージしやすくするために配列$dataに全データを格納することを前提としていました。

fgetcsvでファイルデータを取得しながらスキップする
というのでもいいと思います。
<?php
$handle = fopen("test.csv", "r");
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
/* ここでスキップ処理 */
/* 該当データの表示 */
/* 表示件数が目的の件数表示すればbreak; */
}
fclose($handle);
?>


ファイルポインタは fseek で好きなところにおけるようですが、詳しい動作については分かりません。

スキップの方法の法が分かりやすいかもしれませんね。
Parent: 2934  reply: 2936 返信 編集 削除

[2936] なるほど

user: 江戸川 | created: 2006-02-16 21:11
「GET変数を出す」って変な書き方をしてしまいましたが、mode=treeだったらGETで送信して、mode=何か別の文字列だったら、POST送信が出来るのかな?と思って聞いたのです。
しかし、URLの?以降に書いて渡す変数はGET送信のみなんですね。了解しました。

まあ、何番目のデータを表示したいかなんて情報はセキュリティー云々する内容じゃないですね。第一、URLに書いて外から丸見えな訳ですし。
それを、フォームボタンを作ってワザワザPOST送信するまでも無いですね。

fseekは私もどう使って良いか判らないのです(^^;
なので、とりあえずfgetcsvでスキップしながら表示する方法でトライしたいと思います。
Parent: 2935  reply: 2937 2940 返信 編集 削除

[2937] セキュリティ

user: ぱぴよん | created: 2006-02-17 09:47
> まあ、何番目のデータを表示したいかなんて情報はセキュリティー云々する内容じゃないですね。第一、URLに書いて外から丸見えな訳ですし。

私もセキュリティに対して詳しいわけではありませんが、
GETの場合だとユーザが何となくどれがどういった役割を
している変数なのかが分かることがあります。

そういったときにURLを直接入力するという利用の仕方もされます。
普通(URLをいじったりしないで)にシステムを使っている上では問題が起こらなくても、
直接URLを入力されたりしたときに問題が発生したりということがあるので、
直接URLを入力されても江戸川さんが期待している値じゃなければ受け取らないようにすればいいです。

例えば、私のサンプルで
  $page = 1;
if(isset($_GET['page'])) $page = (int)$_GET['page'];
となっていたと思います。
これは、isset()で ?・・・&page=・・・ というpageという変数が
GETで渡されているかを判断しています。
もし渡されていなければ初期化されているので1という値が入ります。

渡されていればpageを整数型にして$pageに入れています。
もし、?・・・&page=abc&・・・ となっていても$pageには整数を入れるためです。
この場合、$pageには0が入ります。


-----------------------------------------------
0になっちゃいますね^^;
if($page < 1) $page = 1;
というのを次の判断に付け加えた方がいいです。
-----------------------------------------------


このようにユーザが変更可能(GETなど)なものやフォームデータなどは信じてはいけません。
まず疑ってください。全ての人が正しい使い方をしてくれるわけではないので^^;
どんな入力(期待外)をされてもそれをプログラムの中で
値を強制的に修正してあげたり、警告を出してあげることでユーザの入力によるバグを減らすことができます。

数値を期待しているのなら数値しか通さない。
半角英数字を期待しているなら半角英数字しか通さない。
など入力に制限がかかってくるものが多々あると思いますので
そういったときにしっかりとチェックした方がいいです。
文字列のバイトチェックや桁数の決まったコードの桁数なども・・・
考えられる制限があるもの全てに対してチェックをすることで起こりうる問題を少しでも回避することができます。

これはWebアプリだけではなく、どんなプログラム言語でも同じことが言えると思います。



また、POST送信も安全ではありません。
例えば、ユーザが勝手にフォームを作って送信してきたり(formのactionに設定)、
ページ(HTML)を保存してhiddenの値に好きな値を入れて変更して送信したり・・・
色々あると思います。
ですのでデータを登録しているところや大事な処理をしているところでは値をチェックすることが望ましいです。

全てをすることは難しいと思いますが^^;(私もできていません)
Parent: 2936  reply: 2941 返信 編集 削除

[2941] Re.セキュリティー

user: 江戸川 | created: 2006-02-17 16:05
私のサイトもフォームでデータを受け付ける(POST送信)ページが結構あるのですが、先ずはJavaScriptで入力チェックを行っています。そして、重要な値やデータベースと照合が必要な場合はPHPでもチェックしています。
しかし、これらは主に善意のユーザーの入力ミスを防ぐ目的なので、悪意のユーザに対してはどうかというと、フォーム値を表示する場合に、HTMLタグをエスケープするくらいですかね。

そういう意味では、URLの後ろに変数を入れて表示した場合も何かクラッキング対策が必要なんでしょうか。

それと、細かい話ですが、ぱぴよんさんのサンプルで、if()の後の処理は中カッコ{}でかこま無くても良いのでしょうか?
また、(int)だけで整数にする、と言うコマンドになるのでしょうか?
Parent: 2937  reply: 2945 返信 編集 削除

[2945] Re: Re.セキュリティー

user: ぱぴよん | created: 2006-02-17 17:15
> そういう意味では、URLの後ろに変数を入れて表示した場合も何かクラッキング対策が必要なんでしょうか。

想定外のデータでないかをチェックすればいいと思います。
例えば、データベースの操作でSQL文を使われていると思います。
何もチェックしないで直接変数を組み込むと危ないです。
SQL文を全く違った命令に換えてしまう可能性があるからです。

◆PHPマニュアル:データベースのセキュリティ
http://jp2.php.net/manual/ja/security.database.php

「SQLインジェクション」聞いたことあると思いますが、
こういった脅威にさらされます。



> それと、細かい話ですが、ぱぴよんさんのサンプルで、if()の後の処理は中カッコ{}でかこま無くても良いのでしょうか?

if文で{}がなければ直後の1行文を対象とします。
◆「{}」なし
if(条件)
print "処理";
◆「{}」あり
if(条件) {
print "処理";
}
上記は同じ意味です。


◆「{}」なし
if(条件)
print "処理1";
print "処理2";
◆「{}」あり
if(条件) {
print "処理1";
print "処理2";
}
これは違った処理になります。

{}がなければ print "処理1"; は条件がつきますが print "処理2"; は無条件で表示されてしまいます。

{}を使うか使わないかは人それぞれです。
ただし、{}をつけた方が誰が見ても分かりやすいと思います。



> また、(int)だけで整数にする、と言うコマンドになるのでしょうか?

はい、型変換の命令です。

◆PHPマニュアル:型キャスト
http://jp2.php.net/manual/ja/language.types.type-juggling.php

また、 settype() という関数でも型変換を行うことができます。

PHPでは型を特に意識しないでもPHP君が柔軟に処理を行ってくれます。
ですからプログラム言語などのように宣言も必要ありません。
ですが、いくら勝手に変換してくれるかといってPHP君に頼りっきりではいけません。
PHP君がどんなときでも江戸川さんが思ったように処理してくれるわけではないので^^;

例)
$a = 1;
$b = "23ab";
print($a + $b);
[結果]
24
これはPHP君が
「$aと$bは足し算をしているのだから数字じゃない部分は無視して計算しよう」
として結果として $aの「1」と$bの「23」を有効としてそれ以外は無視した形で
計算結果を出してくれるのです。

これがPHPでいう「PHP君が型を柔軟に変換して処理をしてくれる」という意味です。

C言語などで同じことをするとどうでしょうか。
「型が違いますよっ!」C君が怒りませんか?
これがPHP君とC君の違いです。


ただし、これがいつでも江戸川さんが思ったとおりの動作ではない場合があります。
そういうことが起こらないように江戸川さん自身が型を意識したプログラミングを
行うことでより正確なシステムとなるのです。
Parent: 2941  reply: 2949 返信 編集 削除

[2949] Re3.セキュリティー

user: 江戸川 | created: 2006-02-18 23:21
幸か不幸かSQL注射は怖くありません。恥ずかしながら知識が無いので、データベースにSQLは使っていないのです。今はCSVを単に上から検索していくと言う強引な方法をとっています。

PHPには型が幾つかあるんですね。なるほど。でも私は省略すると間違いそうなので、教科書どおりカッチリ書くと思います。
まあ、PHP君が物分りの良いやつだということは、何となくわかります。プログラミング初心者の私が書いてもそれなりに動いてしまうのですから。
Parent: 2945  返信 編集 削除

[2940] fseek

user: ach | created: 2006-02-17 12:26
スキップには配列を作る必要が無いのでfgetcsvではなくfgetsを使うほうが良いです。

fseekを使ったやり方ですが、CSVに格納されているデータの一行分を固定長にすると非常に簡単です
例えばCSVが次のように書かれているとしましょう"%"はここではchr(0)、いわゆるNull文字をさし、"$"はchr(10)、改行文字をさします。
この場合1行は常に64バイトです
0000002006,0000000002,0000000017,hoge%%%%,仕事%%%%%%%%%%%%%%%$
0000002006,0000000002,0000000017,foo%%%%%,遊園地%%%%%%%%%%%%%$

で、これを読むコードは次のようになります
define("LENGTH",64);    //1レコード長を設定
define("ROWNUM",20); //一ページに表示する行数
$page = 2; //仮に移動先を2ページ目とする

$fp = fopen($fileName,"r"); //ファイルポインタを取得
fseek($fp,($page-1)*ROWNUM*LENGTH); //$pageページ目の先頭へ移動
$data = fread($fp,ROWNUM*LENGTH); //ROWNUM分データを読み込む
fclose($fp); //ファイルを閉じる

//$dataの加工

//PHP4
$data = str_replace(chr(0),'',$data); //Nullバイト文字を消去
$data = explode(chr(10),$data); //各行を配列に展開
//PHP5
//$data = str_split($data,LENGTH); //各行を配列に展開
//$data = str_replace(chr(0),'',$data); //Nullバイト文字を消去
//$data = str_replace(chr(10),'',$data); //改行文字を消去

この方式をとっているのはExciteの英和辞書とかですね。
読み込みの発生量が少ないので高速ですが、ファイルサイズが大きくなるという問題があります。
Parent: 2936  reply: 2943 返信 編集 削除

[2943] Re.fseek

user: 江戸川 | created: 2006-02-17 16:19
なるほど、fgetsの方が配列にしないでよい分、fgetcsvより処理が早いって事ですか。試してみます。

fseekを使う場合は、Null文字を入れて行の長さを全て同じにしないといけないんですね。
それをしないとfseekは使えない?そもそもfseekとは何か?ファイルポインタとは?・・・・うーん、怪しくなって来ました(^^;もう少し修行します。
Parent: 2940  reply: 2944 返信 編集 削除

[2944] fseek

user: ぱぴよん | created: 2006-02-17 16:51
PHPマニュアルに下記のように書かれています。

fseek -- ファイルポインタを移動する

int fseek ( resource handle, int offset [, int whence] )

handle が指しているファイルのファイル位置識別子を ファイル・ストリーム中の
offset バイト目に セットします。新規位置は、ファイルの先頭からのバイト数で 測られます。
これは whence で指定した位置に offset を追加することにより得られます。
この値は、以下のように定義されます。
◆PHPマニュアル:fseek
http://jp2.php.net/manual/ja/function.fseek.php


指定できるファイルポインタというのはバイト数なので
「固定長にしておかないと正しく行の指定ができない」
ということだと思います。

配列の場合のポインタでは要素(添え字のほうが分かりやすいでしょうか)を基準にしてポインタ移動しますが・・・
fseekでは

江戸川さんは1レコードは一行ごとにファイルに書いているって分かっても
fseek君はバイト数でしか判断できないので1レコードが固定ではなくバラバラだと
どこからどこまでが1レコード分のデータなのか判断できないのです。
なので、ファイルのデータの1レコード分を予めfseek君が理解できるように
固定長にしておくということだと思います。

ちょっと難しいかもしれませんが、こんな感じだと思います^^;

csvファイルを
$data = file(CSVファイル);
として1行(1レコード)ごとに格納してあげれば以前私がサンプルで行ったような
処理が可能ですが、これだと全てのデータを読み込む必要があるので無駄が出る
ってことだと思いますし・・・
Parent: 2943  reply: 2948 返信 編集 削除

[2948] Re2.fseek

user: 江戸川 | created: 2006-02-18 23:03
うーむ、ファイルポインタはバイト単位で移動するんですか。fgetcsvとかfgetsが行を見分けるように、fseek君も行を行単位で動くのかと思ってしまいました(^^;

エクセル風に言うと各セルの最大文字数が今後どうなるか判らないくらいなので、1行のバイト数をきっちり定めるのはかなり難しいですね。
Parent: 2944  返信 編集 削除

[2931] リンク

user: ぱぴよん | created: 2006-02-16 10:54
前のページや次のページのリンクについては
前や次があるかを現在のページ数から判断します。

例えば2ページ目を表示しているのであれば前のページ(1ページ目)
が存在することが分かりますね。
また、最後のページを表示しているのかどうかについては
全データ数と表示ページ(表示データのインデックスが分かる)を
比較してあげれば最後のページなのかそうでないのかが分かります。

/********
* 前ページの判断
*/
if($page > 1) print "<a href=\"?page=".($page-1)."\"><< 前ページ</a>";
else print "<span style=\"color:gray;\"><< 前ページ</span>";

/********
* 次ページの判断
*/
if($page * $row < count($data)) print "<a href=\"?page=".($page+1)."\">次ページ >></a>";
else print "<span style=\"color:gray;\">次ページ >></span>";
Parent: 2928  返信 編集 削除
スレッド表示 | フラット表示〕 全トピック 920 件中 274 番目 次≫ ≪前
ページの一番上へ
Googleグックマークに登録 Yahooグックマークに登録 livedoorクリップに登録 @niftyクリップに登録 はてなブックマークに登録 deliciousに登録 Buzzurlに登録 FC2ブックマークに登録
最近更新された掲示板トピックス
管理人Blog
Yahoo Search

最近更新したNote
PHPマニュアル
今日のブックマーク
PHPマニュアル関数検索
関数名を入力し検索ボタンをクリック↑