奇特なブログ

「殊勝に値する行いや心掛け」を意味する、奇特な人になる為のブログです

小さな技術ネタ8つ

一つ一つが小さいので、まとめましたよ。


1.switchの分岐

もう、すっかりお約束かと思うんですが、

「switch($data)」と書いた時に、「==」で比較されますよっていうやつ。

で、以下の様な解決策が、提示されてますと。

ケースごとに異なる処理を、ポリモーフィズムで表現する(生PHPで) - Qiita

あるいは、上記の参考リンクに書かれている以下とか。

PHP :: switch の case 文で厳密な型比較をする [Tipsというかメモ]

・・・これなら別に、if~elseでもよくない?と思ってしまったんですよね(苦笑)

いや、別の記事にもリンク貼った事ありますが、

個人的に、以下の様なコードもあるので、stateパターンを使う事に否定的ってわけじゃないんですけど。

final_magic/view.php at master · kitoku-magic/final_magic · GitHub

ただ、分岐した「後」の、各クラス毎の処理の差異が殆どない様なケースだったら、

別に、そこまでしなくても良いんじゃないかなぁと。

「今はまだ少ないけど、これから差異が増える(この考えもYAGNI観点からは微妙ですが)」のが、

「確定」レベルならアリでしょうけど、そうじゃないのにって所じゃないですかね。

単に、「何がなんでもデザパタを適用する」的な、信者的な考えが苦手って話な気もしますけどね(苦笑)


2.HTMLのnameやDBのカラム名の、スネーク・キャメルの統一

結構、昔から時々感じていたんですけどね。

「user_id」と「userId」が混ざってますと。

で、混ざっているが故に、以下の様なコード(あくまで一例)をわざわざ書く必要があると。

$data = array(
  'user_id' => $_POST['userId'],
);
$entity->setUserId($_POST['user_id']);
$result = array(
  'userId' => $row['user_id'],
);
$entity->setUserId($row['user_id']);

とりあえず、最も多そうなケースとして、

「DBのカラムでuserIdというケースを見た事がない」のは、大文字・小文字を区別しない以下が原因かなと。

【MySQL】カラム名の大文字、小文字の区別 at softelメモ

といっても、別に「userId」というカラム名にしても良いじゃないかとも思うんですが、一旦置いておきましょう。

ただ、そうなると命名を統一するには「set_user_id()」となり、ここに違和感を感じる人が多いんだろうと。

いや、「setUserId」だろう?なんか、変じゃね?と。

といっても、set_user_idにしたら、何か問題が起きるのかという所なんでしょうと。

・・・何が理由があるんですかね?いや、命名が統一されているケースは、おそらく一度も見た事が無いので。
問題があるなら、統一されてなくても全然良いんですけど。

個人的にはスネーク派ですが、何も問題が無いなら「どちらかに統一」をって思うんですよね。

統一されていれば、キャネルでも良いよと。


3.DateTime::createFromFormat

これはマニアックな気がしますが、例えば以下の様(PHP7.2.10)に書くと。

<?php

// ケース1、問題無し
$datetime = new DateTime('24:59:59');
var_dump($datetime);

// ケース2、問題無し
$datetime = new DateTime();
$datetime->setTime(25, 0, 0);
var_dump($datetime);

// ケース3、問題無し
$datetime = DateTime::createFromFormat('H:i:s', '25:00:00');
var_dump($datetime);

// ケース4、指定されているフォーマットと違うので、$datetimeがfalseになる
$datetime = DateTime::createFromFormat('His', '25:00:00');
var_dump($datetime);

// ケース5、Fatal Error
$datetime = new DateTime('25:00:00');
var_dump($datetime);

$ php datetime_test.php

object(DateTime)#1 (3) {
  ["date"]=>
  string(26) "2018-11-25 00:59:59.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(10) "Asia/Tokyo"
}
object(DateTime)#2 (3) {
  ["date"]=>
  string(26) "2018-11-25 01:00:00.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(10) "Asia/Tokyo"
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(26) "2018-11-25 01:00:00.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(10) "Asia/Tokyo"
}
bool(false)

Fatal error: Uncaught Exception: DateTime::__construct(): Failed to parse time string (25:00:00) at position 0 (2): Unexpected character in 
・・・以下、省略

となるので、「DateTime::createFromFormat()」が良いんじゃないの?と。

特にケース4が良いと思いますね。

応用すると、以下の様な事も出来ますし。

【PHP】DateTimeクラスでcheckdate()より汎用性のある日付チェックを行う - Qiita

注意点としては、上記の場合「年月日が翌日になる」って所でしょうか。


4.DateTime同士の日時比較

↑の3と関連しているんですが。

PHP: DateTime::diff - Manual


注意:

PHP 5.2.2 以降では、DateTime オブジェクトを 比較演算子 で比較できるようになりました。

ちょっと意外だったので、驚きましたね。

実例は、上記リンク先に書いてあるので、省略します。


5.stringとintの比較時のキャスト

例えば「リクエストパラメータの値(string)と、DBから取得してきた値(最近は、intのケースが多いか?)」を比較する時に、

「intをstringにキャスト」するか、「stringをintにキャストするか」的な話なんですが、

<?php

$request_parameter = '1a';
$db_value = 1;

if ((int) $request_parameter === $db_value)
{
  echo "string to int\n";
}

if ($request_parameter === (string) $db_value)
{
  echo "int to string\n";
}

$ php cast_test.php
string to int

と通ってしまうので、「intの方をstringにキャストする」が良いかなと。

といっても、値が大きくてfloatの場合、

stringにキャストしたら以下の様になるので、そこは別途注意でしょうけどね。

<?php

$db_value_int = PHP_INT_MAX;
$db_value_float = PHP_INT_MAX + 1;
var_dump($db_value_int);
var_dump($db_value_float);
var_dump((string) $db_value_int);
var_dump((string) $db_value_float);
var_dump(sprintf('%u', $db_value_int));
var_dump(sprintf('%u', $db_value_float));

$ php cast_test.php
int(9223372036854775807)
float(9.2233720368548E+18)
string(19) "9223372036854775807"
string(19) "9.2233720368548E+18"
string(19) "9223372036854775807"
string(19) "9223372036854775808"


6.NOT NULLが付いていない

「数値型の0や、文字列型の空文字の値に、特別な意味があったり、既存との値の体系的に、どうしてもNULLが必要」

ってケースぐらいですかね、NULLを許容する必要があるのって。

それすら微妙でしょうか?

user_idが0、日付が0001-01-01(ここは、さほど調べてませんが)、名前が空文字、statusが0・・・

statusが0のケースが時々って所じゃないですかね。

新規で作るならstatusが0の場合にも意味を持たせない方が良いと思いますけど。

書き切れないぐらい色々なケースがあるでしょうけど、やっぱりNOT NULLを付ける形が良いんじゃないかなと。

いや、単純に以下の様な事をいちいち意識したくないってのが、本音ですけどね(笑)

NULL撲滅委員会


7.O/Rマッピングについて

先日、以下の記事が盛り上がってましたので。

O/Rマッピングは百害あって一利なし! - Qiita

これ、以下の記事では主題じゃなかったので書かなかったですが。

続・MVCのMのクラス設計草案など - 奇特なブログ

以下の様に、「SQLは書くけど、全部じゃないよ」的なO/Rマッパーじゃダメなんですかね。

other/order_repository_impl.php at master · kitoku-magic/other · GitHub

「和洋折衷」というか、良いとこ取りみたい感じですけど(笑)

あと、直でSQLを書いているというルール違反については、

「SELECT 」「INSERT INTO 」「UPDATE 」「DELETE FROM 」とかでGREPして、

以下のクラス「以外」がヒットしたら「おや?」とかね。

other/rdbms_storage_handler.php at master · kitoku-magic/other · GitHub

なので、既存のFWでも、あまりガッツリ使わなければ良いんじゃないかと思いますね。

要は、「変わったSQLを書くときに、どうか?」ってのがポイントなんじゃないのかと。

以下の様なSQLを、業務で書く必要があったらとか。

other/reservation_double_booking_check.php at master · kitoku-magic/other · GitHub


