質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Q&A

解決済

1回答

5809閲覧

【PHP】SplFileObject +URL読み込み

tajix_japan

総合スコア132

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

0グッド

0クリップ

投稿2017/05/26 13:04

編集2017/05/26 13:05

先日、こちらで、ttt.txt に記載したタブ区切りの指定したデータを拾ってくるサンプルソースを作っていただきました。

ttt.txt の中身 「 abc def ghi jkl mno 」

test.php?p=2&e=3 とすると、◇マークと行番号を付与した上で、2行目から3つのデータを拾ってきて、

◇2 def
◇3 ghi
◇4 jkl

と表示します。

php

1 2 3<?php 4 5use SplFileObject as File; 6 7// パラメータ受け取り 8$p = max(filter_input(INPUT_GET, 'p') - 1, 0); // 0以上を保証 9$e = filter_input(INPUT_GET, 'e'); 10 11// 実験データ 12$filename = ("ttt.txt"); 13 14// SplFileObjectを使ったほうがコードがきれいになります 15$file = new File($filename, 'rb'); 16$file->setFlags(File::READ_CSV | File::SKIP_EMPTY | File::READ_AHEAD | File::DROP_NEW_LINE); // CSVモード 17$file->setCsvControl("\t"); // セパレータをタブ文字に 18 19// イテレータを配列に変換して切り取り 20$records = array_slice(iterator_to_array($file), $p, $e, true); 21 22?> 23<!DOCTYPE html> 24 25<meta charset="UTF-8"> 26<title>Example</title> 27 28<style> 29ul { 30 list-style: none; 31 margin-left: 0; 32 padding-left: 1.2em; 33 text-indent: -1.2em; 34} 35li:before { 36 content: "◇"; 37 display: block; 38 float: left; 39 width: 1.2em; 40} 41</style> 42 43<ul> 44<?php foreach ($records as $i => $record): ?> 45 <li><?=($i+1)."\t".implode("\t", array_map('htmlspecialchars', $record))?></li> 46<?php endforeach; ?> 47</ul> 48

今回の変更点

$filename = ("ttt.txt");

の部分について

$filename = ("http://example.com/ttt.txt");

とURLで拾ってきたいというのが質問の趣旨です。

【当方でテストしてみたこと】
まずは、php.ini の「Fopen wrappers」が「allow_url_fopen = On」になっていることを確認しました。

その上でttt.txt に記載したタブ区切りの内容( abc def ghi jkl mno ) をURLから拾って全て書き出すPHPを作りました。

php

1<?php 2$inputs = new NoRewindIterator(new SplFileObject("http://example.com/ttt.txt")) 3foreach($inputs as $line) { 4 print $line; 5} 6?> 7

上記のPHPは、http://example.com/ttt.txt のタブ区切り内容を全て拾うことができます。

上記の2つを合体させれば問題ないと考え、下記のように作成してみました。

php

1<?php 2 3$inputs = new NoRewindIterator(new SplFileObject("http://example.com/ttt.txt")); 4 5use SplFileObject as File; 6 7// パラメータ受け取り 8$p = max(filter_input(INPUT_GET, 'p') - 1, 0); // 0以上を保証 9$e = filter_input(INPUT_GET, 'e'); 10 11// 実験データ 12$filename = $inputs; 13 14// SplFileObjectを使ったほうがコードがきれいになります 15$file = new File($filename, 'rb'); 16$file->setFlags(File::READ_CSV | File::SKIP_EMPTY | File::READ_AHEAD | File::DROP_NEW_LINE); // CSVモード 17$file->setCsvControl("\t"); // セパレータをタブ文字に 18 19// イテレータを配列に変換して切り取り 20$records = array_slice(iterator_to_array($file), $p, $e, true); 21 22?> 23<!DOCTYPE html> 24 25<meta charset="UTF-8"> 26<title>Example</title> 27 28<style> 29ul { 30 list-style: none; 31 margin-left: 0; 32 padding-left: 1.2em; 33 text-indent: -1.2em; 34} 35li:before { 36 content: "◇"; 37 display: block; 38 float: left; 39 width: 1.2em; 40} 41</style> 42 43<ul> 44<?php foreach ($records as $i => $record): ?> 45 <li><?=($i+1)."\t".implode("\t", array_map('htmlspecialchars', $record))?></li> 46<?php endforeach; ?> 47</ul> 48 49

結果
下記のようなエラーが出ます。

Fatal error: Uncaught exception 'RuntimeException' with message 'SplFileObject::__construct() expects parameter 1 to be a valid path, object given' in C:\xampp\htdocs\test.php:15 Stack trace: #0 C:\xampp\htdocs\test.php(15): SplFileObject->__construct(Object(NoRewindIterator), 'rb') #1 {main} thrown in C:\xampp\htdocs\test.php on line 15

