Laravelは、開発体験(DX)を改善するのに役立つ多くの強力な機能を提供しています。しかし、定期的なリリース、日々の仕事のストレス、および利用可能な多数の機能の出現により、コードの改善に役立つあまり知られていない機能を簡単に見逃すことができます。
この記事では、私のお気に入りのLaravelモデルの使用のヒントをいくつか紹介します。これらのヒントが、よりクリーナー、より効率的なコードを書いて、一般的な落とし穴を回避するのに役立つことを願っています。
最初に、n 1クエリの問題を発見して防止する方法を紹介します。
関連付けが遅延している場合、一般的なn 1クエリの問題が発生する可能性があります。ここで、nは関連モデルを取得するために実行されるクエリの数です。
これはどういう意味ですか?例を見てみましょう。データベースからすべての投稿を取得し、それらを繰り返し、投稿を作成したユーザーにアクセスしたいとします。私たちのコードは次のようになるかもしれません:
$posts = Post::all(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
上記のコードはよく見えますが、実際にはn 1の問題を引き起こします。データベースに100個の投稿があるとします。最初の行では、すべての投稿を取得するために1つのクエリを実行します。次に、$post->user
foreach
のループでは、新しいクエリをトリガーして、追加の100クエリになります。これは、合計101のクエリを実行することを意味します。ご想像のとおり、これは良くありません!アプリケーションを遅くし、データベースに不必要な圧力をかけます。
ありがたいことに、Laravelは、これらのn 1の問題を発見および防止するために使用できる便利な
方法を提供します。この方法では、Laravelが関係を怠zyなときに例外を投げるように指示するので、あなたの関係が常に熱心にロードされていることを確認できます。
この方法を使用するには、Model::preventLazyLoading()
メソッド呼び出しを
Model::preventLazyLoading()
AppProvidersAppServiceProvider
ここで、上記のコードを実行して各投稿を取得し、その投稿を作成したユーザーにアクセスする場合は、次のメッセージでスローされた
namespace App\Providers; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot(): void { Model::preventLazyLoading(); } }
メソッドを使用して、IlluminateDatabaseLazyLoadingViolationException
を達成できます
<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
欠落している
モデルに存在するが存在しないと思われるフィールドにアクセスしようとする頻度はどれくらいですか?エラーを入力したかもしれませんし、実際にはfull_name
と呼ばれている場合、name
フィールドがあると思われるかもしれません。
次のフィールドを備えたAppModelsUser
モデルがあるとします。
id
name
email
password
created_at
updated_at
$posts = Post::all(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
アクセサアがないと仮定すると、full_name
変数は$name
になります。しかし、これがnull
フィールドが実際にfull_name
であるため、またはデータベースからフィールドを取得できなかったため、またはモデルにフィールドが存在しないためかはわかりません。ご想像のとおり、これは予期しない動作につながる可能性があり、検出が難しい場合があります。 null
メソッドを提供します。この方法は、モデルの現在のインスタンスに存在しないフィールドにアクセスしようとすると、Laravelに例外をスローするように指示します。 Model::preventAccessingMissingAttributes()
この機能を有効にするには、
クラスに追加します:Model::preventAccessingMissingAttributes()
AppProvidersAppServiceProvider
namespace App\Providers; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot(): void { Model::preventLazyLoading(); } }
フィールドにアクセスしてみたい場合は、次のメッセージでスローされたAppModelsUser
例外が表示されます。
full_name
IlluminateDatabaseEloquentMissingAttributeException
<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
preventAccessingMissingAttributes
$posts = Post::with('user')->get(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
これは、既存のクエリを更新するときに非常に便利です。たとえば、過去には、モデルにはいくつかのフィールドしか必要としていなかったかもしれません。ただし、現在アプリケーションの機能を更新しており、別のフィールドにアクセスする必要がある場合があります。この方法が有効になっていない場合、まだロードされていないフィールドにアクセスしようとしていることに気付かない場合があります。
$user = User::query()->first(); $name = $user->full_name;
メソッドがLaravelドキュメント(コミット)から削除されたことは注目に値しますが、それでも機能します。なぜそれが削除されたのかはわかりませんが、それは注意の問題です。これは、将来削除されることを示している可能性があります。
preventAccessingMissingAttributes
(次のコンテンツは元のテキストと同じです。一貫性を維持するために、元のテキストを保持し、もう書き直しません)
preventAccessingMissingAttributes
に類似して、Laravelはモデルを更新するときに予期しない動作を防ぐのに役立つpreventSilentlyDiscardingAttributes
メソッドを提供します。
次のようにAppModelsUser
モデルクラスがあるとします:
$posts = Post::all(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
ご覧のとおり、name
、email
、password
フィールドはすべて充填可能なフィールドです。しかし、モデルに存在しないフィールド(例:full_name
)または存在するが埋められないフィールド(例:email_verified_at
)を更新しようとするとどうなりますか? :
namespace App\Providers; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot(): void { Model::preventLazyLoading(); } }
とfull_name
フィールドの両方が充填可能なフィールドとして定義されていないため無視されます。しかし、エラーがスローされていないため、これらのフィールドが静かに破棄されていることはわかりません。 email_verified_at
メソッドを使用できます。これは、モデルに存在しない、または埋められないフィールドを更新しようとすると例外をスローします。 preventSilentlyDiscardingAttributes
この方法を使用するには、
クラスに追加します:Model::preventSilentlyDiscardingAttributes()
AppProvidersAppServiceProvider
<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
さて、上記のサンプルコードを実行してユーザーの
およびフィールドを更新しようとすると、次のメッセージがfirst_name
にスローされます。
email_verified_at
IlluminateDatabaseEloquentMassAssignmentException
$posts = Post::with('user')->get(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
などのメソッドを使用する場合にのみ強調表示されることに注意してください。各プロパティを手動で設定した場合、これらのエラーはキャッチしません。たとえば、次のコードを見てみましょう
preventSilentlyDiscardingAttributes
上記のコードでは、fill
フィールドはデータベースに存在しないため、Laravelは私たちのためにそれをキャプチャしませんが、データベースレベルでそれをキャプチャします。 MySQLデータベースを使用している場合、次のようなエラーが表示されます。
update
$user = User::query()->first(); $name = $user->full_name;
前述の3つの方法を使用する場合は、full_name
メソッドを使用してすぐに有効にすることができます。この方法では、
namespace App\Providers; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot(): void { Model::preventAccessingMissingAttributes(); } }
メソッド呼び出しをModel::shouldBeStrict()
クラスに追加します:preventLazyLoading
preventAccessingMissingAttributes
preventSilentlyDiscardingAttributes
これは次のとおりです
$posts = Post::all(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
preventAccessingMissingAttributes
メソッドはlaravelドキュメント(コミット)から削除されましたが、それでも機能します。これは、将来削除されることを示している可能性があります。 shouldBeStrict
uuidは、リソースを一意に識別するために使用できる128ビット(または36文字)の英数字ストリングです。それらがどのように生成されるかのため、彼らが別のUUIDと衝突する可能性は非常に低いです。 UUIDの例は次のとおりです。
1fa24c18-39fd-4ff2-8f23-74ccd08462b0
モデルの主要な鍵としてUUIDを使用することができます。または、Auto-Incremented IDを保持してアプリケーションとデータベースの関係を定義することもできますが、PublicFacs IDSにはUUIDを使用します。このアプローチを使用すると、攻撃者が他のリソースのIDを推測することを難しくすることにより、セキュリティの追加レイヤーを追加できます。
ルーティングで自動インクリメンタルIDを使用しているとします。以下に示すように、ユーザーにアクセスするためのルートがある場合があります。
ルートが安全でない場合、攻撃者は他のユーザーのプロファイルにアクセスしようとするために、ID(例えば -
、namespace App\Providers; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot(): void { Model::preventLazyLoading(); } }
など)の上にループできます。そして、UUIDを使用する場合、URLは/users/1
、/users/2
、/users/3
に似ている可能性があります。ご想像のとおり、これらは推測するのが難しいです。 /users/1fa24c18-39fd-4ff2-8f23-74ccd08462b0
/users/b807d48d-0d01-47ae-8bbc-59b2acea6ed3
もちろん、UUIDを使用するだけではアプリケーションを保護するわけではありません。セキュリティを改善するために実行できる追加のステップにすぎません。料金の制限、認証、承認チェックなど、他のセキュリティ対策も使用することを確認する必要があります。 /users/ec1dde93-c67a-4f14-8464-c0d29c95425f
まず、主キーをUUIDに変更する方法を見てみましょう。
これが
テーブルの基本的な移行を作成したと仮定します:$table->uuid
移行で見たように、UUIDフィールドを定義しました。デフォルトでは、このフィールドはcomments
と呼ばれますが、必要に応じて列名を
<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
次に、Laravelに、新しいuuid
モデルの主要なキーとして新しいuuid
フィールドを使用するように指示する必要があります。また、LaravelがUUIDを自動的に生成できる機能を追加する必要があります。これを行うことができます
属性を使用することができます。
uuid
AppModelsComment
ここで、モデルを構成し、UUIDをプライマリキーとして使用する準備ができている必要があります。このサンプルコードを見てみましょう:$primaryKey
$posts = Post::all(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
ダンプモデルでは、uuid
フィールドにUUIDが入力されていることがわかります。
内部関係に自動インクリメントIDを使用したいが、パブリック面IDにUUIDを使用する場合は、モデルにUUIDフィールドを追加できます。
テーブルにはid
およびuuid
フィールドがあると想定しています。 id
フィールドをプライマリキーとして使用するため、モデル上の$primaryKey
属性を定義する必要はありません。
IlluminateDatabaseEloquentConcernsHasUuids
機能によって提供されるuniqueIds
メソッドをオーバーライドできます。この方法では、UUIDを生成する必要があるフィールドの配列を返す必要があります。
AppModelsComment
:uuid
:
namespace App\Providers; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot(): void { Model::preventLazyLoading(); } }
AppModelsComment
さあ、新しいuuid
モデルをダンプしたい場合は、
<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
後で、この記事でモデルとルートを更新する方法を説明し、これらのUUIDがルート内の公開IDとして使用されるようにします。
を使用します LaravelモデルでUUIDを使用するのと同様に、ユニバーサルユニークな辞書ソート識別子(ulid)を使用する場合があります。
ulidは、リソースを一意に識別するために使用できる128ビット(または26文字)の英数字ストリングです。 ulidの例は次のとおりです。 01J4HEAEYYVH4N2AKZ8Y1736GD
機能を使用する代わりに、IlluminateDatabaseEloquentConcernsHasUlids
機能を使用する必要があることです。 IlluminateDatabaseEloquentConcernsHasUuids
たとえば、
モデルを更新して、ulidを主キーとして使用する場合、これを行うことができます。
AppModelsComment
$posts = Post::with('user')->get(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
ルーティングモデルバインディングにより、Laravelアプリケーションルートに渡されたデータに基づいてモデルインスタンスを自動的に取得できます。
デフォルトでは、Laravelはモデルの主要なキーフィールド(通常は
フィールド)を使用してモデルバインディングをルーティングします。たとえば、個々のユーザー情報を表示するためのルートがある場合があります。
上記の例で定義されているルートは、データベースに存在するユーザーを見つけて、提供されたIDを持っていることを試みます。たとえば、データベースにIDがあるユーザーがいるとします。 urlid
にアクセスすると、laravelはデータベースからID
$user = User::query()->first(); $name = $user->full_name;
ただし、データベースからモデルを取得する方法を定義するために、さまざまなフィールド(一次キーではなく)を使用する場合があります。
たとえば、前述したように、内部関係のモデルの主要なキーとして、自動増入IDを使用することをお勧めします。ただし、PublicFacing IDにUUIDを使用することもできます。この場合、フィールドではなく、ルーティングモデルのバインディングにuuid
フィールドを使用することをお勧めします。 id
同様に、ブログを構築している場合は、
フィールドに基づいて投稿を取得することをお勧めします。これは、slug
フィールドの読みが簡単で、自動化されたIDよりもSEOに優しいためです。 id
slug
すべてのルーティングされたフィールドを変更します
たとえば、getRouteKeyName
フィールドを使用すると仮定します。これを行うことができますAppModelsPost
モデルにslug
メソッドを追加することができます。
id
Post
これは、このようなルートを定義できることを意味します。
getRouteKeyName
$posts = Post::all(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
を自動的に取得し、操作のために閉鎖関数(またはコントローラー)に渡します。
namespace App\Providers; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot(): void { Model::preventLazyLoading(); } }
/posts/my-first-post
ただし、単一のルートで使用されているフィールドのみを変更する場合があります。たとえば、ルーティングモデルのバインディングには1つのルートでslug
フィールドを使用することもできますが、他のすべてのルートでmy-first-post
フィールドを使用します。
フィールドを使用すると仮定します。このようなルートを定義できます:slug
id
フィールドでデータベースから投稿を取得しようとすることを意味します。 :field
カスタムモデルコレクションの使用slug
<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
などのメソッドを使用してデータベースから複数のモデルを取得する場合、Laravelは通常、slug
クラスのインスタンスに配置します。このクラスは、返されたモデルを処理するための多くの有用な方法を提供します。ただし、デフォルトのコレクションクラスの代わりにカスタムコレクションクラスを返すことをお勧めします。
Laravelを使用すると、返品するコレクションタイプを簡単にオーバーライドできます。 AppModelsUser::all()
例を見てみましょう。 AppModelsPost
モデルがあり、データベースからそれらを取得したときに、カスタムAppCollectionsPostCollection
クラスのインスタンスに戻したいとします。
新しいapp/Collections/PostCollection.php
ファイルを作成して、次のようなカスタムコレクションクラスを定義できます。
$posts = Post::all(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
クラスのインスタンスのみが含まれることも指定しました。これは、IDEがコレクションに含まれるデータの種類を理解するのに役立ちます。 AppCollectionsPostCollection
次に、次のようにIlluminateSupportCollection
メソッドをオーバーライドすることにより、AppModelsPost
モデルを更新してカスタムコレクションクラスのインスタンスを返すことができます。
モデル配列をAppModelsPost
メソッドに渡して、カスタムnewCollection
クラスの新しいインスタンスを返します。
namespace App\Providers; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot(): void { Model::preventLazyLoading(); } }
newCollection
比較モデルAppModelsPost
AppCollectionsPostCollection
プロジェクトに取り組んでいる際に私が抱えている一般的な問題は、モデルを比較する方法です。これは、ユーザーがリソースにアクセスできるかどうかを確認する場合、通常、承認チェックにあります。
いくつかの一般的な落とし穴と、おそらくそれらを避けるべき理由を見てみましょう。
2つのモデルが同じかどうかを確認するときは、<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
モデルに
関係が存在し、データベースの最初のコメントが最初の投稿に属しているとします。例を見てみましょう。
また、2つのモデルが同じかどうかを確認するときは、を使用しないでください。これは、オブジェクトを比較するときに===
チェックが同じクラスのインスタンスであるかどうか、および同じプロパティと値を持っているかどうかをチェックするためです。ただし、これは予期しない動作につながる可能性があります。 ===
false
この例を見てください:
上記の例では、AppModelsComment
post
$posts = Post::with('user')->get(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
を返します。しかし、==
モデルのプロパティを変更して異なるものにするとどうなりますか? ==
メソッドを使用して、
と$user = User::query()->first(); $name = $user->full_name;
$posts = Post::all(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
$comment->post
が$post
と同じモデルであっても、==
false
を返します。モデルには異なるロードプロパティがあるためです。ご想像のとおり、これは、特にクエリにselect
メソッドを遡及的に追加し、テストが失敗し始めた場合、追跡が困難な把握できない動作につながる可能性があります。
代わりに、laravelが提供するis
およびisNot
メソッドを使用するのが好きです。これらのメソッドは、2つのモデルを比較し、同じクラスに属し、同じプライマリキー値を持ち、同じデータベース接続を持っているかどうかを確認します。これはモデルを比較するためのより安全な方法であり、予期しない動作の可能性を減らすのに役立ちます。
is
メソッドを使用して、2つのモデルが同じかどうかを確認できます。
namespace App\Providers; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot(): void { Model::preventLazyLoading(); } }
メソッドを使用して、2つのモデルが異なるかどうかを確認できます。
isNot
<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
whereBelongsTo
最後のトリックは個人的な好みのようなものですが、クエリが読みやすくなり、理解しやすくなることがわかりました。
データベースからモデルを取得しようとすると、関係ベースのフィルタリングクエリを書いていることがあります。たとえば、特定のユーザーに属するすべてのコメントを取得して投稿することをお勧めします。
$posts = Post::with('user')->get(); foreach ($posts as $post) { // 对帖子执行某些操作... // 尝试访问帖子的用户 echo $post->user->name; }
メソッドを提供します(私の意見では)。この方法を使用して、このような上記のクエリを書き直すことができます:whereBelongsTo
$user = User::query()->first(); $name = $user->full_name;
あなたまたはあなたのチームは、
条項を書くために、より明確なアプローチを使用することを好むかもしれません。したがって、この手法は誰にとっても適していないかもしれません。しかし、あなたがあなたのアプローチを一貫している限り、どちらも良いと思います。 where
を使用する必要があります。 whereBelongsTo
以上がLaravelモデルのヒントの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。