8.SQLで取ってきたデータに、使いたいカラムが含まれていない

上記の7とも少し絡むんですが、

SQLで項目が指定可能だと良い」と。

いや、別にどうって事ない話なんですが。

というのも、項目が固定だと「不要だと思ったら、後で必要になった」的なケースが何度も起きて、

その度にソース修正するの?と。

だから、常に全項目取得(パフォーマンスはありますが、ここはさほど)で良いんでは?とも思いますが、

流石に不要項目(上記の通り、不要だと思ったら、後で必要になったりもしますがね)があまりにも多過ぎる場合にはとも思いまして。

なので、「項目を指定しなければ全項目取得だが、項目を指定する事も出来る」が良いかなと。

あと、別にコレSQLに限らず、外部のAPIを呼び出して、

必要なデータを取得する時に、項目が指定可能とかも。

外部APIの場合は、項目を細かく指定するのは難しいかもしれないので、

「全項目フラグ=true or false」みたくなるかもしれないですけどね。


以上です。

ここまで書き終えて、以下の話じゃないけど、

基本的に「大したことじゃない事」に、頭を使いたくないみたいですね(苦笑)

どこにリソースを突っ込むのか? って選択は大事だよねぇ - gallu’s blog

あと、本日のBGM(曲の途中から)は・・・以下かな(笑)

Within Temptation - Whole World is Watching ft. Dave Pirner - YouTube

Radiohead - 2+2=5 - YouTube

2018年11月17日のダイジェスト

1.最初から全て決まっている様に見える

たま~に思う程度ではあるんですけどね。

そしたら、以下の記事を目にしまして。

『〈わたし〉は脳に操られているのか』著エリエザー・スタンバーグ レビュー①(※コメント返し中心) | 超個人的美学2~このブログは「超個人的美学と題するブログ」ではありません

『〈わたし〉は脳に操られているのか』レビュー②~人間は運命の奴隷であるのか?~ | 超個人的美学2~このブログは「超個人的美学と題するブログ」ではありません

『〈わたし〉は脳に操られているのか』レビュー③~思考とは脳内物質の化学反応であるのか?~ | 超個人的美学2~このブログは「超個人的美学と題するブログ」ではありません

あぁ・・・昔から「落ち着きがない」所がありましたけど、

それが「サーフィン」になり、

一方、妙に「ハマり症でオタクっぽい所」もありましたけど、

それが「本職」になったと。

だから、これはもう性格(と後天的な環境とか)的な部分で、変えようがないので、

グレードアップするしかないと思いましたね(笑)

「性格変える」って、昔以下の本読みましたけど、どうかなと思いますし。

性格は捨てられる
Amazon CAPTCHA


2. 川上量生「中国のネット管理政策は正しい」 | 最新の週刊東洋経済 | 東洋経済オンライン | 経済ニュースの新基準

この方、ブロッキングとか他を見てもそうですが、

個人の力を軽く見ている所があるんですかね。

といっても、以下の米津玄師は、ご自身の立ち上げたサービス出身の様ですので、

地上の星を、見誤ったんじゃないですかね。

「米津玄師」の曲がロングヒットし続ける理由 | スージー鈴木の「月間エンタメ大賞」 | 東洋経済オンライン | 経済ニュースの新基準

あぁ、確かに「キモい奇声」や「がなり声」や「ヤギ声」じゃなく、
聴きやすい声ではありますね。
あと、あぁ「温故知新」ですか。

コォ~~ルミ~~~~~ダァ~イナソ~~~!が、別の所で起きた様で(笑)


3. 憲法違反「ブロッキング議論」を誘発した、クールジャパン機構を巡る官民の癒着構造(山本一郎) - 個人 - Yahoo!ニュース

なんだか、関連が凄まじいですね。

FEには、さすがにビックリしましたけど。

加賀さんは、作り手「としては」優秀だったと思いましたけどね。


4. 港区南青山の児童相談所開設問題の総括と燻る反対論への是非(追記あり)(山本一郎) - 個人 - Yahoo!ニュース

ここで反論されている方は、他の上流の方だったり、

超上流の方と接してみると良いんじゃないかと思うんですけどね。

節約も大事ですが、考え方が「リッチ」じゃないなと。


5. 高知県立大学で蔵書3万8000冊焼却 貴重な郷土本、絶版本多数|高知新聞

禁書的という声もありましたが、仮に「需要がない」なら、それは禁書ではないのではと。

これに限らず、「○○は禁止」って、ものによっては当然必要なわけですが、

昨今は、効果が出づらい様に思いますね。


6. ググっちゃダメなキーワード

リンクは貼りませんが、Laravel関連で・・・うお~~~~~~~!

「本番環境」には、さすがにビックリしましたね。


7. ソフバンCMの兄役ダンテさん、妻にバラエティ番組でフィギュアを処分され離婚 : ガハろぐNewsヽ(・ω・)/ズコー

あえて擁護するなら、

「仕事をせず借金だらけで、趣味の物で部屋が一杯一杯になっていて歩けない」とかなら、

さすがに仕方ないかもと思いますけどね。


8. 【後払いガチャって何だ?】結果を見てから課金するか決められる『ぼくころ3』をやってみた [ファミ通App]


おお、これは企画の勝利感が。
よく考えましたね。


9. NHK「キズナアイ」騒動、バーチャルユーチューバーや萌え絵柄は番組のパーソナリティとして適切か(山本一郎) - 個人 - Yahoo!ニュース

これは思わないですが、

ただ、以下の胸を強調している様なのとかは、どうかなとは思うんですけどね。

実際、ライトノベルの表紙はキモい - Togetter

なんですが、こういう類のものは「スルー」してしまうものでして。

なんか、スルーすれば良いものを、スルーしないのを見ると、

「つながりすぎたのかなぁ」と思ってしまいますね。


10. 可愛いも良いけどさぁ・・・

元DMMのFANZAが性に関する統計調査を発表 世界的に「熟女」への注目高まる - ライブドアニュース

そうそう(笑)

最近は、キャバクラに行くときも、「熟女キャバクラ」を選択する傾向にありますし(笑)

あと、熟女「ならでは」の「劣化の美学」というのもありますし(笑)

あと、以下とか(笑)

椎名林檎 - 至上の人生 - YouTube

ちょっと、やり過ぎだ林檎嬢!(笑)

ところで、上記のキズナアイを叩いた方は、むしろ「リアル人間」のこっちを叩いた方が。


11. 【若者に朗報】東京オリンピックの奴隷ボランティアに50代以上の人が殺到しボランティア数の目標達成:ハムスター速報

敗北感?いや・・・

むしろ、いつか来る「インデペンデンスデイジャッジメントデイ?)」の到来を、

早める為に、ご協力していただけるとは、大変ありがたいじゃないですか!


12. 世界とインターネットの「あちら側」と「こちら側」 - いつか電池がきれるまで

メジャーなのかマイナーなのかみたいな話で、

単に、ターゲットは違うけどやってる事は同じだと思うので、

特に、あちら側・こちら側って分断はしないですけどね。


13. ハイキング中に生理になってしまった女性 → 友人男性の『イケメンすぎる対応』でピンチを脱出! 男性の行動にネット上でも喝采集まる「こんなの絶対に惚れるわ!」 | ロケットニュース24

あぁ、そうか・・・これは「女性しか知らないはずの知識を、男性が知っている」のが、
「優しい」なのか「キモい」なのかだと。

「たくましい」と「暴力的」は「紙一重」だと思ってますが、
「優しい」と「キモい」も、そうかもしれないですね。


14. 日本人が「他人のズル」を激しく妬む理由(プレジデントオンライン) - Yahoo!ニュース

う~ん、「違い」が例えば、

以下の「笛(ホイッスル?)の音が好き」みたくマニアック過ぎるとか(笑)

AURI - The Space Between (OFFICIAL LYRIC VIDEO) - YouTube

あ、僕の性癖もそうか(苦笑)

あるいは、「ピカソの絵って良いよね?(僕も良さが分からん)」みたく、「芸術的」なものとかは、

無理(少なくとも、相当キツイ)だと思ってますし、そういうものじゃないものを問題にした方が。

