एलोक्वेंट के साथ एक मॉडल के उपप्रकार का उदाहरण प्राप्त करें


22

मेरे पास तालिका के 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), लेकिन यह वह नहीं है जो मैं चाहता हूं: मैं ऑब्जेक्ट के प्रकार को केवल एक बार यह निर्धारित कर सकता हूं कि यह लाया गया था। मैं एनिमल भी ला सकता था, और फिर find()सही ऑब्जेक्ट पर चल सकता था, लेकिन यह दो डेटाबेस कॉल कर रहा है, जो मुझे स्पष्ट रूप से नहीं चाहिए।

मैंने डॉग से एनिमल जैसे एलोकेंट मॉडल को "मैन्युअल" करने का तरीका खोजने की कोशिश की, लेकिन मुझे इसके अनुरूप कोई तरीका नहीं मिला। किसी भी विचार या विधि मैं याद किया?


@ B001 B बेशक, मेरे कुत्ते या बिल्ली वर्ग के पास समान इंटरफेस होने जा रहा है, मैं नहीं देखता कि यह यहां कैसे मदद करता है?
क्लेमेंटएम

@ क्लिमेंट एक से कई पॉलिमॉर्फिक
vivek_23

@ vivek_23 वास्तव में नहीं, इस मामले में यह किसी दिए गए प्रकार की टिप्पणियों को फ़िल्टर करने में मदद करता है, लेकिन आप पहले से ही जानते हैं कि आप अंत में टिप्पणियां चाहते हैं। यहां लागू नहीं होता है।
ClmentM

मुझे लगता है कि यह करता है। जानवर या तो बिल्ली या कुत्ता हो सकता है। इसलिए, जब आप जानवरों की मेज से जानवर के प्रकार को पुनः प्राप्त करते हैं, तो यह आपको डेटाबेस में संग्रहीत किए गए कुत्ते या बिल्ली का उदाहरण देता है। अंतिम पंक्ति में कहा गया है कि टिप्पणी मॉडल पर टिप्पणी करने योग्य संबंध पोस्ट या वीडियो उदाहरण पर वापस आ जाएगा, इस पर निर्भर करता है कि किस प्रकार का मॉडल टिप्पणी का मालिक है।
विवेक_ 23

@ vivek_23 मैंने दस्तावेज़ीकरण में अधिक गोता लगाया और इसे एक कोशिश दी, लेकिन एलोकेंट *_typeउप-प्रकार मॉडल को निर्धारित करने के लिए नाम के साथ वास्तविक कॉलम पर आधारित है । मेरे मामले में मेरे पास वास्तव में केवल एक तालिका है, इसलिए जब यह एक अच्छी विशेषता है, तो मेरे मामले में नहीं।
क्लेमेंटएम

जवाबों:


7

आप लारवेल में पोलीमॉर्फिक संबंधों का उपयोग कर सकते हैं जैसा कि आधिकारिक लारवेल डॉक्स में बताया गया है । यहां बताया गया है कि आप ऐसा कैसे कर सकते हैं।

दिए गए मॉडल में रिश्तों को परिभाषित करें

class Animal extends Model{
    public function animable(){
        return $this->morphTo();
    }
}

class Dog extends Model{
    public function animal(){
        return $this->morphOne('App\Animal', 'animable');
    }
}

class Cat extends Model{
    public function animal(){
        return $this->morphOne('App\Animal', 'animable');
    }
}

यहां आपको animalsतालिका में दो स्तंभों की आवश्यकता होगी , पहला है animable_typeऔर दूसरा यह animable_idहै कि रनटाइम के दौरान इससे जुड़े मॉडल का प्रकार निर्धारित किया जाए।

आप दिए गए कुत्ते या बिल्ली के मॉडल को ला सकते हैं,

$animal = Animal::find($id);
$anim = $animal->animable; //this will return either Cat or Dog Model

उसके बाद, आप $animउपयोग करके ऑब्जेक्ट की कक्षा की जांच कर सकते हैं instanceof

