勉強日記

毎日投稿

Laravel Meetup Tokyo Vol.12 参加した

メモ


LaravelTokyo

Eloquentに別れを告げるタイミングについて考えた / 吉田あひる

  • ORM x Active Record x Domain Model
    • 【補】Domain Modelではなくないか
      • Domain LayerのことをDomain Modelって言ってるのかな
  • Eloquentの功罪
    • 高機能
  • クエリ最適化
    • slow query吐かれる
    • クリティカルなところだけSQL書けばいい
    • 別れるほどではない
  • Repository Pattern(DDD)と相性悪い
    • Repositoryの外でSQLを書けてしまう
    • 別れるのほどではない
      • レビューしろ
  • fatになりやすい
    • DBとのやりとり
    • relation
    • ドメインロジック
    • バリデーション
      • mutatorでバリデーション(boiler plate)
    • プレゼンテーションロジック
      • derivationでHTMLを返したり(boiler plate)
    • 別れるほどではない
      • 責務分けてクラス設計しろ
  • モデルとテーブルが一対一対応しないと破綻する
    • そもそものパターンの前提としてそう
  • テーブルから独立したドメインオブジェクトが欲しくなる
    • 【補】Domain Model + Data Mapper使え

Laravel Novaの適切な使い方を考えてみる / suthio

  • 管理画面作るやつ
    • 自社向け、特定のユーザが使うやつとか
  • Laravel Novaはいいぞ
  • Laravel Novaって何
    • 有料
      • やすい
    • 公式
    • 5分くらいでダッシュボードできる
  • ここが素敵
    • ドキュメント充実
      • 公式が有能
    • modernな見た目
    • ソースコード読みやすい
  • よくある管理画面パッケージ
    • 初期の速度は速い
    • カスタマイズつらい
      • bad hack
      • パッケージの意味とは
  • 開発者用の管理画面として有用
    • ちょっと便利なPHPMyAdmin
    • サービス立ち上げ期に使うやつとか
  • 長所
    • それ用のパッケージがある
  • 短所
    • Laravel 5.8^ only
    • OSS
  • みんな使って!
    • 情報増えてうれしい

Laravel+Slack通知で実践したリアルタイムエラー共有開発 / くまモンエンジニア

  • エンジニアがjoinしてきて、初のチームプレー
  • チームっぽい事したい
  • エラー共有してみんなで解決する仕組み
    • エラーに関する知見の共有
    • 質問が苦手な新人の育成
  • 5.7.14からslack-notification-channelが外部パッケージになった
  • Slack APIとかをガシガシ使いたいのでGuzzleで自前開発した
  • 当事者意識
    • 他人のエラーは俺のもの
    • 俺のエラーも俺のもの
  • チーム全員が同じ分のエラーを共有できる

エラー時にログに出力する情報と画面に表示する情報を分ける / おかしょい

  • エラー: アプリケーションでハンドリングされなかった例外
  • ユーザ向けメッセージと開発用メッセージは異なる
  • app\Exception\Handler.phpをいい感じに実装する
<?php

    /**
     * Prepare a response for the given exception.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception $e
     * @return \Symfony\Component\HttpFoundation\Response
     */
    protected function prepareResponse($request, Exception $e)
    {
        if (! $this->isHttpException($e) && config('app.debug')) {
            return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
        }

        if (! $this->isHttpException($e)) {
            $e = new HttpException(500, $e->getMessage());
        }

        return $this->toIlluminateResponse(
            $this->renderHttpException($e), $e
        );
    }
  • これ使え
        if (! $this->isHttpException($e)) {
            $e = new HttpException(500, $e->getMessage());
        }
  • ユーザ定義例外にtoHttpException(): HttpExceptionメソッドとか生やす
  • appのHandlerの中でtoHttpException()呼ぶ
  • getMessage()でユーザ向け文言書く

LaravelでAuth0を使ってECサイトを実装してみた

  • composer require auth0/login
  • 認証のdriverをauth0に

PoEAA ch18 Registry

martinfowler.com


Registry

A well-known object that other objects can use to find common objects and services.

  • OOPでオブジェクトを検索するとき、通常は関連(association)しているオブジェクトを起点に行う
  • ある顧客customerの注文をすべて取得したい
    • customerと関連していれば、customer.getOrdersAll()で手に入る
  • customerオブジェクトが無い場合は?
    • custmerオブジェクトは、customerFinder.findById(customerId)で手に入る
  • customerFinderオブジェクトが無い場合は?
  • Registryの出番
    • グローバルオブジェクトのようにふるまう
      • 実際にそうかは別問題(後述)

