Laravelでの開発で気になった点や、メモをざっと書き出しています。
DBスキーマ設計例(Railsですが):
https://techblog.lclco.com/entry/2018/02/09/093000
- Railsの規約に寄り添う
- 主キーは「id」(フレームワークで自動的に主キーに「id」が割り振られる為)
-
SQLアンチパターンでは基本「id」はつけない
-
主キー
- テーブル内の行が一意であることを保証
- 外部キーから参照されることでテーブルの関連付けを行う
-
アンチパターン
- すべてのテーブルに「id」列を用いる
- 冗長なキーが作成されてしまう
- 主キーとして使えそうな列があってもidを使用してしまう等
- 重複行を許可してしまう
- 複合キーを使用する場合に一意であることを保証できない(idによるAutoIncrements)
- キーの意味がわかりづらくなる
- 冗長なキーが作成されてしまう
- idには長い文字列を指定しない(インデックスが非効率になるため)
- すべてのテーブルに「id」列を用いる
-
アンチパターンを用いていい場合
- ORM等での開発時、規約に従ってidを指定した方が開発しやすい場合
-
注意
- すべてのテーブルが擬似キーを必要とするわけではない
- 疑似キーを「id」と必ず命名する必要はない
- 主キーは制約であり、データ型ではない
- テーブルには一意になるキー項目が必要
-
参考
リソースコントローラの自動生成
$ php artisan make:controller PhotoController --resource
パラメータの取得 $value = $request->input('value'); $values = $request->get();
https://qiita.com/piotzkhider/items/feaba3acda27d2e432d8
https://readouble.com/laravel/6.x/ja/requests.html
Eloquentモデル $fillableと$guardの利用
- insert、updateどちらでも適用される
- $fillableか$guardedのどちらか一方を使用する。両方一度には使えない。
- どちらかに必ず指定が必要
- $fillableに指定していない(または$guardedで指定している)値を入れるには、属性を直埋め → saveメソッドを利用する
- 直で属性を入れればfillable等のチェックを受けなくて済む = 何も考えずにぶち込んでしまうようになるとチェックされずに入ってしまう
- 値を設定させない項目は、コントローラで値をセットせずに、モデルで直に入れるようにするのが一番いいのでは?
- リクエストの入力値で登録できると問題が起きる項目(権限やパスワードといった、悪意を持ったユーザに変更されると困るもの)が$guardedの対象
- $fillableや$guardedでのチェックが通るように、fill()メソッドや::createを使うのも書き方を統一しないといけないのであまりいい書き方ではなさそう。(全部の項目を$fillableに設定しないとだめだし)
- アクセサとミューテタはモデルの取得や値を設定するときに、Eloquent属性のフォーマットを可能にする
- 属性を直埋め → saveメソッドだと、間違えて直埋めしてしまったときが辛いので、modelsでミューテータメソッド作って固定化した方がいいのでは?
- ミューテタはモデルのfirst_name属性へ値を設定する時に自動的に呼びだされる→insert時に値の設定を行うコードをかかないといけない。
- 現状のベスト:fillで更新していい値を指定する、新規登録時にfillで指定したもの以外を設定する場合は直埋めする
- $fillableで指定している項目が多くなってきたらテーブル設計を見直すタイミング
- $fillableで設定された項目以外が入ってきたらエラーになるので、更新時は「$user->fill($request->all())->save();」が楽
- updateメソッドやinsertメソッドは、fillでチェックしないので使わない方がいい
- $fillableを無視したい場合はforceFill()を使う
- 普通、forceFillを使うのはデータベースシーダー
- Eloquentクエリの結果は、常にCollectionインスタンスを返す
- モデルにクエリ処理を置くようにする
- コントローラとモデルの分離がきれいになる
- https://qiita.com/ryokurosu/items/9bf7eaafbb892472163e
- クエリースコープでクエリをメソッドにまとめることが出来る
- https://qiita.com/henriquebremenkanp/items/e21de43e4b9079265d7f
- 命名規約:
https://readouble.com/laravel/6.x/ja/eloquent.html
https://blog.zuckey17.org/entry/2018/01/14/214919
https://www.larajapan.com/2016/09/03/%E5%85%A5%E5%8A%9B%E3%81%AE%E3%83%96%E3%83%A9%E3%83%83%E3%82%AF%E3%83%AA%E3%82%B9%E3%83%88%E3%81%A8%E3%83%9B%E3%83%AF%E3%82%A4%E3%83%88%E3%83%AA%E3%82%B9%E3%83%88/
https://qiita.com/katsunory/items/87a73297f44a65f1474f
https://qiita.com/henriquebremenkanp/items/cd13944b0281297217a9
- travisci
- https://dragon-taro.com/college/post-613
- travis.ymlをgithubでpush,pullrequestすると設定したスクリプトが自動で走る
- 環境変数をtravisci用の設定ファイルを作る
- mysqlの利用方法は、travisciの公式ドキュメントで説明があるのでこれを参照:https://docs.travis-ci.com/user/database-setup/
- ユーザー名travisまたはrootと空のパスワードを使用して接続
- travisci上でphpunit実行されると、General error: 1 no such table: usersになる(travisci上で
- php artisan migrateは走っていてusersテーブルは生成されているように見える
- 作っていたmigrateファイルだとusersテーブルがもしかして生成されない?と思ってテーブル全部消してローカルでphp artisan migrateしてみるも問題なく生成されるので、そっちも問題なさそう
- Laravelのtravisci設定例:https://dev.to/nahuelhds/basic-travis-ci-configuration-for-laravel-4k8c
- テーブル一覧をコントローラで表示してみるもテーブルが全く生成されていない
- $tableList = DB::connection()->getDoctrineSchemaManager()->listTableNames();
- https://qiita.com/ucan-lab/items/f4cabe23ea5f78847a0e
- 設定ファイルのキャッシュが行われている?と思ったので「php artisan config:cache」を追加
- 設定ファイルがキャッシュされると、.envファイルはロードされない
- 設定ファイルのキャッシュは、bootstrap/cache/config.phpに生成される
- 「php artisan key:generate」で生成されるAPP_KEYに関してもキャッシュに保存される為、先に「php artisan config:clear」しておく
- mysqlの接続情報もキャッシュされる
- APP_ENV:環境によって異なる処理を行わせたい場合に利用
- mysqlの利用方法は、travisciの公式ドキュメントで説明があるのでこれを参照:https://docs.travis-ci.com/user/database-setup/
DB_CONNECTION=testing
DB_HOST=localhost
DB_PORT=3306
DB_TEST_DATABASE=blog
DB_TEST_USERNAME=root
DB_TEST_PASSWORD=
- mysqlの設定どうすればいい?
- .travis.yml
- コスト(実装にかかる時間はどの程度か
- 不具合が起きやすい箇所か
- なにを目的にしているか
- リファクタリングにおいて、挙動が変化していないか、バグが混入していないかチェックする回帰テスト
- ソースコードにバグが混入しないか継続的に監視する監視役として活用
- 設計の検討・改善の支援や、プログラミングの進捗確認の手段として活用
- ソースコードのふるまいを説明するドキュメントとして扱う
- 仕様漏れや認識間違いには気づけない(実装されてないコードへはテスト出来ない
- テストコードが正しく記述されていることを保証することが出来ない
新しいテストケースを作成する
# php artisan make:test UserTest
# php artisan make:test UserTest --unit
テストの実行
# vendor/bin/phpunit
-
UnitTest(ユニット・テスト)と FeatureTest(フィーチャー・テスト)の2つのテストがある
-
保存されるディレクトリが tests/{Unit,Feature} と違うだけで、テストの書き方は変わらない
-
違いはテストの 対象範囲
- UnitTestは小さなコードの一部をテストする(単体のモデルやモデルのメソッドを独立してテストする
- FeatureTestは複数のオブジェクトが関連した機能をテストする(主にコントローラのテスト
- ブラウザテストはブラウザを自動で操作し、単一または複数の動作を検証(JavaScript による動的な制御が含まれる場合に有用
- 例外テストをする場合にモックやスタブを注入しやすいようにコードを書く必要がある(例外処理を行う際にユニットテストを行うべきか
- 例外テストはモックやスタブを注入するため、ユニットテストを行う
- https://qiita.com/grohiro/items/4efc6c569be26e36aef2
- https://qiita.com/nunulk/items/c7ea6a29f7893e68f791
-
どうテスト計画を立てればいい?
- フィーチャーテストを主に行う
- 時間がかけられない、既存のコードの依存関係が強くユニットテストができない→フィーチャーテスト
- 変更が多い箇所はユニットテストを行うことで、すぐエラー箇所を特定できる
- コントローラ・モデルは修正がかかることが多いので、ユニットテストを行う
- 指定したURLで200が返るかのテストは、ルーティングのテストとして必要。だが他のテストで補えるのなら別個にテストケースを作成する必要はない
-
Controllerのクラスがテストしづらい
- Controller からドメインロジックを追い出す
- サービスプロバイダに登録して、コントローラのconstructの引数から受け取る
- コントローラにあるORMやドメインロジックをどこに置くべきか
- ORMはモデルのクエリスコープに置くべき?(結局モデルに依存してしまうので、サービスプロバイダに移した方がいいか
- https://qiita.com/nunulk/items/fb2568e60a9d553b8e41
- https://www.ritolab.com/entry/108
-
Repositoryパターン
- データの操作に関連するロジックをビジネスロジックから切り離し、抽象化したレイヤに任せることで保守や拡張性を高めるパターン
- https://www.ritolab.com/entry/165
- https://qiita.com/bmf_san/items/c8d7b38b5f1f5747c2fd
-
サービスプロバイダ
- Laravelアプリケーション全体の起動処理における、初めの心臓部
- Laravelのコアサービス全部もサービスプロバイダを利用し、初期起動処理を行う
- サービスコンテナの結合や、イベントリスナ、フィルター、それにルートなどを登録することを一般的に意味する
- サービスコンテナ:クラス間の依存を管理する
-
Faker
- https://qiita.com/Sa2Knight/items/fb82be7551cc84764267
- https://github.com/fzaninotto/Faker
- Fakerのローケルは、config/app.php設定ファイルのfaker_localeオプションで指定できる
- database/factories
- $factory->define:各属性にデフォルト値を設定
- 親テーブルの外部キーと同じ値を設定したい(ユーザテーブルのユーザIDと、記事テーブルのユーザIDを同一にしたい)
- https://qiita.com/winmonaye/items/8fdac3449c11401dd66f
- 'user_id' => factory(App\User::class) 新しいユーザインスタンスも生成
- https://qiita.com/okadak/items/573731d9253252ab88c1 app/config.phpを変更した場合は、下記コマンドを実行する(そうしないと設定が反映されない) https://qiita.com/Yorinton/items/17a5082f9830ece714b8
-
$response->assertViewHas():レスポンスビューが指定したデータを持っていることを宣言
-
$response->assertSuccessful():レスポンスが成功(200)ステータスコードであることを宣言
-
テストクラスに独自のsetUpメソッドを定義する場合は、親のクラスのparent::setUp()/parent::tearDown()を確実に呼び出すこと
-
起きたエラー
- The response is not a view:応答のビューがない(ビュー表示する際にエラーが発生している?
- Trying to get property 'user_id' of non-object (View: /var/www/blog/resources/views/notes/index.blade.php)
- 原因:$this->note->orderBy('updated_at', 'desc')->paginate($this->item);で帰ってきたオブジェクトにuser_idが含まれていない?
- 帰ってくるオブジェクトは「Illuminate\Pagination\LengthAwarePaginator」
-
factory(App\User::class)->make():makeメソッドでモデルを生成し、データベースには保存しない
-
factory(App\User::class)->create():モデルインスタンスを生成するだけでなく、Eloquentのsaveメソッドを使用しデータベースへ保存
-
エラーデバッグ
- $this->withoutExceptionHandling():エラーメッセージの表示
- $response->content():ビューとして出力されるHTMLを確認できる
テストでレンダリングされたHTMLを確認できる
https://stackoverflow.com/questions/51445065/laravel-unit-test-the-response-is-not-a-view
$response->content()
php artisan config:cache
インタフェースと実装の結合:
- EventPusherの実装クラスが必要な時に、コンテナがRedisEventPusherを注入する
$this->app->bind(
'App\Contracts\EventPusher',
'App\Services\RedisEventPusher'
);
Laravelは様々な、グローバル「ヘルパ」PHP関数を用意している
- Auth::user()でなくauth()->user();でもOK
- https://readouble.com/laravel/6.x/ja/helpers.html?header=auth()
- コントローラに、Auth::user()を含めるとテストが書けないのでコンストラクタインジェクションしたい
- $guardを注入させる形にする($guard->user())
- https://teratail.com/questions/182744
モデル:リレーションの設定
https://qiita.com/yukibe/items/b7186f05d1c266076a35
- Eloquentのリレーション(関係)は、Eloquentモデルクラスのメソッドとして定義
- Modelでリレーションを定義することでテーブルの依存関係がわかりやすくなる
- リレーションを設定することで、子テーブルのデータを外部キーから取得できるようになる(子テーブルから親テーブルを参照することも可)
- 多対多リレーションの操作をする為の中間テーブルへのアクセスを楽に行える
ページネーション
$posts = Post::paginate(10);
return view('posts', [ 'posts' => $posts, ]);
-
@foreach ($posts as $post)
- {{ $post->title }} @endforeach
モデルの生成
- クラス名を複数形の「スネークケース」にしたものが、テーブル名として使用される
- 主キー:主キーがidというカラム名であると想定,オーバーライドする場合は、protectedのprimaryKeyプロパティを定義する
- 主キーを自動増分される整数値であるとも想定
- 自動増分ではない、もしくは整数値ではない主キーを使う場合、モデルにpublicの$incrementingプロパティを用意し、falseをセットする
- データベース上に存在するcreated_at(作成時間)とupdated_at(更新時間)カラムを自動的に更新
リクエスト
ルートの保護
Route::get('profile', function() { // 認証済みのユーザーのみが入れる })->middleware('auth');
連番(auto increments)を外部キーにしてしまうと、テーブルに再度入れ直すときにidが変わってしまう可能性があるので避ける
uuid
https://www.webopixel.net/php/1380.html
https://qiita.com/NewBieChan/items/3d0f8025accd770bd6d3
生成時と保存時
{$model->getKeyName()} = Uuid::generate()->string; }); } } ## Eloquent ### fill() - モデルで $fillable を定義しておく必要がある - $fillableに指定したカラムのみ、create()やfill()、update()で値が代入される - カラムが増えるたびに追加する必要があるので、guardプロパティで除外する形のほうがおすすめ https://qiita.com/tkt989/items/c2e01ec2fe91ef5f08d2 https://readouble.com/laravel/6.x/ja/eloquent.html ### guardプロパティ - Modelに指定する$fillableや$guardedは、Model・DB単位で予期せぬ代入が起こると困るものを書くべき - $guardedに指定したカラムのみ、create()やfill()、update()で値が代入される ## storage シンボリックリンクを貼る(storage/app/public→public/storage) # php artisan storage:link https://readouble.com/laravel/6.x/ja/filesystem.html - localドライバを使う場合、filesystems設定ファイルで指定したrootディレクトリからの相対位置で全ファイル操作が行われる