Laravel Relations with Resources -> Trying to get property ‘id’ of non-object

I tried looking high and low for a solution to this, but met with failure. So here goes:

I am making a photo gallery module for a website. I have one model called ‘GalleryName’ and another called ‘GalleryPhoto’ and established a hasMany relation between them.

I am using Vue JS and the package I used to display the photos demand that I use the word ‘title’ as key for the name of the photo and ‘src’ as key for the full path to the file I have in storage. Instead of changing my database and other logic, I decided to use a resource file to change the keys and values.

First of all, here are my migrations:

<?php

use IlluminateDatabaseMigrationsMigration;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateSupportFacadesSchema;

class CreateGalleryNamesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('gallery_names', function (Blueprint $table) {
            $table->id();
            $table->string('gallery_name');
            $table->string('gallery_cover_photo');
            $table->timestamps();
        });
    }

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

<?php

use IlluminateDatabaseMigrationsMigration;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateSupportFacadesSchema;

class CreateGalleryPhotosTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('gallery_photos', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('gallery_name_id');
            $table->string('photo_title');
            $table->string('photo_image');
            $table->timestamps();
        });
    }

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

Here are my Models:
// Gallery Name
<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class GalleryName extends Model
{
    use HasFactory;

    protected $guarded = [];

    public function gallery_photos(){
        return $this->hasMany(GalleryPhoto::class);
    }
}

// Gallery Photo
<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class GalleryPhoto extends Model
{
    use HasFactory;

    protected $guarded = [];

    public function gallery_name(){
        return $this->belongsTo(GalleryName::class);
    }  
}

Here are my resource files:
// Gallery Name Resource
<?php

namespace AppHttpResources;

use IlluminateHttpResourcesJsonJsonResource;

class GalleryNameResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  IlluminateHttpRequest  $request
     * @return array
     */
    public function toArray($request)
    {
        // return parent::toArray($request);

        return [
            'id' => $this->id,
            'gallery_name' => $this->gallery_name,
            'gallery_cover_photo' => $this->gallery_cover_photo,
            'created_at' => $this->created_at,
            'gallery_photos' => GalleryPhotoResource::collection($this->gallery_photos)
        ];   

    }
}

I am using this line, ‘gallery_photos’ => GalleryPhotoResource::collection($this->gallery_photos), since that is what the documentation tells you to do when you need to use resource inside a relationship.
// Gallery Photo Resource
<?php

namespace AppHttpResources;

use IlluminateHttpResourcesJsonJsonResource;

class GalleryPhotoResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  IlluminateHttpRequest  $request
     * @return array
     */
    public function toArray($request)
    {
        // return parent::toArray($request);

        return [
            'id' => $this->id,
            'gallery_name_id' => $this->gallery_name_id,
            'title' => $this->photo_title,
            'src' => '/storage/photo_gallery_images/'.$this->photo_image,
            'created_at' => $this->created_at,            
        ];       
    }
}

I am using this line, ‘src’ => ‘/storage/photo_gallery_images/’.$this->photo_image, since I stored the filename only in database and now I need the full qualified path and not just the filename as per my package requirements.

I declared a show method in my controller like this:

// Gallery Name Controller
<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;
use AppModelsGalleryName;
use AppHttpResourcesGalleryNameResource;

class GalleryNameController extends Controller
{

public function show($id)
{
        $gallery_name = new GalleryNameResource(GalleryName::where('id', $id)->select('id', 'gallery_name','gallery_cover_photo' , 'created_at')->with(['gallery_photos' => function($query) {
            return $query->select(['id', 'gallery_name_id', 'photo_title', 'photo_image', 'created_at']);
        }])->first());

        if($gallery_name){
            return response()->json($gallery_name, 200);
        }

        return response()->json('Gallery Not Found !!', 404);
    }
}

As you can see, this is using a relationship, but also the Resource files I declared earlier.

Now when I am using Postman and when I provide an id that exists(as a route parameter), it shows ther right JSON response. e.g

{
    "id": 1,
    "gallery_name": "Some Gallery 1",
    "gallery_cover_photo": "pexels-lena-goncharova-8692688-400x300_1628567178.jpg",
    "created_at": "2021-08-10T03:46:18.000000Z",
    "gallery_photos": [
        {
            "id": 1,
            "gallery_name_id": 1,
            "title": "Photo 1",
            "src": "/storage/photo_gallery_images/pexels-lena-goncharova-8692688-900x800_1628567237.jpg",
            "created_at": "2021-08-10T03:47:17.000000Z"
        },
        {
            "id": 2,
            "gallery_name_id": 1,
            "title": "Photo 2",
            "src": "/storage/photo_gallery_images/pexels-lena-goncharova-8692688-400x300_1628567251.jpg",
            "created_at": "2021-08-10T03:47:31.000000Z"
        }
    ]
}

But when I provide an id that is non-existant in database, it gives me an ‘Trying to get property ‘id’ of non-object’ error. I found a work-around but in a hacky fashion. Regardless, I wish to know the nature of this error or why this is happening since I can’t find what I did wrong.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

Your GalleryNameResource is the problem here.

Let’s reformat your code so it’s easier to read:

public function show($id)
{

        $gallery_instance = GalleryName::where('id', $id)->select('id', 'gallery_name','gallery_cover_photo' , 'created_at')->with(['gallery_photos' => function($query) {
            return $query->select(['id', 'gallery_name_id', 'photo_title', 'photo_image', 'created_at']);
        }])->first();

        // $gallery_name == null if id doesn't exist
        
        $gallery_name = new GalleryNameResource($gallery_instance);

        // again, if the id doesn't exist, you are passing null to the resource, 
        // it's like writing new GalleryNameResource(null);

        // Remember: $gallery_name, in your code, IS NOT a GalleryName instance. 
        // It is a GalleryNameResource instance. 
        // It'll never be null or false, so the next condition is always true.

        if($gallery_name){
            return response()->json($gallery_name, 200);
        }

        return response()->json('Gallery Not Found !!', 404);
    }
}

When you send your GalleryNameResource to the response, Laravel will convert it to JSON.

This is why you get the error:

public function toArray($request)
    {
        // return parent::toArray($request);

        return [
            'id' => $this->id,

            // 'id' => $this->id, is very likely your error

           // Laravel will try to get the id of... null
           // $this->id translates to $this->resource->id
           // so it translates to $this->null->id

           // Trying to get property id.... Error.

            'gallery_name' => $this->gallery_name,
            'gallery_cover_photo' => $this->gallery_cover_photo,
            'created_at' => $this->created_at,
            'gallery_photos' => GalleryPhotoResource::collection($this->gallery_photos)


        ];   

    }

How to fix your problem?

public function show($id)
{
        $gallery_name = GalleryName::where('id', $id)->select('id', 'gallery_name','gallery_cover_photo' , 'created_at')->with(['gallery_photos' => function($query) {
            return $query->select(['id', 'gallery_name_id', 'photo_title', 'photo_image', 'created_at']);
        }])->first();

        if(!$gallery_name){
           return response()->json('Gallery Not Found !!', 404);
        }

        return response()->json(new GalleryNameResource($gallery_name), 200);
    }
}

A cleaner way of solving this would be to use implicit route bindings, check this in the docs: https://laravel.com/docs/8.x/routing#implicit-binding


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x