How It Works

  • インタフェースと実装とは異なるよ、という話
    • インタフェースはstaticメソッド
      • グローバルアクセス可能
    • メソッドがstaticだからといって、データまでもがstaticである必要性はない
      • むしろ定数以外でstaticなデータは持たない
  • データのスコープ
    • プロセス内グローバル
    • スレッド内グローバル
    • セッション内グローバル
  • スコープが異なれば、実装も異なってくる
  • が、staticメソッドを叩く人は、得られるデータのスコープを知っている必要はない
  • シングルスレッドならSingleton Pattern使え
    • プロセススコープ
      • データをmutableなstaticフィールドで持っても実現できる
      • が、置換不可能だからよしたほうがいい
        • テストしたいとき困る
    • private static Registry soleInstance;を1つ持つ感じ
    • Registry::soleInstance->getFoo()
    • Registry::getFoo()と書けるとなおよい
  • マルチスレッドならSingletonはやめておけ
    • スレッド横断的にmutableなグローバル領域にアクセスするのは無謀
      • 同期制御すれば不可能ではないが難しい
    • immutableだったり、めったに変更されないデータならこの限りではない
      • 合衆国の州のリストとか
  • 一般的なRegistryはスレッドスコープ
    • 例: DBコネクション
    • スレッドスコープの実装例
      • 環境がスレッド別ストレージを提供している
        • JavaThreadLocal(後述)
      • スレッドをキーとする辞書で自前管理する
  • スコープによらず、Registryのインタフェースは変わらないことに注意
    • Registry::getDbConnection()
    • プロセススコープだろうがスレッドスコープだろうが変わらない
  • セッションスコープでも同じ
    • セッションIDをキーとする辞書で管理
    • …をスレッドスコープの辞書に入れることも
  • スレッドスコープのデータにstaticメソッドでアクセスするとパフォーマンス問題あるかも
  • その場合はスレッド別インスタンスに直接アクセスしてボトルネックを解消せよ
  • Registryが複数あるケース
    • システムのレイヤーや実行コンテキストで区切られるのが普通
    • 著者としては、どう使われるかで分けるのが好み

When to Use It

  • 濫用するな
    • 基本はあくまでオブジェクト間相互作用
  • Registryを使わない方法
    • 広く使われるオブジェクトは明示的にメソッドの引数で渡す
      • デメリット: 自分が使わなくても下の層で使うから受け取らないといけない、というオブジェクトが現れる
      • 渡ってきたパラメータが9割がた使われないならRegistryを検討せよ
    • コンストラクタの引数で渡し、参照を保持する
      • 使わなくても、コンストラクタの引数しか汚れない
  • Registryの問題
    • 管理するデータが追加されるたびに、Registryクラスを変更しないといけない
      • これを嫌って、グローバルデータの保持には汎用の辞書クラスを使う人もいる
  • 著者見解としては、汎用辞書ではなく、明示的なRegistryクラスを作るほうが良い
    • 識別子のあるメソッドを生やせる
      • 何が使えるかソースコードを見ればわかる
      • ドキュメント自動生成もできる
      • 辞書を使うとありがちな「何のキーで何が得られるんだっけ」という混乱がない
      • 型安全
    • 静的なインタフェースで実装をカプセル化できる
      • スコープ
  • グローバルはグローバル。無実を証明しない限りは罪であることを忘れぬよう

Example: A Singleton Registry (Java)

  • どうってことのないSingleton Pattern。略
  • privateなインスタンスフィールドにstubインスタンスをセットするためのメソッドを生やしておく(テスト用)

Example: Thread-Safe Registry

class ThreadLocalRegistry ...

    private static ThreadLocal instances = new ThreadLocal();
     
    public static ThreadLocalRegistry getInstance() {
        return (ThreadLocalRegistry) instances.get();
    }
  • こんな感じにスレッドローカル変数を作れる
    • instances自体はプロセススコープ。辞書みたいなヤツ
    • instances.get()すると、処理中のスレッドをキーに、スレッド固有のインスタンスを取り出してくれる
    • instances.set(何か)すると、処理中のスレッドをキーに、スレッド固有のインスタンスに何かをセットしてくれる

英語

  • asylum
    • 亡命

