web-dev-qa-db-ja.com

Laravel:ポリモーフィックリレーションの名前空間所有者を返す

これに関する多くの議論を見つけることができますが、明確な解決策はありません。ここに2つのリンクがありますが、ここで私自身の質問ですべてをカバーします。

Githubの問題

Laravel.ioの議論

簡単な説明

これは、すでにLaravelの多態的な関係に精通している人にとっての私の問題の簡単な説明です。

$ morphClassを使用する場合、データベースにモーフ「タイプ」として保存されている$ morphClassの内容は、多態的な関係の所有者を見つけようとするときにクラス名に使用されます。これはエラーになります。$ morphClassの重要な点は、クラスの完全な名前空間の名前ではないということです。

多態的な関係が使用するクラス名をどのように定義しますか?

より詳細な説明

これは、私がやろうとしていることと、例でそれをやろうとしている理由を正確に説明するより詳細な説明です。

Laravel=で多態的なリレーションシップを使用する場合、データベースの「morph_type」タイプとして保存されたものはすべてクラスであると想定されます。

したがって、この例では:

class Photo extends Eloquent {

    public function imageable()
    {
        return $this->morphTo();
    }

}

class Staff extends Eloquent {

    public function photos()
    {
        return $this->morphOne('Photo', 'imageable');
    }

}

class Order extends Eloquent {

    public function photos()
    {
        return $this->morphOne('Photo', 'imageable');
    }

}

データベースは次のようになります。

staff

 - id - integer
 - name - string

orders

 - id - integer
 - price - integer

photos

 - id - integer
 - path - string
 - imageable_id - integer
 - imageable_type - string

写真の最初の行は次のようになります。

id、path、imageable_id、imageable_type

1、image.png、1、スタッフ

これで、スタッフモデルの写真または写真モデルのスタッフメンバーにアクセスできます。

//Find a staff member and dump their photos
$staff = Staff::find(1);

var_dump($staff->photos);

//Find a photo and dump the related staff member
$photo = Photo::find(1);

var_dump($photo->imageable);

ここまでは順調ですね。しかし、それらに名前空間を付けると、問題が発生します。

namespace App/Store;
class Order {}

namespace App/Users;
class Staff {}

namespace App/Photos;
class Photo {}

データベースに保存されるのは次のとおりです。

id、path、imageable_id、imageable_type

1、image.png、1、アプリ/ユーザー/スタッフ

しかし、私はそれを望んでいません。これは、そのようなデータベースに完全な名前空間付きクラス名を保存するというひどい考えです!

幸いなことにLaravelには$ morphClass変数を設定するオプションがあります。

class Staff extends Eloquent {

    protected $morphClass = 'staff';

    public function photos()
    {
        return $this->morphOne('Photo', 'imageable');
    }

}

これで、データベースの行は次のようになります。

id、path、imageable_id、imageable_type

1、image.png、1、スタッフ

また、スタッフの写真を取得することはまったく問題ありません。

//Find a staff member and dump their photos
$staff = Staff::find(1);

//This works!
var_dump($staff->photos);

しかし、写真の所有者を見つけるという多態的な魔法は機能しません。

//Find a photo and dump the related staff member
$photo = Photo::find(1);

//This doesn't work!
var_dump($photo->imageable);

//Error: Class 'staff' not found

おそらく$ morphClassを使用するときに使用するクラス名の多態的な関係を通知する方法が必要ですが、ドキュメント、ソースコード、またはGoogleでこれがどのように機能するかについての参照は見つかりません。

助けがありますか?

34
John Mellor

2つの簡単な方法があります。1つは以下、もう1つはTaylor Otwellが提案した@lukasgeiterの回答です。チェックすることもお勧めします。

// app/config/app.php or anywhere you like
'aliases' => [
    ...
    'MorphOrder' => 'Some\Namespace\Order',
    'MorphStaff' => 'Maybe\Another\Namespace\Staff',
    ...
]

// Staff model
protected $morphClass = 'MorphStaff';