上記でエラーとなったいる場所は

$file = new File($filename, 'rb');

の部分です。

どこが間違っているのでしょうか?

よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

NoRewindIterator なんてあったんですね,勉強になりました。不可逆なストリームを扱う際には必須ですね。

あと調べていたらLimitIteratorなるものもあるようです!一度全部配列に変換してから切り取るよりも,部分的にイテレータの段階で読み捨てを行ったほうがメモリ効率が圧倒的にいいので是非こちらで。

php

1<?php 2 3use SplFileObject as File; 4 5// パラメータ受け取り 6$p = max(filter_input(INPUT_GET, 'p') - 1, 0); // 0以上を保証 7$e = filter_input(INPUT_GET, 'e'); 8 9// SplFileObjectでURLをオープンする 10$file = new File('http://example.com/ttt.txt', 'rb'); 11$file->setFlags(File::READ_CSV | File::DROP_NEW_LINE); // CSVモード (但し空行読み飛ばしモードは使えない) 12$file->setCsvControl("\t"); // セパレータをタブ文字に 13 14// Rewindされないようにラップする 15$it = new NoRewindIterator($file); 16// 空行を読み飛ばすようにラップする 17$it = new CallbackFilterIterator($it, function ($row) { return $row !== [null]; }); 18// $pオフセットから$e件に制限するようにラップする 19$it = new LimitIterator($it, $p, $e); 20 21// 配列に変換 22$records = iterator_to_array($it); 23 24?> 25<!DOCTYPE html> 26 27<meta charset="UTF-8"> 28<title>Example</title> 29 30<style> 31ul { 32 list-style: none; 33 margin-left: 0; 34 padding-left: 1.2em; 35 text-indent: -1.2em; 36} 37li:before { 38 content: "◇"; 39 display: block; 40 float: left; 41 width: 1.2em; 42} 43</style> 44 45<ul> 46<?php foreach ($records as $i => $record): ?> 47 <li><?=($i+1)."\t".implode("\t", array_map('htmlspecialchars', $record))?></li> 48<?php endforeach; ?> 49</ul>

蛇足ですが,

  • ob_startを使って出力HTMLのバッファリングを行っている
  • エラーや例外が発生したときには正常系で使うHTMLのバッファを破棄してエラー用のHTMLに切り替える処理を書いている

上記を満たす場合,一時的な配列を生成する必要すらありません。イテレータのままforeachで回してもらって構いません。ただし条件を満たしていないのにやってしまうと,foreachループ中に通信障害が発生したときにHTMLが中途半端な状態でぶっ壊れて出力されてしまうので注意。

一度配列を作る意味としては,「一度作ってしまえば配列は絶対に壊れることが無い」と保証できるからですね。HTTP経由でURLをオープンしているイテレータは不安定なことを意識しなければなりません。

投稿2017/05/26 13:50

編集2017/05/27 06:32
mpyw

総合スコア5223

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tajix_japan

2017/05/26 20:35

いつもありがとうございます!! また、丁寧なアドバイス有難うございます。本当に勉強になります。 早速、頂いたソースでトライ致しました。 結果 なぜか表示されませんでした。但しエラーも出ません。 試しに、 $file = new File('http://127.0.0.1:8080/ttt1.txt'); の部分を $file = new File('ttt1.txt', 'rb'); と直接叩くことも試してみましたが表示されません。 http://127.0.0.1:8080/ttt1.txt をクリックするとちゃんとデータは表示されます。 表示ソースを見ると、<ul></ul>となっており、PHPの配列表示部分がまるまる表示されないようです。 アドバイスいただけましたら幸いです。 よろしくお願い申し上げます。
mpyw

2017/05/27 06:26 編集

どうやら不可逆なHTTP URLのイテレータに関しては,File::READ_AHEADおよびFile::SKIP_EMPTY,要するに空行を読み飛ばすためのオプションが使えないようです。なので更にCallbackFilterIteratorをかぶせる形で対応しました。(そもそもCSV中に空行が含まれていないという保証があれば要らないのですが) fgetcsv系の処理は,空行が見つかると [null] というNULL1個だけ入った配列を返すので,それと比較しています。
tajix_japan

2017/05/27 07:34

有難うございます!! 出来ました。 少しだけ弄りました。 下記の部分だけit2にするだけで見事に動きました。 弘法にも筆の誤りですね! // $pオフセットから$e件に制限するようにラップする $it2 = new LimitIterator($it, $p, $e); // 配列に変換 $records = iterator_to_array($it2); 本当に有難うございました。 深く御礼申し上げます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問