PoEAA ch18 Gateway

martinfowler.com


Gateway

f:id:wand_ta:20190521000324p:plain

An object that encapsulates access to an external system or resource.

  • OOシステムはオブジェクトではない外部リソースを取り扱わねばならないことがある
  • 外部リソースはそれ専用のAPIをもつ
  • そのまま使うのはよくない
    • プログラムを理解しづらくなる
    • 将来RDBXMLに置換できなくなる
  • オブジェクトでラップしろ

How It Works

  • やることは至ってシンプルなラッピング
  • 用途を満たすシンプルなAPI(=メソッドとか)を定義し、外部リソースの操作にマッピングする
  • GatewayService Stubのよい適用場所でもある
    • 後述
  • Service Stubを適用しやすい設計にするのは重要なこと
    • テスト容易性
  • Gatewayはシンプルに保て
    • 一番大事な役割は...
      • 外部リソースのラッピング・クライアントコードの要求への適合
      • スタブの適用箇所の提供
    • これらを満足できる程度の最小のものであるべき
    • 複雑なロジックはクライアントコードに任せろ
  • コード自動生成を使うのは良いアイデア
  • 2つ以上のオブジェクトでGatewayを作るのもよい
    • バックエンドとフロントエンド
      • バックエンド: ミニマルなラッパ
      • フロントエンド: バックエンドを叩き、シンプルなAPIを提供する
        • 【補】GoFFacade Pattern
    • 外部サービスのラッピングと、クライアントコードの要求への適合が相応に複雑ならば良い
    • 単純なら分けるほどではない

When to Use It

  • 外部のもの感があり、インタフェースがぎこちない(OOPと合わない)ものがあればGateway適用を検討せよ
    • ぎこちなさをシステム全体に波及させるよりも、Gatewayで閉じ込める
    • Gatewayで包むデメリットはまずない
    • クライアントコード読みやすくなる
  • Gatewayを導入するとテストが容易になる
    • Service Stubsの明確な適用箇所
    • 外部リソースのインタフェースがGatewayで包むまでもなくOOPに適合していても、
      Service Stubの適用のためにGatewayで包むのは有用
  • Gatewayでを導入すると置換可能になる
    • 【補】クライアントコードをinterfaceに依存させれば、特定の実装には依存しなくなる
    • テスト用Gatewayへの置換も可能(後述)

f:id:wand_ta:20190521000426p:plain
別の外部サービスに置換可能

  • Mapperという別の選択肢
    • 外部リソースとの結合を切り離す
    • Gatewayよりも複雑
      • クライアントと外部リソースを互いにignorantにするため

f:id:wand_ta:20190521000451p:plain
Mapper

  • これって改めてデザインパターンとして挙げるほどのものか?
    • GoFのなにがしかと同じじゃないの?
    • 下記の理由につき区別した
      • Facadeとの違い
        • Facade
          • 複雑なAPIの単純化
          • 汎用
          • 異なるインタフェースが裏にあることを示唆
        • Gateway
          • 特定用途
          • 外部リソースのAPIへの単なるマッピングだったりする
          • 置換やテストを旨とする
      • Adapterとの違い
        • Adapter
          • 既存のインタフェースに寄せるやつ
        • Gateway
          • 既存のインタフェースは(普通)ない
          • Adapterパターンをとることもある
            • GatewayインタフェースがAdapterのインタフェース(Target)
            • 外部リソースの実装を包むのがAdapter
            • 外部リソースがAdaptee
          • この場合adapterはGatewayの実装の一部
      • Mediatorとの違い
        • Mediator
          • 各クラスはMediatorを知っている
        • Gateway
          • Gatewayに包まれる外部リソースはGatewayのことを知らない
      • 【疑問】Bridgeパターンとはどう違うの
        • 既存のインタフェースはないが、様々な実装ありきでインタフェースを作る点がそっくり
        • Bridgeパターンは「種類の継承ツリーと実装の継承ツリーとを分離する」ことを旨とするから違うのかな
        • Facade, Adapter, Mediatorよりは似てると思う