あと、好きなのは全然違うけど、嫌い(望まない)のは共通している場合も多いと思いますけどね。


15. 契約を知らずに高稼働している人が多い件。 | トラッシュブリーフィング合同会社's Blog

おお、これは面白いですね。


このやり方だと後半でヤバいことにならね?と言う問題に気づいたときは、主要取引先だろうが気にせずどんどん指摘してあげよう。それで契約切られようが、こちらは義務を果たした訳だし、契約きったのは客側の判断だ。

いや、そうすると、切られた事はないですが、

スキルシートとしては、「期間が短くなりがち」で、大変に微妙になりましてね。


スキルシートの偽造の話を求職者からちょくちょく聞くが

上述の通り微妙なので、むしろしたいわ~~出来るなら。


この業界でよくある、採用の為のキラキラした求人にあこがれて勘違いして入ってくる人や、Web上で『SIerの技術者のレベル低くて高稼働、Webサービスはその逆!』的なこと言っちゃってる人がいて、そいつらの声が結構でかい感じの中、そんな人らの一部がrubyソースのリファクタリングもできなかったりテストコードも書けなかったりで、実は一部Webサービス運営の採用側から忌諱されてて、採用基準がSI出身者優遇になってたりとか、そんな話もあって。

過去の事を思い出してましたけど、人によるとしか。

偶然かもしれないですが、Web系に来てからは「NOT NULL」が「減った」という印象はありますけどね。


Webに流れる広告のカネは大きくなってきており、なにかを検索した際、ブログ然とした広告がずらっと並ぶ事も多くなってきました。個人的にも認知バイアス的な勘違いをさせられてしまう事が最近増えてる印象です。(禁煙のときにえらい目にあった。)TVだけの時代と同じようにですね。ちょっと今までとはワンランク上のリテラシーを持つ必要性を感じます。

そうですね。この辺があるので、検閲的な事はあまり効果がないと思ってしまう方ですね。

いや、良い事ばかりだとは思ってないですよ、選択肢が「多い」わけですからね。


16. PMの必要条件に「技術力」は含まれるのか? - gallu’s blog


「炎上しなくても書いてる」

ここが、ちょっとなぁ。

「PMは、コードを書かずに、それ以外の事を」と考える方ですけどね。

というのも、それ以外の事って、そんなに簡単な事ですかね。

いや、単に人材不足でそうなっているなら、仕方ないんですけどね。


17. 解答:間違ったCSRF対策~初級編~ | 徳丸浩の日記

問題を見た時に、

・ログイン後に、セッションID変えてないな
・ENT_QUOTESとENT_COMPATが混ざっているから、シングルクオートをエスケープしていない系の脆弱性
・もしかして、ワンタイムトークンの作り方が変?
・「type=submit」、怪しいよねぇ
・「$_POST['token'] !== $_SESSION['token']」、NULLとNULLの場合にすり抜けないか?

とは思ったんですけどね。


これを動かしてみましょう。あなたは現在被害者の立場です。mypage.phpを閲覧して対象スクリプトにログインします。
この状態で、あなた(被害者)は、CSRFの罠をうっかり閲覧してしまいました。

は?いや、むしろ「スクリプトをどうやって設置して踏ませるんだ?」という方を考えていた(だからtype=submitが怪しいと思った)んですが、特に必要ないと。
PoCの練習をした方が良いかもしれませんね。


18. 解答:間違ったCSRF対策~中級編~ | 徳丸浩の日記

こっちは、以下かなと思いましたが、どうやら違う様で。

PHPで==の代わりにstrcmp関数を使うことによる問題点 - hnwの日記

strcmpは、↑が原因か覚えてませんが、いつからか使わなくなってましたけどね。


19. 聴けば世界が広がる、歴代アルバム売上トップ32 - 脱R論

「おわりに」の所に着目したんですが。


音楽は誰でも気軽に楽しめるコンテンツではあるんだけど、
それ故に物語性をメインに据えるにはやや不向きなジャンルなのかな。
だから音楽は何かのサブコンテンツとして消費される位置付けになった。

むしろ、サブなので、「マルチタスクが容易」という強みが(笑)

現状の僕のライフスタイルには、この方があってますね。

物語が嫌いとは「間違っても」言えないですが(笑)時間がかかりますのでねぇ。


それに聴覚からの刺激は他の刺激に比べて
最も脳にダイレクトに作用するらしい。
だから気軽に様々な効果が期待できるのが音楽というわけだ。

この仕事してから特にそうですが、「耳」を使わないなぁと。

でも、せっかくあるので、有効活用しようかと(笑)


20.音楽

前回から、そんな傾向がありましたが、

しばらくは、色々なアーティストの曲を聴いてみようかと。


(1)エアロスミス

ナイン・ライヴス
https://www.amazon.co.jp/dp/B005OCSU2Y

こちらは、以前にベストは聴いていたわけですが、

特に「Hole In My Soul」がヤバい!

Aerosmith - Hole In My Soul - YouTube

何という、綺麗なメロディ(Bメロ以降)のバラード・・・

他も、「Full Circle」等々、思ったよりバラエティに富んでいて、よろしかったですね。

洋楽慣れしていて、タイラーの声に抵抗が無ければ良いんじゃないかと。

では、次行ってみよう(笑)


(2)ZOOLANDER

‎ZOOLANDERの「I JUST WANNA BE LOVED - Single」をApple Musicで

たまたま見かけて、ちょっと聴いてみたわけですが、良いぞ!

これは「テクノ」ですか。

テクノは、もっと長くても良いと思うので、強いて言えばその辺がって所ですね。

では、次行ってみよう(笑)


(3)宇多田ヒカル

B'zが勝てなかった(シングルもアルバムも)このお方に触れないわけには(笑)

初恋
https://www.amazon.co.jp/dp/B07CKQR6T3

似たタイプ(女性シンガーソングライターつながり)のKOKIAとの比較だと、

「バラードはKOKIAの方が良いが、売れ線はヒッキーの方が良い」という感じですね。

ただ、タイトル曲の「初恋」は、バックのストリングス込みで良い味を出していて良い!

あと、「嫉妬されるべき人生」ですかね。

本人のインタビューからは、幸せ的な曲に感じますが、曲調的にそうは思えませんでしたけど(笑)

宇多田ヒカル 『初恋』 〈諸行無常〉という言葉があるけど、それを理解して受け入れるのはそんなに簡単なことじゃない | Mikiki

これは、もうヒッキーは嫉妬され続けるだろうと自分で思って書いたんですかね。

キャリアとか見ると、ヒッキー(や息子)に生まれたいとは思わなかったんですけど。

しかし、この人はやっぱり独特過ぎるので、今後の展開も楽しみですね。

では、次行ってみよう(笑)


(4)Mr.Children

また、B'zが勝てなかった(シングルとオリジナルアルバム)このお方を。

深海
https://www.amazon.co.jp/dp/B00005H020

いや、これがフロイドの狂気に似ているとかで、じゃあと思いまして。

雰囲気と、曲が繋がっている点が似てるぐらいでしたけどね。

ただ、こっちは当たり前ですが、「死ぬほど聴きやすい」ので、楽でしたけどね。

ところで、「名もなき詩」は、歌詞「も」良かったみたいで。

http://j-lyric.net/artist/a001c7a/l006fd9.html


誰かを想いやりゃあだになり
自分の胸につきささる

だけど
あるがままの心で生きようと願うから
人はまた傷ついてゆく

傷付くのは、あるがままに生きている故じゃないですかね。


愛情ってゆう形のないもの
伝えるのはいつも困難だね

この曲(に限りませんが)もそうですが、全くですね。

どうやら、ミスチルは、以下を読むと、

他にも、こういうタイプの曲があるみたいですね。

Mr.Childrenの優しくない歌 - NAVER まとめ

なので、もう少し・・・・次行ってみよう(笑)


(5)Radiohead

実は全然知らなかったんですが、フロイドを聴いていた時に時々名前を見かけたのと、

以下の記事を読んで、じゃあと(笑)

椎名林檎はなぜ「加爾基 精液 栗ノ花」を作ったか(椎名林檎とレディオヘッド論) - 本とのこと

で、以下を聴いてみたわけですが。