यदि आप अनुप्रयोग में किसी अन्य पशु प्रकार (यानी लोमड़ी या शेर) को जोड़ते हैं तो यह दृष्टिकोण आपको भविष्य के विस्तार में मदद करेगा। यह आपके कोडबेस को बदले बिना काम करेगा। अपनी आवश्यकता को प्राप्त करने का यह सही तरीका है। हालांकि, बहुरूपता को प्राप्त करने के लिए कोई वैकल्पिक दृष्टिकोण नहीं है और एक बहुरूपिए संबंध का उपयोग किए बिना एक साथ लोड करने के लिए उत्सुक है। यदि आप एक पॉलीमॉर्फिक संबंध का उपयोग नहीं करते हैं , तो आप एक से अधिक डेटाबेस कॉल के साथ समाप्त करेंगे। हालाँकि, यदि आपके पास एकल स्तंभ है जो मोडल प्रकार को अलग करता है, तो शायद आपके पास एक गलत संरचित स्कीमा है। मेरा सुझाव है कि आप सुधार करें कि यदि आप भविष्य के विकास के लिए इसे सरल बनाना चाहते हैं।

मॉडल के आंतरिक को फिर से लिखना newInstance()और newFromBuilder()एक अच्छा / अनुशंसित तरीका नहीं है और फ्रेमवर्क से अपडेट प्राप्त करने के बाद आपको इस पर फिर से काम करना होगा।


1
प्रश्न की टिप्पणियों में उन्होंने कहा, कि उनके पास केवल एक तालिका है और ओपी के मामले में बहुरूपी विशेषताएं उपयोग करने योग्य नहीं हैं।
शॉक_गोन_विल्ड

3
मैं सिर्फ यह बता रहा हूं कि दिया गया परिदृश्य कैसा है। मैं व्यक्तिगत रूप से
पॉलिमॉर्फिक

1
@KiranManiya आपके विस्तृत उत्तर के लिए धन्यवाद। मुझे अधिक पृष्ठभूमि में दिलचस्पी है। क्या आप विस्तृत कर सकते हैं कि (1) प्रश्नवाचक डेटाबेस मॉडल गलत क्यों है और (2) सार्वजनिक / संरक्षित सदस्य कार्यों का विस्तार करना अच्छा / अनुशंसित नहीं है?
क्रिस्टोफ क्लुज

1
@ChristophKluge, आप पहले से ही जानते हैं। (1) लार्वा डिज़ाइन पैटर्न के संदर्भ में DB मॉडल गलत है। यदि आप लार्वा द्वारा परिभाषित डिज़ाइन पैटर्न का पालन करना चाहते हैं, तो आपके पास इसके अनुसार DB स्कीमा होना चाहिए। (२) यह एक ढांचा आंतरिक तरीका है जिसे आपने ओवरराइड करने का सुझाव दिया है। अगर मैं कभी इस मुद्दे का सामना करता हूं तो मैं ऐसा नहीं करूंगा। लारवेल फ्रेमवर्क में अंतर्निहित बहुरूपता समर्थन है इसलिए हम पहिया का फिर से आविष्कार करने के लिए उसका उपयोग क्यों नहीं करते हैं? आपने उत्तर में एक अच्छा सुराग दिया था लेकिन मैंने कभी भी नुकसान के साथ कोड को प्राथमिकता नहीं दी, इसके बजाय हम कुछ ऐसा कोड कर सकते हैं जो भविष्य के विस्तार को आसान बनाने में मदद करता है।
किरण मनिया

2
लेकिन ... सारा सवाल लारवेल डिजाइन पैटर्न के बारे में नहीं है। फिर से, हमारे पास एक दिया हुआ परिदृश्य है (शायद डेटाबेस बाहरी अनुप्रयोग द्वारा बनाया गया है)। हर कोई इस बात से सहमत होगा कि यदि आप स्क्रैच से निर्माण करते हैं, तो बहुरूपता ही रास्ता तय करेगा। वास्तव में आपका उत्तर तकनीकी रूप से मूल प्रश्न का उत्तर नहीं देता है।
शॉक_गोन_विल्ड

5

मुझे लगता है कि आप मॉडल newInstanceपर विधि को ओवरराइड कर सकते हैं Animal, और विशेषताओं से प्रकार की जांच कर सकते हैं और फिर संबंधित मॉडल को इंआईट कर सकते हैं।

    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.
        $modelName = ucfirst($attributes['type']);
        $model = new $modelName((array) $attributes);

        $model->exists = $exists;

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

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

        $model->mergeCasts($this->casts);

        return $model;
    }

आपको newFromBuilderविधि को ओवरराइड करने की भी आवश्यकता होगी ।


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

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

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

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

        return $model;
    }