Example: A Gateway to a Proprietary Messaging Service (Java)

  • コード略(pp.468-472)
  • Gatewayのインタフェースについて
    • send('CNFRM', [1, 10, 'hoge'])
      • 汎用
    • sendConfirmation(1, 10, 'hoge')
      • 特定用途
    • 良し悪し
      • 汎用インタフェース
        • 引数の正当性が静的にわからない
          • typoでエラー出たりしそう
      • 特定用途インタフェース
        • 包んでいる外部リソースに追加変更があるたびにGatewayクラスに変更が生じる
          • 実装の単純なマッピングじゃないから
          • 自動生成じゃないから
    • 両方あるとよい
  • 戻り値から例外へのマッピング
    • exit 0: 正常終了
    • ほか: 異常終了
  • Gatewayのスタブ
    • テスト用protectedメソッドを生やしておく
      • 外部リソースの操作を単に包むだけのやつ
    • 本メソッドをオーバライドして、外部サービスへのアクセスをモックする
      • 【補】サービス自体のスタブを作るわけではない
  • もう一つのテスト方法: Service Stub
    • 外部サービス自体のスタブを作る
      • 【補】JSON Serverとか
    • スタブの開発作業が困難でなければうまくいく
  • GatewayのスタブとServiceのスタブを併用するのもよい
    • Gatewayのスタブ: クライアントコードのテスト用
    • Serviceのスタブ: Gatewayのテスト用
  • Pluginパターンを併用することで、本物とスタブの選択をconfiguration timeまで遅延できる

f:id:wand_ta:20190521000648p:plain
本物のGatewayを継承してテスト用Gatewayを作る(interface切ったほうが行儀いいと思う)

f:id:wand_ta:20190521000703p:plain
Serivce Stub ... サービスのスタブでGatewayをテスト


英語

  • do the trick
    • うまくいく
  • ludicrous
    • ばかばかしい
  • laudable
    • 称賛に値する

PoEAA ch18 Separated Interface

martinfowler.com


Separated Interface

f:id:wand_ta:20190519200908p:plain

Defines An Interface In A Separate Package From Its Implementation.

  • 開発を進めるにあたり、システムの部品間の結合を減らして品質を高めることがある
  • どうやる
  • クラスをパッケージにまとめ、パッケージ単位で依存性を管理する
    • DomainパッケージからPresentationパッケージを呼ばない、等
  • 一般的な依存構造に反してメソッドを呼ばないといけないことがある
    • 知っていてはならないはずのものを呼ぶ
      • Domain ObjectがDataMapperを呼ぶとか
  • こういうときはインタフェースだけ別パッケージに切り出す

How It Works

  • 依存方向
    • クライアント -> インタフェース
    • 実装 -> インタフェース
  • クライアントは特定の実装に依存しない
  • もちろん、遅くともruntimeまでには実装クラスを決定しないとプログラムは動かない
    • compile timeに決定
    • configuration timeに決定 ... Plugin
  • インタフェースを別パッケージに切り出してもよい
  • 誰がインタフェース定義に責任をもつか
    • クライアントが責任をもつなら、クライアントパッケージに同梱
    • そうでない場合や、実装側が責任をもつなら、別パッケージに切り出す
      • 複数のクライアントから使用する場合など
  • 言語機構
    • interface
      • 実装を強制する
    • abstract class
      • デフォルト実装を提供できる
        • 任意でoverride
  • どうやってインスタンシエートするの
    • 具象クラスと、そのインスタンシエート方法の知識が必要
    • Factory classつくる
      • Factoryのインタフェースを再度切り出す
        • 【補】GoFAbstract Factory / Factory Method 的な
      • Factoryの実装クラスはconfiguration timeに決定する(Plugin)
      • 依存性が完全になくなるわけではないが、実装の選択がconfiguration timeにまで遅延される
        • 【補】遅延束縛(late-binding)
    • インタフェースと実装クラスの両方を知っているパッケージを用意する
      • 【補】LaravelのServiceProvider/DIコンテナがまさにこれ
      • アプリケーション起動時に適切にインスタンシエートする
        • 実装クラス自身がインスタンシエート方法を知っている
        • ファクトリに作らせる

