Slide 1

Slide 1 text

自分たちのコードを
 Composer パッケージに
 分割して開発する
 2021/05/29 PHP カンファレンス沖縄 2021
 @okashoi


Slide 2

Slide 2 text

岡田 正平/おかしょい
 Twitter: @okashoi
 GitHub: @okashoi
 所属:株式会社ウィルゲート
 
 登壇:
 
 
 
 
 寄稿:


Slide 3

Slide 3 text

目次
 • 導入(問題提起、提案)
 • ローカルのディレクトリを Composer パッケージとして扱う方法
 • パッケージを分割する際の考え方
 • こぼれ話


Slide 4

Slide 4 text

目次
 • 導入(問題提起、提案)
 • ローカルのディレクトリを Composer パッケージとして扱う方法
 • パッケージを分割する際の考え方
 • こぼれ話


Slide 5

Slide 5 text

開発についてまわる問題
 何も意識せずにフレームワークを使って開発していると
 巨大なひとつの構造物となっていく
 
 結果、次のような状態を引き起こしやすい
 ● Controller に書き下される一連の詳細な処理
 ● 責任の境界が曖昧なクラス
 ● あちらこちらに出てくる同じような手順
 ● フレームワークの機能に過度に依存したコード
 
 → 可読性や再利用性の低下、バグの原因


Slide 6

Slide 6 text

ソフトウェア設計の領域において古くから存在する
 「モジュール化」というアプローチ
 
 →しかし PHP は言語機能としてモジュールに相当するものを備えていない
 
 ※「モジュール」という語彙自体は「拡張モジュール」で使われるが別物
 モジュール化


Slide 7

Slide 7 text

PHP における依存関係管理ツール
 
 
 依存関係を「パッケージ(※)」単位で扱う
 
 ※Composer におけるパッケージとは
  composer.json が存在するディレクトリ
 Composer


Slide 8

Slide 8 text

PHP における依存関係管理ツール
 
 
 依存関係を「パッケージ(※)」単位で扱う
 
 ※Composer におけるパッケージとは
  composer.json が存在するディレクトリ
 Composer
 本発表で紹介する方法 Composer パッケージを「モジュール」に近い概念として扱う
 


Slide 9

Slide 9 text

目次
 • 導入(問題提起、提案)
 • ローカルのディレクトリを Composer パッケージとして扱う方法
 • パッケージを分割する際の考え方
 • こぼれ話


Slide 10

Slide 10 text

Packagist


Slide 11

Slide 11 text

Packagist からパッケージを持ってくることだけが選択肢ではない
 
 Composer には「リポジトリ」という概念があり、
 GitHub のリポジトリやローカルに存在するディレクトリを
 composer install や composer require の対象にできる
 Composer におけるリポジトリ


Slide 12

Slide 12 text

Packagist からパッケージを持ってくることだけが選択肢ではない
 
 Composer には「リポジトリ」という概念があり、
 GitHub のリポジトリやローカルに存在するディレクトリを
 composer install や composer require の対象にできる
 Composer におけるリポジトリ
 今回はこっち


Slide 13

Slide 13 text

ローカルのディレクトリを Composer パッケージとして扱う
 ... ├── apps │ └── my-app │ └── composer.json ├── packages │ └── my-package │ └── composer.json ...

Slide 14

Slide 14 text

ローカルのディレクトリを Composer パッケージとして扱う
 ... ├── apps │ └── my-app │ └── composer.json ├── packages │ └── my-package │ └── composer.json ...

Slide 15

Slide 15 text

ローカルのディレクトリを Composer パッケージとして扱う
 ... ├── apps │ └── my-app │ └── composer.json ├── packages │ └── my-package │ └── composer.json ...

Slide 16

Slide 16 text

ローカルのディレクトリを Composer パッケージとして扱う
 { ... "repositories": [ { "type": "path", "url": "../../packages/my-package" } ], ... } ... ├── apps │ └── my-app │ └── composer.json ├── packages │ └── my-package │ └── composer.json ...

