Upgrade to Pro — share decks privately, control downloads, hide ads and more …

LaravelユーザーのためのBEAR.Sunday入門

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

 LaravelユーザーのためのBEAR.Sunday入門

ver 0.2.0

Avatar for Akihito Koriyama

Akihito Koriyama

October 20, 2026

More Decks by Akihito Koriyama

Other Decks in Technology

Transcript

  1. PHP FRAMEWORK ユーザーのための Laravel BEAR.Sunday 自習用テキスト 年 月 2026 10

    入門 暗黙的でフレームワーク中心の書き方から、宣言的で リソース中心の書き方へ。ブログを題材に、Laravel 11と BEAR.Sunday 1.xを並べて読みます。
  2. 目次 01 02 03 04 05 考え方の違い リソースを書く 依存と横断的関心事 データ・キャッシュ・テスト

    フレームワークとドメイン ユーザーのためのBEAR.Sunday入門 3 · Laravel
  3. 考え方の違い 拠り所の違い 01 · :開発者体験(DX) : と Laravel BEAR.Sunday OOP

    REST コミュニティが重視する書きやすさや生産性を基準に、規約や ヘルパーが設計されています。 オブジェクト指向とRESTという原則を基準に、DI・AOP・ リソースが設計されています。 基準は書く人の側にあり、時代とともに変化します。 基準は書く人の外側にあり、時代が変わっても変わりません。 ユーザーのためのBEAR.Sunday入門 5 · Laravel
  4. 考え方の違い 暗黙と宣言 01 · :規約と暗黙の解決 :宣言と明示 Laravel BEAR.Sunday ファサード、ヘルパー、ルートモデルバインディングなどに より、少ない記述でフレームワークが多くを補います。

    依存はコンストラクタ、振る舞いはアトリビュート、束縛は モジュールに書きます。コードを読めば動作がわかる構成です。 ユーザーのためのBEAR.Sunday入門 6 · Laravel
  5. 01 · 考え方の違い での暗黙的な記述 Laravel コードに現れない処理 は、ルートの {post} からモデルが 自動で取得されます。

    PostController Post $post // routes/web.php Route::get('/posts/{post}', [PostController::class, 'show']); や view() はグローバルヘルパーで、依存は シグネチャに現れません。 // PostController public function show(Post $post) { $this->authorize('view', $post); return view('posts.show', [ 'post' => $post, 'user' => auth()->user(), ]); } auth() ユーザーのためのBEAR.Sunday入門 7 · Laravel
  6. 01 · 考え方の違い での宣言的な記述 BEAR.Sunday コードに書かれた処理 キャッシュすること、どのリソースを含むかを、 アトリビュートで宣言します。 入力は引数、依存はコンストラクタに書くので、 クラス単体で読めます。

    Page/Posts.php #[CacheableResponse] class Posts extends ResourceObject { #[Embed(rel: 'posts', src: 'app://self/posts')] public function onGet(): static { return $this; } } ユーザーのためのBEAR.Sunday入門 8 · Laravel
  7. 考え方の違い つの基盤 01 · 3 Ray.Di Ray.Aop リソース 依存をコンストラクタで 受け取り、束縛を

    モジュールにまとめます。 ログやトランザクションなど 横断的な処理を、 アトリビュートで 適用します。 機能をURIとHTTPメソッドで 表します。ページの中で使う データも、同じURIで 呼びます。 ユーザーのためのBEAR.Sunday入門 9 · Laravel
  8. 考え方の違い 継承と合成 01 · :継承で機能を得る Laravel class Post extends Model

    { // save(), find(), where(), // toArray(), relations ... // 多数のメソッドを継承する } // コントローラもヘルパーや // トレイトで機能を得る :合成で機能を得る BEAR.Sunday class Post extends ResourceObject { // 親クラスのメソッドは呼ばない public function __construct( private PostItemInterface $postItem, private LoggerInterface $logger, ) {} } // 機能はすべて注入された // オブジェクトから得る ユーザーのためのBEAR.Sunday入門 10 · Laravel
  9. 考え方の違い 概念の対応表 01 · L AR AVEL BEAR.SUNDAY ルート+コントローラ Page

    サービスプロバイダ Ray.Di ミドルウェア Ray.Aop Eloquent Ray.MediaQuery 補足 リソース パスとクラスが対応する モジュール 束縛をコードで宣言する インターセプタ 任意のメソッドに適用できる とインターフェースで記述 SQL ユーザーのためのBEAR.Sunday入門 11 · Laravel
  10. 考え方の違い 概念の対応表(続き) 01 · L AR AVEL BEAR.SUNDAY 補足 Blade

    Qiq リソースのbodyを描画する FormRequest #[JsonSchema] 入出力をJSON Schemaで検証する Cache::remember #[CacheableResponse] イベント駆動で無効化する テスト Feature テスト、リソーステスト、 ハイパーメディアテスト HTTP からもURIからも呼べる HTTP ユーザーのためのBEAR.Sunday入門 12 · Laravel
  11. 01 · 考え方の違い の特徴的な機能 BEAR.Sunday 中心のイベント駆動キャッシュ:更新までCDNと304で応答し続け、更新の 瞬間に依存するページまで連鎖して入れ替わる CDN アプリケーションの合成:別アプリを、マイクロサービスにせず同じ ランタイムで動かす

    コンテキスト別束縛:同じコードが、束縛の違いでJSONにもHTMLにも 並列実行にもなる と インターフェースの束縛:実装クラスを書かずにSQLを実行する SQL PHP ユーザーのためのBEAR.Sunday入門 13 · Laravel
  12. 考え方の違い なぜそれが可能になったか 01 · 中心のイベント駆動キャッシュ:REST原則に従い、リソースが依存を で宣言しているから CDN URI アプリケーションの合成:OOPとRESTの原則に従い、構成がアプリごとに 閉じ、機能がURIで呼べるから

    コンテキスト別束縛:OOP原則に従い、コードが抽象にだけ依存しているから と インターフェースの束縛:Nullオブジェクトを束縛し、AOPでSQLの 実行を織り込んだから SQL PHP ユーザーのためのBEAR.Sunday入門 14 · Laravel
  13. 考え方の違い 設計の表面積 01 · 観点 L AR AVEL カバーする範囲 認証・キュー・メール・

    フロントまで揃える BEAR.SUNDAY 記述 規約とヘルパーが記述を引き受ける 依存と振る舞いをコードに明示する 情報源 書籍・記事・動画 公式マニュアル 更新 毎年メジャーバージョン 1.0 リソース層に絞り、ほかはライブラリを 選んで組み合わせる 以降、後方互換を保つ ユーザーのためのBEAR.Sunday入門 16 · Laravel
  14. 02 · リソースを書く が情報の単位 URI ページもデータもリソース page://self/posts page://self/post app://self/posts //

    記事一覧ページ // 記事詳細ページ // 記事データ でアドレスを持つのは、HTTPのルートだけです。 コントローラーの中のデータ取得は、外から呼べません。 Laravel どこからでも同じURIで呼ぶ // HTTP GET /posts // CLI php bin/page.php get /posts // テスト・ほかのリソース $this->resource->get('app://self/posts'); では、ページの中で使うデータも、URIで 外から呼べます。 BEAR.Sunday ユーザーのためのBEAR.Sunday入門 18 · Laravel
  15. リソースを書く ルーティングとファイル配置 02 · : Laravel routes/web.php Route::get('/posts', [PostController::class, 'index']);

    Route::get('/posts/{post}', [PostController::class, 'show']); Route::post('/posts', [PostController::class, 'store']); : BEAR.Sunday src/Resource src/Resource/ ├── Page/ │ ├── Posts.php # /posts │ └── Post.php # /post └── App/ ├── Posts.php # app://self/posts └── Post.php # app://self/post ユーザーのためのBEAR.Sunday入門 19 · Laravel
  16. リソースを書く 記事一覧:Laravel 02 · PostController::index public function index() { return

    view('posts.index', [ 'posts' => Post::latest()->get(), ]); } posts/index.blade.php <ul> @foreach ($posts as $post) <li> <a href="{{ route('posts.show', $post) }}"> {{ $post->title }} </a> </li> @endforeach </ul> ユーザーのためのBEAR.Sunday入門 20 · Laravel
  17. リソースを書く 記事一覧:BEAR.Sunday 02 · src/Resource/Page/Posts.php class Posts extends ResourceObject {

    #[Embed(rel: 'posts', src: 'app://self/posts')] public function onGet(): static { return $this; } } var/qiq/template/Page/Posts.php <ul> {{ foreach ($posts as $post): }} <li> <a href="/post?id={{u $post['id'] }}"> {{h $post['title'] }} </a> </li> {{ endforeach }} </ul> ユーザーのためのBEAR.Sunday入門 21 · Laravel
  18. 02 · リソースを書く でリソースを呼ぶ URI app://self/posts は外部に公開する ページ、app:// は アプリケーション内部の

    リソースです。 page:// ページでは #[Embed] でURIを宣言し、テストでは リソースクライアントから同じURIで呼び出します。 ユーザーのためのBEAR.Sunday入門 22 · Laravel
  19. リソースを書く アプリケーションの合成 02 · 複数アプリの協調 : つのプロセスで2つのアプリを同時に動かせないため、 アプリを分けるとマイクロサービスとしてネットワーク越しに 連携します。 Laravel

    1 :アプリごとにDIとAOPの構成が完結している ため、別アプリをインポートして実行時に合成できます。 BEAR.Sunday 呼び出しは同一プロセス内で、自身のリソースとの違いは URIのホスト名だけです。 AppModule と呼び出し // AppModule::configure() $this->install(new ImportAppModule([ new ImportApp( 'comment', 'MyVendor\Comment', 'prod-app', ), ])); // 自身のリソースと同じ形で #[Embed(rel: 'comments', src: 'app://comment/comments{?id}')] ユーザーのためのBEAR.Sunday入門 23 · Laravel
  20. リソースを書く 合成とシステム境界 02 · 境界はアプリケーション マイクロサービスと同じく小さなアプリの集合として構築でき、 インフラのオーバーヘッドは増えません。 境界がnamespaceの規約ではなくアプリそのものなので、 モジュラーモノリスより境界が明確です。 アプリはパッケージとして、Laravelなど他の

    フレームワークからも呼び出せます。 BEAR から呼び出す Laravel // composer で BEAR アプリを入れておく $resource = Injector::getInstance( 'MyVendor\Comment', 'prod-api-app', base_path('vendor/my-vendor/comment'), )->getInstance(ResourceInterface::class); $comments = $resource->get( '/comments', ['id' => 1] ); ユーザーのためのBEAR.Sunday入門 24 · Laravel
  21. リソースを書く 記事詳細 02 · : Laravel PostController::show // GET /posts/{post}

    public function show(Post $post) { return view('posts.show', [ 'post' => $post, ]); } : BEAR.Sunday Page/Post.php // GET /post?id=1 class Post extends ResourceObject { public function __construct( private PostItemInterface $postItem, ) {} } public function onGet(int $id): static { $this->body = ($this->postItem)($id); return $this; } ユーザーのためのBEAR.Sunday入門 25 · Laravel
  22. リソースを書く 入力の受け取り方 02 · : から取り出す Laravel Request public function

    store(Request $request) { $title = $request->input('title'); $body = $request->input('body'); // ... } 何を受け取るかは、メソッドの中を読むまでわかりません。 :引数で宣言する BEAR.Sunday public function onPost( string $title, string $body, ): static { // ... } シグネチャが入力の仕様です。HTTP、CLI、テストのどこから でも同じ引数で呼べます。 ユーザーのためのBEAR.Sunday入門 26 · Laravel
  23. リソースを書く 記事投稿 02 · : Laravel PostController::store public function store(StorePostRequest

    $request) { $post = Post::create( $request->validated() ); } return redirect() ->route('posts.show', $post); : BEAR.Sunday Page/Post.php #[JsonSchema(params: 'post.add.json')] public function onPost( string $title, string $body, ): static { $id = $this->postAdd->add($title, $body)->id; $this->code = 201; $this->headers['Location'] = "/post?id={$id}"; } return $this; ユーザーのためのBEAR.Sunday入門 27 · Laravel
  24. リソースを書く 入力の検証 02 · : Laravel StorePostRequest class StorePostRequest extends

    FormRequest { public function rules(): array { return [ 'title' => 'required|max:100', 'body' => 'required', ]; } } var/json_validate/post.add.json { } "type": "object", "required": ["title", "body"], "properties": { "title": { "type": "string", "maxLength": 100 }, "body": { "type": "string" } } ユーザーのためのBEAR.Sunday入門 28 · Laravel
  25. リソースを書く 出力の検証 02 · 出力もスキーマで守る では、出力の形をAPI Resourceで整えます。スキーマに よる検証は標準では行いません。 Laravel では、リソースのbodyをJSON

    Schemaで 検証します。スキーマはAPIドキュメントの元にもなります。 BEAR.Sunday Page/Post.php とスキーマ #[JsonSchema(schema: 'post.json')] public function onGet(int $id): static // var/json_schema/post.json { "type": "object", "required": ["id", "title", "body"], "properties": { "id": { "type": "integer" }, "title": { "type": "string" }, "body": { "type": "string" } } } ユーザーのためのBEAR.Sunday入門 29 · Laravel
  26. 02 · リソースを書く ドキュメントの自動生成 API 宣言がそのまま文書になる BEAR.ApiDoc Laravel では、注釈や推論でドキュメントを作り、実装との一致は OpenAPIでレスポンスを検証するテストで確かめます。

    $ composer require bear/api-doc --dev $ ./vendor/bin/apidoc init $ ./vendor/bin/apidoc 引数、アトリビュート、JSON Schemaがコードに 宣言されているので、ドキュメントをアプリケーションから 生成できます。実装と乖離しません。 docs/ ├── index.html ├── openapi.json ├── llms.txt └── schemas/ # APIドキュメント # OpenAPI 3.1 # AI向け概要 # JSON Schema ユーザーのためのBEAR.Sunday入門 30 · Laravel
  27. リソースを書く コンテキストで表現を切り替える 02 · 同じリソース、別の表現 では、コントローラの中で view() とJSONレスポンスを 書き分けます。 Laravel

    コンテキストによってモジュールの組み合わせが変わり、 同じリソースがHTMLにもJSONにもなります。並列実行への切り 替えも、リソースのコードを変えずに行えます。 HtmlModule とコンテキスト // src/Module/HtmlModule.php $this->install(new QiqModule($qiqDir)); # コンテキストの例 html-app Qiqで描画したHTML hal-app HAL+JSON prod-html-app 本番設定+HTML prod-async-html-app #[Embed]を並列取得 ユーザーのためのBEAR.Sunday入門 31 · Laravel
  28. リソースを書く モードとコンテキスト 02 · : Laravel APP_ENV / APP_DEBUG #

    .env APP_ENV=production APP_DEBUG=false // コードがフラグを読んで分岐する if (app()->isProduction()) { // ... } コードが config('app.debug') などを読んで、分岐します。 : BEAR.Sunday prod-html-app // 3つのモジュールの組み合わせ prod ProdModule 本番用の束縛 html HtmlModule Qiqで描画 app AppModule アプリの束縛 // 束縛が入れ替わり、分岐はない コードはモードを読みません。違いは、注入される 依存だけです。 ユーザーのためのBEAR.Sunday入門 32 · Laravel
  29. 依存と横断的関心事 ランタイム解決とコンパイル解決 03 · : Laravel AppServiceProvider class AppServiceProvider extends

    ServiceProvider { public function register(): void { $this->app->bind( Clock::class, SystemClock::class, ); } } : と生成コード BEAR.Sunday AppModule // AppModule $this->bind(Clock::class) ->to(SystemClock::class); // 実行時に動くのは、解決済みのコード $instance = new SystemClock(); return $instance; 依存の解決は、原則として実行前に済んでいます。 依存の解決は、リクエストのたびに実行時に行われます。 ユーザーのためのBEAR.Sunday入門 34 · Laravel
  30. 依存と横断的関心事 依存の受け取り方 03 · :ファサードとヘルパー Laravel public function store(Request $request)

    { Log::info('posted'); Cache::forget('posts'); $user = auth()->user(); $token = config('services.api.token'); // ... } コンストラクタ注入もできますが、主流はファサードと ヘルパーです。 :コンストラクタ注入 BEAR.Sunday public function __construct( private LoggerInterface $logger, private PostAddInterface $postAdd, #[Named('api_token')] private string $token, ) {} // モジュール $this->bind()->annotatedWith('api_token') ->toInstance($_ENV['API_TOKEN']); サービスも設定値も、依存は必ずシグネチャに現れます。 ユーザーのためのBEAR.Sunday入門 35 · Laravel
  31. 依存と横断的関心事 ミドルウェアとインターセプタ 03 · 適用する範囲 ミドルウェアは、HTTPリクエストの入口にだけ置けます。 インターセプタは、どのメソッドにも置けます。どこに 掛けるかは条件で指定し、その束縛はコンテキストごとに 替えられます。 AppModule::configure()

    // アトリビュートで個別に $this->bindInterceptor( $this->matcher->any(), $this->matcher->annotatedWith(Loggable::class), [LoggerInterceptor::class], ); // メソッド名で全てに $this->matcher->startsWith('onPost') // 記録先:コンテキストごとにDIで替える // dev: DebugLogger、prod: SqlLogger $this->bind(LoggerInterface::class) ->to(FileLogger::class); ユーザーのためのBEAR.Sunday入門 36 · Laravel
  32. 依存と横断的関心事 トランザクションの宣言 03 · :クロージャで囲む Laravel DB::transaction(function () use ($data)

    { $post = Post::create($data); $post->tags()->sync($data['tags']); }); :アトリビュートで宣言 BEAR.Sunday #[Transactional] public function onPost( string $title, string $body, ): static { $this->postAdd->add($title, $body); $this->code = 201; } return $this; ユーザーのためのBEAR.Sunday入門 37 · Laravel
  33. 04 · データ・キャッシュ・テスト と Eloquent Ray.MediaQuery を中心に置く SQL は、モデルのメソッドをつないで、 クエリを組み立てます。

    Eloquent は、SQLを自分で書き、インターフェースに 対応づけます。実装クラスは書きません。 Ray.MediaQuery インターフェースとSQL // src/Query/PostItemInterface.php interface PostItemInterface { #[DbQuery('post_item', type: 'row')] public function __invoke(int $id): array; } -- var/sql/post_item.sql SELECT id, title, body FROM post WHERE id = :id; ユーザーのためのBEAR.Sunday入門 39 · Laravel
  34. 04 · データ・キャッシュ・テスト と の比較 ORM MediaQuery 観点 ELOQUENT 読み書き

    とチューニング SQL 戻り値 リレーション つのモデル(ActiveRecord)で 読み書きする クエリビルダが組み立てたSQLを 確認して調整する 1 モデル モデルに定義し、Eager Loadingで N+1を避ける Eloquent R AY.MEDIAQUERY 読み込みと書き込みを別の インターフェースに分ける ファイルを直接チューニングする SQL で生成したドメインオブジェクト JOIN、またはリンクのクロールで合成。 N+1はDataLoaderで解決 factory ユーザーのためのBEAR.Sunday入門 40 · Laravel
  35. データ・キャッシュ・テスト リンクをたどって結合する 04 · :リレーションで結合する Laravel // Post モデル public

    function comments() { return $this->hasMany(Comment::class); } // 呼ぶ側 Post::with('comments')->get(); 結合できるのは、同じDBのモデル同士です。 :リンクをクロールする BEAR.Sunday // app://self/posts #[Link(crawl: 'post-tree', rel: 'comments', href: 'app://self/comments?post_id={id}')] public function onGet(): static // 呼ぶ側 $this->resource->get->uri('app://self/posts') ->linkCrawl('post-tree')->eager->request(); リンク先はURIなので、DB以外のリソースも結合できます。 ユーザーのためのBEAR.Sunday入門 41 · Laravel
  36. 04 · データ・キャッシュ・テスト でドメインオブジェクトにする factory インターフェース interface UserItemInterface { #[DbQuery('user_item',

    factory: UserFactory::class)] public function __invoke(int $id): User; } -- user_item.sql SELECT name, birthdate FROM user WHERE id = :id; の birthdate が、factoryで age を持つドメインオブジェクトに なります。 DB UserFactory final class UserFactory { public function __construct( private ClockInterface $clock, ) {} public function factory( string $name, string $birthdate, ): User { return new User($name, new DateTimeImmutable($birthdate), $this->clock->now()); } } ユーザーのためのBEAR.Sunday入門 42 · Laravel
  37. データ・キャッシュ・テスト 必要な値だけを持つ 04 · :出さないものを書き足す Laravel class User extends Model

    { protected $casts = ['birthdate' => 'date']; protected $hidden = ['birthdate']; protected $appends = ['age']; protected function age(): Attribute { return Attribute::get( fn () => $this->birthdate->age); } } birthdate を持ち続け、age は読むたびに計算します。 :受け取って、age だけを持つ BEAR.Sunday final class User { public readonly int $age; public function __construct( public readonly string $name, DateTimeImmutable $birthdate, DateTimeInterface $today, ) { $this->age = $birthdate->diff($today)->y; } } birthdate は受け取るだけで、プロパティにしません。 ユーザーのためのBEAR.Sunday入門 43 · Laravel
  38. データ・キャッシュ・テスト 一覧と投稿のクエリ 04 · src/Query interface PostListInterface { #[DbQuery('post_list')] public

    function __invoke(): array; } interface PostAddInterface { #[DbQuery('post_add')] public function add( string $title, string $body ): InsertedRow; } var/sql -- post_list.sql SELECT id, title FROM post ORDER BY id DESC; -- post_add.sql INSERT INTO post (title, body) VALUES (:title, :body); ユーザーのためのBEAR.Sunday入門 44 · Laravel
  39. データ・キャッシュ・テスト 採番IDを受け取る 04 · :モデルから読む Laravel $post = Post::create([ 'title'

    => $title, ]); $id = $post->id; :戻り値の型で宣言する BEAR.Sunday interface PostAddInterface { #[DbQuery('post_add')] public function add(string $title): InsertedRow; } $id = $this->postAdd->add($title)->id; 何が返るかは、create() が決めます。 何を受け取るかは、戻り値の型で決めます。 ユーザーのためのBEAR.Sunday入門 45 · Laravel
  40. データ・キャッシュ・テスト 現在時刻を扱う 04 · :グローバルな時計 Laravel // 実装 Post::create(['published_at' =>

    now()]); // テスト Carbon::setTestNow('2026-01-01'); は共有の時計を読み、テストではその 時計を書き換えます。 now() :時刻も注入される BEAR.Sunday #[DbQuery('post_add')] public function add( ?DateTimeInterface $at = null, ): InsertedRow; // テスト用モジュール $this->bind(DateTimeInterface::class) ->to(UnixEpochTime::class); 引数を省くと現在時刻が注入されます。テストでは束縛を 替えるだけで固定できます。 ユーザーのためのBEAR.Sunday入門 46 · Laravel
  41. 04 · データ・キャッシュ・テスト を渡さない ID :モデルにトレイトを足す Laravel :型で注入される BEAR.Sunday class

    Post extends Model { use HasUuids; } #[DbQuery('post_add')] public function add( string $title, ?Uuid $id = null, ): void; // テスト Str::createUuidsUsing(fn () => $fixed); を作る処理は、モデルが持ちます。 // モジュール $this->bind(Uuid::class) ->toProvider(UuidProvider::class); ID は、束縛されたUuidが用意します。呼ぶ側は渡さなくてよい。 ID ユーザーのためのBEAR.Sunday入門 47 · Laravel
  42. データ・キャッシュ・テスト 値の出どころを知らない 04 · :呼ぶ側が用意する Laravel Post::create([ 'title' => $title,

    'author_id' => auth()->id(), ]); 値を使う場所で、出どころを読みます。 :束縛が用意する BEAR.Sunday #[DbQuery('post_add')] public function add( string $title, ?AuthorId $authorId = null, ): void; // 呼ぶ側(AuthorIdは渡しても、渡さなくてもいい) $this->postAdd->add($title); 省いたときの値を、メソッドの外で、束縛として決められます。 ユーザーのためのBEAR.Sunday入門 48 · Laravel
  43. データ・キャッシュ・テスト ドメインの型で渡す 04 · :モデルのcastsで変換する Laravel class Post extends Model

    { protected function casts(): array { return ['author_id' => AuthorIdCast::class]; } } 変換の規則は、モデルが持ちます。 : の直前で値になる BEAR.Sunday SQL final class AuthorId implements ToScalarInterface { public function __construct(private int $id) {} public function toScalar(): int { return $this->id; } } #[DbQuery('post_add')] public function add(AuthorId $authorId): void; 変換の規則は、値オブジェクト自身が持ちます。 ユーザーのためのBEAR.Sunday入門 49 · Laravel
  44. データ・キャッシュ・テスト 入力をそのまま保存へ 04 · :検証済みの配列を渡す Laravel public function store(StorePostRequest $request)

    { Post::create($request->validated()); } 検証済みの値は、配列で渡ります。 :入力オブジェクトを渡す BEAR.Sunday final class PostInput { public function __construct( #[Input] public readonly string $title, #[Input] public readonly string $body, ) {} } #[DbQuery('post_add')] public function add(PostInput $input): void; 検証済みの値は、型の付いたオブジェクトのまま渡ります。 ユーザーのためのBEAR.Sunday入門 50 · Laravel
  45. データ・キャッシュ・テスト 結果のコレクション 04 · : を継承する Laravel Collection class PostCollection

    extends Collection { public function published(): static { return $this->whereNotNull('published_at'); } } // Post::newCollection() で差し替える Collection の多くのメソッドを引き継ぎます。 :必要なものだけ持つ BEAR.Sunday // PostQuery は「クエリの後」の意味 final class PostList implements PostQueryInterface { public function __construct( public array $rows) {} public static function fromContext($c): static { return new static($c->rows); } } 継承せず、必要なメソッドだけを自分で書きます。 ユーザーのためのBEAR.Sunday入門 51 · Laravel
  46. データ・キャッシュ・テスト ページネーション 04 · :ページングのメソッドを呼ぶ Laravel $posts = Post::latest()->paginate(10); //

    ?page= で指定された // 1ページ分を取得済み リクエストのページ番号で、その1ページを取り出します。 : が ページングオブジェクトになる BEAR.Sunday SQL interface PostListInterface { #[DbQuery('post_list'), Pager(perPage: 10)] public function __invoke(): PagesInterface; } $pages = ($this->postList)(); count($pages); // ここで件数のSQL $pages[2]; // ここで2ページ目のSQL インターフェースに宣言するだけで、使ったときに SQLが走ります。 ユーザーのためのBEAR.Sunday入門 52 · Laravel
  47. データ・キャッシュ・テスト スローログから探す 04 · :生成されたSQL Laravel :ログにSQLファイルのIDが付く BEAR.Sunday # Query_time:

    2.103 SELECT * FROM `posts` WHERE `id` = 1 LIMIT 1 テーブル名からモデルを推し、組み立てたコードを 探します。発行元を残すには、laravel-sql-commenter などの パッケージを入れます。 # Query_time: 2.103 -- post_item.sql SELECT * FROM post WHERE id = 1 がそのままファイル名なので、開いて直せます。 ID ユーザーのためのBEAR.Sunday入門 53 · Laravel
  48. 04 · データ・キャッシュ・テスト の実行に処理を挟む SQL :リスナーを登録する Laravel // AppServiceProvider DB::listen(function

    ($query) { Log::info($query->sql, [ 'time' => $query->time, ]); }); :実行そのものを差し替える BEAR.Sunday // モジュール $this->bind(PerformSqlInterface::class) ->to(LoggingPerformSql::class); 束縛ひとつで、すべてのクエリの実行が替わります。 実行後に呼ばれるリスナーを足します。 ユーザーのためのBEAR.Sunday入門 54 · Laravel
  49. 04 · データ・キャッシュ・テスト も同じ形で Web API : クライアントを呼ぶ Laravel HTTP

    $res = Http::get( 'https://api.example.com/posts/' . $id ); :インターフェースに対応づける BEAR.Sunday $post = $res->json(); interface PostApiInterface { #[WebQuery('post_item', type: 'row')] public function get(string $id): Post; } とWeb APIで、呼び方が変わります。 // web_query.json // {"id": "post_item", "method": "GET", // "path": "https://{domain}/posts/{id}"} DB DB と同じく、ドメインオブジェクトで返ります。 ユーザーのためのBEAR.Sunday入門 55 · Laravel
  50. 04 · データ・キャッシュ・テスト の結果をドメインオブジェクトに API :取得してから組み立てる Laravel $data = Http::get($url)->json();

    $post = new Post( $data['title'], $markdown->convert($data['body']), ); 変換のコードは呼ぶ側に書きます。 :ファクトリを宣言する BEAR.Sunday #[WebQuery('post_item', type: 'row', factory: PostFactory::class)] public function get(string $id): Post; // PostFactory もDIで組み立てられる final class PostFactory { public function __construct( private Markdown $markdown, ) {} } 変換はファクトリが受け持ちます。 ユーザーのためのBEAR.Sunday入門 56 · Laravel
  51. 04 · データ・キャッシュ・テスト を起点に作る Fake JSON 最初に仕様を決める post_item の作り方 テーブルより先に、返す形をJSONで決めます。このJSONが、

    その後のすべての仕様になります。 // 1. 返したいJSONを書く(post_item.json) {"id": 1, "title": "Hello"} まず固定のJSONを返すFakeで動かし、画面やAPIを作ります。 // 2. それを返すメソッドを宣言する #[DbQuery('post_item')] public function __invoke(int $id): ?Post; はあとで、同じJSONが出るように書いて差し替えます。 メソッドも、呼ぶ側のコードも変わりません。 SQL // 3. あとで同じJSONが出るSQLに差し替える // post_item.sql github.com/ray-di/Ray.FakeQuery ユーザーのためのBEAR.Sunday入門 57 · Laravel
  52. データ・キャッシュ・テスト キャッシュの基本 04 · 観点 L AR AVEL 無効化 TTL

    更新イベントで即時(+TTL) キャッシュの場所 サーバーサイド サーバーサイド、CDN、クライアント 部分キャッシュ 標準ではなし ドーナツキャッシュ 条件付きリクエスト 標準ではなし ETag で失効 BEAR.SUNDAY を自動更新し304で応答 ユーザーのためのBEAR.Sunday入門 58 · Laravel
  53. データ・キャッシュ・テスト リソースのキャッシュ 04 · #[CacheableResponse] クラスに付けると、 イベント駆動の無効化、 依存の追跡、ETagが 適用されます。 ドーナツキャッシュ

    CDN 304 と キャッシュできる部分と できない部分を合成し、穴の 更新で全体を再生成します。 変更をAOPが検知してCDNに PURGEし、ETagの問い 合わせには304で応答します。 ユーザーのためのBEAR.Sunday入門 59 · Laravel
  54. データ・キャッシュ・テスト キャッシュの宣言 04 · :依存をアプリで管理 Laravel // 記事を更新したら、依存する // キーをアプリが列挙して破棄する

    Cache::forget("post:{$id}"); Cache::forget('posts'); Cache::forget('home'); :依存をBEARが管理 BEAR.Sunday #[CacheableResponse] class Index extends ResourceObject { #[Embed(rel: 'posts', src: 'app://self/posts')] public function onGet(): static { return $this; } } // post が更新されると // post → posts → Index と連鎖して // キャッシュとETagが無効化される ユーザーのためのBEAR.Sunday入門 60 · Laravel
  55. データ・キャッシュ・テスト テストの書き方 04 · : テスト Laravel Feature public function

    test_index(): void { $this->get('/posts') ->assertOk() ->assertSee('Hello'); } :リソーステスト BEAR.Sunday public function testOnGet(): void { $ro = $this->resource->get( 'app://self/posts' ); } $this->assertSame(200, $ro->code); $this->assertIsArray($ro->body); ユーザーのためのBEAR.Sunday入門 61 · Laravel
  56. フレームワークとドメイン どちらが合わせるか 05 · :フレームワークに合わせる :フレームワークが合わせる Laravel BEAR.Sunday 「フレームワークと戦わない」。ドメインをフレームワークの 型に合わせます。少ない記述で動き、規約に乗っている

    間は摩擦がありません。テーブルとドメインの形が近い 問題に向いています。 「フレームワークと緊張関係にない」。フレームワークを ドメインに合わせます。ドメインのクラスは素のPHPのままで、 配線は別の場所に書きます。 ユーザーのためのBEAR.Sunday入門 63 · Laravel
  57. 05 · フレームワークとドメイン とドメインオブジェクト Model :フレームワークに合わせる Laravel class Post extends

    Model { protected $fillable = ['title', 'body']; } public function comments() { return $this->hasMany(Comment::class); } // 属性はテーブル定義から実行時に現れる :フレームワークが合わせる BEAR.Sunday final class Post { public function __construct( public readonly int $id, public readonly string $title, public readonly string $body, ) { if ($title === '') { throw new EmptyTitle(); } } } ユーザーのためのBEAR.Sunday入門 64 · Laravel
  58. フレームワークとドメイン 知識の向き 05 · :ドメインがフレームワークを知る Laravel は を継承し、生成や保存の約束事を共有します。 コンストラクタはフレームワークが握ります。 Post

    Model :フレームワークがドメインを知る BEAR.Sunday はコンストラクタのシグネチャを読んで組み立てます。 ドメインのPostはRay.DiやMediaQueryを知りません。 Ray.Di ユーザーのためのBEAR.Sunday入門 65 · Laravel
  59. フレームワークとドメイン フレームワークの入り込む深さ 05 · 浅い → 深い ドメインのコードに入るもの フレームワークを外すと なし

    素のPHPだけ(BEAR.Sunday) そのまま動く アトリビュート #[Transactional] BEAR.Sunday 型・インターフェース Request 継承 extends Model Laravel ( ) を受け取る(Laravel) ( ) 業務は同じ。横断処理だけ消える シグネチャを書き換える クラスの意味ごと失われる ユーザーのためのBEAR.Sunday入門 66 · Laravel
  60. フレームワークとドメイン フレームワークを 外したときに残るもの 05 · Laravel データベースのスキーマとデータ URLの設計 サービスクラスに分けたロジック 、

    、 で書いた部分は、Laravelと 一緒に置き換えが必要になります。 Eloquent FormRequest Blade BEAR.Sunday ファイル SQL (入力・出力) URIとリソースの設計 ドメインオブジェクトとクエリのPHPインターフェース リソースの依存構造(#[Embed]) ハイパーリンク(#[Link]) ALPSによる語彙の定義 JSON Schema ユーザーのためのBEAR.Sunday入門 68 · Laravel