मैं यह कैसे काम करता है geht नहीं है। पशु :: ढूंढें (1) एक त्रुटि फेंक देगा: "अपरिभाषित सूचकांक प्रकार" यदि आप पशु कहते हैं :: खोज (1)। या क्या मैं कुछ न कुछ भूल रहा हूं?
शॉक_गोन_विल्ड

@shock_gone_wild क्या आपके पास typeडेटाबेस में नाम का कॉलम है?
क्रिस नील

हाँ मेरे पास है। लेकिन अगर मैं dd ($ attritubutes) करता हूं तो $ विशेषताएँ सरणी खाली है। जो पूरी तरह से समझ में आता है। आप इसका वास्तविक उदाहरण में उपयोग कैसे करते हैं?
सदमे_गोन_विला

5

यदि आप वास्तव में ऐसा करना चाहते हैं, तो आप अपने पशु मॉडल के अंदर निम्नलिखित दृष्टिकोण का उपयोग कर सकते हैं।

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Animal extends Model
{

    // other code in animal model .... 

    public static function __callStatic($method, $parameters)
    {
        if ($method == 'find') {
            $model = parent::find($parameters[0]);

            if ($model) {
                switch ($model->type) {
                    case 'dog':
                        return new \App\Dog($model->attributes);
                    case 'cat':
                        return new \App\Cat($model->attributes);
                }
                return $model;
            }
        }

        return parent::__callStatic($method, $parameters);
    }
}

5

जैसा कि ओपी ने अपनी टिप्पणियों के अंदर कहा है: डेटाबेस का डिज़ाइन पहले से ही सेट है और इसलिए लारवेल के पॉलीमॉर्फिक संबंध यहां विकल्प नहीं हैं।

मुझे क्रिस नील का जवाब पसंद है क्योंकि मुझे हाल ही में ऐसा ही कुछ करना था (अपने डेटाबेस ड्राइवर को लिखने के लिए डबसे / डीबीएफ फ़ाइलों के लिए एलोकेंट का समर्थन करने के लिए) और लारवेल के एलोक्वेंट ओआरएम के आंतरिक लोगों के साथ बहुत अनुभव प्राप्त किया।

मैंने प्रति मॉडल एक स्पष्ट मानचित्रण रखते हुए कोड को अधिक गतिशील बनाने के लिए इसमें अपना व्यक्तिगत स्वाद जोड़ा है।

समर्थित सुविधाएँ जिन्हें मैंने जल्दी परीक्षण किया:

  • Animal::find(1) आपके प्रश्न में पूछे गए अनुसार काम करता है
  • Animal::all() साथ ही काम करता है
  • Animal::where(['type' => 'dog'])->get()AnimalDogएक संग्रह के रूप में वापसी करेंगे
  • डायनामिक ऑब्जेक्ट मैपिंग प्रति एलोकेंट-क्लास जो इस विशेषता का उपयोग करता है
  • Animalयदि कोई मैपिंग कॉन्फ़िगर नहीं की गई है (या डीबी में एक नई मैपिंग दिखाई गई है) तो -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);
    }
}

जिज्ञासा के कारण। क्या यह जानवरों के साथ भी काम करता है :: सभी () परिणामस्वरूप संग्रह 'कुत्तों' और 'बिल्लियों' का मिश्रण है?
शॉक_गोन_विल्ड

@shock_gone_wild बहुत अच्छा सवाल! मैंने इसका स्थानीय स्तर पर परीक्षण किया और इसे अपने उत्तर में जोड़ा। के रूप में अच्छी तरह से काम करने के लिए लगता है :-)
क्रिस्टोफ़ Kluge

2
फ़ंक्शन में निर्मित लार्वा को संशोधित करना सही तरीका नहीं है। लारवेल को अपडेट करने के बाद सभी परिवर्तन ढीले हो जाएंगे और यह सब कुछ गड़बड़ कर देगा। जागरूक रहें।
नवीन डी। शाह

हे नवीन, इस उल्लेख के लिए धन्यवाद, लेकिन यह पहले से ही स्पष्ट रूप से मेरे उत्तर के अंदर नुकसान के रूप में कहा गया है। काउंटर सवाल: फिर सही तरीका क्या है?
क्रिस्टोफ क्लुज

2

