Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

movepixels's avatar

Polymorphic Help

I will try to make it as simple as possible to understand. Public site -> Vue front end, laravel api backend. User adds photos, posts, does a bunch of adding records, now there is a "reviewable" polymorphic table that holds the info accordingly, so in the admin site (a completely different site / url / not public web accessable)

I can login to the admin site and see all the reviewable records. But I can not seem to get the actual related relationship.

Admin side i have following models:

Reviewable.php

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

Photo.php

public function review()
    {
        return $this->morphOne(App\Models\Reviewable::class,  'reviewable');
    }

I am stuck at getting the reviewable for the photo or the photo record for the reviewable

$photo->review or $photo->reviewable both return relationship undefined.

reviewables table:

  • id
  • review_id
  • review_type

When i examine the reviewables table i can see all the various models saved in the review_type, i can fetch the reviewables in the admin site, but cant get the related model or vice versa.

Any help would be greatly appreciated.

Thanks

0 likes
42 replies
aleahy's avatar

I believe your relationships are looking for the columns reviewable_id and reviewable_type. So to get it to work, you need to change the Photo.php relationship so it can find the columns:

public function review()
    {
        return $this->morphOne(App\Models\Reviewable::class,  'review');
    }

Then you can call $photo->review.

There seems to be some confusion about the terms review and reviewable.

To me it looks like a photo is one model type that can have a Review, so the model should have been named Review, rather than Reviewable.

A photo is then a reviewable model, i.e. it is a model that can have a review. This would then make sense that the columns should be reviewable_id and reviewable_type.

movepixels's avatar

@aleahy Thanks I will give it a try

As for the term "review" or "reviewable" it means that the admin needs to review it. Look at it and decide if it's acceptable. When looking at the "reviewable" there is / will be a button for approve or rejected. Not a review like "This photo is nice I really like it and i give it 4/5 stars" kind of actual review.

Its just a table full of records that have to be moderated by the admin

aleahy's avatar

@movepixels Ah! That makes more sense.

The relationship names still seem a little off though. Particularly a reviewable morphing to a reviewable.

movepixels's avatar

Still nothing

Updated code: Photo.php

public function review()
    {
        return $this->morphOne('App\Models\Review', 'review');
    }

Review.php

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

API Resource collection

'photo' => $photo->review,

Property [review] does not exist on this collection instance.

aleahy's avatar

Where are you getting $photo from in your API Resource?

movepixels's avatar

@aleahy Correct, in the API resource.

The resource pulls a full profile and all the "reviewables" is the intention. Idea is i can then once "review" the photo also delete it's review.id. I could be going about it all wrong. I just trying to get the review record first before having it wrong and going to much deeper into the project

movepixels's avatar

I am getting a Profile$profile bound model and passing that to the DashboardProfilePageResource($profile) in the controller API Resource code:

