フレームワークに頼らず素のPHPでオブジェクト指向を意識しながらコードを書いてみる(2)

2019/01/04

こちらの記事の続きです。

フレームワークに頼らず素のPHPでオブジェクト指向を…(1)

DBにつなげっ

収集したFeedデータはDBへの保存を想定しております。

が、SQLite かもしれないし、MySqlかもしれないし、MySqlでもWordPress用テーブルのどこかかもしれないし・・。

ということで、Interface と Abstract Class を作ってOOP的にシュッとしたヤツを・・と思ったものの、この辺りで、自分のデザインパターンに関する知識のなさが露呈してきます。

教科書に書かれてるデザインパターンとやらをきちんと使った記憶が無いんですよね。

SingletonとかFacade辺りはフムフムと学んでみるものの、フレームワークなしでスクラッチ設計する、なんてプロジェクトに関わったことないので、うっすらした知識だけで終わってます。

とは言え、手段に振り回されて疲れるのも嫌なので(むかーしEJBで疲弊してた同僚思い出した・・)、目的を達成しつつ、データの保存先が変わっても対応できる、ぐらいにしておけば良いかな・・ぐらいで進めます。

まずは ConcreteClass として

  • StorageMySql

クラスを作って、DB接続のテストをしてみます。

(もしかして、データ取得部分はスタブにしておいて、Viewレイヤーからテスト+実装する、という手順の方が良いのかもしれないけれど。)

TDDを意識しつつテスト作成。

PHPUnit を使いましょ、ということで composer.json に以下を追加。

"require-dev": {
    "phpunit/phpunit": "^7"
}

AbstractStorageクラスを継承した、

  • StorageMySql.php

を作成し、テストロジック

  • StorageMySqlTest.php

を作成して、とりあえず connect できるところまでテスト。

それにしても PSR に従って AbstractStorage というクラス名にしたんだけれど、クラス名に Abstractって単語が明確に含まれている抽象クラスに対して AbstractStorage::getInstance() って呼ぶのは、なんだか違和感があるんだけれど考えすぎだろうか。

抽象クラスのクラス名を単に Storage にして、 Storage::getInstance() ってしたくなるのだけれど・・。

なんだか気持ち悪かったので、Storage クラスを別に作って、呼び出すときには Storage:getInstance() で呼ぶようにしてみました。

Storage::getInstance() 内では、Config で指定されたDBタイプを判断して、それ用のClass(StorageMySqlなど)を生成して返す、なんていう風にしてみました。

が、このためにわざわざ1クラス準備するのもずいぶん冗長な気がするなぁ・・。

どうするか悩んだのだけれど、結局元に戻すことに。

「しばらく時間が経って、すっかり記憶をなくしているアンポンタンな自分(もしくは経緯を知らない、自分ではない誰か)が見た時に「すんなり理解しやすいかどうか?」を考えると、AbstractStorage::getInstance() で呼び出すようにした方が、分かり易いだろうな、という判断です。

最終的にはWordPressのプラグインにしたいので、データはMySqlに・・と思っていたんですが、WordPressのDBを汚染してしまう、というのはちょっと気が引けます。(自分のPlugin専用のTableを追加するのは気持ち悪いし、かといってWP_OPTIONSにシリアライズして大量のデータ突っ込むのもイヤですし・・)

考えてみれば、RSSフィードから取得したデータをキャッシュしておきたいだけなので、SQLite使えばいいや、ということで、もう一つ SQLite 用の ConcreteClass を追加しました。

  • StorageSQLiteTest.php

connect() のロジックだけ書き換えればOK、ということで TemplateMethod パターンがうまい具合に使えているような気がします。

この時点では、Storageクラスに load() や save() みたいな読み書きメソッドが必要だろうなー、ぐらいにしか考えていませんでした。

(このあたり、結局あとで大幅に書き換えることになります。)

テストファーストを意識しつつ

