ホームページ >バックエンド開発 >PHPチュートリアル >Laravelでクエリスコープをマスターすることを学びます

Laravelでクエリスコープをマスターすることを学びます

Emily Anne Brown
Emily Anne Brownオリジナル
2025-03-06 02:28:09513ブラウズ
<p> <img src="/static/imghwm/default1.png" data-src="https://img.php.cn/upload/article/000/000/000/174119929536701.jpg" class="lazy" alt="Learn to master Query Scopes in Laravel"></p> <p>Laravelアプリケーションを構築する場合、アプリケーション全体の複数の場所で使用される制約のあるクエリを作成する必要がある場合があります。多分あなたはマルチテナントアプリケーションを構築しており、ユーザーのチームによってフィルタリングするためにクエリに常に<code>where</code>制約を追加する必要があります。または、ブログを構築している場合があり、ブログ投稿が公開されている場合は、クエリに<code>where</code>制約を常に追加する必要があります。 </p> Laravelでは、クエリスコープを使用して、これらの制約を1つの場所にきちんと維持し、再利用できます。 <p> この記事では、ローカルクエリスコープとグローバルクエリスコープを調査します。 2つの違い、独自のクエリスコープを作成する方法、およびそれらのテストを作成する方法を学びます。 </p> この記事を読んだ後、Laravelアプリケーションでクエリスコープを自信を持って使用できるはずです。 <p> </p>クエリスコープとは何ですか? <p> </p> <h1>クエリスコープを使用すると、再利用可能な方法で雄弁なクエリの制約を定義できます。これらは通常、Laravelモデルのメソッド、または</h1>インターフェイスを実装するクラスとして定義されます。 <hr> <p>再利用可能なロジックを1つの場所で定義するのに理想的であるだけでなく、単純な関数呼び出し後に複雑なクエリ制約を隠すことでコードをより読みやすくすることもできます。 <code>IlluminateDatabaseEloquentScope</code> クエリ範囲は2つのタイプに分かれています:</p> <p> </p>ローカルクエリ範囲 - これらの範囲をクエリに手動で適用する必要があります。 <p> </p>グローバルクエリスコープ - デフォルトでは、クエリが登録されている場合、これらの範囲はモデル上のすべてのクエリに適用されます。 <ul> <li> </li> Laravelに組み込まれた「ソフト削除」機能を使用したことがある場合は、クエリスコープを知らないうちに使用した可能性があります。 Laravelはローカルクエリスコープを使用して、モデル上の<li>や</li>などのメソッドを提供します。また、グローバルクエリスコープを使用して、モデル上のすべてのクエリに</ul>制約を自動的に追加して、クエリでデフォルトでソフト削除レコードが返されないようにします。 <p> <code>withTrashed</code>Laravelアプリケーションでローカルクエリスコープとグローバルクエリスコープを作成および使用する方法を見てみましょう。 <code>onlyTrashed</code> <code>whereNull('deleted_at')</code>ローカルクエリスコープ</p> <p> </p>ローカルクエリスコープは、雄弁モデルのメソッドとして定義されているため、モデルクエリに手動で適用できる制約を定義できます。 <h1> </h1>管理パネルを使用してブログアプリケーションを構築しているとします。管理パネルには、2つのページがあります。1つは公開されたブログ投稿をリストし、未発表のブログ投稿をリストするためのページです。 <hr> <p>ブログ投稿には<code>AppModelsArticle</code>モデルを使用してアクセスされ、データベーステーブルにはブログ投稿の公開時間を保存する空の<code>published_at</code>列があると想定しています。 <code>published_at</code>が過去にリストされている場合、ブログ投稿は公開されていると見なされます。 <code>published_at</code>が将来リストされている場合、または<code>null</code>である場合、ブログ投稿は未発表とみなされます。 </p> <p>公開されたブログ投稿を取得するには、次のクエリを書くことができます。 </p> <pre class="brush:php;toolbar:false"><code>use App\Models\Article; $publishedPosts = Article::query() ->where('published_at', '<', now()) ->get();</code></pre>未発表のブログ投稿を取得するには、次のクエリを書くことができます。 <p> </p>上記のクエリは特に複雑ではありません。ただし、アプリケーション全体の複数の場所でそれらを使用するとします。発生数が増えると、私たちが間違いを犯したり、1か所でクエリを更新するのを忘れたりする可能性が増えています。たとえば、開発者は、公開されたブログ投稿を照会するために<pre class="brush:php;toolbar:false"><code>use App\Models\Article; use Illuminate\Contracts\Database\Eloquent\Builder; $unpublishedPosts = Article::query() ->where(function (Builder $query): void { $query->whereNull('published_at') ->orWhere('published_at', '>', now()); }) ->get();</code></pre>の代わりに誤って<p>を使用する場合があります。あるいは、ブログ投稿が公開されているかどうかを判断するロジックが変更される可能性があり、すべてのクエリを更新する必要があります。 <code>>=</code> <code><</code>これは、クエリスコープが非常に便利な場所です。それでは、</p>モデルにローカルクエリスコープを作成して、クエリを整理しましょう。 <p> <code>AppModelsArticle</code>ローカルクエリスコープは、</p>で始まり、スコープの予想名で終了するメソッドを作成することによって定義されます。たとえば、<p>という名前のメソッドは、モデルに<code>scope</code>範囲を作成します。このメソッドは、<code>scopePublished</code>インスタンスを受け入れて、<code>published</code>インスタンスを返す必要があります。 <code>IlluminateContractsDatabaseEloquentBuilder</code> <code>IlluminateContractsDatabaseEloquentBuilder</code> </p>モデルに両方の範囲を追加します:<p> <code>AppModelsArticle</code> 上記の例で見たように、</p>前のクエリからの制約を2つの別々のメソッドに移動しました:<pre class="brush:php;toolbar:false"><code>declare(strict_types=1); namespace App\Models; use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; final class Article extends Model { public function scopePublished(Builder $query): Builder { return $query->where('published_at', '<', now()); } public function scopeNotPublished(Builder $query): Builder { return $query->where(function (Builder $query): Builder { return $query->whereNull('published_at') ->orWhere('published_at', '>', now()); }); } // ... }</code>と</p> <p>。このようなクエリでこれらの範囲を使用できるようになりました。 <code>where</code> 私の個人的な意見では、これらのクエリが読みやすく、理解しやすいと思います。これはまた、将来同じ制約でクエリを書く必要がある場合、これらのスコープを再利用できることを意味します。 <code>scopePublished</code> <code>scopeNotPublished</code>グローバルクエリスコープ</p> <pre class="brush:php;toolbar:false"><code>use App\Models\Article; $publishedPosts = Article::query() ->published() ->get(); $unpublishedPosts = Article::query() ->notPublished() ->get();</code></pre> <p>グローバルクエリスコープは、ローカルクエリスコープと同様の関数を実行します。ただし、クエリごとに手動で適用されるわけではありませんが、モデル上のすべてのクエリに自動的に適用されます。 </p> 前述のように、Laravelの組み込み「Soft Delete」関数は、<h1>グローバルクエリスコープを使用します。この範囲は、モデル上のすべてのクエリに</h1>制約を自動的に追加します。それがどのように機能するかを理解することに興味がある場合は、GitHubのソースコードをこちらで確認できます。 <hr> たとえば、<p>管理パネルを使用してマルチテナントブログアプリケーションを構築しているとします。ユーザーが自分のチームに属する記事を表示できるようにするだけかもしれません。したがって、次のようなクエリを書くことができます:</p> <pre class="brush:php;toolbar:false"><code>use App\Models\Article; $publishedPosts = Article::query() ->where('published_at', '<', now()) ->get();</code></pre> <p>このクエリは良いですが、<code>where</code>制約を追加するのを忘れがちです。別のクエリを書いて制約を追加するのを忘れている場合、ユーザーがチームの一部ではない記事と対話できるようにするアプリケーションのエラーが発生します。もちろん、私たちはこれが起こることを望んでいません! </p> これを防ぐために、すべての<p>モデルクエリに自動的に適用できるグローバルな範囲を作成できます。 <code>AppModelArticle</code> </p>#グローバルクエリスコープを作成する方法<h3> </h3>すべてのクエリを<p>列でフィルターするグローバルクエリスコープを作成しましょう。 <code>team_id</code> </p>この記事の目的のために、例を簡単に保つことに注意してください。実際のアプリケーションでは、ユーザーが認証されていない、またはユーザーが複数のチームに属しているなどの状況に対処するために、より強力なアプローチを使用することをお勧めします。しかし、今のところ、グローバルクエリ範囲の概念に集中できるように、シンプルにしておきましょう。 <p> </p>最初にターミナルで次の職人コマンドを実行します:<p> </p> <pre class="brush:php;toolbar:false"><code>use App\Models\Article; use Illuminate\Contracts\Database\Eloquent\Builder; $unpublishedPosts = Article::query() ->where(function (Builder $query): void { $query->whereNull('published_at') ->orWhere('published_at', '>', now()); }) ->get();</code></pre>これにより、新しい<p>ファイルが作成されるはずです。このファイルを更新してから、完成したコードを確認します。 <code>app/Models/Scopes/TeamScope.php</code> 上記のコードの例では、</p>インターフェイスを実装し、<pre class="brush:php;toolbar:false"><code>declare(strict_types=1); namespace App\Models; use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; final class Article extends Model { public function scopePublished(Builder $query): Builder { return $query->where('published_at', '<', now()); } public function scopeNotPublished(Builder $query): Builder { return $query->where(function (Builder $query): Builder { return $query->whereNull('published_at') ->orWhere('published_at', '>', now()); }); } // ... }</code></pre>と呼ばれる単一のメソッドがある新しいクラスがあることがわかります。これは、モデルクエリに適用する制約を定義する方法です。 <p> <code>IlluminateDatabaseEloquentScope</code>グローバルスコープが利用可能になりました。これは、ユーザーチームにクエリを絞り込む任意のモデルに追加できます。 <code>apply</code> </p> <p>モデルに適用しましょう。 </p> <p> #applyグローバルクエリスコープ<code>AppModelsArticle</code> </p>モデルにグローバルスコープを適用する方法はいくつかあります。最初の方法は、モデルに<h3>属性を使用することです。 </h3> <p>もう1つの方法は、モデルの<code>IlluminateDatabaseEloquentAttributesScopedBy</code>メソッドで</p>メソッドを使用することです。 <pre class="brush:php;toolbar:false"><code>use App\Models\Article; $publishedPosts = Article::query() ->published() ->get(); $unpublishedPosts = Article::query() ->notPublished() ->get();</code></pre> <p>両方のメソッドは、<code>booted</code>モデルのすべてのクエリに<code>addGlobalScope</code>制約を適用します。 </p> <pre class="brush:php;toolbar:false"><code>use App\Models\Article; $articles = Article::query() ->where('team_id', Auth::user()->team_id) ->get();</code></pre>これは、<p>列のフィルタリングを心配せずにクエリを書くことができることを意味します:<code>where('team_id', Auth::user()->team_id)</code> <code>AppModelsArticle</code> </p>ユーザーが<p>であるチームに属していると仮定した場合、上記のクエリのために次のSQLが生成されます。 <code>team_id</code> </p>これはかっこいいですよね? ! <pre class="brush:php;toolbar:false"><code>php artisan make:scope TeamScope</code></pre> <p> #anonymousグローバルクエリスコープ<code>team_id</code> <code>1</code>グローバルクエリ範囲を定義および適用する別の方法は、匿名のグローバルスコープを使用することです。 </p> <pre class="brush:php;toolbar:false"><code>declare(strict_types=1); namespace App\Models\Scopes; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; use Illuminate\Support\Facades\Auth; final readonly class TeamScope implements Scope { /** * Apply the scope to a given Eloquent query builder. */ public function apply(Builder $builder, Model $model): void { $builder->where('team_id', Auth::user()->team_id); } }</code></pre>匿名のグローバルスコープを使用するように<p>モデルを更新してみましょう:</p> <h3> 上記のコードの例では、</h3>メソッドを使用して、モデルの<p>メソッドで匿名のグローバルスコープを定義しました。 </p>メソッドは、2つのパラメーターを受け入れます<ul> <li>スコープの名前 - クエリで無視する必要がある場合は、この名前を使用してスコープを参照できます</li> <li>範囲の制約 - 閉鎖を定義して制約に適用する</li> </ul> <p>他の方法と同様に、これは<code>where('team_id', Auth::user()->team_id)</code>モデルのすべてのクエリに<code>AppModelsArticle</code>制約を適用します。 </p> 私の経験では、匿名のグローバル範囲は、別のクラスでグローバル範囲を定義するほど一般的ではありません。しかし、緊急事態の場合に利用できることを知ることは有益です。 <p> </p> #ignoreグローバルクエリスコープ<h3> </h3>モデルに適用されたグローバルクエリスコープを使用しないクエリを作成する場合があります。たとえば、グローバルクエリの範囲に関係なく、すべてのレコードを含める必要があるレポートまたは分析クエリを作成する場合があります。 <p> </p>これが当てはまる場合、2つの方法のいずれかを使用して、グローバルな範囲を無視できます。 <p> </p>最初の方法は<p>です。この方法では、パラメーターが渡されない場合、モデル上のすべてのグローバルスコープを無視できます。 <code>withoutGlobalScopes</code> </p>または、グローバルスコープの特定のセットのみを無視したい場合は、スコープ名を<pre class="brush:php;toolbar:false"><code>use App\Models\Article; $publishedPosts = Article::query() ->where('published_at', '<', now()) ->get();</code></pre>メソッドに渡すことができます:<p> <code>withoutGlobalScopes</code> 上記の例では、</p>および<pre class="brush:php;toolbar:false"><code>use App\Models\Article; use Illuminate\Contracts\Database\Eloquent\Builder; $unpublishedPosts = Article::query() ->where(function (Builder $query): void { $query->whereNull('published_at') ->orWhere('published_at', '>', now()); }) ->get();</code></pre>と呼ばれる別の架空の匿名のグローバル範囲を無視します。 <p> <code>AppModelsScopesTeamScope</code>または、単一のグローバルスコープのみを無視したい場合は、<code>another_scope</code>メソッドを使用できます。 </p> グローバルクエリスコープ<p>の##precautions <code>withoutGlobalScope</code>グローバルクエリスコープは、モデルを介して行われたクエリのみに適用されることを覚えておくことが重要です。 </p>外観を使用してデータベースクエリを記述する場合、グローバルクエリスコープは適用されません。 <pre class="brush:php;toolbar:false"><code>declare(strict_types=1); namespace App\Models; use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; final class Article extends Model { public function scopePublished(Builder $query): Builder { return $query->where('published_at', '<', now()); } public function scopeNotPublished(Builder $query): Builder { return $query->where(function (Builder $query): Builder { return $query->whereNull('published_at') ->orWhere('published_at', '>', now()); }); } // ... }</code></pre> たとえば、このクエリを書いて、ログインしたユーザーのチームに属する記事のみをクロールしたいとしてください。 <h3> 上記のクエリでは、</h3>モデルのグローバルクエリスコープが<p>モデルで定義されている場合でも、スコープは適用されません。したがって、データベースクエリに制約が手動で適用されることを確認する必要があります。 <code>IlluminateSupportFacadesDB</code> </p>ローカルクエリスコープ<p>をテストします </p> <pre class="brush:php;toolbar:false"><code>use App\Models\Article; $publishedPosts = Article::query() ->published() ->get(); $unpublishedPosts = Article::query() ->notPublished() ->get();</code></pre>クエリスコープの作成と使用方法を学んだので、それらのテストを作成する方法について説明します。 <p> <code>AppModelsArticle</code>クエリの範囲をテストするにはいくつかの方法があり、選択した方法は、あなたの個人的な好みや書いている範囲のコンテンツに依存する場合があります。たとえば、スコープのユニットスタイルのテストをさらに作成することをお勧めします。または、コントローラーなどのコンテキストで範囲をテストする統合スタイルのテストをさらに作成することをお勧めします。 <code>AppModelsScopesTeamScope</code> 個人的には、スコープが正しい制約を追加し、スコープが実際にクエリで使用されていることを確認できるように、2つを混ぜるのが好きです。 </p> <h1>以前の例から始めましょう</h1>および<hr> <p>>それらのテストを記述します。 2つの異なるテストを作成する必要があります(各範囲ごとに1つ)</p> <ul> <li>テストチェック<code>published</code>範囲は公開された記事のみを返します。 </li> <li>テストチェック<code>notPublished</code>範囲は未発表の記事のみを返します。 </li> </ul> <p>これらのテストを見てから、何が行われているかを話し合いましょう:</p> <pre class="brush:php;toolbar:false"><code>use App\Models\Article; $publishedPosts = Article::query() ->where('published_at', '<', now()) ->get();</code></pre> <p>上記のテストファイルで確認できます。最初に<code>setUp</code>メソッドでいくつかのデータを作成します。公開された2つの記事、1つの予定外の記事と1つのアレンジされた記事を作成しました。 </p> <p> <code>only_published_articles_are_returned</code> <code>published</code>範囲をチェックして公開された記事のみを返すテスト(<code>only_not_published_articles_are_returned</code>)があります。また、未発表の記事のみを返すように<code>notPublished</code>範囲をチェックするテスト(</p>)もあります。 <p> これを行うことにより、クエリスコープが予想どおり制約を適用していることを確認できます。 </p> コントローラーのテスト範囲<h1></h1> <hr> <p>私たちが述べたように、クエリの範囲をテストする別の方法は、コントローラーで使用されるコンテキストでそれらをテストすることです。スコープの分離テストは、スコープがクエリに正しい制約を追加していることを主張するのに役立ちますが、実際には、スコープが期待どおりにアプリケーションで使用されているかどうかをテストしません。たとえば、コントローラーメソッドのクエリに<code>published</code>範囲を追加することを忘れる場合があります。 </p> <p>これらのタイプのエラーは、コントローラーメソッドでスコープを使用するときに正しいデータを返すことを主張するテストを作成することでキャプチャできます。 </p> <p>マルチテナントのブログアプリケーションを持っている例を見て、記事をリストするコントローラーメソッドのテストを書きましょう。次のように、非常にシンプルなコントローラー方法があると仮定しましょう。 </p> <pre class="brush:php;toolbar:false"><code>use App\Models\Article; use Illuminate\Contracts\Database\Eloquent\Builder; $unpublishedPosts = Article::query() ->where(function (Builder $query): void { $query->whereNull('published_at') ->orWhere('published_at', '>', now()); }) ->get();</code></pre> <p>モデルが<code>AppModelsArticle</code>を適用したと仮定します。 <code>AppModelsScopesTeamScope</code> </p>ユーザーチームに属する記事のみを返すと断言します。テストケースは次のようになる場合があります:<p> </p> 上記のテストでは、2つのチームを作成しています。次に、Team Oneに属するユーザーを作成します。チーム1の3つの記事とチーム2の2つの記事を作成しました。次に、ユーザーとして行動し、記事をリストするコントローラーメソッドにリクエストを行います。コントローラー方法は、チーム1に属する3つの記事のみを返す必要があるため、記事IDを比較することでそれらの記事のみが返されると主張します。 <pre class="brush:php;toolbar:false"><code>declare(strict_types=1); namespace App\Models; use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; final class Article extends Model { public function scopePublished(Builder $query): Builder { return $query->where('published_at', '<', now()); } public function scopeNotPublished(Builder $query): Builder { return $query->where(function (Builder $query): Builder { return $query->whereNull('published_at') ->orWhere('published_at', '>', now()); }); } // ... }</code></pre> これは、コントローラー法で予想どおりにグローバルクエリスコープが使用されていることを確認できることを意味します。 <p> </p>結論<p> </p> この記事では、ローカルクエリの範囲とグローバルクエリの範囲について学びました。それらの違い、それらを作成して使用する方法、およびそれらのテストを作成する方法を学びました。 <h1> </h1> Laravelアプリケーションでクエリスコープを自信を持って使用できるようになることを願っています。 <hr>

以上がLaravelでクエリスコープをマスターすることを学びますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。