$response = [
      'type' => 'DashboardProfilePageResource',
      'id' => $this->user->id,
      'attributes' => $attributes,
      'relationships' => [
        'profile' => new ProfileResource($this),
        'user' => $this->user,
        'photo' => $this->photo->review, // review breaks the code review not defined on this relationship
       //  'photo' => $this->photo, pulls full photo record from Photo
      ],
aleahy's avatar

@movepixels Does it know that $this->photo is a Photo eloquent model, and not just a std class?

aleahy's avatar

@movepixels Can you post the full api resource, as well as the query you use to get the data?

movepixels's avatar

DashboardProfilePageResource.php

namespace App\Http\Resources\Pages\Dashboard\Profile;

use Illuminate\Http\Resources\Json\JsonResource;

// INDIVIDUAL MODULES
use App\Http\Resources\Modules\Profile\ProfileResource;

class DashboardProfilePageResource extends JsonResource
{
  /**
   * Transform the resource into an array.
   *
   * @param  \Illuminate\Http\Request  $request
   * @return array
   */
  public function toArray($request)
  {
    $response = [
      'type' => 'DashboardProfilePageResource',
      'id' => $this->user->id,
      'attributes' => [],
      'relationships' => [
        'profile' => new ProfileResource($this),
        'user' => $this->user,
        'photo' => $this->photo,
      ],
    ];
    return $response;
  }
}

ProfilesController.php

public function show(Profile $profile){
    DashboardProfilePageResource::withoutWrapping();
	return new DashboardProfilePageResource($profile);
  }
movepixels's avatar

@aleahy There is no actual query I created. Its all pulled in thru the model relationship to Profile

  • profile hasMany photo
  • user hasOne profile
  • profile belongsTo user
aleahy's avatar

@movepixels Are you using a with variable in the Profile model? It's important that you eager load the relationships if you can, just to avoid extra database queries you don't need.

movepixels's avatar

I just verified the model files are placed in exact folder structure on both sites. So the user in the front uploads a photo the reviewable db table shows App\Models\Profile\Photo

So in the admin backend there is a folder to match that structure so when attempting to get the relationships they match

aleahy's avatar

@movepixels So if it has many photos, you have a collection of photos rather than a single photo. That would be why it doesn't have a review.

movepixels's avatar

When i view the response the photo key is an object of "photo" records. I can see all the images the user uploaded in the loop thru photo key, Basically i am trying to include the "review" record with the photo

movepixels's avatar

Manually i can take the photo.id and query the reviewable table and see its matching record.

For sake of it I have an uploaded photo id => f1bcf118-f5c7-11ec-bb31-a760af6730a8

The reviews table has:

id, profile_id, review_id, review_type, note, created_at, updated_at

'f1beba16-f5c7-11ec-be0e-6d8c6a8cc2b3', '89ab1306-d368-11ec-8854-99bb1c7971be', 'f1bcf118-f5c7-11ec-bb31-a760af6730a8', 'App\Models\Profile\Photo', NULL, '2022-06-27 00:49:24', '2022-06-27 00:49:24'

So what i am trying to accomplish is to get this reviewable record that match's the photo.id so when i say yes this photo has been reviewed i can delete the reviewable record since its no longer needed

movepixels's avatar

@aleahy Photo.php

public function review()
    {
        return $this->morphMany('App\Models\Review', 'review');
    }

Still get the relationship undefined error

aleahy's avatar

@movepixels Don't use morphMany. A photo doesn't have many reviewables.

I always see a polymorphic relationship like a "Has One" or "Has Many", and a "Belongs To".

MorphTo is equivalent to BelongsTo. MorphOne and MorphMany is equivalent to HasOne and HasMany.

aleahy's avatar
aleahy
Best Answer
Level 25

@movepixels The data you get back when you put

'photo' => $this->photo

is an array of photos, isn't it? It may only have one photo, but if a profile has many photos, then it will be an array.

You need to load your Review relationship in your controller for the photos. This way your photos will include the review when they are obtained from the database.

Eg:

$profile->load('photo.review');
movepixels's avatar

Ok i will give it a try. And yes the API resource returns an array of photos. In the admin section i can visually see all the images the user uploaded in a loop as intended.

Will try the

$profile->load('photo.review');

Update once tested

movepixels's avatar

@aleahy

public function show(Profile $profile){

   $profile->load('photo.review');
   
   DashboardProfilePageResource::withoutWrapping();
		return new DashboardProfilePageResource($profile);
 }

Returns Class "App\Models\Review" not found

Yet i have that model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Review extends BaseModel
{
  /**
   * The database table used by the model.
   *
   * @var string
   */
  protected $table = 'reviewables';

  public $incrementing = false;

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

aleahy's avatar

@movepixels It's the relationship in your photo model it is looking for. What is it there?

movepixels's avatar

Maybe Model directory structure?

Models folder

- Review.php
- Profile folder
-- Profile.php
-- Photo.php
aleahy's avatar

Have you changed the relationship on Photo?

Is it still

public function review()
{
    return $this->morphOne(App\Models\Reviewable::class,  'review');
}

If you open up php artisan tinker and get a photo which has a Reviewable (or is it Review now?)

Can you call $photo->find('f1bcf118-f5c7-11ec-bb31-a760af6730a8')->review

movepixels's avatar

The photo has:

public function review()
    {
        return $this->morphMany('App\Models\Review', 'review');
    }
movepixels's avatar

tinker just returns errors

$photo->find('f1bcf118-f5c7-11ec-bb31-a760af6730a8')->review;
<warning>PHP Warning:  Undefined variable $photo in Psy Shell code on line 1</warning>
PHP Error:  Call to a member function find() on null in Psy Shell code on line 1

I never ever used it so zero clue

aleahy's avatar

@movepixels Sorry, my fault.

Should have been:

Photo::find('f1bcf118-f5c7-11ec-bb31-a760af6730a8')->review;

Tinker is great for just testing out some code that would appear in your controller.

movepixels's avatar

Only thing new is there is a review key in the photo array, but blank / empty

"review": []

There are photos since i am getting a photo array of $profile->photo but still no reviewable records included with each photo

movepixels's avatar

Nice tool, i knew but never used.

But i get the review that way

 App\Models\Review {#3505
         id: "f1beba16-f5c7-11ec-be0e-6d8c6a8cc2b3",
         profile_id: "89ab1306-d368-11ec-8854-99bb1c7971be",
         review_id: "f1bcf118-f5c7-11ec-bb31-a760af6730a8",
         review_type: "App\Models\Profile\Photo",
         note: null,
         created_at: "2022-06-27 00:49:24",
         updated_at: "2022-06-27 00:49:24",
       },
     ],
   }
aleahy's avatar

Try a few things to see if you get the data you want:

Photo::with('review')->find('f1bcf118-f5c7-11ec-bb31-a760af6730a8');

$profile = Profile::find('89ab1306-d368-11ec-8854-99bb1c7971be');
$profile->load('photo.review');
1 like
movepixels's avatar

in tinker this one is what i want

$profile->load('photo.review');
aleahy's avatar

You need to do the two lines to define $profile.

aleahy's avatar

If that works in tinker, then it should work in your controller. You should be passing a profile which has the data you want to your API resource.

movepixels's avatar

Its fine in tinker, but how to relate that into the controller / API response?

Tinker spits out exacly the array structure the api respose is returning but missing the review info

movepixels's avatar

But in the controller I am simply passing $profile, there is no photo id or any param to pass like we are doing in tinker.

Think i got lost somewhere along the way.

Tinker these examples asa you kindly provided:

Photo::with('review')->find('f1bcf118-f5c7-11ec-bb31-a760af6730a8');

$profile = Profile::find('89ab1306-d368-11ec-8854-99bb1c7971be');
$profile->load('photo.review');

But i do not follow where these go in relation to the controller / API resource? I have no id to pass or grab from somewhere.

I used the id as an example to say i went to mysql db and pulled a record that exists and pasted it into laracast to say yes there is a record. But that was me and the mouse getting the id, the Controller / API has no clue about this id

movepixels's avatar

This is all the controller has:

public function show(Profile $profile){

    $q = Profile::find($profile->id);
    $q->load('photo.review');

    DashboardProfilePageResource::withoutWrapping();
		return new DashboardProfilePageResource($q);
  }

But still no review data.

I see what you did in tinker example so I tried to mimic it but still empty

Next

Please or to participate in this conversation.