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イメージって言ってたかな?言い得て妙だけれども、個人的にはレゴのジョインの方がぴったりきました
こんなイメージです。要求 / 応答を一致させれば、使い回しも出来ます。
ロジックを見ていただいたからなら分かりますが、変数の受け口、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を付けてやればデバグできました!