KID A
Amazon CAPTCHA

カルキ~とジャンルも違うし別に似てないけど、「商業的自殺」つながりではあるのかなと(笑)

・・・これは、フロイドを越えて「聴きにくいランキングトップ」ですね(苦笑)

ですが、これまでの経験値がものをいったか、比較的早く馴染みましたが。

特にだと、「Everything In Its Right Place」と「The National Anthem」が良かったですね。

あと、詞を書いているトム・ヨークは「絶望請負人」ですね(苦笑)

「俺が、あらゆる絶望を表現してやる!」とでも言わんばかりの。

でも、それがどうやら聴き手側には「良い効果(とは限らないですが、勿論)」を与える傾向もあるようで、

だから、人気なのか(苦笑)

ところで、以下の記事がありましたが。

【歌詞解説】Everything in Its Right Place / Radiohead - 我々は正しいものを許せるのか? - 深読み、Radiohead通信|歌詞和訳と曲の解釈

「2 + 2 = 5」じゃなくて、「4」じゃないんですかね。

あと、「2 + 2 = 4」は「正しい」ですが、「正義」ではないと思いますね。

「2 + 2 = 4です!正義です!」って、「声高」に言う事ではないと思いますし。

むしろ、「合っている(間違っていない)」って状態を軽んじているのかなと。以下とか見ると。

最近の小学生は足し算を『さくらんぼ計算』という解法で解くことを強制され、テストで省略すると減点されているという話 - Togetter

白黒を付けるのが難しい話も確かにありますが、

だからこそ、まずは「正しい(合っている)」って状態を、適切に評価するのが大事だと思いますね。

そうじゃないと、「2 + 2 = 3?4?5?」、

どれが正しいのが分からない!と迷走しがちになる習慣が付いてしまうのではと。

う~ん、これはアルバム全部聴いてみたいレベルでしたが、まだまだ他にもありますので、

次行ってみよう・・・もう無いけど(笑)

続・MVCのMのクラス設計草案など

前回記事:
MVCのMのクラス設計草案など - 奇特なブログ

前回の草案ソースとの差分(ちょっと多過ぎますが):
モデルクラスの設計草案を作成(一旦完成) · kitoku-magic/other@f89baf7 · GitHub

もう、そんなに書く事も浮かばないんですけどね。

とりあえず、「何でもかんでも自作でやろうとしないで、利用出来るものは利用した方が良い」と思いますね。

既存のフレームワークやらライブラリやらを使って、車輪の再発明するなと。

というのも、業務で書くにあたって↑のコードを書くのは、

業務ロジックはゼロですし、完全に無駄な時間なわけで。

勿論、既存のフレームワークやライブラリで対応出来なかったり、

業務要件的に必要だったり、その他の事情でフレームワークを導入出来ないとかなら、

書く必要はあるわけですけどね。

っていう事と、現存のPHPフレームワークで、デフォルトでDDDの構造になっているものが・・・ありますかね?

って所を考えると、現存のフレームワークのクラス構造じゃダメなのか?という。

実際、DDDの案件の時は、Cakeでしたけどフレームワークの機能は殆ど使わず(モデル以外は使える)自作でしたからね。

なので、その部分はロスなわけですが、そのロスを取り返せるのかどうか(勿論、取り返せるなら良い)と。

・・・以下の、「覆水盆に返らず」みたいですね。勿論、グダグダ設計のグローバルは、言うまでもないとして。

覆水は盆に返せるのか? 余談 - gallu’s blog



あと、これは↑とは別の現場ですが、そこでのDDDでは、

「Modelというクラス名なのに、フィールドに対するgetterとsetter(こっちは無かったかも)しか無かった」

という、なんじゃこりゃ?感(これはエンティティでは?)なケースもあり、

「用語が指している意味がバラバラ」かもとは思いましたね。

ただ、その現場は、資料で詳しく解説があったので、

「あぁ、そういう意味で使ってるのね」とは思ったので、特に問題無いかとは思いましたけど。

これは、DDDの原理原則からは外れているのかもしれないですが、

原理原則の形で「しか」使えないなら、柔軟性には欠けるかなぁと。

だから、前回のリンクにもありましたが、以下のやり方もアリだと思いますし。

PHP Mentors -> ドメイン駆動設計のリポジトリパターンをプロジェクトへ持ち込む時の話

別件だと、MVCで「コントローラークラスは1つ or 複数?」論争とか、

以下の様な、「どっちに書く?」系とか、最近は気にしなくなったかなと。

Model、どうすっかねぇ? 的な - gallu’s blog



あとは、DDDの時に思ったのが、「1クラスの担当責務」にうるさいので、

Utilクラスが無かったり、Constantが超細分化(これ自体は良いと思うが、ちゃんと構成を考える必要がある)したりと。

それに拘ったからですかね、色々なクラスに「private const NEW_LINE = "\r\n"」みたいなコードがあったのは。

いや、PHP_EOLじゃダメなんですか?って感じなんですけど。

Utilとか、色々なクラスで使うのに、何故Utilに書かない?ってのは、

「縦割り」というか、横を意識しなかったのかなと。

更に・・・まぁ、良いわ。せめて今回僕が↑に書いたコードぐらいには、

通化はして欲しい(fetch()とか、やろうと思えば、まだ出来るけど)とは思いましたね。



まとめる(というか、前回のに追加する)と、プロジェクト的(要件・メンバー構成・期間・予算)に、

どの方法が最適解か、よ~く検討するって所でしょうか。

DDDは、本を見ても「対象範囲がかなり広い」ので、

気を付けて導入しないとハイリスク・ローリターンになると思いますのでね。


あと、本日のBGMは、B'zの「弱い男」 http://j-lyric.net/artist/a00067d/l043c9d.html でした。

いや、こういう微妙な話は、自分一人では決めれない弱い男なので(笑)

traitに期待し過ぎ?(笑)

ピンと来て、出来てからにしようかと思ったんですが、まぁ良いかなと(笑)

「ログの出力をしたいけど、クラス毎にログの出力先を変えたい」という仕様があったとして、

↓のコード(PHP5.2というか、traitが無い状況の場合)を書いたんですが。

<?php

class log
{
  private $log_save_path;

  public function __construct($called_class_instance)
  {
    $this->set_log_save_path(get_class($called_class_instance));
  }

  public function set_log_save_path($called_class_name)
  {
    $this->log_save_path = '/tmp/' . $called_class_name;
  }

  public function write($log_message)
  {
    file_put_contents($this->log_save_path, $log_message . PHP_EOL, FILE_APPEND);
  }
}
<?php

require_once('log.php');
require_once('test_service.php');

class test_controller
{
  private $log;

  public function __construct()
  {
    $this->log = new log($this);
  }

  public function exec()
  {
    $this->log->write('controller1');

    $service = new test_service();
    $service->exec();

    $this->log->write('controller2');
  }
}

$test_controller = new test_controller();
$test_controller->exec();
<?php

require_once('log.php');

class test_service
{
  private $log;

  public function __construct()
  {
    $this->log = new log($this);
  }

  public function exec()
  {
    $this->log->write('service1');
  }
}

とする事で、test_controllerとtest_serviceで、保存先が別になると。
で、これをPHP7.2(trait有りバージョン)で書くと、

<?php

trait log
{
  // privateでも動くのは、ちょっとビックリ
  private function write($log_message)
  {
    // get_called_class()は、PHP5.3以上で使用可能なので、traitが使えれば絶対使える
    file_put_contents('/tmp/' . get_called_class(), $log_message . PHP_EOL, FILE_APPEND);
  }
}
<?php

require_once('log.php');
require_once('test_service.php');

class test_controller
{
  use log;

  public function exec()
  {
    $this->write('controller1');

    $service = new test_service();
    $service->exec();

    $this->write('controller2');
  }
}

$test_controller = new test_controller();
$test_controller->exec();
<?php

require_once('log.php');

class test_service
{
  use log;

  public function exec()
  {
    $this->write('service1');
  }
}

ということで、簡素化したかなとは思うものの、

それほどでも?とも思い、タイトルの事を思ったんですけどね(笑)

なんですが、以下を見てみた所。

PHPのトレイトを使うならおさえておきたい5つのこと

