Search code examples
phplaraveleloquenteloquent-relationship

Set default value for relation in Laravel model based on another relation's value


I am querying for a Food record and its relationships the following way, setting a default value for one of the relation:

$this->foodModel->with(['mainImage', 'category'])->find($foodId);

App\Models\Food

public function category()
{
    return $this->belongsTo('App\Models\FoodCategory', 'food_categ_id');
}

public function mainImage() 
{
    $placeholderUrl = asset('images/placeholders/food.jpg');
    
    return $this->morphOne('App\Models\Media', 'parentable')
        ->where('primary', 1)
        ->withDefault([
            'url' => $placeholderUrl
        ]);
}

But now I want to set the default value according to the Food's category relation for that specific record and I'm not sure how to access the data.

public function mainImage() 
{
    $placeholderUrl = asset('images/placeholders/food-categories/' . $category->name . '.jpg') // IS IT POSSIBLE?

    return $this->morphOne('App\Models\Media', 'parentable')
        ->where('primary', 1)
        ->withDefault([
            'url' => $placeholderUrl
        ]);
}

Solution

  • What about to add mainImage into FoodCategory model too and to set default image for each categories?

    App\Models\FoodCategory

    public function mainImage() 
    {
        return $this->morphOne('App\Models\Media', 'parentable')->where('primary', 1)->withDefault(function (Media $media, FoodCategory $category) {
            $media->url = asset('images/placeholders/food-categories/' . $category->name . '.jpg');
        });
    }
    

    and then into Food model to have Attribute image like this:

    App\Models\Food

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = ['image'];
    
    public function image(): Attribute
    {
        return Attribute::make(
            get: fn () => $this->mainImage ?? $this->category->mainImage
        );
    }
    
    public function mainImage()
    {
        return $this->morphOne('App\Models\Media', 'parentable')->where('primary', 1);
    }
    

    and you need to add category.mainImage for lazy loading too

    $this->foodModel->with(['mainImage', 'category', 'category.mainImage'])->find($foodId);
    

    If the food has mainImage the image and mainImage will be the same:

    enter image description here

    But if the foot does not have mainImage, image will contain the mainImage from the category

    {
      "id": 3,
      "name": "Dr. Reba Dicki",
      "food_categ_id": 1,
      "image": {
        "id": 16,
        "parentable_id": "3",
        "parentable_type": "App\\Models\\Food",
        "primary": 1,
        "url": "https://www.kirlin.com/et-consectetur-aliquam-deserunt-fugit-aut-vel-voluptate"
      },
      "main_image": {
        "id": 16,
        "parentable_id": "3",
        "parentable_type": "App\\Models\\Food",
        "primary": 1,
        "url": "https://www.kirlin.com/et-consectetur-aliquam-deserunt-fugit-aut-vel-voluptate"
      },
      "category": {
        "id": 1,
        "name": "Christophe Hansen"
      }
    }
    

    ... without primary Food media

    {
      "id": 5,
      "name": "Julie Smitham",
      "food_categ_id": 5,
      "image": {
        "id": 5,
        "parentable_id": "5",
        "parentable_type": "App\\Models\\FoodCategory",
        "primary": 1,
        "url": "http://www.padberg.info/et-ab-numquam-harum-ut-aut-doloribus.html"
      },
      "main_image": null,
      "category": {
        "id": 5,
        "name": "Columbus Krajcik",
        "main_image": {
          "id": 5,
          "parentable_id": "5",
          "parentable_type": "App\\Models\\FoodCategory",
          "primary": 1,
          "url": "http://www.padberg.info/et-ab-numquam-harum-ut-aut-doloribus.html"
        }
      }
    }
    

    UPDATE

    There is a way withDefault but I'm not prefer this because will make a db query for each:

    Update only Food model and mainImage to be:

    public function mainImage()
    {
        return $this->morphOne('App\Models\Media', 'parentable')
            ->where('primary', 1)
            ->withDefault(function (Media $media, Food $food) {
                $meda = $media->where([
                    'parentable_type' => FoodCategory::class,
                    'parentable_id' => $food->food_categ_id,
                    'primary' => true,
                ])->first();
    
                if (!$meda) {
                    $meda = new Media();
                    $meda->url = asset('images/placeholders/food.jpg');
                }
    
                return $meda;
            });
    }
    

    with Food media

    {
      "id": 3,
      "name": "Dr. Reba Dicki",
      "food_categ_id": 1,
      "main_image": {
        "id": 16,
        "parentable_id": "3",
        "parentable_type": "App\\Models\\Food",
        "primary": 1,
        "url": "https://www.kirlin.com/et-consectetur-aliquam-deserunt-fugit-aut-vel-voluptate"
      },
      "category": {
        "id": 1,
        "name": "Christophe Hansen"
      }
    }
    

    without Food media, but with FoodCategory media

    {
      "id": 5,
      "name": "Julie Smitham",
      "food_categ_id": 5,
      "main_image": {
        "id": 5,
        "parentable_id": "5",
        "parentable_type": "App\\Models\\FoodCategory",
        "primary": 1,
        "url": "http://www.padberg.info/et-ab-numquam-harum-ut-aut-doloribus.html"
      },
      "category": {
        "id": 5,
        "name": "Columbus Krajcik"
      }
    }
    

    without Food and FoodCategory media

    {
      "id": 4,
      "name": "Clovis Koepp",
      "food_categ_id": 4,
      "main_image": {
        "url": "http://localhost/images/placeholders/food.jpg"
      },
      "category": {
        "id": 4,
        "name": "Bernita Kuhn"
      }
    }
    

    UPDATE 2

    For Laravel < 9, you need to use eloquent mutators

    App\Models\Food

    public function getImageAttribute()
    {
        return $this->mainImage ?? $this->category->mainImage;
    }