idがstring型で定義されたテーブルA、およびA.idにリレーションを張ってるテーブルBがあるとします。
テーブル
テーブルA
CREATE TABLE table_a (
`id` varchar(16) NOT NULL COMMENT '主キー',
`name` varchar(16) NOT NULL COMMENT '名前',
PRIMARY KEY (`id`)
)
テーブルB
CREATE TABLE table_b (
`id` int NOT NULL COMMENT '主キー',
`a_id` varchar(16) NOT NULL COMMENT 'table_a.id',
PRIMARY KEY (`id`),
CONSTRAINT `a` FOREIGN KEY (`a_id`) REFERENCES `table_a` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
)
モデル
モデルA
class A extends Model
{
protected $table = 'table_a';
// IDは文字列型
protected $casts = [
'id' => 'string',
];
}
モデルB
class B extends Model
{
protected $table = 'table_b';
// a_idは文字列型
protected $casts = [
'a_id' => 'string',
];
// Aへのリレーション
public function table_a()
{
return $this->belongsTo('Path/To/Model/A', 'a_id', 'id');
}
}
コントローラ
public function hogeAction(Request $request){
// BとAを取得
$BwithA = B::where('id', 1)->with(['table_a'])->get();
}
発行されたSQL
DB::getQueryLogで確認できます。
array:2 [
0 => array:3 [
"query" => "select * from `b` where `id` = ?"
"bindings" => array:1 [
0 => 1
]
]
1 => array:3 [
"query" => "select * from `a` where `a`.`id` in (999)"
"bindings" => []
]
]
おい"
どこ行った。
現象
b.a_id
がどのような形であろうが、数値として扱われてしまいます。
a_id
に"hoge"とか文字列が入っていたとしても気にせず数値にするのでwhere a.id IN (0)
になってしまいます。
結果として正常にリレーションを持って来れません。
調査
Illuminate\Database\Eloquent\Relations\BelongsTo
BelongsToクラス内のaddEagerConstraintsでWHERE IN句を生成しているようだ。
public function addEagerConstraints(array $models){
$whereIn = $this->whereInMethod($this->related, $this->ownerKey);
$this->query->{$whereIn}($key, $this->getEagerModelKeys($models));
}
この$whereIn
に、whereIntegerInRaw
とかいういかにもintを返すっぽい値が入っていた。
Illuminate\Database\Eloquent\Relations\Relation
Relationクラス内のwhereInMethodメソッドが、whereIntegerInRaw
もしくはwhereIn
という値を返している。
protected function whereInMethod(Model $model, $key)
{
return $model->getKeyName() === last(explode('.', $key))
&& $model->getIncrementing()
&& in_array($model->getKeyType(), ['int', 'integer'])
? 'whereIntegerInRaw'
: 'whereIn';
}
こういう書き方やめてくれよ。
さて$model->getKeyType()
が悪さをしているようだ。
コントローラでちょっと確認してみる。
public function hogeAction(Request $request){
$a = new A();
var_dump($a->getKeyType());
}
$a
はint
になった。
なんで?
解決
主キーの型指定は$casts
ではなく$keyType
の設定が必要だったとかいう落ち。
主キーが整数でない場合は、モデルのprotectedの$keyTypeプロパティへstring値を設定してください。
感想
最初から主キーの型という方向で調べればすぐわかったと思うのだが、with
やbelongsTo
から調べ始めてしまったために無駄に時間がかかった。
ところでこれ、今回は外部キーの向き先がテーブルAの主キーだったから$keyType
で解決できたけど、主キーではなく他のカラムだった場合はどうなるんだろう?