はじめに
この記事はLaravelのAPIを開発する際によく使われるメソッドおよび、Laravelの開発方法についてまとめたメモになります
都度、更新予定です
Laravelサーバーを起動する
php artisan serve
コントローラーを作成する
Laravelでコントローラーを作成するArtisanコマンドです
ControllerName
は作成したいコントローラーの名前に置き換えます
例:CustomerController など
php artisan make:controller ControllerName
indexメソッド(一覧表示)
一般的にLaravelのコントローラーでリソース(データベースのレコードなど)の一覧を取得し、表示するために使われるメソッドです。
ルーティング
use App\Http\Controllers\ControllerName;
Route::get('/index', [ControllerName::class, 'index']);
api.phpが存在しない場合
テーブル単体から全レコードを取得する(メソッド)
use App\Models\Product;
class ControllerName extends Controller
{
public function index()
{
// Productテーブルから全てのレコードを取得
$products = Product::all();
// JSON形式で返す
return response()->json($products);
}
}
withメソッドを使ってリレーションデータを取得する
Withメソッドは次のようにリレーション関係を持ったテーブル
から関連データを取得できます
リレーションの使用例
- 1対多の場合:親テーブル(
hasMany
)
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\Product;
class Category extends Model
{
public function products()
{
return $this->hasMany(Product::class, 'category_id');
// 外部キーは省略可能
}
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\Category;
use App\Models\Order;
class Product extends Model
{
public function category()
{
return $this->belongsTo(Category::class, 'category_id');
// 外部キーは省略可能
}
public function orders()
{
return $this->hasMany(Order::class, 'product_id');
// 外部キーは省略可能
}
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\Product;
class Order extends Model
{
public function product()
{
return $this->belongsTo(Product::class, 'product_id');
// 外部キーは省略可能
}
}
外部キーは省略できますが、省略するとLaravelは親モデルの名前に基づいて外部キーを推測します。これにより、特定のテーブル名の場合には、実際には存在しない外部キーを参照しようとすることがあるため、外部キーを明示的に指定することがより確実です。
多対多リレーションの設定方法(リンク)
APIの使用例
- ProductとCategoryのデータを取得する場合
use App\Models\Product;
public function index()
{
$products = Product::with('category')->get();
return response()->json($products);
}
Withメソッドを使うことでProductモデルから全ての商品データを取得し、関連するCategoryデータも同時に取得できます
ProductとCategoryおよびOrderのデータを同時に取得
use App\Models\Product;
public function index()
{
$products = Product::with(['category', 'orders'])->get();
return response()->json($products);
}
ProductモデルはCategoryとOrderに対して直接リレーションを組んでいるため、CategoryとOrderのデータを一度に取得しています。
Orderモデルからリレーションデータを取得
use App\Models\Order;
public function index()
{
$orders = Order::with('product.category')->get();
return response()->json($orders);
}
OrderモデルはProductとは直接関係がありますが、Categoryとは直接関係がありません。CategoryはProductを介して初めてアクセスできるものなので、Orderモデルから関連するProductを取得しさらに関連しているCategoryのデータを取得しています
Withメソッドについて
- Withメソッドには必ず引数が必要になるため、単一のテーブルに対しての利用は不向きです
- withメソッドでは、親モデルからのリレーション名を最初に、次にそのリレーションに続く子モデルのリレーション名を指定します
- 複数のリレーションを指定する場合は、ドットでつなげて記述することができます
-
'product.category'
という指定は、親モデルであるOrder
からProduct
へ、さらにそのProduct
からCategory
へという順番でリレーションをたどる必要があります
Laravelでフロントエンドを実装する場合
Laravelでビューを表示さえるルーティングはweb.php
に記述します
use App\Http\Controllers\YourControllerName;
Route::get('/index', [YourControllerName::class, 'index']);
次のようにモデルのデータをビューに送信します
use App\Models\Product;
public function index()
{
// カテゴリ情報も一緒に取得
$products = Product::with('category')->get();
// 'index'はビュー名で、'products'は渡すデータのキーです
return view('index', ['products' => $products]);
}
データを表示させるためにはBladeテンプレートが必要です
例:resources/views/index.blade.php
<!-- resources/views/index.blade.php -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>商品一覧</title>
</head>
<body>
<h1>商品一覧</h1>
<ul>
@foreach($products as $product)
<li>
<strong>{{ $product->name }}</strong> (カテゴリ: {{ $product->category->name }})
</li>
@endforeach
</ul>
</body>
</html>
Showメソッド
特定のIDに関するレコードを取得する
showメソッドのリクエストは一般的にGETリクエストを使用することが標準とされています。
次のようにformタグに対してaction属性を用いてURLを設定し、受け取った変数でIDなどを設定すれば『送信ボタン』を押した際に詳細画面(show)を開くことができます
例:/request/1
@foreach($customers as $customer)
<li>
<form action="/request/{{ $customer->customer_id }}" method="get">
<input type="submit" value="送信"></p>
</form>
</li>
@endforeach
Route::get('/request/{id}', [***Controller::class, 'show']);
モデルのfindメソッド
を使うことで、指定されたIDのレコードを取得できますが、findメソッド単体ではリレーションデータは取得できません
public function show($id)
{
// 指定されたIDをもとにユーザー情報を取得
$customer = User::find($id);
// ユーザーが見つからない場合、404エラーを返す
if (!$user) {
return response()->json(['message' => 'User not found'], 404);
}
// ユーザーが見つかった場合、ユーザー情報の詳細ページを返す
// views/customers/show
return view('customers.show', compact('customer'));
}
findOrFailメソッド
を使うとデータが見つからなかった場合、自動的に404エラーを発生させるためコードの簡略化に役立ちます
public function show($id)
{
// 指定されたIDをもとにユーザー情報を取得、存在しない場合は404を自動で返す
$user = User::findOrFail($id);
// ユーザー情報をJSON形式で返す
return response()->json($user);
}
特定のIDからリレーションデータを取得する
Productモデル
がCategoryモデル
にbelongsToのリレーションを持っている
場合、findメソッド
とwithメソッド
を使ってリレーションのデータを一緒に取得することができます。
$product = Product::with('category')->find(1);
with('category')
の部分は、Productモデルで定義されているcategoryというリレーションを指定しています。
storeメソッド(保存)
新しいレコードを保存する
POSTリクエストで送信されたデータを使用して新しいレコードを作成します。
use App\Models\Client;
use Illuminate\Http\Request;
class ClientController extends Controller
{
public function store(Request $request)
{
// バリデーション
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:clients,email',
]);
// 新しいクライアントの作成
$client = Client::create($validatedData);
return response()->json($client, 201);
}
}
保存動作(store)に必要なメソッド
storeメソッドを動作させるためには最低限、次のようなメソッドが必要になります
-
Request::validate()
- リクエストデータが正しい形式かどうかを確認(バリデーション)するために使用します
- 一般的にデータはjsonで送信されている
-
create()
- データベースに新しいレコードを作成するためのメソッドであり、通常はJSON形式のデータを与えることでレコードを追加します
-
response()->json()
- データをJSON形式で返すための方法です
- このメソッドは、主に2つの引数を受け取ります(以下)
- 『JSON形式で返したいデータ』,『HTTPステータスコード』
バリデーションとエラーハンドリングの参考リンク
マスアサインメントの設定
見本のように連想配列を用いてデータを保存したい場合は、modelファイルに対してマスアサインメントの設定をする必要があります
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Client extends Model
{
// マスアサインメントを許可するフィールドを指定
protected $fillable = ['name', 'email'];
}
マスアサインメントが不要になるケース
マスアサインメントの設定が不要となる状況は、手動で個々の属性を設定する場合です。具体的にはcreate()
やupdate()メソッド
の代わりに、モデルのインスタンスを生成して個別にフィールドを指定して保存する方法です。
ただし、マスアサインメントを使用すれば、リクエストデータを直接モデルに渡すことができ、コードがシンプルで読みやすくなるため、複数のフィールドを個別に設定する必要がなく、開発効率が向上するためマスアサインメントを使用する方が一般的です
public function store(Request $request)
{
// リクエストデータのバリデーション
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'price' => 'required|integer|min:0',
'description' => 'nullable|string|max:500', // 必須でない項目(オプション)
'category_id' => 'required|integer|exists:categories,id', // 外部キー制約の確認
'stock_quantity' => 'required|integer|min:0'
]);
// 新しいProductインスタンスを作成
$product = new Product();
// プロパティに個別に値を設定(マスアサインメントは不要)
$product->name = $validatedData['name'];
$product->price = $validatedData['price'];
$product->description = $validatedData['description'] ?? ''; // descriptionがnullの場合は '' を設定する
$product->category_id = $validatedData['category_id'];
$product->stock_quantity = $validatedData['stock_quantity'];
// 保存
$product->save();
// レスポンスを返す
return response()->json(['message' => 'Product created successfully!'], 201);
}
プロパティに直接、値を代入する方法について
new Product()で新しいモデルインスタンスを作成して直接プロパティに値を設定しています。この場合は$fillable
の設定は不要になります
save()メソッドを使用する方法
create()やupdate()メソッドは使用せずに値を保存するためにはsave()メソッド
使用します。この場合マスアサインメントの設定は不要になります
リレーションテーブルへ保存する場合
外部キー
でリレーション関係にあるテーブルがある場合でも、条件を満たせば各々のテーブルにレコードを保存をすることは可能です
- Categorysテーブル
型 | 説明 | |
---|---|---|
id | 整数 | 主キー |
name | 文字列 | カテゴリ名 |
- Productテーブル
型 | 説明 | |
---|---|---|
id | 整数 | 主キー |
name | 文字列 | 商品名 |
category_id | 整数 | 外部キー(Category テーブルの id を参照) |
リレーションテーブルにレコードを追加する場合に発生するエラー
子テーブルにレコードを追加する際、指定した外部キー(category_id)が親テーブルに存在しない場合、エラーが発生します
ただし、次の条件の場合にはエラーは発生しません
-
親テーブルにレコードを追加する場合
- テーブルに外部キーの制約がない限り、問題なく追加可能
-
子テーブルにレコードを追加する場合
- 親テーブルに存在する外部キーを指定すれば問題なく追加可能
storeメソッドが失敗した場合
バリデーションを設定することで予期しないエラーが発生する可能性もあるため、以下の方法でバリデーションを外した状態を確認することが可能です
マスアサインメントを使う場合
public function store(Request $request){
// $validateData = $request->validate([
// 'name' => 'required|string|max:255',
// 'description' => 'required|string|max:255',
// 'price' => 'required|integer|max:255',
// 'category_id' => 'required|integer|max:255',
// 'stock_quantity' => 'required|integer|max:255',
// ]);
// $product = Product::create($request->all);
$product = Product::create($request->all());
return response()->json($product, 201);
}
マスアサインメントを使わない場合
public function store(Request $request)
{
// リクエストデータのバリデーション
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'price' => 'required|integer|min:0',
'description' => 'nullable|string|max:500', // 必須でない項目(オプション)
'category_id' => 'required|integer|exists:categories,id', // 外部キー制約の確認
'stock_quantity' => 'required|integer|min:0'
]);
// 新しいProductインスタンスを作成
$product = new Product();
// プロパティに個別に値を設定(マスアサインメントは不要)
$product->name = $validatedData['name'];
$product->price = $validatedData['price'];
$product->description = $validatedData['description'] ?? ''; // descriptionがnullの場合は '' を設定する
$product->category_id = $validatedData['category_id'];
$product->stock_quantity = $validatedData['stock_quantity'];
// 保存
$product->save();
// レスポンスを返す
return response()->json(['message' => 'Product created successfully!'], 201);
}
カスケード操作について
外部キー制約を設定することで、親テーブルのレコードが削除または更新された際に子テーブルの関連するレコードも自動的に削除または更新する機能のことをカスケードと呼びます
カスケードはマイグレーションファイルで設定することができ、外部キー制約を設定する際にカスケード操作を指定します
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('category_id')->constrained()->onDelete('cascade'); // カスケード削除を設定
$table->timestamps();
});
上記の例では、category_id が削除された場合、関連する Product レコードも自動的に削除される設定になります。
MySQLなどでカスケード設定
MySQLやPostgreSQLなどのデータベース管理ツールを使用して、テーブルを作成する際に、SQL文で直接カスケード操作を設定することも可能です
ALTER TABLE products
ADD CONSTRAINT fk_category
FOREIGN KEY (category_id)
REFERENCES categories(id)
ON DELETE CASCADE; -- カスケード削除を設定
updateメソッド(更新)
PUTリクエスト
もしくはPATCHリクエスト
で送信されたデータを使用して特定のレコードを更新します。
ルーティング
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\***Controller;
// レコードを更新するルーティング
// putの場合
Route::put('/update/{id}', [ApplicationController::class, 'update']);
// patchの場合
Route::patch('/update/{id}', [ApplicationController::class, 'update']);
putとpatchの違いについて
- PUT
プット
- テーブルのレコードを全て更新する場合に使用します
- 全てのフィールドの値を指定して、新しいデータでリソースを置き換えます
- PATCH
パッチ
- テーブルのレコードを一部更新する場合に使用します
- 更新フィールドのみを指定し、他のフィールドはそのまま保持されます
この使い分けにより、APIの設計がより明確になり、クライアント側も意図する更新操作を分かりやすく行うことができます。
逆にPUTで一部の更新したりPATCHで全ての更新することもできますが、サーバー側で意図しないデフォルト値やnullに置き換わる可能性
もあるため、各々使い分けすることが推奨されます
メソッド
public function update(Request $request, $id)
{
// バリデーション
$validatedData = $request->validate([
'name' => 'sometimes|required|string|max:255',
'email' => 'sometimes|required|email|unique:clients,email,' . $id,
]);
$client = Client::findOrFail($id);
$client->update($validatedData);
return response()->json($client);
}
-
findOrFail($id)
指定されたIDに一致するレコードをデータベースから検索し、そのレコードを返します。 該当するレコードが見つからない場合は、ModelNotFoundException例外をスローし、通常は404エラーページ(「Not Found」)が表示されます。 -
update()
既存のEloquentモデルインスタンスに対して、データを更新するために使用されるメソッドです。 -
sometimes
リクエストにemailフィールドが存在する場合のみ、以下のバリデーションを適用するという意味です。つまり、emailフィールドが存在しない場合はスキップされます。
更新時には、必ずしもemailを送信する必要がない場合があるため、存在する場合のみバリデートするために使用します。 -
'unique:clients,email,'
clientsテーブルのemailカラム内で、メールアドレスがユニーク(重複していない)であることをチェックします。 -
$id
顧客情報を更新する際、既に存在する顧客のメールアドレスをそのまま使う場合があるので、現在の顧客($idで識別される)のメールアドレスは重複チェックから除外します。つまり、自分のレコードに対しては重複エラーを出さないようにしています。
リクエストの送信方法(URL)について
- HTTPメソッドが
PUT
もしくはPATCH
の場合、特定のリソース(データ)のIDをリクエストするには、一般的にはURLにプロパティとしてIDを含めます- URL: http://localhost:8000/api/put/1
- メソッド:
put
もしくはpatch
{
"name": "テスト",
"email": "tesuto@example.com"
}
destroyメソッド(削除)
Laravelのコントローラー内で特定のリソース(例えば、データベースのレコード)を削除するために使用されます。
Route::delete('/delete/{id}', [****Controller::class, 'destroy']);
リクエストの送信方法(URL)について
HTTPメソッドがPUTもしくはPATCHの場合、特定のリソース(データ)のIDをリクエストするには、一般的にはURLにプロパティとしてIDを含めます
URL: http://localhost:8000/api/delete/1
メソッド: delete
public function destroy($id){
$client = Client::findOrFail($id);
$client->delete();
return response()->json(null, 204);
}
-
json(null, 204);
-
null
レスポンスのボディに含まれるデータです。ここではnullを指定しているため、レスポンスのボディにはデータが含まれません。 -
HTTPステータスコードの204
「No Content」を意味し、リクエストが成功したことを示しますが、返すべきデータがないことを示します
-