あぁ、「既に継承しているクラスで、継承元の親クラスに追加しない(それ以外のクラスでも使う処理なら、場所が不適切)で、更に機能を増やしたい時」に良さそうですね。

当初、ログやDBハンドルとか、色々な所で使うクラスをtraitにすれば良いのでは?と思っていたんですが、

さすがに今回は、処理が単純過ぎたから、本領発揮とはいかなかった様ですね(笑)

良い事例が見つかったら、改めてということで。

あと、本日のBGMは、中島みゆきの二艘の船 http://j-lyric.net/artist/a000701/l00f4b9.html でした(笑)

MVCのMのクラス設計草案など

いや~、一時期どこでも「DDD」の話題が出たり、

また、実際に現場で導入していたりということで、

ちょっと、考えてみますかねと思いまして。

DDDといっても、範囲が広すぎるので、今回はタイトル付近の話ですが。


まずは、参考元だったり、引用元だったりを。

それぞれの記事の「日時」が注目でしょうか。

やってる所は、昔からやってるでしょうからね。


DAOの作成単位について(2006-04-13
https://qa.atmarkit.co.jp/q/564
ドメインオブジェクトとテーブルのマッピング(2009-11-27)
http://masuda220.jugem.jp/?eid=318
[ドメイン駆動設計] ウェブアプリケーションの構造について(2010-09-27)
http://d.hatena.ne.jp/j5ik2o/20100927/1285609764
ドメイン駆動設計のリポジトリパターンをプロジェクトへ持ち込む時の話(2014-6-17)
http://phpmentors.jp/post/88996330583/ddd-repository
DDDにおいてリポジトリとDBのトランザクションは切り離せないのか?(2015-09-14)
http://pospome.hatenablog.com/entry/20150914/1442210241
DDDと高負荷サービス(というか I/Oアクセス)は相性が悪いのか?(2016-06-28)
http://pospome.hatenablog.com/entry/20160628/1467121942
やはりお前たちのRepositoryは間違っている(2018-05-21)
https://qiita.com/mikesorae/items/ff8192fb9cf106262dbf
オブジェクト指向と10年戦ってわかったこと(2018-08-05)
https://qiita.com/tutinoco/items/6952b01e5fc38914ec4e


あとは、↓にソース(の一部)が出てきますが、

そのソースの全容は、以下に入っています。

「草案」だから、まだ途中ですけどね。


other/model_test at master · kitoku-magic/other · GitHub


さて、大きく分けて、3本立てでお送りしたいと思います。

1.色々なIO処理をまとめる

いや、これはモデルを物凄く抽象化して考えた時に、
「ある所から、データを取得する」「ある所に、データを保存する」となり、
その、ある所に、以下の様なストレージが入るんじゃないの?と。


主なストレージ:
RDBMS
KVS
ファイル(HDD)
メモリ
S3


例えば、経験は無いですが、プロジェクト初期において、

「いや~、データを何処から取って来るかは、RDBMSとは限らなくて、まだ決まってないんだよね」

的な話が出ても、別におかしくはないわけで。

なので、以下の様なクラス設計も、あり得るのではないかと。

interface storage_handler
{
	// 該当のストレージからデータを取得する
	public function get();

	// 該当のストレージにデータを保存する
	public function set();
}
// 汎用RDBMS
abstract class rdbms_storage_handler
{
	// トランザクションを開始する
	abstract public function begin();

	// トランザクションをコミットする
	abstract public function commit();
}
// MySQL版
class mysql_storage_handler extends rdbms_storage_handler implements storage_handler
{
	public function get()
	{

	}

	public function set()
	{

	}

	public function begin()
	{

	}

	public function commit()
	{

	}
}
// PostgreSQL版
class postgresql_storage_handler extends rdbms_storage_handler implements storage_handler
{
	public function get()
	{

	}

	public function set()
	{

	}

	public function begin()
	{

	}

	public function commit()
	{

	}
}
// 汎用KVS
abstract class kvs_storage_handler
{
	// 独自のメソッドが必要なら書く
}
// Memcache版
class memcache_storage_handler extends kvs_storage_handler implements storage_handler
{
	public function get()
	{

	}

	public function set()
	{

	}
}
// Redis版
class redis_storage_handler extends kvs_storage_handler implements storage_handler
{
	public function get()
	{

	}

	public function set()
	{

	}
}

ファイル(HDD)・メモリ・S3は省略。

と、こんな感じのクラス構成で。

で、処理の流れとしては、以下の様な感じで。

コントローラー(各処理の開始の起点。サービスを呼び、最後は画面を表示する)

サービス(業務ロジックを書く)

モデル(各ストレージに対する処理を書く)

controller内で

{
	$service = new {サービス名}(new mysql_storage_handler());
	$service->exec();
}

service内で

{
	private $storage_handler;

	public function __construct(storage_handler $storage_handler)
	{
		$this->storage_handler = $storage_handler;
	}

	public function exec()
	{
		// ここで、上記のstorage_handlerを使ってモデルに対して操作を行う
	}
}

みたく書くと、サービスのテストケースを書く時に、
コンストラクタの引数を変えれば、ストレージが変えれますよと。
要は、以下の記事の話とか。

やはりお前たちのRepositoryは間違っている


例えばDBコネクションやストレージのパス等はReposiotoryのインターフェースからは隠蔽され、Repositoryのユーザは永続化ストレージが何であるか(例えばMySQLやRedis等)を意識することなく保存や検索の操作を行うことができるようになります。

これによりRepositoryを利用するロジックは業務的な操作に集中できるようになる他、データベースの移行等の永続化層の変更が発生した際にロジックへの影響を切り離すことができるようになります。

ええ、ただ「実際に変われば」ですけどね。

なので、確率的な話と、

あと、変わった時期が、極端ですが「プロジェクト初期」なのか「納期間近」なのか。

いや、上記の通り、プロジェクト初期なら全然アリでしょうけど。


でも、これをやる動機が、

「ソースの作り的に変更に強いから、仕様は決めなくて良い」的な話だったら、

やめた方が良いですね。


変わったのが、これ「だけ」だったら、そこまでコストかからないかもしれないですけど、

こういうのは、雪崩の様に、押し寄せたりしますし。


あと、昔から思っていたんですが、

「仕様が決まらない状態」って、「無駄コード」が多くなるんですよね。

まさに、↑のソースがそうなるかも的なんですけど。


なので、別に上のコードを書いちゃダメとは思わないですけど、

それはそれとして、「仕様は決めましょう」&「納期直前にMySQL→Redis(全然違うケース)に」とか言われたら、

その分のお金と時間も頂きましょうって所じゃないでしょうかね。

これは、↓の話でも同じですけどね。

2.アクセスするリポジトリの単位

ある単位(ここがドメインか?)にまとめてリポジトリを作るか、テーブル単位で作るか?ってのが、
以下でも書かれていました。

DAOの作成単位について - QA@IT

じゃあ、どんな感じなのか、実際書いてみると、

・ある単位毎

interface order_repository
{
	// 商品を注文する
	public function order();

	// 商品のステータスを変更する(発送済みにしたり、キャンセルしたり)
	public function change_order_status($order_id);

	// 注文IDから注文した商品を取得する
	public function get_item_by_order_id($order_id);

	// 注文した全ての商品を取得する(自分の注文に限定した方が良いでしょうが)
	public function get_all_item();
}
// これもインタフェース実装しても良いけど。あとストレージはDBに固定している
class base_repository
{
	private $db_storage_handler;

	public function __construct(storage_handler $db_storage_handler)
	{
		$this->db_storage_handler = $db_storage_handler;
	}

	// 継承先のクラスからしかアクセスさせない
	protected function select()
	{
		// SELECT文を実行する
		// get()の中に、SQLを実行する処理を書く
		$this->db_storage_handler->get();
	}

	// 継承先のクラスからしかアクセスさせない
	protected function insert()
	{
		// INSERT文を実行する
	}

	// 継承先のクラスからしかアクセスさせない
	protected function update()
	{
		// UPDATE文を実行する
	}

	// 継承先のクラスからしかアクセスさせない
	protected function delete()
	{
		// DELETE文を実行する
	}
}
// order関連のデータ操作を行うリポジトリ
class order_repository_impl extends base_repository implements order_repository
{
	public function order()
	{
		// orderテーブルとorder_itemテーブルにレコードを追加する
		$this->insert();
	}

	public function change_order_status($order_id)
	{
		// orderテーブルのstatusを変更する
		$this->update();
	}

	public function get_item_by_order_id($order_id)
	{
		// order_itemテーブルからorder_idに該当するレコードを取得する
		$this->select();
	}

	public function get_all_item()
	{
		// order_itemテーブルから全てのレコードを取得する(ユーザーIDを指定する方が良いでしょうが)
		$this->select();
	}
}

・テーブル単位毎

interface order_repository
{
	// 商品を注文する
	public function order();

	// 商品のステータスを変更する(発送済みにしたり、キャンセルしたり)
	public function change_order_status($order_id);
}
interface order_item_repository
{
	// 商品を注文する
	public function order_item();

	// 注文IDから注文した商品を取得する
	public function get_item_by_order_id($order_id);

	// 注文した全ての商品を取得する(自分の注文に限定した方が良いでしょうが)
	public function get_all_item();
}

base_repositoryは、おそらく上記と同じなので省略。

// orderテーブルのデータ操作を行うリポジトリ
class order_repository_impl extends base_repository implements order_repository
{
	public function order()
	{
		// orderテーブルにレコードを追加する
		$this->insert();
	}

	public function change_order_status($order_id)
	{
		// orderテーブルのstatusを変更する
		$this->update();
	}
}
// order_itemテーブルのデータ操作を行うリポジトリ
class order_item_repository_impl extends base_repository implements order_item_repository
{
	public function order_item()
	{
		// order_itemテーブルにレコードを追加する
		$this->insert();
	}

	public function get_item_by_order_id($order_id)
	{
		// order_itemテーブルからorder_idに該当するレコードを取得する
		$this->select();
	}

	public function get_all_item()
	{
		// order_itemテーブルから全てのレコードを取得する(ユーザーIDを指定する方が良いでしょうが)
		$this->select();
	}
}

ここは、以下の意見が、一番しっくりきたんですけどね。

https://qa.atmarkit.co.jp/q/564/a/3995/revisions

だから、テーブル単位の方がしっくりきます。

勿論、デメリット(↑のリンク先でも書かれていますが)もあるんですけど、

特に、気にならないレベルかなと思いましたし。

で、呼び出し側からは、これはINSERTの場合ですが、

controller内で

{
	// DBに限定してますが
	$db_storage_handler = new mysql_storage_handler();
	$service = new {サービス名}(
		new order_repository_impl($db_storage_handler),
		new order_item_repository_impl($db_storage_handler)
	);
	$service->exec();
}

service内で

{
	private $order_repository;

	private $order_item_repository;

	public function __construct(order_repository $order_repository, order_item_repository $order_item_repository)
	{
		$this->order_repository = $order_repository;
		$this->order_item_repository = $order_item_repository;
	}

	public function exec()
	{
		$this->order_repository->begin();

		try
		{
			$this->order_repository->order();
			$this->order_item_repository->order_item();

			$this->order_repository->commit();
		}
		catch (Exception $e)
		{
			$this->order_repository->rollback();
		}
	}
}

となるかなと。

いやいや、それだと「$this->order_item_repository->order_item();」を忘れたら(order()と必ず一緒に行う前提)、

バグるじゃないかと。

確かにそうなので、じゃあ以下かなと。

// orderテーブルのデータ操作を行うリポジトリ
class order_repository_impl extends base_repository implements order_repository
{
	private $order_item_repository;

	public function __construct(order_item_repository $order_item_repository)
	{
		$this->order_item_repository = $order_item_repository;
	}

	public function order()
	{
		// orderテーブルにレコードを追加する
		$this->insert();
		// order_itemテーブルにレコードを追加する
		$this->order_item_repository->insert();
	}

	// 以下、省略
}

そして、controller内で

{
    $db_storage_handler = new mysql_storage_handler();
    $service = new test_service(
      // ちょっと歪かなとは思うけど
      new order_repository_impl($db_storage_handler, new order_item_repository_impl($db_storage_handler))
      // ↓が元コード
      // new order_item_repository_impl($db_storage_handler)
    );
    $service->exec();
}

service内で

{
	private $order_repository;

	// private $order_item_repository;

	// public function __construct(order_repository $order_repository, order_item_repository $order_item_repository)
	public function __construct(order_repository $order_repository)
	{
		$this->order_repository = $order_repository;
		// $this->order_item_repository = $order_item_repository;
	}

	public function exec()
	{
		$this->order_repository->begin();

		try
		{
			$this->order_repository->order();
			// $this->order_item_repository->order_item();

			$this->order_repository->commit();
		}
		catch (Exception $e)
		{
			$this->order_repository->rollback();
		}
	}
}

で、いけるんじゃないかと。

じゃあ、テーブル単位だと、JOINがあるSELECTはどうするのか?

class order_repository_impl extends base_repository implements order_repository
{
  private $order_item_repository;

  public function __construct(storage_handler $db_storage_handler, order_item_repository $order_item_repository)
  {
    parent::__construct($db_storage_handler, 'order', array('order_id'), array('order_item' . parent::ENTITY_CLASS_SUFFIX => null));
    $this->order_item_repository = $order_item_repository;
  }

  ・・・省略

  // 注文IDに該当する商品名を取得する
  public function get_order_item_name($order_id)
  {
    $db_storage_handler = $this->get_db_storage_handler();
    $db_storage_handler->set_columns('oi.item_name');
    $db_storage_handler->set_from('`order` o INNER JOIN `order_item` oi ON o.order_id = oi.order_id');
    $db_storage_handler->set_where('o.order_id = :order_id');
    $db_storage_handler->set_bind_params(array(
      array(
        'name' => ':order_id',
        'value' => $order_id,
        'data_type' => PDO::PARAM_INT,
      )
    ));
    $result = $this->select();
    
    return $result;
  }
}
class base_repository
{
  // 継承先のクラスからしかアクセスさせない
  protected function select()
  {
    // SELECT文を実行する
    // get()の中に、SQLを実行する処理を書く
    return $this->db_storage_handler->get();
  }
}
class mysql_storage_handler extends rdbms_storage_handler implements storage_handler
{
  public function get()
  {
    $sql = 'SELECT ' . $this->columns . ' FROM ' . $this->from;
    if ('' !== $this->where)
    {
      $sql .= ' WHERE ' . $this->where;
    }
    // 以下、GROUP BYなどが続く

    $handle_instance = $this->get_handle_instance();
    $this->statement = $handle_instance->prepare($sql);

    foreach ($this->bind_params as $bind_param)
    {
      $this->statement->bindValue($bind_param['name'], $bind_param['value'], $bind_param['data_type']);
    }

    $result = $this->statement->execute();

    return $result;
  }
}

で、問題無し。

で、サービスからは、

class test_service
{
  public function exec()
  {
    $order_id = 1;
    $result = $this->order_repository->get_order_item_name($order_id);
    var_dump($result);
  }
}

という感じで。

「JOINの部分など」を直書きするってのがどうかって所なんでしょうけどね。

↑の「ある単位」毎にってのも、「業務分析」したら、

おそらくある単位が出来てくるとは思いますけど、

これに限らず、「費用対効果」ってのがあるかなと。

保守性を重視ってのが、勿論間違ってるとは思わないけど、

その保守性が活かされる前に、サービスが終了するとか。

「中長期を気にし過ぎて、逆に短期の方がおろそかになった」と言い換えても良いかと思いますが。

逆のケースを「山程」知っているので、そういう発想が出てくるのもアリなんですけど、

「短期が存在しないと、中長期は絶対に存在しない」わけですし。

3.戻り値の情報の持つ単位(テーブル単位?)

ここも、DDDだと「集約」とかがあり、悩ましい所ですね。

で、これはこれで、何度も書いている通り、別に何かがおかしいわけではないけど、でも「費用対効果」が。

なので、以下の様な感じはどうなのかと。

あと、いわゆるGeneration Gapパターンを使ってます。

デザインパターン紹介

// エンティティの基底クラス
abstract class entity
{
  public function is_property_exists($field_name)
  {
    return array_key_exists($field_name, $this->get_table_columns());
  }

  abstract public function get_table_columns();
}
// これは自動生成されるクラス
class order_entity_base extends entity
{
  private $order_id;

  private $order_status;

  // order_id
  public function get_order_id() { return $this->order_id; }
  public function set_order_id($order_id) { $this->order_id = $order_id; }

  // order_status
  public function get_order_status() { return $this->order_status; }
  public function set_order_status($order_status) { $this->order_status = $order_status; }

  public function get_table_columns()
  {
    return array(
      'order_id' => null,
      'order_status' => null,
    );
  }
}
// これも自動生成されるクラス
class order_item_entity_base extends entity
{
  private $order_item_id;

  private $order_id;

  private $item_name;

  // order_item_id
  public function get_order_item_id() { return $this->order_item_id; }
  public function set_order_item_id($order_item_id) { $this->order_item_id = $order_item_id; }

  // order_id
  public function get_order_id() { return $this->order_id; }
  public function set_order_id($order_id) { $this->order_id = $order_id; }

  // item_name
  public function get_item_name() { return $this->item_name; }
  public function set_item_name($item_name) { $this->item_name = $item_name; }

  public function get_table_columns()
  {
    return array(
      'order_item_id' => null,
      'order_id' => null,
      'item_name' => null,
    );
  }
}
// ここを、自分で実装する
class order_entity extends order_entity_base
{
  private $order_item_entities;

  // order_item_entities
  public function get_order_item_entities() { return $this->order_item_entities; }
  public function set_order_item_entities($order_item_entities) { $this->order_item_entities = $order_item_entities; }
  public function get_order_item_entity($index) { return $this->order_item_entities[$index]; }
  public function add_order_item_entity(order_item_entity_base $order_item_entity) { $this->order_item_entities[] = $order_item_entity; }
}
// 何もないけど(笑)
class order_item_entity extends order_item_entity_base
{
}

で、さっきのJOINでSELECTしたデータを取ってみましょう。

class order_repository_impl extends base_repository implements order_repository
{
  private $order_item_repository;

  public function __construct(storage_handler $db_storage_handler, order_item_repository $order_item_repository)
  {
    // 第2引数は、テーブル名。第3引数は、主キー(複合に対応する為、配列)。第4引数は、関連するエンティティクラス名(文字列)
    parent::__construct($db_storage_handler, 'order', array('order_id'), array('order_item' . parent::ENTITY_CLASS_SUFFIX => null));
    $this->order_item_repository = $order_item_repository;
  }

  public function get_order_item_name($order_id)
  {
    // 全件取る様に変えました
    $db_storage_handler = $this->get_db_storage_handler();
    $db_storage_handler->set_columns('*');
    $db_storage_handler->set_from('`order` o INNER JOIN `order_item` oi ON o.order_id = oi.order_id');
    $db_storage_handler->set_where('');
    $db_storage_handler->set_bind_params(array(
      /*array(
        'name' => ':order_id',
        'value' => $order_id,
        'data_type' => PDO::PARAM_INT,
      )*/
    ));
    $result = $this->select();

    $entities = array();
    if (true === $result)
    {
      $entities = $this->fetch_all_associated_entity();
    }

    return $entities;
  }
}

で、この「$this->fetch_all_associated_entity()」って所は、

class base_repository
{
  const ENTITY_CLASS_SUFFIX = '_entity';

  private $db_storage_handler;

  private $table_name;

  private $primary_keys;

  private $associated_entities;

  public function __construct(storage_handler $db_storage_handler, $table_name, $primary_keys, $associated_entities)
  {
    $this->db_storage_handler = $db_storage_handler;
    $this->table_name = $table_name;
    $this->primary_keys = $primary_keys;
    $this->associated_entities = $associated_entities;
  }

  protected function fetch_all_associated_entity()
  {
    $this->db_storage_handler->set_repository_class($this);
    $this->db_storage_handler->set_entity_class_name($this->table_name . self::ENTITY_CLASS_SUFFIX);
    // 第2引数にfalseを設定すると、主キーが同じレコードでも、別の配列の要素となる
    //return $this->db_storage_handler->fetch_all_associated_entity($this->get_associated_entities(), false);
    return $this->db_storage_handler->fetch_all_associated_entity($this->get_associated_entities());
  }
}
class mysql_storage_handler extends rdbms_storage_handler implements storage_handler
{
  const ENTITY_CLASS_DIRECTORY = '../../model/entity/';

  public function fetch_all_associated_entity($associated_entities, $unique_primary_key_data = true)
  {
    $entities = array();

    if (0 === count($associated_entities))
    {
      return $entities;
    }

    if (null !== $this->statement)
    {
      // TODO: 当然、存在チェックもする
      require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . self::ENTITY_CLASS_DIRECTORY . $this->entity_class_name . '.php');
      $main_entity = null;
      while (false !== ($row = $this->statement->fetch(PDO::FETCH_ASSOC)))
      {
        $entity_created = false;
        // 主キーが、既に設定済みのエンティティがある時に、インスタンスを再生成しない為の処理
        if (true === $unique_primary_key_data &&
            null !== $main_entity)
        {
          $row_values = array();
          $entity_values = array();
          $main_entity_index = array_search($main_entity, $entities, true);
          $primary_keys = $this->repository_class->get_primary_keys();
          foreach ($primary_keys as $primary_key)
          {
            if (true === isset($row[$primary_key]))
            {
              $row_values[] = $row[$primary_key];

              if (false !== $main_entity_index)
              {
                $method_name = 'get_' . $primary_key;
                $entity_values[] = call_user_func_array(array($entities[$main_entity_index], $method_name), array());
              }
            }
          }
          // 取得したカラムに、全ての主キーが含まれている &&
          // 既に設定済みのエンティティの全ての主キーの値に、nullが含まれていない &&
          // 取得したカラムの主キーの値と、既に設定済みのエンティティの主キーの値が同じ
          if (count($primary_keys) === count($row_values) &&
              false === in_array(null, $entity_values, true) &&
              $entity_values === $row_values)
          {
            $entity_created = true;
          }
        }
        if (false === $entity_created)
        {
          $main_entity = new $this->entity_class_name;
        }
        // 関連するエンティティにデータを入れていく
        foreach ($associated_entities as $associated_entity_class_name => $value)
        {
          require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . self::ENTITY_CLASS_DIRECTORY . $associated_entity_class_name . '.php');
          $associated_entity_class = new $associated_entity_class_name;
          foreach ($row as $column_name => $column_value)
          {
            // 関連するエンティティのフィールドに、取得したカラムが存在するか?
            if (true === $associated_entity_class->is_property_exists($column_name))
            {
              $method_name = 'set_' . $column_name;
              call_user_func_array(array($associated_entity_class, $method_name), array($column_value));
            }
            if (false === $entity_created &&
                true === $main_entity->is_property_exists($column_name))
            {
              $method_name = 'set_' . $column_name;
              call_user_func_array(array($main_entity, $method_name), array($column_value));
            }
          }
          // 関連するエンティティに入ったデータを、メインのエンティティの配列に入れていく
          $method_name = 'add_' . $associated_entity_class_name;
          call_user_func_array(array($main_entity, $method_name), array($associated_entity_class));
        }
        if (false === $entity_created)
        {
          $entities[] = $main_entity;
        }
      }
    }

    return $entities;
  }
}

