Home > Back-end >  Laravel Polymorphic Relationship - Many to Many issue
Laravel Polymorphic Relationship - Many to Many issue

Time:10-01

Have students, that can have documents. Documents can either be 'just documents' that belong to students (normal one to many relationship) However, Students can also have 'passports' and 'visas' (amongst others). Each passport and visa can have a document too. A single document can belong to many things (eg, one document can be associated with a passport and a visa). For the purpose of this troubleshooting, lets keep it simple and between Student / Passport (I've also left out other class stuff like fillable just to keep this brief).

Student Model:

class Student extends Model
{
    public function documents() {
        return $this->hasMany('App\StudentDocument');
    }

    public function visas() {
        return $this->hasMany('App\StudentVisa');
    }

    public function passports() {
        return $this->hasMany('App\StudentPassport');
    }
}

Student Passport Class

class StudentPassport extends Model
{
    public function student_documents()
    {
        return $this->morphToMany(StudentDocument::class, 'student_documentable');
    }
}

Student Passport Store:

public function store(StudentPassportRequest $request, $student_id)
{
    $student = Student::findOrFail($student_id);
    $passport = $student->passports()->create($request->all());

    if ($request->file('student_document_file')->isValid()) {
        $uploaded_file = $request->file('student_document_file');
        $filename = time().'-'.$uploaded_file->getClientOriginalName();

        Storage::disk('local')->putFileAs(
            'student_document_files/'. \Auth::user()->userable_id .'/'. $student_id .'/',
            $uploaded_file,
            $filename
        );

        $student_document = new StudentDocument;
        $student_document->filename = $filename;
        $student_document->student_document_type_id = StudentDocumentType::where('student_document_type','Passport')->first()->id;
        $student_document->original_filename = $uploaded_file->getClientOriginalName();
        $student_document->mime = $uploaded_file->getMimeType();
        $student_document->student_id=$student_id;

        $passport->student_documents()->save($student_document);
    }

    return redirect('/baadmin/students/'. $student_id .'#kt_tabs-passports')->with('flash_message', ['success','Created Successfully','Student Passport "'. $request->input('passport_number') .'" created successfully!']);
}

Error:

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'student_documentables' doesn't exist
INSERT INTO `student_documentables` (
`student_document_id`,
`student_documentable_id`,
`student_documentable_type`
)
VALUES
  (5, 503, App \ StudentPassport)

I took the example as found in the Laravel Documentation here and just renamed 'tag' to student_documents' essentially. The student_documentable table doesnt exist of course, as it should be plugging it into the student_documents table.

Schema:

CREATE TABLE `student_documents` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `student_id` bigint(20) unsigned NOT NULL,
  `student_document_type_id` bigint(20) unsigned NOT NULL,
  `filename` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `mime` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `original_filename` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  `deleted_at` timestamp NULL DEFAULT NULL,
  `primary_date` datetime NOT NULL DEFAULT current_timestamp(),
  `secondary_date` datetime DEFAULT NULL,
  `student_documentable_id` bigint(20) DEFAULT NULL,
  `student_documentable_type` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `student_id_index` (`student_id`),
  KEY `student_document_type_id_index` (`student_document_type_id`),
  KEY `student_documentable_id_index` (`student_documentable_id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

What am I doing wrong or is Laravel just not understanding 'student_documents'? I can of course change the Student Passport Class to not plug into 'student_documentable' and use 'student_document' then it would try put in the data to the correct table, but I dont know if this is right since all documentation refers to adding an 'able' at the end...

CodePudding user response:

You should first create a pivot table for that MorphMany relation,

Schema::create('student_documentables', function (Blueprint $table)
{
    // optional depends if you want an id or not
    $table->id();
    // here singular is used, to generate student_documentable_type and student_documentable_id fields
    $table->morphs('student_documentable');
    // the foreign key to student_document
    $table->unsignedInteger('student_document_id');
    $table->foreign('student_document_id')->on('student_documents')->references('id')->onUpdate('cascade')->onDelete('cascade');
});

In your StudentDocument :

// we define a relation to retrieve all documentables like passport that are linked to that document
public function student_documentables()
{
    return $this->morphTo('student_documentables');
}

In your StudentPassport :

// we define a relation to retrieve all documents linked to that passport
public function student_documents()
{
    return $this->morphMany(StudentDocument::class, 'student_documentables');
}
  • Related