When to Use It

  • 使いどころの例
    • F/Wから特定のアプリケーション用のコードを呼ばせる
      • F/Wは「特定のアプリケーション」なんか知らないので、インタフェースを提供する
      • 特定のアプリケーション用のコードは、当該インタフェースを実装/継承して作る
    • 呼び出したいが、特定の実装に依存したくない
      • 例えば、Domain ModelからDataMapper
      • Domain ModelからはDataMapperのインタフェースのメソッドを呼ぶようにする
      • 特定の実装には依存しなくなる
    • 別の開発グループによって開発された関数を呼び出す
      • そのAPIへの依存性を持ち込みたくない
  • 全クラスにinterfaceを切るのはやりすぎ
    • 余計な仕事
    • 使いどころを絞る
      • 依存を切り離したい場合
      • 独立した実装が複数ある場合
        • 【補】データソースとしてRDBとNoSQLとファイルがある、とか
    • interfaceを切って依存を切り離すのは軽微なリファクタリングなので、必要になるまで行わなくてよい
  • 依存性の管理について
  • 通常は、特定の実装クラスへの依存は、オブジェクト生成時にのみ必要となる
    • 以降はinterfaceに依存すれば十分なはず
  • それを強制し始めると困難が生じる
    • ビルド時に依存性チェックを行う等
    • すべての依存を排除しなければならない
  • 小規模システムでは割に合わない
    • 余計な依存があってもあまり問題にならない
  • 大規模システムでは遵守するに値する

PoEAA ch18 Mapper

martinfowler.com


Mapper

An object that sets up a communication between two independent objects.

  • 2つのサブシステムをつなぐ
  • 疎結合に保ちたい
    • 変更できないから
    • 変更できるにしても、依存させたくないから

f:id:wand_ta:20190519180433p:plain
Mapper

How It Works

  • サブシステムをつなぐ
  • コミュニケーションの詳細を制御する
    • 両サブシステムが意識しなくて良いように
  • shuffle
  • mapperをどうやって起動するか?
    • サブシステムからmapperを直接起動しては意味がない
      • サブシステムが何かに依存するのを避けるためのmapperなのだから
    • 第三のサブシステムを設けてmapper起動する
    • Observer Pattern取り入れる
      • サブシステム: Publisher
      • mapper: Observer
      • mapperはイベントをリッスンして起動する
  • mapperがどう動作するかは、マッピング対象の層の種類による
    • Data Mapperとか

When to Use It

  • システムの部品の結合をほぐす
  • MapperにするかGatewayにするか

f:id:wand_ta:20190519180452p:plain
Gateway (Mapperとは依存の方向が異なる)

  • Gatewayのほうが、書くにも使うにもシンプル
  • サブシステム間で互いに依存がないことを保証する必要がある場合にのみMapper使え
    • 【補】片方向依存があってもいいならGateway
    • サブシステム間の相互作用がとりわけ複雑
    • それでいて、サブシステムの主要な目的には関係がない
  • 例えばどんなとき
    • Domain ModelとDBとのO/Rマッピング(Data Mapper)
      • データ変換は複雑
      • Domain ModelはDBのことは意識するべきではない
      • DBも誰に使われるか知っているべきではない
  • GoFMediatorに似ている
  • 異なる点
    • Mediator Pattern: 各クラスはMediatorを知っている
    • Mapper Pattern: 各クラスはMapperを知らない

f:id:wand_ta:20190519180510p:plain
GoFのMediator Pattern (双方向依存)

DebianベースのcomposerコンテナでphpDocumentorを動かした話


TL;DR

  • alpineベースだとうまく動かないのでdebianベース等使え

動機

  • composerとかがなかった太古の時代のソースコードのドキュメント自動生成
    • したがって、composer配下ではない。composer.jsonとか置いてない
  • 普段遣いの環境にPHPを直接入れたくないので、dockerコンテナで実行

構成

  • windowsで作業していたのでtreeコマンドコンテナ使用
docker container run --rm -v ${pwd}:/root wandta/tree -L 3
.
├── app
│   ├── composer.json
│   └── src
│      └── Hoge.php
└── docker-compose.yml
  • docker-compose.yml