以前、CodeRetriet というイベントに参加した際に、「細かくテストと実装を繰り返していく」という、TDDのテンポみたいなものを体験しました。

今回も、

  • まずはテストメソッドを実装し、
  • 失敗させ、
  • ロジックをコーディングし、
  • テストして、
  • 失敗したら直して、
  • またテストして
  • success でイヤッホー!

を繰り返していきます。

例えば、 Channel クラスを作るときは、こんなコードを書いてすぐテスト、とね。

class ChannelTest extends TestCase
{
    public function testCreateChannel()
    {
        $channel_1 = new Channel('https://example.com/rss/feed');
        $this->assertEquals($channel_1->getSubscribeUrl(), 'https://example.com/rss/feed');
        // title is null.
        $this->assertNull($channel_1->getTitle());
    }
}

 

何かが違うような・・。

さて、途中経過を少々端折りまして・・。

コーディングしている間に、なんだか実装に違和感が出てきました。

Channel に対して、「今君が持っている Channel 情報をDBに保存してくれたまえ。」と命令する場合は、

$channel->save();

で良いと思うんですが、全 Channel の一覧を取得する場合は、誰に頼みましょう・・?

Channel モデルは、自分自身の Channel に関する属性は知っていても、他の Channel に関する情報は知らなそうですし。

となると $aggregator->getChannels(); でしょうか。

あれ、でも Channel の情報は DB から取得するはずで、となると Aggregator にもDBアクセスの機能を持たせないといけない?

Aggregator はコントローラー。でもDBへのアクセスはモデルが担当するらしいんだけど・・。

・・という訳で、MVC を意識しすぎなのか、よく分からなくなってきました。

悩んで進まないのも嫌なので、モヤモヤしながらもコーディング。

  • DBへのアクセス関連は1箇所にまとめてしまいたい。
  • DBではないかもしれない(例えばCSVとか)かもしれないので、SQLをガリガリ書くのは AbstractStorage を継承した AbstractStoragePDO クラス内になりそう。

なんてことを考えて作っていった結果、 AbstractStoragePDO 内に Channel や Episode に対する操作メソッドがどっちゃりと増えてしまいました。

うーん、なんだか気持ち悪いけれど、ともあれ動くところまでは漕ぎ着けました、っとね。

ちなみに、最初のバージョンを仕上げた後に知ったのがこちら。これを読んでいたら、また違ったつくりになっていたのかもしれません(^_^;)

(しんばらさんのポッドキャスト「PHPの現場」オススメです。)

テンプレートエンジンをどうするか?

さて、View レイヤーを作り込んでいくときに、テンプレートエンジンを使ってみようと思い立ちました。

Laravel は既に触っていたので、blade の存在は知っていたんですが、ちょっと大げさな気がします。

軽量のもので何かないかな・・と探して見つけたのが、この記事。

なんと、テンプレートエンジンを自分で作る、というお話のよう。

せっかくなので、写経しつつテンプレートエンジンの作り方を学んでしまえ、ということで、こちらの記事に書かれているソースを使わせて頂きました。

なるほど、ファイル内の特定文字列を置換して、そいつを include してやれば動くってことなんですね。

と言う訳で、なんとか見られるものが作れたかなー・・、ということで、こちらで公開してみるのでした。

ソースは Github にてご覧いただけます。

実際に動くものはこちら。

ログ出力とかエラーハンドリングとか、まだまだな感じがあるんですが、取り急ぎ公開してみた、という感じです。アドバイスやコメントなどありましたら、お手柔らかに Twitter 等でお声がけ頂けたら嬉しいですm(_ _)m。

PHPのお仕事、募集中です。

前回の記事の冒頭に書いたように、お仕事環境を変えようと目論んでおります。

もし「一度話だけでも聞いてやるか・・」という懐の深い会社さんがありましたら、お仕事募集の案内をこちら↓のページに記載しましたので、ご覧頂ければ幸いです。m(_ _)m