で、最後にサービス。

class test_service
{
  public function exec()
  {

    $order_id = 1;
    $result = $this->order_repository->get_order_item_name($order_id);
    var_dump($result);
  }
}

このコードで、以下のテーブルの状態の時に。

mysql> select * from `order`;
+----------+--------------+
| order_id | order_status |
+----------+--------------+
|        1 |            1 |
|        2 |            1 |
+----------+--------------+
2 rows in set (0.00 sec)
mysql> select * from `order_item`;
+---------------+----------+---------------------------------+
| order_item_id | order_id | item_name                       |
+---------------+----------+---------------------------------+
|             1 |        1 | order_item::item_nameテスト1    |
|             2 |        1 | order_item::item_nameテスト2    |
|             3 |        2 | order_item::item_nameテスト3    |
|             4 |        2 | order_item::item_nameテスト4    |
|             5 |        2 | order_item::item_nameテスト5    |
+---------------+----------+---------------------------------+
5 rows in set (0.00 sec)

以下の様な戻り値になりますと。

array(2) {
  [0]=>
  object(order_entity)#8 (3) {
    ["order_item_entities:private"]=>
    array(2) {
      [0]=>
      object(order_item_entity)#9 (3) {
        ["order_item_id:private"]=>
        string(1) "1"
        ["order_id:private"]=>
        string(1) "1"
        ["item_name:private"]=>
        string(31) "order_item::item_nameテスト1"
      }
      [1]=>
      object(order_item_entity)#10 (3) {
        ["order_item_id:private"]=>
        string(1) "2"
        ["order_id:private"]=>
        string(1) "1"
        ["item_name:private"]=>
        string(31) "order_item::item_nameテスト2"
      }
    }
    ["order_id:private"]=>
    string(1) "1"
    ["order_status:private"]=>
    string(1) "1"
  }
  [1]=>
  object(order_entity)#11 (3) {
    ["order_item_entities:private"]=>
    array(3) {
      [0]=>
      object(order_item_entity)#12 (3) {
        ["order_item_id:private"]=>
        string(1) "3"
        ["order_id:private"]=>
        string(1) "2"
        ["item_name:private"]=>
        string(31) "order_item::item_nameテスト3"
      }
      [1]=>
      object(order_item_entity)#13 (3) {
        ["order_item_id:private"]=>
        string(1) "4"
        ["order_id:private"]=>
        string(1) "2"
        ["item_name:private"]=>
        string(31) "order_item::item_nameテスト4"
      }
      [2]=>
      object(order_item_entity)#14 (3) {
        ["order_item_id:private"]=>
        string(1) "5"
        ["order_id:private"]=>
        string(1) "2"
        ["item_name:private"]=>
        string(31) "order_item::item_nameテスト5"
      }
    }
    ["order_id:private"]=>
    string(1) "2"
    ["order_status:private"]=>
    string(1) "1"
  }
}

