Eloquentのmorphとwithの併用は可能か?
- 評価
- クリップ 0
- VIEW 1,525
EloquentのPolymorphic RelationsでHogeとFugaを使い分けているデータがあるとします。
hoge
id | name | number |
---|---|---|
1 | hoge | 0 |
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Hoge extends Model
{
public function base() {
return $this->morphMany(Base::class, 'ref');
}
}
fuga
id | name | text |
---|---|---|
1 | fuga | a |
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class fuga extends Model
{
public function base() {
return $this->morphMany(Base::class, 'ref');
}
}
base
id | ref_id | ref_type |
---|---|---|
1 | 1 | Hoge |
2 | 1 | Fuga |
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Base extends Model
{
public function ref() {
return $this->morphTo();
}
}
このとき、withメソッドを使わずBaseを得たときの挙動はこうなります。
$base_list = Base::all();
// [
// ['id' => 1, 'ref_id' => 1, 'ref_type' => 'Hoge'],
// ['id' => 2, 'ref_id' => 1, 'ref_type' => 'Fuga']
// ]
さらに、withを使うとこうなります。
$base_list = Base::with(['ref'])->all();
// [
// ['id' => 1, 'ref_id' => 1, 'ref_type' => 'Hoge', 'ref' => [id => 1, 'name' => 'hoge', 'number' => 0]],
// ['id' => 2, 'ref_id' => 1, 'ref_type' => 'Fuga', 'ref' => [id => 1, 'name' => 'fuga', 'text' => 'a']],
// ]
ここまではいいのですが、例えばHogeにだけ新たにImageモデルのリレーションを加えたとします。
image
id | hoge_id | path |
---|---|---|
1 | 1 | image.jpg |
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Image extends Model
{
public function hoge() {
return $this->belongsTo();
}
}
Hoge(改変)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Hoge extends Model
{
public function base() {
return $this->morphMany(Base::class, 'ref');
}
public function images() {
return $this->hasMany(Image::class);
}
}
このあと、HogeのImageも欲しいので以下のようにwithを書き直すとエラーになります。
$base_list = Base::with(['ref', 'ref.images'])->all();
// Hogeにはimagesがあるが、Fugaにはimagesのリレーションがないのでエラー
当たり前だ、と言われればそれまでなのですが、「あったら取る」「なければnull、またはプロパティ自体が無い」状態のデータが取得したいのです。なんとか方法はないでしょうか。
試したこと
- Hogeモデルのappendsにimageを加え、getImageAttributeを実装する
class Hoge extends Model
{
public function base() {
return $this->morphMany(Base::class, 'ref');
}
public function images() {
return $this->hasMany(Image::class);
}
protected $appends = ['images'];
public function getImagesAttribute($value) {
return $this->images; // なぜかここでimagesというattributeは無いとエラーになる。
// なお外部から$hoge->imagesとアクセスした場合はエラーにならない
}
}
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
check解決した方法
+1
試した方法の「$appendsにimageを加える」で、なぜエラーになるのかをコードを追っていったところ、以下のコードを見つけました。
// Illuminate\Database\Eloquent\Concerns\HasAttribute
public function getAttribute($key)
{
if (! $key) {
return;
}
// If the attribute exists in the attribute array or has a "get" mutator we will
// get the attribute's value. Otherwise, we will proceed as if the developers
// are asking for a relationship's value. This covers both types of values.
if (array_key_exists($key, $this->attributes) ||
$this->hasGetMutator($key)) {
return $this->getAttributeValue($key);
}
// Here we will determine if the model base class itself contains this given key
// since we do not want to treat any of those methods are relationships since
// they are all intended as helper methods and none of these are relations.
if (method_exists(self::class, $key)) {
return;
}
return $this->getRelationValue($key);
}
このgetAttribute
というメソッドはモデルに対し__get
の呼び出し時に呼ばれるメソッドで、$appendsで加えたいプロパティ名と、リレーションのメソッド名が同一である場合、以下のifに引っかかってしまうようです。
if (method_exists(self::class, $key)) {
return;
}
なので、このgetAttribute
の最後で呼ばれているgetRelationValue
を直接呼べばいいんじゃないか?と思って試したところ、希望通りの動作をしました。
class Hoge extends Model
{
public function base() {
return $this->morphMany(Base::class, 'ref');
}
public function images() {
return $this->hasMany(Image::class);
}
protected $appends = ['images'];
public function getImagesAttribute($value) {
return $this->getRelationValue('images');
}
}
やはり多少面倒でもコードを追うことは大事だということをまた実感させられました。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
-1
以下のように、一度対象のプロパティにアクセスすれば、強引に値を読み込ませることができ、以降はimageもtoArrayに含まれるようになりました。
$base_list = Base::with(['ref'])->all();
foreach($base_list as $index => $base) {
if($base->ref_type == 'Hoge') {
$base->images;
}
}
// [
// ['id' => 1, 'ref_id' => 1, 'ref_type' => 'Hoge', 'ref' => [id => 1, 'name' => 'hoge', 'number' => 0, 'images' => [['id' => 1, 'hoge_id' => 1, 'path' => 'image.jpg']]]],
// ['id' => 2, 'ref_id' => 1, 'ref_type' => 'Fuga', 'ref' => [id => 1, 'name' => 'fuga', 'text' => 'a']],
// ]
とても泥臭いので、もう少しだけ他の方の意見を待ちたいと思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.34%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2017/10/04 11:15