Slide 17

Slide 17 text

ローカルのディレクトリを Composer パッケージとして扱う
 { ... "repositories": [ { "type": "path", "url": "../../packages/my-package" } ], ... } ... ├── apps │ └── my-app │ └── composer.json ├── packages │ └── my-package │ └── composer.json ...

Slide 18

Slide 18 text

ローカルのディレクトリを Composer パッケージとして扱う
 ... ├── apps │ └── my-app │ └── composer.json ├── packages │ └── my-package │ └── composer.json ... $ composer require "my/package:*@dev"

Slide 19

Slide 19 text

ローカルのディレクトリを Composer パッケージとして扱う
 ... ├── apps │ └── my-app │ └── composer.json ├── packages │ └── my-package │ └── composer.json ... $ composer require "my/package:*@dev" "name": "my/package"

Slide 20

Slide 20 text

ローカルのディレクトリを Composer パッケージとして扱う
 ... ├── apps │ └── my-app │ └── composer.json ├── packages │ └── my-package │ └── composer.json ... apps/my-app/vendor/my/package から 
 packages/my-package へとシンボリックリンクが張られる 
 
 → Composer の autoload 機能を経由して 
   my-app から my-package を利用できるようになる 


Slide 21

Slide 21 text

Laravel におけるディレクトリ構造の例
 ├── app │ ├── ... ... ├── composer.json ... ├── app │ ├── ... ... ├── packages │ └── my-package │ └── composer.json ... ├── composer.json ... ※ “my-package” はあくまで例なので、 
  適切な名前をつけてあげてください。 


Slide 22

Slide 22 text


 
 
 
 
 
 
 
 
 
 後藤知宏, “Laravel Package Development”, Laravel JP Conference 2019, https://speakerdeck.com/mikakane/laravel-package-development
 
 参考資料


Slide 23

Slide 23 text

目次
 • 導入(問題提起、提案)
 • ローカルのディレクトリを Composer パッケージとして扱う方法
 • パッケージを分割する際の考え方
 • こぼれ話


Slide 24

Slide 24 text

パッケージに切り出す(モジュール化する)こと
 = インターフェースをデザインすること
 
 ポイントは
 ● (特にフレームワークとの)依存関係
 ● 凝集度と結合度
 どうやって分割していくか?


Slide 25

Slide 25 text

フレームワークに依存している状態
 = 「フレームワークのその場所」から動かすのが困難な状態
 
 ● コードの再利用性が下がっている
 ● フレームワークのバージョンアップに追従しにくくなる
 ● 長期運用するアプリケーションではフレームワークを乗り換えることも
 ○ 過度に依存しているとほぼ書き直しになる
 「フレームワークに依存させないこと」の動機


Slide 26

Slide 26 text

Laravel の例を挙げれば
 ● Illuminate に依存しない(use したり、完全修飾名で利用しない)
 ○ extends、implements、引数、戻り値に出てこない
 ● グローバルヘルパ関数や Class Alias(\Log とか)を使わない
 
 これらを取り除いて残るのは、自ずと「自分たち」固有のコードになるはず
 
 ※ここでいう「自分たち」とはサービスやビジネス
 フレームワークに依存していない状態


Slide 27

Slide 27 text

自分たち固有のコードとは?
 • 見積もりの計算ロジックかもしれない
 • マッチングのアルゴリズムかもしれない
 • データのアクセス権限に関する条件式かもしれない
 • 税金の計算かもしれない
 
 これら正しく抽出するには開発する対象領域への深い理解が必要
 →この過程こそがモデリング
 「自分たちのコード」


Slide 28

Slide 28 text


 
 
 
 
 
 
 
 
 
 @77web, “そのコード、フレームワークの外でも動きますか?”, PHPerKaigi20201, https://speakerdeck.com/77web/sofalsekodo-huremuwakufalsewai-demodong-kimasuka 
 
 参考資料