まだINSERT・UPDATE・DELETE等もありますが、といっても実装出来ない感じはしないですけどね。
あと、これだと、エンティティに限らず「子クラスをいじっている分(各機能を実装している時)」には、
そんなに「複雑」じゃないと思うんですよ、「集約」に比べれば。
といっても、その複雑ってのが、人によってマチマチと分かるのが、以下なのですが。

ウェブアプリケーションの構造について - じゅんいち☆かとうの技術日誌

コメント欄の方です。


気持ちはわかるのですが、こんなFATな設計を実装できるほど予算のあるプロジェクトなんて今どきないんですよね…
そんな時代だし、設計思想や言語だってどんどん変わって行っちゃうからこそ、設計的にも言語的にもライトなものが全盛なんじゃないかと思うのですが、いかがでしょうか。

もうすこし現実的な「現代版ドメイン駆動設計」を披露していただけると嬉しいです。

「予算のあるプロジェクト」ってのがポイントかなぁと。

でも、予算を取るのって、当然「成功の確度」が高くないと無理で、

で、昨今やる前から、その辺がわかるかっていうと・・・

だから、スタートアップなんかは、間違いなく合わないと思いますね。

逆に、実績があるならアリなわけですが、その場合は当然「時間に余裕がある」はず(何故かないってのも見ますが)で、