मुझे लगता है कि मुझे पता है कि आप क्या देख रहे हैं। इस सुरुचिपूर्ण समाधान पर विचार करें जो लारवेल क्वेरी स्कोप का उपयोग करता है, अतिरिक्त जानकारी के लिए https://laravel.com/docs/6.x/eloquent#query-scopes देखें:

साझा तर्क रखने वाले एक मूल वर्ग बनाएं:

class Animal extends \Illuminate\Database\Eloquent\Model
{
    const TYPE_DOG = 'dog';
    const TYPE_CAT = 'cat';
}

वैश्विक क्वेरी स्कोप और savingईवेंट हैंडलर के साथ एक बच्चा (या एकाधिक) बनाएँ :

class Dog extends Animal
{
    public static function boot()
    {
        parent::boot();

        static::addGlobalScope('type', function(\Illuminate\Database\Eloquent\Builder $builder) {
            $builder->where('type', self::TYPE_DOG);
        });

        // Add a listener for when saving models of this type, so that the `type`
        // is always set correctly.
        static::saving(function(Dog $model) {
            $model->type = self::TYPE_DOG;
        });
    }
}

(यह दूसरे वर्ग पर लागू होता है Cat, बस स्थिरांक को बदलें)

वैश्विक क्वेरी स्कोप एक डिफ़ॉल्ट क्वेरी संशोधन के रूप में कार्य करता है, जैसे कि Dogक्लास हमेशा रिकॉर्ड के साथ दिखेगा type='dog'

कहें कि हमारे पास 3 रिकॉर्ड हैं:

- id:1 => Cat
- id:2 => Dog
- id:3 => Mouse

अब कॉलिंग में Dog::find(1)परिणाम होगा null, क्योंकि डिफ़ॉल्ट क्वेरी गुंजाइश नहीं मिलेगी id:1जो कि एक है Cat। कॉल करना Animal::find(1)और Cat::find(1)दोनों काम करेंगे, हालांकि केवल अंतिम एक आपको एक वास्तविक कैट ऑब्जेक्ट देता है।

इस सेटअप की अच्छी बात यह है कि आप संबंध बनाने के लिए ऊपर की कक्षाओं का उपयोग कर सकते हैं:

class Owner
{
    public function dogs()
    {
        return $this->hasMany(Dog::class);
    }
}

और यह संबंध आपको केवल सभी जानवरों को type='dog'( Dogकक्षाओं के रूप में ) देगा। क्वेरी स्कोप अपने आप लागू हो जाता है।

इसके अलावा, कॉल Dog::create($properties)स्वचालित रूप से सेट हो जाएगा typeकरने के लिए 'dog'की वजह से saving(देखें घटना हुक https://laravel.com/docs/6.x/eloquent#events )।

ध्यान दें कि कॉलिंग में Animal::create($properties)डिफ़ॉल्ट नहीं है typeइसलिए यहां आपको मैन्युअल रूप से सेट करने की आवश्यकता है (जो कि अपेक्षित है)।


0

यद्यपि आप लारवेल का उपयोग कर रहे हैं, इस मामले में, मुझे लगता है कि आपको लारवेल के शॉर्ट-कट से नहीं रहना चाहिए।

यह समस्या जिसे आप हल करने का प्रयास कर रहे हैं, वह एक क्लासिक समस्या है जिसे कई अन्य भाषाएं / फ्रेमवर्क फ़ैक्टरी विधि पैटर्न ( https://en.wikipedia.org/wiki/Factory_method_pattern ) का उपयोग करके हल करते हैं ।

यदि आप अपना कोड आसानी से समझना चाहते हैं और कोई छिपी हुई चाल के साथ, तो आपको हुड के नीचे छिपी / जादू की चाल के बजाय एक प्रसिद्ध पैटर्न का उपयोग करना चाहिए।


0

सबसे आसान तरीका अभी तक पशु वर्ग में विधि बनाना है

public function resolve()
{
    $model = $this;
    if ($this->type == 'dog'){
        $model = new Dog();
    }else if ($this->type == 'cat'){
        $model = new Cat();
    }
    $model->setRawAttributes($this->getAttributes(), true);
    return $model;
}

हल करने वाला मॉडल

$animal = Animal::first()->resolve();

यह मॉडल प्रकार के आधार पर क्लास एनिमल, डॉग या कैट का उदाहरण लौटाएगा

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.