laravelにてファイルからhtmlを作成する話
今回はシンプルにcsvやyamlファイルからデータを読み取りHMTLにて表示する話です。
ま、bladeは今回の説明範囲外ですが… と管理責任範囲を言ってみますw
うん、良くある話ですが、ここまではサービス – ここまではシステムって言う折衷箇所が発生します!
要するに、<html><head><title>の部分まで、内部のロジック作っているシステムまでが関与しますか?
もちろんNOです!
それは営業・サービス系のお仕事です。html位だれでもいぢれるでしょ?w
っと入っても、blade.phpの敷居が…. しらねぇ〜!勉強しろ!
さて、今回はこんな感じで始まりますw
どうすれば、システムから手離れできるか?
そりゃ、動的な部分を洗い出せばOKでしょう
あとは静的な部分は営業・サービスさんにお願いします。
簡単にbladeを考えてみる
今回はCSV,YAMLからデータを読み出して、結果を返します。
シンプルに、dateとstringにしましょう
string,string
これを、表にしましょう
<table> <thead> <tr> <th>日付</th> <th>コメント</th> </tr> </thead> <tbody> <tr> <th>2020/11/22</th> <td>初回です、よろしく</td> </tr> </tbody> </table>
って感じで表示しましょう
もちろん、tbodyは配列で続いてきます….
はいっ!ここまで来ると、先に出ますね?
画面定義書w
うん、一致するはずです!!です!すっ…
簡単にclass化するとこんな感じです
<?php namespace App\Classes\Views\Packages\elements; class ReadTableLineElement{ public $date; public $comment; }
ですよねぇ〜w
さて、このページにこれを当てはめるのでそのクラスはこんな感じ
<?php namespace App\Classes\Views\Packages; use App\Classes\Views\Packages\elements\ReadTableLineElement; class ReadViewPackage{ /** * @var ReadTableLineElement[] 結果テーブル配列 */ public $table = array(); }
うん、シンプル!bladeを作りますが、シンプルに…
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <title>read result</title> </head> <body> <h1>result.</h1> <table> <thead> <tr> <th>日付</th> <th>コメント</th> </tr> </thead> <tbody> <tr> <th>2020/11/22</th> <td>初回です、よろしく</td> </tr> </tbody> </table> </body> </html>
値ねぇ〜w
さっそくリクエストのtable要素の繰り返しをして、それぞれを当てはめます
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <title>read result</title> </head> <body> <h1>result.</h1> <table> <thead> <tr> <th>日付</th> <th>コメント</th> </tr> </thead> <tbody> @foreach($table as $tableElement) <tr> <th>{{ $tableElement->date }}</th> <td>{{ $tableElement->comment }}</td> </tr> @endforeach </tbody> </table> </body> </html>
ま、解説するまでもないシンプルです
簡単なアクセス引数とサービスをまとめておきます
/read/csv はCSVを使ったサービス
/read/yml はyamlファイルを使ったサービス
/read/ 試験用の変数で作ったサービス
プログラムソースに直接値をほりこむ方式から全体のわだちを作ります。
流れ全体を考えると…
/read -> なんりゃかのController -> とりあえずControllerService -> 値を取得するService
そんな流れかな?
まず、Controllerを考えますが、今回はreadって感じなので、ReadControllerってします。
Http/Controllers/ReadController.php
<?php namespace App\Http\Controllers; class ReadController extends Controller{ }
うん。で… 何もないののメソッドと考えると… indexかなw
indexで来たのを先のクラスを作って返します…
ControllerServiceも作りますのでちょっぴり複雑です
Classes/Services/Controllers/ReadControllerService/Request.php
Classes/Services/Controllers/ReadControllerService/Response.php
とりあえず、サービスのI/Oはまだ決まっていないので空にしました。
Response.phpとReadViewPackage.phpが入っているはずですし、viewの名前が入るわけですから下のようになります
<?php namespace App\Classes\Services\Controllers\ReadControllerService; use App\Classes\Views\Packages\ReadViewPackage; class Response{ /** * @var string view名 */ public $view_name = 'read'; /** * @var ReadViewPackage ページ構成要素 */ public $package = null; }
うん、これでOK今回は説明を省くためコメントも入れましたw
これでサービスの応答が作成できました。
次は… 要求… Request… ないですねw
<?php namespace App\Classes\Services\Controllers\ReadControllerService; /** * Readクラスサービス要求 * Class Request * @package App\Classes\Services\Controllers\ReadControllerService */ class Request{ }
これはこれで良い
構築と思ったらが、次は結果Outputを作ります
storage/tests/Feature/read/csv.html
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <title>read result</title> </head> <body> <h1>result.</h1> <table> <thead> <tr> <th>日付</th> <th>コメント</th> </tr> </thead> <tbody> <tr> <th>2020/11/22 22:00</th> <td>リース</td> </tr> <tr> <th>2020/11/22 23:00</th> <td>並ぶ</td> </tr> <tr> <th>2020/11/23 00:00</th> <td>アトラクション</td> </tr> </tbody> </table> </body> </html>
とりあえず、いろいろ結果がこれになればOK
第零章 試験用の変数を使ったサービス
はじめに…試験を書きます!とりあえず!ビールで!
public function test_index(){ $response = $this->get('/read'); $response->assertStatus(200); $contents = \File::get(storage_path('tests/Feature/read/index.html')); $this->assertEquals($contents,$response->getContent()); }
File::get以外は普通ですね。
File::getは他の記事を漁ってください、ファイル内容を取ってくる物です
これで試験とか言うのは無しで…
これでInput / Outputの試験ができあがりました!
内容はありませんが;;
次は、routeを書きましょう!/routeをアクセスするとReadController@indexです
Route::get('/read', [ReadController::class,'index']);
※ 今回も説明が必要ですが、getはデフォルトメソッドはindexなのでメソッド名を省きました
※ indexは省けません!https://qiita.com/nagamoridaiki/items/12f2ff2a50ea4a13c84d
でっ!
app/Http/Controllers/ReadController.php
を埋めます…
index出来たら先の内容を埋める配列を作る…
public function index(){ $vewPackage = new ReadViewPackage(); $vewPackage->table = array(); $element = new ReadTableLineElement(); $element->date = '2020/11/22 22:00'; $element->comment = 'リース'; $vewPackage->table[] = $element; $element = new ReadTableLineElement(); $element->date = '2020/11/22 23:00'; $element->comment = '並ぶ'; $vewPackage->table[] = $element; $element->date = '2020/11/23 00:00'; $element->comment = 'アトラクション'; $vewPackage->table[] = $element; }
こいつを返せば問題ないとは思いますが、もう少しトリッキーにします(あとの2つも含めて考えます。)
要求を作成する!
して、コントローラサービスに処理を渡す
class ReadController extends Controller{ public $serviceController = null; public function __construct(ReadControllerService $serviceController){ $this->serviceController = $serviceController; } public function index(){ $serviceRequest = new Request(); $response = $this->serviceController->route($serviceRequest); return view($response->view_name,(array)$response->package); } }
class ReadControllerService { public function route(Request $request){ $vewPackage = new ReadViewPackage(); $vewPackage->table = array(); $element = new ReadTableLineElement(); $element->date = '2020/11/22 22:00'; $element->comment = 'リース'; $vewPackage->table[] = $element; $element = new ReadTableLineElement(); $element->date = '2020/11/22 23:00'; $element->comment = '並ぶ'; $vewPackage->table[] = $element; $element = new ReadTableLineElement(); $element->date = '2020/11/23 00:00'; $element->comment = 'アトラクション'; $vewPackage->table[] = $element; $response = new Response(); $response->view_name = 'read'; $response->package = $vewPackage; return $response; } }
試験を実施….OK
Time: 00:00.108, Memory: 20.00 MB OK (1 test, 2 assertions) Process finished with exit code 0
… でっ!考えたら同じメソッドを流用するではないか!
だったら分岐をしなくては!
まずはサービスの要求を変更
<?php namespace App\Classes\Services\Controllers\ReadControllerService; /** * Readクラスサービス要求 * Class Request * @package App\Classes\Services\Controllers\ReadControllerService */ class Request{ /** * 一般的なのぉ〜 */ const GENERIC = 0; /** * CSVモード */ const CSV = 1; /** * YAMLモード */ const YAML = 2; /** * @var int 要求モード */ public $type = Request::GENERIC; /** * 要求モード指定型のコンストラクタ * Request constructor. * @param int $type 要求モード */ public function __construct(int $type){ $this->type = $type; } }
んで、ControllerService本体の処理の変更
public function route(Request $request){ $response = new Response(); $response->view_name = 'read'; $response->package = $this->readViewPackage($request->type); return $response; }
ん?処理がなくなりました!w
readViewPackageにまとめましたw
public function readViewPackage($type){ $vewPackage = new ReadViewPackage(); $repository = new GenericReader(); foreach ($repository->findAll() as $readElement){ $vewPackage->table[] = new ReadTableLineElement($readElement->date,$readElement->comment);; } return $vewPackage; }
はい、This is it.です。$typeは使っていませんが新しい処理Repositoryが登場しました!!!w
Repositoryとは… 調べてねw要するにファイルとかDBとかのI/Oを仕切るサービスです….我理論!
今回は、変数でもCSVでも、YAMLでも同じ結果が欲しいわけです。
それを定義します。
要求は無視して、応答のインタフェイスは…
<?php namespace App\Classes\Repository\Read; class ReadElement{ /** * @var string 日付 */ public $date; /** * @var string コメント */ public $comment; }
こんな感じ。
実際のRepositoryの処理は..
<?php namespace App\Repository\Read; use App\Classes\Views\Packages\elements\ReadTableLineElement; class GenericReaderRepository extends AbstractReaderRespository{ public function findAll(){ $results = array(); $results[] = new ReadTableLineElement('2020/11/22 22:00', 'リース'); $results[] = new ReadTableLineElement('2020/11/22 23:00','並ぶ'); $results[] = new ReadTableLineElement('2020/11/23 00:00','アトラクション'); return $results; } }
ま、こんな感じPHPのレベルとしては低いですが、プログラムの構築系のレベルではちょっぴり高いですね。
いつも考えるイメージは、レゴのジョイント部分です…
師匠もそんなことを言っていました
Lollipopイメージって言ってたかな?言い得て妙だけれども、個人的にはレゴのジョインの方がぴったりきました
![◇レゴ∥LEGO【ボールジョイントブロックのセット 22個】 [R54177] の落札情報詳細| ヤフオク落札価格情報 オークフリー・スマートフォン版](https://img.aucfree.com/r365200735.1.jpg)
こんなイメージです。要求 / 応答を一致させれば、使い回しも出来ます。
ロジックを見ていただいたからなら分かりますが、変数の受け口、CSVの受け口、YAMLの受け口をそれぞれ切り換えられるように作ります。
んでもって!それをPHPUnitで書いてやります。
試験のパーツ > 実際のパーツ > 試験のパーツ
これを作ると実際のパーツは確実です
んで… 何やってたかなw
悩んで… 試験して… コミットw
第一章 CSVファイルを読み取る方法
さて、仕切り直しまして、つぎはCSVファイル内容を出力するページを作ります
まずは試験を作ります
storage/files/reads/csv.csv
2020/11/22 22:00,リース 2020/11/22 23:00,並ぶ 2020/11/23 00:00,アトラクション
はいw
ここでついて行けない人は泣いてください。
次は、シンプルに試験を作ります
public function test_csv(){ $response = $this->get('/read/csv'); $response->assertStatus(200); $contents = \File::get(storage_path('tests/Feature/read/csv.html')); $this->assertEquals($contents,$response->getContent()); }
試験しても NGです…. か?
Expected status code 200 but received 404. Failed asserting that 200 is identical to 404.
ですねw ちょっと脱線
まずは、先のわだちから考えると… 処理に入る部分が必要です
routeを追加
Route::get('/read/csv', [ReadController::class,'csv']);
csvメソッドに行きましょう!!!
app/Http/Controllers/ReadController.php
public function csv(){ // ControllerServiceへの要求 $serviceRequest = new Request(Request::CSV); // ControllerServiceの処理 $response = $this->serviceController->route($serviceRequest); // viewの返却 return view($response->view_name,(array)$response->package); }
Request::CSV以外はindex(変数型)と同じですね… (リファクタリングしたいw)
serviceController->route($serviceRequest)の部分が変わりますので書きます!
public function route(Request $request){ $response = new Response(); $response->view_name = 'read'; $response->package = $this->readViewPackage($request->type); return $response; } public function readViewPackage($type){ $vewPackage = new ReadViewPackage(); $repository = new GenericReaderRepository(); // switch($type) foreach ($repository->findAll() as $readElement){ $vewPackage->table[] = new ReadTableLineElement($readElement->date,$readElement->comment);; } return $vewPackage; }
readViewPackageメソッドのswitchがヒントです。リポジトリを$typeで切り換えればOKです!
まだ、作っていませんがw
…ってちょっと悩みましたが、できあがりました
readViewPackageメソッド
public function readViewPackage($type){ $vewPackage = new ReadViewPackage(); $repository = new GenericReaderRepository(); switch($type){ case Request::CSV : $repository = new CSVReaderRepository(storage_path('files/reads/csv.csv')); break; } foreach ($repository->findAll() as $readElement){ $vewPackage->table[] = new ReadTableLineElement($readElement->date,$readElement->comment);; } return $vewPackage; }
ファイルを指定してCSVの内容を持ってきます。
ファイルの読み取りは、SplFileObjectってのを使います!
class CSVReaderRepository { private $file; public function __construct(string $file_path) { $this->file = $file_path; } /** * 全件取得メソッド * @return ReadElement[] 結果配列 */ public function findAll() : array{ $file = new \SplFileObject($this->file); $file->setFlags(\SplFileObject::READ_CSV |\SplFileObject::READ_AHEAD |\SplFileObject::SKIP_EMPTY |\SplFileObject::DROP_NEW_LINE); $results = array(); // 一行ずつ処理 foreach($file as $line) { $data = $line[0]; $comment = $line[1]; $results[] = new ReadTableLineElement($data, $comment); } return $results; } }
それほど難しくないので…
これで問題なくOK!
第二章 YAMLってヤムルって読むらしいよ
次はYAMLファイルを読み込みます…
今回のターゲットYAMLファイルは以下だ!
- date : 2020/11/22 22:00 comment : リース - date : 2020/11/22 23:00 comment : 並ぶ - date : 2020/11/23 00:00 comment : アトラクション
アクセスは「/read/yaml」で同じページを返します…
/** * YAML疎通 */ public function test_yaml(){ $response = $this->get('/read/yaml'); $response->assertStatus(200); $contents = \File::get(storage_path('tests/Feature/read/yaml.html')); $this->assertEquals($contents,$response->getContent()); }
結果のyaml.htmlはcsvと同じです
次はrouteを編集して、/read/yaml -> Controller@yamlに遷移させます
Route::get('/read/yaml', [ReadController::class,'yaml']);
つぎは、そのyamlメソッドをControllerに実装します
public function yaml(){ // ControllerServiceへの要求 $serviceRequest = new Request(Request::YAML); // ControllerServiceの処理 $response = $this->serviceController->route($serviceRequest); // viewの返却 return view($response->view_name,(array)$response->package); }
Request::YAML以外はcsvのコピーです… リファクタリングしたい!
次はこのなかの $this->serviceController->routeを編集します
/** * Readの結果ページのデータを作成します * @param $type int リクエストタイプ * @return ReadViewPackage ページデータ */ public function readViewPackage($type){ $vewPackage = new ReadViewPackage(); $repository = new GenericReaderRepository(); switch($type){ case Request::CSV : $repository = new CSVReaderRepository(storage_path('files/reads/csv.csv')); break; } foreach ($repository->findAll() as $readElement){ $vewPackage->table[] = new ReadTableLineElement($readElement->date,$readElement->comment);; } return $vewPackage; }
ここがキモですね。
switchをYAML対応させる必要があります
なので、YAMLReaderRepositoryを作りますが…
まずはYAMLReaderRepositoryTestを作ります!
YAML解析は… ここが詳しい!
ごめん嘘ですw
あんまり書いていませんが、yaml扱うにはcomposerにてパッケージを追加します
composer require symfony/yaml
色々、書いていましたが、上のコマンドだけでOKです
public function test_findAll(){ $repository = new YAMLReaderRepository(storage_path('files/reads/yaml.yml')); $results = $repository->findAll(); $this->assertEquals('2020/11/22 22:00',$results[0]->date); $this->assertEquals('リース',$results[0]->comment); $this->assertEquals('2020/11/22 23:00',$results[1]->date); $this->assertEquals('並ぶ',$results[1]->comment); $this->assertEquals('2020/11/23 00:00',$results[2]->date); $this->assertEquals('アトラクション',$results[2]->comment); }
CSVとかと同じ書き方です。
実装!!!
class YAMLReaderRepository extends AbstractReaderRepository{ private $file; public function __construct(string $file_path){ $this->file = $file_path; } /** * 全件取得メソッド * @return ReadElement[] 結果配列 */ public function findAll() : array{ $yaml = Yaml::parse(file_get_contents($this->file)); $results = array(); foreach($yaml as $line) { $date = $line['date']; $comment = $line['comment']; $results[] = new ReadTableLineElement($date, $comment); } return $results; } }
ふぅ〜〜 できたぁ〜〜
試験を実行すると…
Time: 00:00.182, Memory: 22.00 MB OK (14 tests, 28 assertions) Process finished with exit code 0
うみぃ〜〜〜〜!!!!
これでブラウザでアクセスしてみるw(今までしてないのかよ!)
おぉぉぉぉけぇぇぇぇぇ〜〜
これで世界平和に貢献できました!
はぁ〜、実は地味ネタと思っていたんですが、めっちゃヘビーでした。
次はどうしよう?DBかな?それとももう少し違うのをするかを考えます!
fin.
番外編 1. CSVにて困った系…
結論から言いましょう…
CSVじゃなくてHTMLを指定していましたよぉ〜どらえもぉ〜んw
class CSVReaderRepositoryTest extends TestCase { public function test_findAll(){ $repository = new CSVReaderRepository(storage_path('files/reads/csv.csv')); $results = $repository->findAll(); $this->assertEquals('2020/11/22 22:00',$results[0]->date); $this->assertEquals('リース',$results[0]->comment); $this->assertEquals('2020/11/22 23:00',$results[1]->date); $this->assertEquals('並ぶ',$results[1]->comment); $this->assertEquals('2020/11/23 00:00',$results[2]->date); $this->assertEquals('アトラクション',$results[2]->comment); } }
こんな試験と、loopにてprint_rを付けてやればデバグできました!