// Order model
protected $morphClass = 'MorphOrder';

完了

$photo = Photo::find(5);
$photo->imageable_type; // MorphOrder
$photo->imageable; // Some\Namespace\Order

$anotherPhoto = Photo::find(10);
$anotherPhoto->imageable_type; // MorphStaff
$anotherPhoto->imageable; // Maybe\Another\Namespace\Staff

重複の可能性を避けるために、実際のクラス名(OrderおよびStaff)は使用しません。何かがMorphXxxxと呼ばれる可能性はほとんどないので、かなり安全です。

これは名前空間を保存するよりも良い(dbの外観は気にしませんが、何かを変更する場合は不便です-App\Models\Userの代わりにCartalyst\Sentinel\Userを使用してくださいなど)必要なのは実装を交換するだけですエイリアスの設定を通じて。

ただし、デメリットもあります。dbを確認するだけでは、モデルが何であるかはわかりませんが、重要な場合に備えて。

45
Jarek Tkaczyk

私は@JarekTkaczyksソリューションが好きなので、それを使うことをお勧めします。しかし、完全を期すために、Taylorが github について簡単に言及している別の方法があります。

imageable_type属性の属性アクセサーを追加し、「classmap」配列を使用して適切なクラスを検索できます。

class Photo extends Eloquent {

    protected $types = [
        'order' => 'App\Store\Order',
        'staff' => 'App\Users\Staff'
    ];

    public function imageable()
    {
        return $this->morphTo();
    }

    public function getImageableTypeAttribute($type) {
        // transform to lower case
        $type = strtolower($type);

        // to make sure this returns value from the array
        return array_get($this->types, $type, $type);

        // which is always safe, because new 'class'
        // will work just the same as new 'Class'
    }

}

ただし、リレーションの反対側からのクエリにはmorphClass属性が必要です。

22
lukasgeiter

Laravel 5.2(またはそれ以降))を使用する場合、新しい機能morphMapを使用してこの問題に対処できます。これをapp/Providers/AppServiceProviderboot関数に追加するだけです。

Relation::morphMap([
    'post' => \App\Models\Post::class,
    'video' => \App\Models\Video::class,
]);

それについての詳細: https://nicolaswidart.com/blog/laravel-52-morph-map

19
dersvenhesse

laravelをdb-namespaceおよびallに入れてください。データベースをきれいにする以外に短いクラス名が必要な場合は、次のようなアクセサを定義します。

<?php namespace App\Users;

class Staff extends Eloquent {

    // you may or may not want this attribute added to all model instances
    // protected $appends = ['morph_object'];

    public function photos()
    {
        return $this->morphOne('App\Photos\Photo', 'imageable');
    }

    public function getMorphObjectAttribute()
    {
        return (new \ReflectionClass($this))->getShortName();
    }

}

Laravelは十分にテストされており、ほとんどの部分で期待どおりに動作します。フレームワークと戦う必要がないのはなぜですか-特にデータベースに名前空間があることに単に腹が立つ場合、そうすることは素晴らしいアイデアではないことに同意しますが、それを乗り越えてドメインコードで作業するのにもっと時間を費やすことができると感じています。

3
Abba Bryant

Photo::with('imageable')->get();などの多相リレーションを積極的にロードするには、型が空の場合にnullを返す必要があります。

class Photo extends Eloquent {

    protected $types = [
        'order' => 'App\Store\Order',
        'staff' => 'App\Users\Staff'
    ];

    public function imageable()
    {
        return $this->morphTo();
    }

    public function getImageableTypeAttribute($type) {
        // Illuminate/Database/Eloquent/Model::morphTo checks for null in order
        // to handle eager-loading relationships
        if(!$type) {
            return null;
        }

        // transform to lower case
        $type = strtolower($type);

        // to make sure this returns value from the array
        return array_get($this->types, $type, $type);

        // which is always safe, because new 'class'
        // will work just the same as new 'Class'
    }

}
2
DTownsend