Laravel『Call to a Member Function on String』の原因と対処法
- 作成日 2025.12.17
- その他
Laravelで「Call to a member function xxx() on string」は、オブジェクトだと思っている変数が実際は文字列(string)になっている状態で、メソッド呼び出し(->xxx())をしてしまったときに発生する。EloquentモデルのつもりがID文字列だった、リクエスト値が配列/オブジェクトではなく文字列だった、コレクションの要素が文字列になっていた、JSONをdecodeしていない、型変換や上書きで途中からstringに変わった、などが典型パターン。まず「その変数は本当は何型なのか」を特定し、モデル取得・decode・型チェック・命名の見直しで解決できる。
- 1. エラーの形と発生条件(典型例)
- 2. 最初にやること:該当行の変数の型を確認する
- 3. 原因1:Eloquentモデルのつもりが「ID文字列」になっている
- 4. 原因2:Query Builder / Collection のつもりが文字列になっている
- 5. 原因3:日付(Carbon)のつもりが日付文字列になっている
- 6. 原因4:JSON文字列をdecodeせずに配列/オブジェクト扱いしている
- 7. 原因5:変数の上書き(同名変数を別用途に使う)
- 8. 原因6:pluck() の結果を勘違いしている(stringを返すケース)
- 9. 原因7:Request入力の取り方の勘違い(配列のつもりが文字列)
- 10. 対処の基本:型を固定する(バリデーション / キャスト / Resource)
- 11. NG→OK 早見表(よくある事故パターン)
- 12. チェックリスト(上から順に確認する)
エラーの形と発生条件(典型例)
よく見るメッセージ例。
Call to a member function save() on string
Call to a member function format() on string
Call to a member function pluck() on string
Call to a member function first() on string発生条件の典型:
・モデル(Userなど)のつもりで、実際は文字列(”1″ や “abc”)
・Carbonインスタンスのつもりで、実際は日付文字列(”2025-12-15″)
・Collectionのつもりで、実際はCSV/JSON文字列
・Requestの入力値を配列だと思って扱い、実際は単一文字列
・同名変数を別用途に上書きしてしまい、途中からstringに変化
最初にやること:該当行の変数の型を確認する
原因特定を早くするには、落ちた行の直前で「型と中身」を確認するのが一番速い。
// 例:$user->save() で落ちるなら
logger()->debug('type', [
'class' => is_object($user) ? get_class($user) : null,
'type' => gettype($user),
'value' => $user,
]);
// 一時的に止めて確認
dd(gettype($user), is_object($user) ? get_class($user) : null, $user);
ポイント:
・object なら get_class() で「何のクラスか」を見る
・string なら「どこでstringになったか」を追う
・多くは “代入している行” が原因(モデル取得漏れ、上書き、入力値そのまま利用)
原因1:Eloquentモデルのつもりが「ID文字列」になっている
典型例:ルートパラメータやフォーム値で渡ってきた「user_id」を、Userモデルだと勘違いする。
// NG: $user は "1" のような文字列
$user = $request->input('user_id');
$user->save(); // Call to a member function save() on string対処:IDからモデルを取得してからメソッドを呼ぶ。
// OK
$user = \App\Models\User::findOrFail($request->input('user_id'));
$user->name = 'New Name';
$user->save();ルートモデルバインディングを使うと、そもそも型の取り違えが減る。
// routes/web.php
Route::put('/users/{user}', [UserController::class, 'update']);
// Controller
public function update(\Illuminate\Http\Request $request, \App\Models\User $user)
{
$user->fill($request->only(['name']))->save();
}原因2:Query Builder / Collection のつもりが文字列になっている
SQLを文字列で保持してしまい、Builderのメソッドを呼んで落ちるケース。
// NG: $query はSQL文字列
$query = "select * from users";
$query->where('id', 1); // Call to a member function where() on string
対処:Builderを使う。
// OK
$query = \App\Models\User::query();
$users = $query->where('id', 1)->get();「get() した後は Collection」「toSql() した後は string」など、戻り値が切り替わるメソッドが混ざると事故が起きやすい。
// 例:toSql() はSQL文字列を返す
$sql = \App\Models\User::where('id', 1)->toSql(); // string原因3:日付(Carbon)のつもりが日付文字列になっている
Eloquentの日時カラムやリクエストの日付をCarbonだと勘違いして format() を呼ぶパターン。
// NG: $date は "2025-12-15" のような文字列
$date = $request->input('date');
echo $date->format('Y-m-d'); // Call to a member function format() on string対処:Carbonにパースしてから扱う。
use Illuminate\Support\Carbon;
$date = Carbon::parse($request->input('date'));
echo $date->format('Y-m-d');Eloquent側でキャストしてCarbon化する方法もある。
// app/Models/Order.php
protected $casts = [
'ordered_at' => 'datetime',
];これで $order->ordered_at はCarbonになりやすい(ただし取得値やDB型が想定外だと文字列のままになることもある)。
原因4:JSON文字列をdecodeせずに配列/オブジェクト扱いしている
外部APIレスポンスやDBに保存したJSONを、文字列のまま扱ってしまう。
// NG: $payload はJSON文字列
$payload = $request->input('payload'); // '{"name":"taro"}'
$name = $payload->get('name'); // Call to a member function get() on string
対処:json_decode してからアクセスする。
$payload = json_decode($request->input('payload'), true); // 配列化
$name = $payload['name'] ?? null;DBのJSONカラムなら、Eloquent castで自動配列化できる。
// app/Models/Log.php
protected $casts = [
'payload' => 'array',
];これで $log->payload は配列として扱える。
原因5:変数の上書き(同名変数を別用途に使う)
途中で変数が別の値(文字列)に上書きされて型が変わるパターン。
// NG
$user = \App\Models\User::findOrFail($id); // object
$user = $request->input('user'); // string に上書き
$user->save(); // Call to a member function save() on string
対処:変数名を分ける、あるいは上書きしない。
// OK
$user = \App\Models\User::findOrFail($id);
$userName = $request->input('user');
$user->name = $userName;
$user->save();同名変数の再代入があるコードは、このエラーの温床になりやすい。
原因6:pluck() の結果を勘違いしている(stringを返すケース)
pluck() は使い方によっては Collection を返すが、first() と組み合わせるとスカラー(string/int)になる。
// 例:emailは文字列
$email = \App\Models\User::where('id', 1)->pluck('email')->first(); // string
$email->lower(); // Call to a member function lower() on string対処:戻り値がスカラーである前提で扱うか、Strヘルパを使う。
use Illuminate\Support\Str;
$email = \App\Models\User::where('id', 1)->value('email'); // string
$lower = Str::lower($email);Collectionが欲しいなら first() をしない。
$emails = \App\Models\User::whereIn('id', [1,2])->pluck('email'); // Collection
原因7:Request入力の取り方の勘違い(配列のつもりが文字列)
フォームやJSON送信側が想定と違い、配列で来るはずが文字列で来ることがある。
// NG: items が "a,b,c" のような文字列
$items = $request->input('items');
$items->map(...); // Call to a member function map() on string対処:入力仕様を固定しつつ、受け取り側でバリデーション・正規化する。
// 例:items は配列で来る前提
$request->validate([
'items' => ['required', 'array'],
'items.*' => ['string'],
]);
$items = collect($request->input('items')); // Collection化
どうしてもCSV文字列で来る可能性があるなら分岐して正規化する。
$items = $request->input('items');
if (is_string($items)) {
$items = array_filter(array_map('trim', explode(',', $items)));
}
$items = collect($items);
対処の基本:型を固定する(バリデーション / キャスト / Resource)
このエラーは「型の揺れ」が本質なので、入口で型を固定すると再発が減る。
・Requestで array/string/integer をバリデーションで固定
・Modelの $casts で datetime/array/int を固定
・レスポンスは Resource で形を固定して、想定外の構造が混ざらないようにする
バリデーション例。
$request->validate([
'user_id' => ['required', 'integer', 'exists:users,id'],
'date' => ['required', 'date'],
'payload' => ['nullable', 'json'],
]);NG→OK 早見表(よくある事故パターン)
# 1) ID文字列をモデルだと思う
# NG
$user = $request->input('user_id');
$user->save();
# OK
$user = User::findOrFail($request->input('user_id'));
$user->save();
# 2) 日付文字列に format()
# NG
$date = $request->input('date');
$date->format('Y-m-d');
# OK
$date = Carbon::parse($request->input('date'));
$date->format('Y-m-d');
# 3) JSON文字列をオブジェクト扱い
# NG
$payload = $request->input('payload'); // string
$payload->get('name');
# OK
$payload = json_decode($request->input('payload'), true);
$payload['name'] ?? null;
# 4) 変数の上書き
# NG
$user = User::findOrFail($id);
$user = $request->input('user');
$user->save();
# OK
$user = User::findOrFail($id);
$userName = $request->input('user');
$user->name = $userName;
$user->save();チェックリスト(上から順に確認する)
1) エラー行で ->xxx() を呼んでいる変数が本当に object か(dd/gettype/get_class)で確認したか
2) その変数が string に変わる代入箇所(上書き)を探したか
3) ルートパラメータやフォーム値(id)をモデルだと勘違いしていないか(findOrFail / ルートモデルバインディング)
4) 日付・JSON・配列などを、文字列のまま扱っていないか(Carbon::parse / json_decode / collect)
5) pluck()->first() や value() など、戻り値がスカラーになる箇所を勘違いしていないか
6) Requestの入力仕様(配列で来るはず等)が本当に守られているか(validateで型固定)
7) Modelの $casts で array/datetime/int を固定できないか
8) Resourceでレスポンスの形を固定し、想定外のネストや型が混ざらないようにできないか
-
前の記事
Laravel『Call to a Member Function on String』の原因と対処法 2025.12.16
-
次の記事
Laravel『TokenMismatchException』の原因と対処法 2025.12.18
コメントを書く