version: "3"
services:
  composer:
    image: composer
    volumes:
      - ./app:/app
  • app/composer.json
    • 普通にcomposer require phpdocumentor/phpdocumentorすると動かない('19/5/18現在)
    • ので真面目にcomposer.json書く
{
    "require-dev": {
        "phpdocumentor/phpdocumentor": "^2.9",
        "jms/serializer": "1.7.*"
    },
    "scripts": {
        "doc": [
            "vendor/bin/phpdoc -d src -t doc"
        ]
    }
}
<?php

/**
 * Hogeクラス
 */
class Hoge
{
    /** @var int */
    private $member = 3;

    /**
     * @param int $input 入力
     * @return int 出力
     */
    function method (int $input): int
    {
        return 3;
    }
}

phpDocumentor実行、エラー出る

docker-compose run --rm composer composer install
docker-compose run --rm composer composer run doc

すごい勢いでエラー出る

> vendor/bin/phpdoc -d src -t doc
Collecting files .. OK
Initializing parser .. OK
Parsing files
Parsing /app/src/Hoge.php
Storing cache in "/app/doc" .. OK
Load cache                                                         ..    0.015s
Preparing template "clean"                                         ..    0.033s
Preparing 17 transformations                                       ..    0.000s
Build "elements" index                                             ..    0.000s
Replace textual FQCNs with object aliases                          ..    0.000s
Resolve @link and @see tags in descriptions                        ..    0.000s
Enriches inline example tags with their sources                    ..    0.000s
Build "packages" index                                             ..    0.004s
Build "namespaces" index and add namespaces to "elements"          ..    0.000s
Collect all markers embedded in tags                               ..    0.000s
Transform analyzed project into artifacts                          .. 
Applying 17 transformations
  Initialize writer "phpDocumentor\Plugin\Core\Transformer\Writer\FileIo"
  Initialize writer "phpDocumentor\Plugin\Twig\Writer\Twig"
  Initialize writer "phpDocumentor\Plugin\Graphs\Writer\Graph"
  Execute transformation using writer "FileIo"
  Execute transformation using writer "FileIo"
  Execute transformation using writer "FileIo"
  Execute transformation using writer "FileIo"
  Execute transformation using writer "FileIo"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"

Warning: file_put_contents(/app/doc//): failed to open stream: Is a directory in /app/vendor/phpdocumentor/phpdocumentor/src/phpDocumentor/Plugin/Twig/Writer/Twig.php on line 117
  Execute transformation using writer "twig"

Warning: file_put_contents(/app/doc//): failed to open stream: Is a directory in /app/vendor/phpdocumentor/phpdocumentor/src/phpDocumentor/Plugin/Twig/Writer/Twig.php on line 117
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"

Warning: file_put_contents(/app/doc//): failed to open stream: Is a directory in /app/vendor/phpdocumentor/phpdocumentor/src/phpDocumentor/Plugin/Twig/Writer/Twig.php on line 117
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "Graph"
Unable to find the `dot` command of the GraphViz package. Is GraphViz correctly installed and present in your path?   0.566s
Analyze results and write report to log                            ..    0.000s

alpineベースだとうまく動かないよう

  • パスの連結まわりでエラーが出てるっぽい
Warning: file_put_contents(/app/doc//): 

It looks like it has to do something with the dubble / in your target path.
But I don't know where this is comming from. Please check your config for that.

FROM php:7-alpine

...

COPY docker-entrypoint.sh /docker-entrypoint.sh

WORKDIR /app

ENTRYPOINT ["/bin/sh", "/docker-entrypoint.sh"]

CMD ["composer"]

debianベースのcomposerイメージ作る

  • マルチステージビルド的なことをする
    • composer公式イメージから、composer実行ファイルのみ引っこ抜いてdebianベースのphpイメージに突っ込む
  • そもそもcomposerがどこにあるのかをまず確認
docker-compose run --rm composer composer install
/usr/bin/composer
  • Dockerfile書く
  • composer-debian/Dockerfile
FROM composer as composer


FROM php:7.3.5-cli-stretch

COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN chmod +x /usr/bin/composer

# composerでパッケージ取得するためにGitとunzip入れる
RUN apt-get update -y && apt-get upgrade -y
RUN apt-get install -y git unzip

WORKDIR /app

CMD ["composer"]
  • イメージビルド必要
  version: "3"
  services:
    composer:
-     image: composer
+     build:
+       context: .
+       dockerfile: ./composer-debian/Dockerfile
      volumes:
        - ./app:/app
docker-compose build
  • 再度phpDocumentor実行
docker-compose run --rm composer composer run doc
Collecting files .. OK
Initializing parser .. OK
Parsing files
Parsing /app/src/Hoge.php

Warning: count(): Parameter must be an array or an object that implements Countable in /app/vendor/phpdocumentor/phpdocumentor/src/phpDocumentor/Plugin/Core/Descriptor/Validator/Constraints/Functions/IsArgumentInDocBlockValidator.php on line 33
  No summary was found for this file
  No summary for method method()
  No summary for property $member
Storing cache in "/app/doc" .. OK
Load cache                                                         ..    0.007s
Preparing template "clean"                                         ..    0.016s
Preparing 17 transformations                                       ..    0.000s
Build "elements" index                                             ..    0.000s
Replace textual FQCNs with object aliases                          ..    0.000s
Resolve @link and @see tags in descriptions                        ..    0.000s
Enriches inline example tags with their sources                    ..    0.000s
Build "packages" index                                             ..    0.003s
Build "namespaces" index and add namespaces to "elements"          ..    0.000s
Collect all markers embedded in tags                               ..    0.000s
Transform analyzed project into artifacts                          .. 
Applying 17 transformations
  Initialize writer "phpDocumentor\Plugin\Core\Transformer\Writer\FileIo"
  Initialize writer "phpDocumentor\Plugin\Twig\Writer\Twig"
  Initialize writer "phpDocumentor\Plugin\Graphs\Writer\Graph"
  Execute transformation using writer "FileIo"
  Execute transformation using writer "FileIo"
  Execute transformation using writer "FileIo"
  Execute transformation using writer "FileIo"
  Execute transformation using writer "FileIo"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "twig"
  Execute transformation using writer "Graph"
Unable to find the `dot` command of the GraphViz package. Is GraphViz correctly installed and present in your path?   0.611s
Analyze results and write report to log                            ..    0.000s
  • failed to open streamエラーが消えた
  • いい感じに出力できている
    • app/doc/classes/Hoge.html

f:id:wand_ta:20190518233525p:plain

phpDocumentorさえ使えればいい場合

  • これを使えばよかったみたい(徒労)

PoEAA ch10 Active Record

martinfowler.com


Active Record

An object that wraps a row in a database table or view,
encapulates the database access, and adds domain logic on that data.

How It Works

  • Active RecordはDBアクセスとドメインロジックの責務を有する
  • どれくらいドメインロジック持つ?
    • ぜんぶ
    • 共通でデータ指向な部分のみ
      • そうでない部分はTransaction Scriptにもたせる
  • クラスのフィールドとテーブルのカラムが一致すること
  • テーブルに限らずビューでもいい
    • UPDATE大変
  • Active Recordクラスが有するメソッド
    • SQL結果からActive Record生成
    • 表へのINSERTからActive Record生成
    • staticなfinderメソッド
    • Active Recordが保持しているデータでUPDATE/INSERT
    • getter/setter
    • ドメインロジック
  • getter/setter: SQLの型ではなく、使用している言語の型
  • 関連する表のActive Recordを返すgetterもありうる
    • 【補】外部キーで参照してコレクション得るとかそういうの
    • 【補】LaravelのEloquentのリレーション
  • RDBの存在は一切隠蔽されない
    • 【訳が怪しい】他のパターンよりもクラス数少ない?
  • Row Data Gatewayとどう違うの
    • Row Data GatewayはDBアクセスのみ
    • Active Recordはドメインロジック含む
    • 線引きはわりとあいまい
  • DBと密結合していることもあり、staticなfinderメソッドが生やされるのをよく見る
  • Row Data Gatewayよろしく、Finderクラスを作ってもよい
    • テスト容易性にすぐれる

When To Use It

  • ドメインロジックがさほど複雑でない場合
    • CRUD
    • 導出やバリデーションも単一レコード単位
      • 【補】導出: 面積=幅x高さ とかそういうやつ
  • ドメイン層にDomain Modelを採用した場合、データソース層の選択はおもに2つ
Active Record Data Mapper
メリット シンプル 複雑
デメリット DBのスキーマに引きずられる 複雑なマッピングできる
  • Active Recordはオブジェクト設計とDB設計とを結合させてしまうのでリファクタが大変、という指摘もある
  • ドメイン層にTransaction Script採用時に、多重化したコードを繰り出すのに使うのは良い
    • まずDBをRow Data Gatewayでラップする
    • 振る舞いを移植する
    • ちょっとずつActive Recordになる

Example: A Simple Person (Java)

  • ほぼ略
  • Derivation(導出)をもたせたりする(=ドメインロジック)
    • UMLのクラス図でいうところの/で始まるフィールド
class Person...

    public Money getExemption() {
        Money baseExemption = Money.dollars(1500);
        Money dependentExemption = Money.dollars(750);
        return baseExemption.add(dependentExemption.multiply(this.getNumberOfDependents()));
    }
  • number_of_dependentsカラムから税控除(exemption)を導出している

英語

  • exemption
    • 税控除