2
votes

I'm trying to retrieve the owner of an Eloquent polymorphic relationship I defined in my app. Here is the relation : a Theme can be posted either by a Enseignant or a Partenaire , so both can post themes. Here are the models and the polymorphic relationship corresponding :

Theme model

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Theme extends Model
{

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

Enseignant model

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Enseignant extends Model
{


    public function utilisateur(){
        return $this->belongsTo('App\Utilisateur');
    }

    public function themes(){
        return $this->morphMany('App\Theme', 'themePoster');
    }
}

Partenaire model

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Partenaire extends Model
{


    public function utilisateur(){
        return $this->belongsTo('App\Utilisateur');
    }

    public function themes(){
        return $this->morphMany('App\Theme', 'themePoster');
    }
}

I'm trying to retrieve the owner of a theme in my blade view as the way showed in the doc.

Here is the Controller show function :

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $theme = Theme::findOrFail($id);
        return view('themeShow', ['theme' => $theme]);
    }

themeShow blade view

@extends('layouts.layout')

@section('content')
<body>
{{$theme->intitule}} <br>
{{$theme->categorie}} <br>
{{$theme->filiereDesiree}} <br>
{{$theme->description}} <br>
<a href=" {{url('themes')}} "><button>Voir tous</button></a>
@if(Auth::guard('web')->user()->hasRole('etudiant'))
<a href=""><button>Choisir thématique</button></a>
Proposé par {{ $theme->themePoster }}
@elseif(Auth::guard('web')->user()->id == $theme->themePoster_id)
<a href=" {{ url('themes/' .$theme->id. '/edit' ) }} "><button>Modifier thématique</button></a>
<form method="POST" action=" {{ route('themes.destroy', $theme->id ) }} ">
    @csrf
    @method('DELETE')
<a class="btn btn-danger"> <input  class="delete" type="submit" name="submit" value="Supprimer thématique"><i class="fa fa-trash"></i></a>
</form>
@endif
@jquery
<script type="text/javascript">
    $("input[type=submit]").on("click", function(){
        return confirm('Etes-vous sûr de vouloir de supprimer cette thématique?');
    });
</script>
</body>
@endsection

So here is the line supposed to retrieve the owner of the theme:

Proposé par {{ $theme->themePoster }}

Yet I'm not getting anything: Proposé par: nothing is returned. What am I missing there..? I'm pretty new at Laravel for this being my first app and first time using polymorphic relationships. Your help would be very welcome

Edit

As the information may be useful for more comprehension.. here is my database structure:

Utilisateurs table

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUtilisateursTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('utilisateurs', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('nom');
            $table->string('prenom');
            $table->string('username')->unique();
            $table->string('email')->unique();
            $table->string('password');
            $table->string('fonction');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('utilisateurs');
    }
}

Note : Enseignant and Partenaire inherits Utilisateur.

Utilisateur's model

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Utilisateur extends Authenticatable
{
    use Notifiable;

    protected $table = 'utilisateurs';

    public function hasRole($role){
        return $this->fonction == $role;
    }

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

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

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

Enseignants table

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateEnseignantsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('enseignants', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->integer('utilisateur_id')->unique();
            $table->string('isAdmin');
            $table->string('nomEntreprise')->nullable();
            $table->string('descEntreprise')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('enseignants');
    }
}

Partenaires table

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePartenairesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('partenaires', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->integer('utilisateur_id')->unique();
            $table->string('structure');
            $table->string('description_structure');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('partenaires');
    }
}

Basically, themePoster_id and themePoster_type are created by getting the current Enseignant or Partenaire id and fonction. Here's the code responsible of getting those attributes:

<input type="hidden" id="themePoster_id" name="themePoster_id" value= "{{Auth::guard('web')->user()->id}}">
<input type="hidden" id="themePoster_type" name="themePoster_type" value= "{{Auth::guard('web')->user()->fonction}}">

and as you can see here in this sample of theme table, it is well recorded but I'm getting issue with this {{ $theme->themePoster }} which returns nothing here.

1
Welcome to Laravel! Firstly, can you tell a little more about your database structure? Secondly, confirm if $theme->themePoster_id and $theme->themePoster_type have values. Thirdly {{ $theme->themePoster }} will output an object, usually not something that should be outputted as a string. - Thomas Van der Veen
Thanks!:) . I've edited the question so you may have a clearer understanding of what I'm trying to do. For the {{ $theme->themePoster }}, I tried as it's said in the documentation, like the same logic they use in it I think.. or what should I normally do to retrieve it then..? - Ted Aaron
A thing that stands out is the use of user()->id and user()->fonction as your morph class. It is not incorrect but above you state " a Theme can be posted either by a Enseignant or a Partenaire" not User. The other thing, and I think this is why it cannot find anything, is the use of the wrong themePoster_id and themePoster_class. I will post an answer later today, if no one has done it already. - Thomas Van der Veen
For the user()->id and user()->fonction part, I've ensured with a middleware that the logged user which can access the themeCreate view is either a Enseignant or a Partenaire and it's working fine. Thank you to have it remarked though - Ted Aaron

1 Answers

0
votes

As stated in the docs we (by default) need the following:

  • The proper database columns.
  • The proper relationship declarations.

In your case this would be the following:

// Themes table:
$table->morphs('themePoster');

// Which is short for:
$table->string('themePoster_type');
$table->unsignedBigInteger('themePoster_id');


// Partenaire and Enseignant models:
public function themes()
{
    return $this->morphMany('App\Theme', 'themePoster');
}

// Theme model:
public function themePoster(){
    return $this->morphTo();
}

(Source) of morphs.

Relating a polymorphable model could be done this way:

$partenaire->themes()->create([
    ...
]);

// The default behavior will result in this theme db record:
 themePoster_type | themePoster_id | more columns here 
 -----------------|----------------|-------------------
 'App\Partenaire' | 1              | ...

The part where I think it is going wrong in your code would be the value of the themePoster_type. An assumption is that your record will look like this:

themePoster_type | themePoster_id | more columns here 
 -----------------|----------------|-------------------
 'partenaire'     | 1              | ...

Laravel cannot find partenaire as a model, so it does not know in which table it should look. My assumption would be Laravel throwing an exception, but I might be wrong.

In short I think the input with Auth::guard('web')->user()->fonction sends the incorrect value. In your case it should be App\Partenaire or App\Enseignant.

Laravel provides a solution to map certain strings to the correct classes. This would be a nice solution if the problem I am stating is correct. You can map the keys to the correct classes. This would result in:

// AppServiceProvider@boot
Relation::morphMap([
    'partenaire' => 'App\Partenaire',
    'enseignant' => 'App\Enseignant',
]);