JavaScriptをがんばるブログ

React,OSS,ソフトウェア開発が中心のブログです👨‍💻

テンプレートエンジンのくせに最近のPHPはオブジェクト志向やらDIやらイキり始めた件

※2017/05/29現在Repositoryの章までしか聞けていません。聞いている際に浮かんだインスピレーションが揮発しないよう永続化する為に書いた記事です。

php-genba.shin1x1.com

まさか日本語でこの内容を聞けるコンテンツがあるとは思わなかったです。
これは英語をマスターすれば

などのPodcastからより多くの興奮を得られる事を意味します。

プログラミング経験3年、細かい修正ばかりで設計レベルの経験値が全くない自分ですが、各章について以前から個人的に思っていた事、お三方の知見からインスピレーションを得た内容を書き残します。

1. DI

「依存性の注入(Dependency Injection)」と「DIコンテナ」を混同して「DI」と呼ばれている点をまずはじめに分割したのは素晴らしかったと思います。

まぁ、DIコンテナPIMPLEトップページの見出しが

ですから混同しないほうがおかしいというかなんというか^^;

まずは「依存性の注入(Dependency Injection)」から。

依存性の注入(Dependency Injection)

自分はSilexを初めて使った時にFabien Potencier氏の以下スライドからDIに入りました。

www.slideshare.net

これ以外にもAngularJSやSymofnyの書籍・ドキュメントでDIについての章を読む機会はありましたが、
深くは追っていません。最初のスライドを見た時から今に至るまで、

「必要な物を何処で入れるか」

の単純な概念以上でも以下でもないという解釈で自分は納得出来ているからです。

コンストラクタインジェクションやセッターインジェクション等で都合よく必要な物を入れるタイミングを調整するテクニックもまぁ便利ですが、
私がDIを使う理由はたった一つです。

なんの模造品(または本物)を入れればテスト可能なのか。またこのクラスが動くには何が必要(依存している)なのか明示されており、それらの模造品(または本物)を注入すれば確実にテスト出来るから。

要はビジネスロジックを担当するクラスが必要とする物(依存するもの)をDIで調達させる事によって確実にテスト出来るからです。

「クラス、メソッドは必要なものだけを引数で受け取るのが設計として正しいからDIを使う」といった感情はあまり持っていません。
なぜなら、

  • フレームワークのコントローラのアクション内からアクセス出来るDIコンテナって何入ってるか分かんない状態だよね?
  • 必要なものだけ明示的に引数で渡すのが正しいんだったら、DIコンテナなんか使わずにそのリクエスト毎にそのアクションで必要なオブジェクトだけnewして引数に渡せば良いんじゃ無いの?
  • 「おいそこのDIコンテナ、お前明らかにこのアクションでは使われないオブジェクト持ってるだろ、所持品検査させろヾ(`ε´)ノ◎ー◎ タイホタイホー!」
  • だけど現実は利便性の観点から、コントローラのアクションレベルではサービスロケータパターンで全部入りコンテナを受け取っている
  • 明らかにユニットテスト無くても困らない、DIの引数リストが多くなりすぎて手間になる場合は原理主義ポイー、DIコンテナお注射して楽になっちまおうぜ

的スタンスの実利主義者なので、DIを使う理由はテストが出来るという実益性です。

「DIコンテナ」

これはあまり深く気にした事が無いので思うところがないです^^;
PHP初心者の頃DIコンテナを見て

  • これオブジェクトだよな?何で配列みたいにアクセス出来てんの(つд⊂)ゴシゴシ

と焦燥を感じてArrayAccessの存在を知った思い出があるくらいでしょうか。

フレームワークのコンパイルタイムが終わったらぶっちゃけDIコンテナは実質グローバル空間みたいなところがあるので、
WordPressの$wp_****グローバル変数群みたいにとりあえず必要な物を調達する存在という認識でした。
DIの引数にDIコンテナを渡す行為が「それグローバル変数のバケツリレーやん」というような趣旨でアンチパターンとして挙げられている文献をいくつか見た事があったので、それは気をつけなきゃなーと頭の片隅には置いていました。

グローバル空間より優れているところを考えてみたのですが、

  • チーム開発において「物の出し入れ方法」が共通化される
    • 必要なクラスをnewしたらとりあえずDIコンテナに突っ込んでそこから取り出してらっしゃーい( ´ â–½ ` )ノ
  • 何がどんな名前で入っているのか確実に追跡出来る

あ、今思いつきましたがJetBrains製IDEの「コンテナから取り出したオブジェクトへのメソッド補完」が無いと正直やってられないです^^;

