Eloquent Ask Question でモデルのサブタイプのインスタンスを取得する

Eloquent Ask Question でモデルのサブタイプのインスタンスを取得する

Animal表に基づいたモデルがありますanimal

このテーブルにはtype、次のような値を含むフィールドが含まれています。または

次のようなオブジェクトを作成できるようにしたいと思います:

class Animal extends Model { }
class Dog extends Animal { }
class Cat extends Animal { }

しかし、このように動物を捕まえることができるのです。

$animal = Animal::find($id);

しかし、フィールドによっては、またはを使用して確認できる$animalインスタンスがどこにあるのか、型ヒント付きメソッドで機能するのかが問題になります。その理由は、コードの 90% が共有されているにもかかわらず、一方が吠え、もう一方が鳴くことができるからです。DogCattypeinstance of

できることはわかっていますDog::find($id)が、それは私が望んでいることではありません。オブジェクトがフェッチされた後にのみ、そのタイプを判別できます。Animal をフェッチしてから、find()適切なオブジェクトに対して実行することもできますが、これは 2 つのデータベース呼び出しを実行することになり、明らかに望んでいません。

Dog のような Eloquent モデルを Animal から「手動で」インスタンス化する方法を探しましたが、対応する方法が見つかりませんでした。私が見逃したアイデアや方法があれば教えてください。

ベストアンサー1

OP がコメントの中で述べたように:データベース設計はすでに設定されているため、Laravelの多態的な関係ここでは選択肢ではないようです。

クリス・ニールの答えのようになぜなら、最近似たようなこと(dbase/DBF ファイル用に Eloquent をサポートする独自のデータベース ドライバーの作成)を行う必要があり、Laravel の Eloquent ORM の内部について多くの経験を積んだからです。

モデルごとに明示的なマッピングを維持しながら、コードをより動的にするために、個人的な趣向を加えました。

簡単にテストしたサポートされている機能:

  • Animal::find(1)質問通り動作します
  • Animal::all()同様に動作する
  • Animal::where(['type' => 'dog'])->get()AnimalDog-オブジェクトをコレクションとして返します
  • この特性を使用する eloquent クラスごとの動的オブジェクト マッピング
  • Animalマッピングが設定されていない場合(または DB に新しいマッピングが表示された場合)は -modelにフォールバックします。

デメリット:

  • newInstance()モデルの内部を完全に書き換えますnewFromBuilder()(コピー アンド ペースト)。つまり、フレームワークからこのメンバー関数に更新がある場合は、手動でコードを採用する必要があります。

これがお役に立てば幸いです。また、シナリオに関するご提案、ご質問、追加ユースケースなどがあれば、ぜひお聞かせください。ユースケースと例は次のとおりです。

class Animal extends Model
{
    use MorphTrait; // You'll find the trait in the very end of this answer

    protected $morphKey = 'type'; // This is your column inside the database
    protected $morphMap = [ // This is the value-to-class mapping
        'dog' => AnimalDog::class,
        'cat' => AnimalCat::class,
    ];

}

class AnimalCat extends Animal {}
class AnimalDog extends Animal {}

これは、これをどのように使用するかの例と、その結果を下記に示します。

$cat = Animal::find(1);
$dog = Animal::find(2);
$new = Animal::find(3);
$all = Animal::all();

echo sprintf('ID: %s - Type: %s - Class: %s - Data: %s', $cat->id, $cat->type, get_class($cat), $cat, json_encode($cat->toArray())) . PHP_EOL;
echo sprintf('ID: %s - Type: %s - Class: %s - Data: %s', $dog->id, $dog->type, get_class($dog), $dog, json_encode($dog->toArray())) . PHP_EOL;
echo sprintf('ID: %s - Type: %s - Class: %s - Data: %s', $new->id, $new->type, get_class($new), $new, json_encode($new->toArray())) . PHP_EOL;

dd($all);

結果は次のようになります。

ID: 1 - Type: cat - Class: App\AnimalCat - Data: {"id":1,"type":"cat"}
ID: 2 - Type: dog - Class: App\AnimalDog - Data: {"id":2,"type":"dog"}
ID: 3 - Type: new-animal - Class: App\Animal - Data: {"id":3,"type":"new-animal"}

// Illuminate\Database\Eloquent\Collection {#1418
//  #items: array:2 [
//    0 => App\AnimalCat {#1419
//    1 => App\AnimalDog {#1422
//    2 => App\Animal {#1425

使用したい場合に備えて、MorphTrait完全なコードはこちらにあります:

<?php namespace App;

trait MorphTrait
{

    public function newInstance($attributes = [], $exists = false)
    {
        // This method just provides a convenient way for us to generate fresh model
        // instances of this current model. It is particularly useful during the
        // hydration of new objects via the Eloquent query builder instances.
        if (isset($attributes['force_class_morph'])) {
            $class = $attributes['force_class_morph'];
            $model = new $class((array)$attributes);
        } else {
            $model = new static((array)$attributes);
        }

        $model->exists = $exists;

        $model->setConnection(
            $this->getConnectionName()
        );

        $model->setTable($this->getTable());

        return $model;
    }

    /**
     * Create a new model instance that is existing.
     *
     * @param array $attributes
     * @param string|null $connection
     * @return static
     */
    public function newFromBuilder($attributes = [], $connection = null)
    {
        $newInstance = [];
        if ($this->isValidMorphConfiguration($attributes)) {
            $newInstance = [
                'force_class_morph' => $this->morphMap[$attributes->{$this->morphKey}],
            ];
        }

        $model = $this->newInstance($newInstance, true);

        $model->setRawAttributes((array)$attributes, true);

        $model->setConnection($connection ?: $this->getConnectionName());

        $model->fireModelEvent('retrieved', false);

        return $model;
    }

    private function isValidMorphConfiguration($attributes): bool
    {
        if (!isset($this->morphKey) || empty($this->morphMap)) {
            return false;
        }

        if (!array_key_exists($this->morphKey, (array)$attributes)) {
            return false;
        }

        return array_key_exists($attributes->{$this->morphKey}, $this->morphMap);
    }
}

おすすめ記事