Slide 29

Slide 29 text

モジュールの設計において古くから用いられている尺度
 
 凝集度:責任範囲が明確でそこに集中しているかどうか。高い方が良い。
 結合度:モジュール間でのデータの共有度合い。低いほうが良い。
 凝集度と結合度


Slide 30

Slide 30 text


 
 
 
 
 
 
 
 
 
 @sonatard, “オブジェクト指向その前に凝集度と結合度”, Object Oriented Conference 2020, https://speakerdeck.com/sonatard/coheision-coupling
 
 参考資料


Slide 31

Slide 31 text

型情報の上でフレームワークに依存しなくなっても
 「見えない依存関係」が存在しているケースがある
 ● 型宣言がない箇所で、フレームワーク固有のクラスを想定している
 ● 関数の呼び出しに暗黙の前提条件が存在している
 ● 型は int や string だが、その値の意味がフレームワークに特化している
 ○ 例)フレームワークで使われるクラス名の文字列
 
 このような実装は「結合度が高い」と言える
 →そのクラスや関数を使うのに、型から得られる情報以上の知識が
  必要になってないかを意識する
 見えない依存関係


Slide 32

Slide 32 text

パッケージに切り出す(モジュール化する)こと
 = インターフェースをデザインすること
 
 ポイントは
 ● (特にフレームワークとの)依存関係
 ● 凝集度と結合度
 どうやって分割していくか?(再掲)


Slide 33

Slide 33 text

パッケージに切り出す(モジュール化する)こと
 = インターフェースをデザインすること
 
 ポイントは
 ● (特にフレームワークとの)依存関係
 ● 凝集度と結合度
 
 →いきなり「自分たちのコード」を網羅的に切り出すのは難しい
  まず、1 つの関数だけでいいので切り出してみよう
 どうやって分割していくか?(再掲)


Slide 34

Slide 34 text

「自分たちのコード」を
 依存の束縛から解き放とう


Slide 35

Slide 35 text

目次
 • 導入(問題提起、提案)
 • ローカルのディレクトリを Composer パッケージとして扱う方法
 • パッケージを分割する際の考え方
 • こぼれ話


Slide 36

Slide 36 text

「同一パッケージ内からのみアクセスできるクラス」は定義できない
 
 パッケージの詳細を(仕組みとして)隠蔽できないため、運用でカバーする必要があ る
 PHP のクラスは全て public


Slide 37

Slide 37 text

ルートパッケージの vendor ディレクトリ内に全ての依存関係が
 フラットに集約されるため、例えば次のような場合もエラーにならない
 依存関係を厳密にチェックできるわけではない
 ルートパッケージ パッケージ A パッケージ B ルートパッケージ パッケージ A パッケージ B ■ composer.json に定義した依存関係
 ■本来依存していないパッケージにアクセスできてしまう
 パッケージ B からパッケージ A のコードを利用


Slide 38

Slide 38 text

私はやったことはないが、想定できるのは
 🙆 分離することで CI のサイクルが早くなる
 🙆 共通のコードを複数のプロジェクトから使える
 🙅 運用やデプロイ(ローカル開発環境含む)が複雑になる
 🙅 インターフェースの決定や変更について取り決めが必要になる
 
 相当の規模の開発でないか、運用上のノウハウが無い限り
 デメリットの方が大きい気がする(経験・知見のある方の意見求ム)
 パッケージごとに Git リポジトリを別にすべきか?


Slide 39

Slide 39 text

「フレームワークにどこまで依存するのか?」は必ずどこかで線引きが必要
 
 フレームワークに依存しないコードは
 • フレームワーク側から適切に呼び出すために、
 フレームワークの一歩踏み込んだ理解が必要になる
 • コードの量が増える傾向にある
 
 あるいは Carbon や Guzzle 等のライブラリへの依存も同様
 最終的にどこまでやるかは「さじ加減」