DDDのデメリット部分も緩和する事が出来るんじゃないかなと。

逆に、肯定的な意見もあり、


何を持って FAT なのかってことだと思うので、人によっては FAT に見えるのは理解できます。
でもその FAT って部分的にトラウマ(色眼鏡)が含まれていることってありえませんか?

個人的には FAT には見えないですね。私の理解では、個別のソースはかなりすっきりすると思います。

実は FAT ってコードだけでなく設計やマネジメントなど全般的に考えないといけないテーマかもしれませんね。

どこかにゆがみがあるから、FAT に見えてしまうと思います。

「実は FAT ってコードだけでなく設計やマネジメントなど全般的に考えないといけないテーマかもしれませんね。」

ええ、そうじゃないですかね、まさに↑がその辺の話かなと。

あと、クラスが多いから、IDEがないとキツイとかも考える必要があるかなと。

あと、「でもその FAT って部分的にトラウマ(色眼鏡)が含まれていることってありえませんか?」って所。

うん、EJB(幸い、業務で触ることは無かったですが)とかね(苦笑)

あと、気になったコメントは、


UserTableにユーザプロファイル情報を含めることはできます。
しかし、カラムの数が多い場合は目的の情報を探すのが困難になるので、
個人情報に関連する情報はプロファイルという別のテーブルに切り出した方が管理しやすいというモチベーションですね。
う~ん、テーブルは、正規化目的じゃないなら、細かくしない方が良いと思いますけどね。

以下に、その辺が言及されてましたが。

ドメインオブジェクトとテーブルのマッピング | システム設計日記

DDDと高負荷サービス(というか I/Oアクセス)は相性が悪いのか? - pospomeのプログラミング日記

あと、クラス数が増えるって事については、以下の観点もあるかなと思いますね。

ドキュメントが充実(ここも時間がかかる)していればアリかとは思いますけど。

マジックナンバー7の誤解 : みんな、他人の頭を過大評価しすぎているんじゃないだろうか。それって不幸だ。 - たたたた。

あと、乱用って意味だと、以下かなぁと。

「全部」のswitch文にstateパターンを適用するの?的な。

switch文を使ってはいけない: Architect Note

いや、stateパターン、以下とか、むしろ好きですけどね(笑)

https://github.com/kitoku-magic/final_magic/blob/master/view.php#L141

あと、複雑なSELECT文についてって所だと、

やはりお前たちのRepositoryは間違っている


恐らく、ユーザのIDをキーに購入履歴テーブルをジョインし、購入日で絞り込んだ件数をcountするクエリをORMで頑張って実現しようとするでしょう。
これらのクエリは大抵プログラム的にもメンテナンスしづらいものとなり、更にユーザ一覧画面などで複数件実行するとパフォーマンス上の問題を引き起こしたりします。


2) CQS(Command Query Separation)を適用してクエリを切り出す

上の問題に対して、下を適用すれば解決するって所に、因果関係があるのか?という。

別のクラスに切り出しただけでは?と。

なんか、「複雑化を排除しようとしたら、また別の形で複雑化した」感がありますね。

「複雑なものを単純に」って響きは良いですが、出来たら天才クラスだと思うので、

「複雑なものは複雑」で良いと思ってるんですけどね。

あと、オブジェクト指向全般としては、以下が大切かなと。

オブジェクト指向と10年戦ってわかったこと


今回例に取り上げたのは、ファーストフードのシステムなので「食品以外のものを販売する」というような変更が発生することは、あまり考えられません。「もしかしたらガソリンやタイヤを販売するかもしれないじゃないか!可能性はゼロではないのだから柔軟に対応できるように設計するべきだ!」と思うかもしれません。しかし、将来的に変更のない箇所を無駄に柔軟に設計することは過剰実装であり、プロジェクトの設計を複雑なものに変えてしまうため、行わない方が良いこともあるのです。
「どこが変わりそうか?」ってのは、「経験値」が問われそうな所だと思いますけどね。

なので、「そう簡単に白黒付けられる話ではない(白黒付けるのは否定的じゃないですよ)」と思ってますけどね。

これで終わりです。

あと、GitHubにも上がってますが、UMLツールを探していました。

Ozlog: BOUML

Miracle Jungle.: フリーのUMLツールをいくつか試してみた

katapedia/リバースエンジニアリングツール.md at master · yutakatay/katapedia · GitHub

「umbrello」が良いんじゃないかと思ったんですが、継承先が複数ある時に、何故か線が引けないんですよね。

BOUMLの方が良いのかもしれない。

あと、まだあのソースは途中なので、完成したら再度告知すると思います(笑)

あと、全然関係ないですが、椎名林檎アイデンティティ http://j-lyric.net/artist/a00450a/l000c60.html 流しながら書いてました(笑)