Home > OS >  Laravel going through hasOne to get attributes
Laravel going through hasOne to get attributes

Time:12-10

I have the following models.

  • Player
    • (hasMany(Monster::class))
  • Monster
    • hasOne(MonsterSpecies::class,'id','species_id')
    • hasOne(MonsterColor::class,'id','color_id'))
  • MonsterSpecies
  • MonsterColor
    • (Both pretty much empty, just public $timestamps = false;)

Then, after seeding, in php artisan tinker I select one player:

$player = Player::all()->first();

It works. Then I check the monsters.

Illuminate\Database\Eloquent\Collection {#3561
     all: [
       App\Models\Monster {#3569
         id: 1,
         created_at: "2021-12-09 16:39:29",
         updated_at: "2021-12-09 16:39:29",
         name: "Alberto Mills",
         level: 17,
         currHealth: 68,
         maxHealth: 76,
         strength: 42,
         defense: 29,
         movement: 13,
         species: 28,
         color: 34,
         player_id: 1,
       },
       App\Models\Monster {#4505
         id: 2,
         created_at: "2021-12-09 16:39:29",
         updated_at: "2021-12-09 16:39:29",
         name: "Darlene Price",
         level: 9,
         currHealth: 16,
         maxHealth: 32,
         strength: 44,
         defense: 19,
         movement: 61,
         species: 28,
         color: 34,
         player_id: 1,
       },
     ],
   }

Then $player->monster->get(0)->color;

 App\Models\MonsterColor {#4508
     id: 34,
     name: "Red_Blue",
   }

Now I believe I can add getSpeciesAttribute() and getSpeciesAttribute() to directly return the name or maybe do something like:

public function getSpeciesAttribute($value)
{
    $colors = explode("_",$value->name); // I don't know if this is how I get the name
    $out = "Your monster's color is ";
    if (count($colors) > 1) {
        $out .= "a mesh of " . implode(", ",array_slice($colors, 0, -1)) . " and ";
    }
    $out .= array_pop($colors);
    return $out;
}

But I don't know how to access the name attribute of MonsterColor.

Edit:

Here's the Monster, MonsterColor and MonsterSpecies models.

class Monster extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'level',
        'currHealth',
        'maxHealth',
        'strength',
        'defense',
        'movement',
        'species',
        'color'
    ];

    public function player()
    {
       return $this->belongsTo(Player::class);
    }

    public function species()
    {
       return $this->hasOne(MonsterSpecies::class,'id','species_id');
    }

    public function color()
    {
        return $this->hasOne(MonsterColor::class,'id','color_id');
    }

    public function getSpeciesAttribute()
    {
        return $this->species->name;
    }

    public function getColorAttribute()
    {
        return $this->color->name;
    }
}

class MonsterColor extends Model
{
    use HasFactory;

    public $timestamps = false;
}

class MonsterSpecies extends Model
{
    use HasFactory;

    public $timestamps = false;
}

CodePudding user response:

The accessor getColorAttribute() creates a "virtual" color property on the model class. But you also have a relationship method called color(). Relationship methods also create virtual properties, which you are trying to access with $this->color.

So the problem is simply one of colliding names. Easiest solution would be to rename the accessor to something else.

The problem is the same with your other accessor, and also they don't accept parameters; they're accessed as properties so there's no way to pass one.

public function getColorNameAttribute()
{
    return $this->color->name;
}

public function getSpeciesDescriptionAttribute()
{
    $colors = explode("_", $this->color->name);
    return sprintf(
        "Your monster's color is %s",
        implode(" and ", $colors)
    );
}

Now you can access these via $player->monsters[0]->color_name or $player->monsters[0]->species_description.


A couple of other notes:

  • you shouldn't have color and species in $fillable as they aren't database columns
  • you don't need to declare column names in relationship methods (e.g. $this->hasOne(MonsterColor::class,'id','color_id')) unless they are non-standard – yours are not
  • if a relationship returns a collection it should be named with a plural (i.e. not $player->monster as in your question)
  • do look into eager loading of relationships.
  • Related