2. テンプレートエンジンのくせに最近のPHPはオブジェクト志向やらDIやらイキり始めた件

ブログタイトルの章ですね、
 
PHPは最初、HTMLに動的な処理を埋め込むツールとして誕生しました。

<h1>現在のタイムスタンプは <?php echo time(); ?> です!</h1>

このようなツールが欲しいというモチベーションで、PHPを開発したと作者のRasmus Lerdorf氏が述べています。

youtu.be

ではなぜフレームワークだのcomposerだのオブジェクト志向だのDIだのややこしい概念がPHPに持ち込まれたのでしょうか。
 
私が知る限りでは、Symfonyの作者であるFabien Potencier氏が「My Take on PHP」の講演でそれについて語っています。


dotScale 2014 - Fabien Potencier - My Take on PHP

このテーマに関する内容は動画の前半から中盤にかけてです。
Fabien氏はフランス人で英語ネイティブではないため大半は一般的な語彙で話しており、
youtubeの自動字幕機能で英語字幕も表示できますので、ぜひ一度ご覧になってみてください∩( ・ω・)∩

スピーチの内容で本件のキーとなるセンテンスを抜き出してみました。

I have hate love relationship with php.
 
think people hate PHP as a Laguage.
 
Peaple like PHP as a Platform.
 
I don’t like PHP the language I like it mostly as platform.
 
peaple love WordPress not because it’s written in PHP but because the way PHP works the way it’s so easy to contribute something os there are hundred thousands of module for Wordpress.
 
every time there is a request we initialize the request be runs on PHP script and then we shut down everything and our should down the request shutdown is really about you know trusted everything about the current request everything you’ve done during the the request is just discard it so you need to do the same things over and over again.

英語は勉強中でふわっとした理解で申し訳ないですが、
上記センテンスの趣旨を意訳してみました。(翻訳ではないです)

  • みんなPHPが言語としてクールだと思っているわけではない。
  • でも何かをする為(WordpressのようなCMSや、Webフレームワーク)のプラットフォームとして都合が良いとみんな思っている。
  • リクエストが来たら初期化されたPHPスクリプトが走って、その後シャットダウンする。現在のリクエストで起こっている全てはシャットダウン後全て破棄されるから(メモリリークとか何か悪いものがあっても)信頼がおけるメカニズムである。

元はテンプレートエンジンだったPHPに大勢の開発者が上記のような魅力を感じ始め、WordPressをはじめとするCMS、小規模向け・大規模向け様々なスタイルのWebフレームワーク(多くはRuby on Railsの影響を受け)が開発された結果、
PHPを取り巻く状況が現在のように成長していったのだと思われます。

どのコンテキストを差して「PHP」と言っているのか分からない問題を「ニコニコ動画」で例えると

この動画共有サイトの黎明期、もちろん動画数は少なかったです。
人気動画の数は人が十分把握出来る数に収まり単に「ニコニコ動画」と言った場合大まかに、

といったコンテキストで括る事が出来ました。

β時代の英雄とは (ベータジダイノエイユウとは) [単語記事] - ニコニコ大百科

しかし今日では「ニコニコ動画」に投稿されている動画の本数・ジャンル、そこに訪れている人々の趣向も膨大で「ニコニコ動画」はもはや、
「コメント可能な動画サイト」というプラットフォームに成長しました。
その結果「ニコニコ動画」を単一のコンテキストで括る事は出来なくなりました。

最近下火なジャンルAが好きな人「最近ニコニコつまんないよねー」
最近ホットなジャンルBが好きな人「いやむしろ最近じゃね?面白くなってきたの」
 
このように特定のプラットフォームがカバーする領域が多くなりすぎるとプラットフォーム内のどの部分を指してものを言っているのか?
分かりにくくなってしまいます。
 
現在一口に「PHPは〜」といってもどの文脈を指しているのか、確認しないと分からないのはこれと同じ原理だと思います。

この章のまとめとして、
2017年現在「PHPとはなんなのか」に対する個人的な見解としては以下のようになります。(思いついた事全部詰めました)

「書き捨てのテンプレートエンジン」  
としても、

「コンパイル不要で書いたコードをローカルで動かすまでのディレイが無く
インタプリタ言語としては申し分ない速度で動作する。
Webサーバが標準でmod_phpとか備えててとにかく動かすのが簡単、技術者の調達もしやすくとにかく融通が効く。
一回のリクエスト毎に新しいプロセスが立ち上がり、処理が終わったらそのプロセスは終了するから多少ラフなプログラミングをしてある程度メモリリークなどがあっても大事には至らない仕組みの信頼の置けるランタイムの上に、
Fabien Potencier氏やTaylor Otwell氏をはじめとする凄い人が開発したFrameWork上で
Javaに匹敵するクラスオートローダー・パッケージマネージャー・静的方付け・オブジェクト指向、などを駆使しつつもJetBrains製IDEで生産性を犠牲にする事無く大規模開発用途に利用する」

 
言語としても使える「ハイブリッド言語」

