Call to undefined method Illuminate\Database\Eloquent\Relations\BelongsTo::type()

15,218

There is a quick solution for that.

First add a foreign key in VocVocabulary model type function

public function type()
{
    return $this->belongsTo('App\VocType', 'type_id');
}

And then remove paranthesis

echo $vocabularyContent->type->id;

But it is not the standard way to do that. You need to setup your relations in standard ways to help Laravel to understand your relations.

First you need to change the function name as camelCase of the model name. For example as your type model name is VocType so your type function should be changed as

public function type()

To

public function vocType()
{
 return $this->belongsTo('App\VocType'); //you don't need a foreign key here
}

In this case you are telling laravel that the function vocType is targeting VocType model. Furthermore you need to change the foreign key in the table of VocVocabulary from type_id to voc_type_id. In this way Laravel clearly understands your relationship otherwise you need to put extra efforts to teach laravel about your relationships.

Share:
15,218
Paiku Han
Author by

Paiku Han

noob

Updated on December 14, 2022

Comments

  • Paiku Han
    Paiku Han over 1 year

    I'm trying to make a api that will return the type of a word (noun, pronoun, verb, etc.) after creating that word in the database. But for some reason I am getting a "Call to undefined method Illuminate\Database\Eloquent\Relations\BelongsTo::type()" error when the type method is clearly defined in my vocabulary model. I am not using a many to many relationship but a one to many (that's why I am using hasMany() and belongsTo). Type has many Vocabulary but Vocabulary has only one Type and many VocabularyContents and VocabularyContent has only one vocabulary it is related to. So clearly no many to many relationship. So clearly my question is not a duplicate of Call to undefined method (laravel 5.2) . Here are parts of the code for the application.

    The first model is the type model it allows me to get the "contents" of a type (model not listed here) and the vocabularies that belongs to a specific type.

    model-code-listing 1: VocType.php

    <?php
    
    namespace App;
    
    use Illuminate\Database\Eloquent\Model;
    
    class VocType extends Model
    {
        public function contents()
        {
            return $this->hasMany('App\VocTypeContent');
        }
    
        public function vocabularies()
        {
            return $this->hasMany('App\VocVocabulary');
        }
    }
    

    this second model allows me to create a word in the vocabulary table access its "contents", type and category. This is where the issue lies.

    model-code-listing 2: VocVocabulary.php

    <?php
    
    namespace App;
    
    use Illuminate\Database\Eloquent\Model;
    
    class VocVocabulary extends Model
    {
        protected $fillable = ['voc_category_id','type_id', 'name', 'context', 'picture'];
        public $timestamps = false;
    
        public function contents()
        {
            return $this->hasMany('App\VocVocabularyContent');
        }
    
        public function type()
        {
            return $this->belongsTo('App\VocType');
        }
    
        public function category()
        {
            return $this->belongsTo('App\VocCategory');
        }
    }
    

    The third model allows me to create the content of a vocabulary and access it parent vocabulary.

    model-code-listing 3: VocVocabularyContent.php

    <?php
    
    namespace App;
    
    use Illuminate\Database\Eloquent\Model;
    
    class VocVocabularyContent extends Model
    {
        protected $fillable = ['voc_vocabulary_id','lang_id', 'content', 'context', 'romanization', 'pronunciation', 'audio'];
        public $timestamps = false;
    
        public function vocabulary()
        {
            return $this->belongsTo('App\VocVocabulary');
        }
    }
    

    below are the three migrations used for the models listed above.

    migration-code-listing 1: create_voc_types_table.php

    <?php
    
    use Illuminate\Support\Facades\Schema;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;
    
    class CreateVocTypesTable extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('voc_types', function (Blueprint $table) {
                $table->increments('id');
                $table->string('name');
                $table->string('abbreviation');
            });
        }
    
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('voc_types');
        }
    }
    

    migration-code-listing 2: create_voc_vocabularies_table.php

    <?php
    
    use Illuminate\Support\Facades\Schema;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;
    
    class CreateVocVocabulariesTable extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('voc_vocabularies', function (Blueprint $table) {
                $table->increments('id');
                $table->unsignedInteger('cat_id');
                $table->unsignedInteger('type_id');
                $table->foreign('cat_id')->references('id')->on('voc_categories')->onDelete('cascade');
                $table->foreign('type_id')->references('id')->on('voc_types')->onDelete('cascade');
                $table->string('name');
                $table->string('context');
                $table->string('picture');
            });
        }
    
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('voc_vocabularies');
        }
    }
    

    migration-code-listing 3: create_voc_vocabulary_contents_table.php

    <?php
    
    use Illuminate\Support\Facades\Schema;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;
    
    class CreateVocVocabularyContentsTable extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('voc_vocabulary_contents', function (Blueprint $table) {
                $table->primary(['voc_id', 'lang_id']);
                $table->unsignedInteger('voc_id');
                $table->unsignedInteger('lang_id');
                $table->foreign('voc_id')->references('id')->on('voc_vocabularies')->onDelete('cascade');
                $table->foreign('lang_id')->references('id')->on('languages')->onDelete('cascade');
                $table->string('content');
                $table->string('context');
                $table->string('romanization');
                $table->string('pronunciation');
                $table->string('audio');
            });
        }
    
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('voc_vocabulary_contents');
        }
    }
    

    This is the controller where I am calling the type() method of vocabulary. basically I have an html form that sends a post request to this controller's method (postVocabularyAPI) if no id is provided in the request a vocabulary will be created (if the language is english). Then whether or not an id is provided with the request the method will create a vocabulary "content" for the given id (if no id is provided the given id will be the id of the previously created vocabulary). Then the postVocabularyAPI method will return a json response containing the id of the type of the vocabulary.

    controller-code-listing 1: Vocabulearn.php

    <?php
    
    namespace App\Http\Controllers;
    
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\DB;
    use App\Language;
    use App\VocTheme;
    use App\VocCategory;
    use App\VocCategoryContent;
    use App\VocVocabulary;
    use App\VocVocabularyContent;
    use App\VocType;
    
    class Vocabulearn extends Controller
    {
    
        //other methods above
    
        public function postVocabularyAPI(Request $request, $language, $theme, $category){
    
            $vocabulary_id = $request->vocabulary_id;
            if($vocabulary_id === NULL){
                if($language == "english"){
                    $vocabulary = VocVocabulary::create([
                        'voc_category_id'   => VocCategory::where("slug", $category)->get()->first()->id,
                        'type_id'           => VocType::where("abbreviation", $request->type)->get()->first()->id,
                        'name'              => ucfirst(addslashes($request->translation)),
                        'context'           => $request->context,
                        'picture'           => ''
                    ]);
                    $vocabulary_id = $vocabulary->id;
                } else {
                    echo '{"success":false, "message":"Create first the English Vocabulary"}';
                }
            }
    
            $vocabularyContent = VocVocabularyContent::where('lang_id', '=', Language::where("slug", $language)->get()->first()->id)
            ->where('voc_vocabulary_id', '=', $vocabulary_id)
            ->first();
    
            if($vocabularyContent !== NULL){
                $vocabularies = DB::table('voc_vocabulary_contents')
                ->where('lang_id', '=', Language::where("slug", $language)->get()->first()->id)
                ->where('voc_vocabulary_id', '=', $vocabulary_id)
                ->delete();
            }
    
            $vocabularyContent = VocVocabularyContent::create([
                'voc_vocabulary_id' => $vocabulary_id,
                'lang_id' => Language::where("slug", $language)->get()->first()->id,
                'content' => ucfirst(addslashes($translation)),
                'context' => addslashes($context),
                'romanization' => strtolower(addslashes($romanization)),
                'pronunciation' => $pronunciation,
                'audio' => $request->audio
            ]);
    
            echo '{"success":true, "type":"'.stripslashes(html_entity_decode($vocabularyContent->vocabulary()->type()->id)).'"}';
        }
    }
    

    doing this gives me a

    "Call to undefined method Illuminate\Database\Eloquent\Relations\BelongsTo::type()"

    even when I change

    echo '{"success":true, "type":"'.stripslashes(html_entity_decode($vocabularyContent->vocabulary()->type()->id)).'"}';
    

    by

    echo '{"success":true, "type":"'.stripslashes(html_entity_decode($vocabularyContent->vocabulary()->get()->first()->type()->id)).'"}';
    

    I get an error stating

    "Call to a member function type() on null"

    which isn't right because the database was properly populated so I shouldn't be getting a null vocabulary.

    • Dilip Hirapara
      Dilip Hirapara over 4 years
    • Mike
      Mike over 4 years
      Are you sure the $vocabularyContent->vocabulary() has a type ? You can get the vocabulary model directly by removing the () so this $vocabularyContent->vocabulary()->type()->id becomes $vocabularyContent->vocabulary->type->id
    • Paiku Han
      Paiku Han over 4 years
      @Laravel I am not using a many to many relationship but a one to many (that's why I am using hasMany() and belongsTo). Type has many Vocabulary but Vocabulary has only one Type and many VocabularyContents and VocabularyContent has only one vocabulary it is related to. So clearly no many to many relationship.
    • Paiku Han
      Paiku Han over 4 years
      @Mike I just tried that and I get "Call to a member function type() on null" which is not true because I checked the database for the vocabularycontent I am creating. I can clearly see its primary key (which is a composite of the vocabulary ID and the language ID) and I also checked that ID and it belongs to an actual vocabulary so I shouldn't be getting "Call to a member function type() on null" .
    • Mike
      Mike over 4 years
      A i see now you are creating the model just before you are trying to get its relations. You need to use the proper functions (attach i think) in order for laravel to setup the relation correctly or reload the model after you have created it Can you try and add $vocabularyContent = VocVocabularyContent::find($vocabularyContent->id); before your final echo statement
    • Mike
      Mike over 4 years
      Also i don't think your relations are setup correctly in your VocVocabulary you have a category relation but the key seems to be voc_category_id this is not standard laravel and you need to specify the primary key like so return $this->hasOne('App\Model', 'foreign_key'); i think this applies to multiple of your models. did you test all your relations and are they working correctly ?
    • Paiku Han
      Paiku Han over 4 years
      @Mike, it shouldn't matter (for two reasons). first is I have a different method in the controller called postCategoryAPI() and I do the same thing in that method: I create a category content and its parent category (if it wasn't already created) then I echo its content ID, romanization, etc. and that method works fine. Second reason is even though I said I am just showing the ID of the type I am actually showing more than that (I just simplified for the sake of simplicity). For instance I am retrieving $vocabularyContent->voc_vocabulary_id and $vocabularyContent->content before vocabulary
    • Paiku Han
      Paiku Han over 4 years
      @Mike setting the foreign_key in the belongsTo did the trick!