モデル
コントローラーのセットアップ
モデルをAPI経由で公開するには、まずそのモデルに対応するコントローラーを作成する必要があります。はじめに - シンプルなCRUDのセクションで見たとおり、モデルコントローラーの定義は非常に簡単です。
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* モデルの完全修飾クラス名
*/
protected $model = Post::class; // または "App\Models\Post"
}
ペジネーションの無効化
デフォルトでは、indexエンドポイントが返すリソースはペジネーションされます。ペジネーションを無効化してすべてのリソースを返すには、DisablePaginationトレイトを使用します。
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
use Orion\Concerns\DisablePagination;
class PostsController extends Controller
{
use DisablePagination;
/**
* モデルの完全修飾クラス名
*/
protected $model = Post::class; // または "App\Models\Post"
}
- モデルコントローラーは常に
Orion\Http\Controllers\Controllerを継承します $modelプロパティには完全修飾のモデルクラス名を設定しますDisablePaginationトレイトを使用すると、モデルリソースとリレーションリソースの両方でペジネーションを無効化できます
ルートのセットアップ
コントローラーを作成したら、次はルートを登録します。
<?php
use Illuminate\Support\Facades\Route;
use Orion\Facades\Orion;
use App\Http\Controllers\PostsController;
Route::group(['as' => 'api.'], function() {
...
Orion::resource('posts', PostsController::class);
...
});
Orion::resourceメソッドは、本質的にはLaravel標準のRoute::apiResourceと同じで、リソースに対するさまざまなアクションを処理するための複数のルートを作成します。
+--------+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+-------------------------------------------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+-------------------------------------------------+
...
| | GET|HEAD | api/posts | api.posts.index | App\Http\Controllers\Api\PostsController@index | api |
| | POST | api/posts/search | api.posts.search | App\Http\Controllers\Api\PostsController@index | api |
| | POST | api/posts | api.posts.store | App\Http\Controllers\Api\PostsController@store | api |
| | GET|HEAD | api/posts/{post} | api.posts.show | App\Http\Controllers\Api\PostsController@show | api |
| | PUT|PATCH | api/posts/{post} | api.posts.update | App\Http\Controllers\Api\PostsController@update | api |
| | DELETE | api/posts/{post} | api.posts.destroy | App\Http\Controllers\Api\PostsController@destroy | api |
| | POST | api/posts/batch | api.posts.batchStore | App\Http\Controllers\Api\PostsController@batchStore | api |
| | PATCH | api/posts/batch | api.posts.batchUpdate | App\Http\Controllers\Api\PostsController@batchUpdate | api |
| | DELETE | api/posts/batch | api.posts.batchDestroy | App\Http\Controllers\Api\PostsController@batchDestroy | api |
ソフトデリート
モデルがSoftDeletesトレイトを使用しており、同じ機能をAPI経由でも公開したい場合は、リソース登録時にwithSoftDeletesメソッドを呼び出します。
<?php
use Illuminate\Support\Facades\Route;
use Orion\Facades\Orion;
Route::group(['as' => 'api.'], function() {
...
Orion::resource('posts', 'Api\PostsController')->withSoftDeletes();
...
});
これにより、restoreおよびbatchRestoreエンドポイントが追加されます。API経由でリソースを完全削除(force delete)する方法については、関連するクエリパラメータのセクションを参照してください。
+--------+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+-------------------------------------------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+-------------------------------------------------+
...
| | POST | api/posts/{post}/restore | api.posts.restore | App\Http\Controllers\Api\PostsController@restore | api |
| | POST | api/posts/batch/restore | api.posts.batchRestore | App\Http\Controllers\Api\PostsController@batchRestore | api |
キーのカスタマイズ
デフォルトでは、エンドポイントはデータベースからモデルを取得する際に主キー(通常はid)を使用します。しかし、主キーはそのまま維持しつつ、別のフィールドを使用してモデルを取得したい場合もあります。その場合は、コントローラーのkeyNameメソッドをオーバーライドします。
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* モデルの完全修飾クラス名
*/
protected $model = Post::class; // または "App\Models\Post"
/**
* データベースからリソースを取得するために使用されるフィールド名。
*
* @return string
*/
protected function keyName(): string
{
return 'slug';
}
}
クエリのカスタマイズ
Orionは非常に柔軟で、各エンドポイントにおけるEloquentクエリの構築方法と実行方法を再定義できます。
個別のエンドポイントの場合
クエリの構築
たとえば、indexエンドポイントで公開済みのブログ記事のみを返したいとします。その場合は、コントローラーのbuildIndexFetchQueryメソッドをオーバーライドします。
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* モデルの完全修飾クラス名
*/
protected $model = Post::class; // または "App\Models\Post"
/**
* index メソッドでエンティティを取得するための Eloquent クエリを構築します。
*
* @param Request $request
* @param array $requestedRelations
* @return Builder
*/
protected function buildIndexFetchQuery(Request $request, array $requestedRelations): Builder
{
$query = parent::buildIndexFetchQuery($request, $requestedRelations);
$query->whereNotNull('published_at');
return $query;
}
}
クエリの実行
よくある例として、モデルの一覧を取得する際に特定のカラムのみを選択するケースがあります。その場合は、runIndexFetchQueryメソッドをオーバーライドします。
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* モデルの完全修飾クラス名
*/
protected $model = Post::class; // または "App\Models\Post"
...
/**
* index メソッドでエンティティを取得するために、指定されたクエリを実行します。
*
* @param Request $request
* @param Builder $query
* @param int $paginationLimit
* @return LengthAwarePaginator
*/
protected function runIndexFetchQuery(Request $request, Builder $query, int $paginationLimit): LengthAwarePaginator
{
return $query->paginate($paginationLimit, ['id', 'title', 'published_at']);
}
}
操作の実行
indexやshowなどのエンドポイントの主な目的は、データベースからデータを取得することであり、変更することではありません。一方、storeやupdateなどのエンドポイントはデータベースに変更を加えます。モデルの保存など、特定の操作の実行方法をカスタマイズすることも可能です。
次の例では、現在認証されているユーザーが管理者である場合に、記事の属性を強制的に設定(force fill)します(ここでのロールの実装は架空のものです)。
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* モデルの完全修飾クラス名
*/
protected $model = Post::class; // または "App\Models\Post"
...
/**
* 指定されたエンティティに属性を設定し、データベースに保存します。
*
* @param Request $request
* @param Model $entity
* @param array $attributes
*/
protected function performStore(Request $request, Model $entity, array $attributes): void
{
if ($this->resolveUser()->hasRole('admin')) {
$entity->forceFill($attributes);
} else {
$entity->fill($attributes);
}
$entity->save();
}
}
performFillメソッドをオーバーライドすることで、モデルへの属性の設定方法もカスタマイズできます。エンドポイントのグループの場合
これでindexエンドポイントは公開済みの記事のみを返すようになりました。しかし、同じ制約をshowエンドポイントにも適用したい場合はどうすればよいでしょうか。もちろん、同じ処理を複製してbuildShowFetchQueryメソッドをオーバーライドすることもできますが、より良い方法があります。
クエリの構築
buildIndexFetchQueryメソッドの実装を見ると、内部でbuildFetchQueryメソッドを使用していることがわかります。実際、このメソッドはindex、show、update、destroy、restoreの各エンドポイントがモデル取得用のクエリを構築するために使用しており、これをオーバーライドすることもできます。
同じ制約をindexとshowエンドポイント(さらにupdate、destroy、restoreにも)に一度に適用する場合は、次のようになります。
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* モデルの完全修飾クラス名
*/
protected $model = Post::class; // または "App\Models\Post"
/**
* エンティティを取得するための Eloquent クエリを構築します。
*
* @param Request $request
* @param array $requestedRelations
* @return Builder
*/
protected function buildFetchQuery(Request $request, array $requestedRelations): Builder
{
$query = parent::buildFetchQuery($request, $requestedRelations);
$query->whereNotNull('published_at');
return $query;
}
...
}
クエリの実行
クエリの実行に関しては、indexエンドポイントはやや例外的な存在ですが(単一のモデルではなくモデルの一覧を取得するため)、show、update、destroy、restoreの各エンドポイントは同じロジックを共有しており、内部でrunFetchQueryを使用しています。
少しリファクタリングすると、公開済みのブログ記事のみを取得し、データベースからid、title、published_atのカラム(属性)のみを取得するコントローラーが完成します。
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* モデルの完全修飾クラス名
*/
protected $model = Post::class; // または "App\Models\Post"
/**
* データベースから取得する属性のリスト
*/
protected $attributes = ['id', 'title', 'published_at'];
/**
* エンティティを取得するための Eloquent クエリを構築します。
*
* @param Request $request
* @param array $requestedRelations
* @return Builder
*/
protected function buildFetchQuery(Request $request, array $requestedRelations): Builder
{
$query = parent::buildFetchQuery($request, $requestedRelations);
$query->whereNotNull('published_at');
return $query;
}
/**
* エンティティを取得するために、指定されたクエリを実行します。
*
* @param Request $request
* @param Builder $query
* @param int|string $key
* @return Model
*/
protected function runFetchQuery(Request $request, Builder $query, $key): Model
{
return $query->select($this->attributes)->findOrFail($key);
}
/**
* index メソッドでエンティティを取得するために、指定されたクエリを実行します。
*
* @param Request $request
* @param Builder $query
* @param int $paginationLimit
* @return LengthAwarePaginator
*/
protected function runIndexFetchQuery(Request $request, Builder $query, int $paginationLimit): LengthAwarePaginator
{
return $query->paginate($paginationLimit, $this->attributes);
}
/**
* 指定されたエンティティに属性を設定し、データベースに保存します。
*
* @param Request $request
* @param Model $post
* @param array $attributes
*/
protected function performStore(Request $request, Model $post, array $attributes): void
{
if ($this->resolveUser()->hasRole('admin')) {
$post->forceFill($attributes);
} else {
$post->fill($attributes);
}
$post->save();
}
}
標準操作メソッド
| メソッド | Build | Run | Perform |
|---|---|---|---|
| index | buildIndexFetchQuery | runIndexFetchQuery | - |
| store | buildStoreFetchQuery | runStoreFetchQuery | performStore |
| show | buildShowFetchQuery | runShowFetchQuery | - |
| update | buildUpdateFetchQuery | runUpdateFetchQuery | performUpdate |
| destroy | buildDestroyFetchQuery | runDestroyFetchQuery | performDestroy |
| restore | buildRestoreFetchQuery | runRestoreFetchQuery | performRestore |