3. InterfaceとAbstract

正しい使い分けについて勉強した事がないので引用出来る資料はありません。
私の個人的なやり方としては、

  • 抽象クラスを作成する場合は原則Interface
  • どう考えても抽象クラスの具象クラスそれぞれに同シグネチャ、同一処理を記述する事になる場合はAbstract

Interfaceを使う場合

車のアクセルペダルとブレーキペダルがあります。
どちらも「踏めば機能する」という点、すなわちユーザーインターフェースは同じです。
でも「加速」「減速」と機能、振る舞いは異なります。
 
PHPのサンプルコードで表してみました。

<?php 

interface Pedal {
    public function useThePedal();
}

class AcceleratorPedal implements Pedal {
    public function useThePedal()
    {
        $this->Acceleration();
    }

    private function Acceleration() {
        // 加速します
    }
}

class BrealePedal implements Pedal {
    public function useThePedal()
    {
        $this->Slowdown();
    }

    private function Slowdown() {
        // 減速します
    }
}

この場合useThePedal()内の実装が「加速」「減速」と異なるのでInterfaceを使います。

Abstract

「熱いお湯ポット」と「キンキンに冷えたお水ポット」があります。 両者の機能は

  • 決められた温度に保温する
  • 注ぐボタン

とします。

保温機能はそれぞれ「保温」という行為は共通していますが「熱い状態に」「冷たい状態に」と実装は異なります。
 
注ぐボタンについては両者全く同じです。
「ボタンを押す」というインターフェースも「中身を注ぐ」という実装も同じです。 現実にこんな商品があったら、この部分はおそらく同じ部品を流用出来るでしょう。
 
こちらもPHPのサンプルコードに表してみました。

<?php

abstract class Pot {
    // 中身の液体です。
    protected $water = 'water';

    // 「お湯ポット」「お水ボット」でやる事が違うのでここでは実装しません。
    abstract protected function KeepTemperature();

    // 「注ぐ」ボタンです。
    public function pour() {
        $water = $this->water; // 中身を取得。この抽象クラス「Pot」は$waterがお湯か水か分かりません。
        // 中身"$water"を出す処理...
    }
}

class HotWaterPot extends Pot {
    protected function KeepTemperature()
    {
        // 中身を熱々に保ちます。
    }

    // 「注ぐ」ボタンは抽象クラスで既に実装されています。
}

class CoolWaterPot extends Pot {
    protected function KeepTemperature()
    {
        // 中身を冷たく保ちます。
    }

    // 「注ぐ」ボタンは抽象クラスで既に実装されています。
}

Interfaceだと名前の通り、あくまでも「外部からの操作窓口」しか定義出来ないので、
抽象クラスにプロパティを定義した方が都合が場合などもAbstractの出番があるかもしれませんね。

4. Repository

Podcast内の以下のような発言が響きました。

結局のところ「プログラム」は「データを操作するツール」に帰結するでしょ

難しくて精密な理解は出来ていませんが、

Martin Fowler氏の以下名著でも同様趣旨の文があった気がします。

エンタープライズアプリケーションアーキテクチャパターン

うる覚えながら私の解釈を要約すると、

  • 結局のところ「プログラム」は「データを操作するツール」である
  • 「データの操作」が「ビジネスロジック」である
  • 「ビジネスロジック」が「苗字と名前をSELECT、連結して返す」など軽微なものに収まるのであればデータベース構造にそのまま対応したActive Recordパターンは良い選択
  • 「ビジネスロジック」がデータベース構造と1対1で結びつかないオブジェクト構造を必要とするようであれば、多少の手軽さは犠牲となるがData Mapperパターンなどを使用する

すみません、記憶があやふやなのでこの解釈はかなり怪しいです。
ただこの本の中で「ユーザーはデータを弄りたくてUIを操作するのだから、操作ロジックとデータ構造が直接対応するような実装になるのは自然な事で、昔のシステムには多く見られた設計だ」という趣旨の文が何度か出てきていたのは覚えています。
この発言のおかげで本書を読み直してみようという気持ちが湧きました、ありがとうございます。

とりあえず急いで書き留めたので、内容をこの先更新するかもです。
1つでも皆さんの参考になる部分がありましたら幸いです。