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

RomainLanz's avatar

Map relation's field directly on the model

Hi,

Is it possible to map relation's field directly on the model without writing a lot of code?

Let me give you an example:

class User extends Model
{
    public function profile()
    {
        return $this->hasOne(UserProfile::class);
    }
}

class UserProfile extends Model
{
    public $fillable = ['first_name', 'last_name', 'address', 'phone', 'birth_on'];
}

Now I can do $user->profile()->first_name; but I want to know if it's possible to do $user->first_name. So map any fields on the UserProfile entity into my user model.

0 likes
17 replies
Phillipp's avatar

You can make a method getFirstNameAttribute(){} in your user model and call the first name from the relation inside the method.

RomainLanz's avatar

But is there any better way to do that?

Like something like:

class User extends Model
{
    protected static $mapped = [
        'first_name' => 'profile.first_name',
        'last_name' => 'profile.last_name',
    ];
}

If not, I think that I'll create a package this afternoon for that.

Phillipp's avatar

I don't think so. A package would be awesome. Please share it with us when it's done! =)

RomainLanz's avatar

@Phillipp Package done, I'll optimize a little bit before sharing the package on github and I need to find a name too ^^.

JarekTkaczyk's avatar

@RomainLanz That was quick :) I was going to show an example, but I don't think it's necessary anymore. Anyway, I suppose I wouldn't go with static property.

RomainLanz's avatar

@Phillipp Seems good to me! :D

@JarekTkaczyk It was quick because the code is ugly for the moment, but it's working. As you can see this is very simple but need refactoring.

public function __get($fieldName)
{
    if ($return = parent::__get($fieldName)) {
        return $return;
    }

    if (property_exists($this, 'mappedField')) {
        foreach (static::$mappedField as $field => $map) {
            if ($fieldName === $field) {
                list($relation, $column) = explode('.', $map);

                if (method_exists($this, $relation)) {
                    return $this->$relation->$column;
                }
            }
        }
    }
}

Why not use static property?

RomainLanz's avatar

I'll remove property_exists and method_exists check. It's better to catch the real error.

JarekTkaczyk's avatar

@RomainLanz Something like this:

protected $mappedAttributes = [
    'profile' => ['first_name', ...];
    // or
    // 'first_name' => 'profile.first_name'
];

public function __get($key)
{
    if ($this->isMappedAttribute($key)) {
        return $this->getMappedAttribute($key);
    }

    return parent::__get($key);
}

Not static in order to make it customizable and consistent with other eloquent features.

And you're making assumption that you can relay only single relation. Maybe you would like more? user->picture_path -> user->profile->picture->path?


@RomainLanz The logic goes in the isMappedAttribute and getMappedValue, so you can use either profile.first_name or profile => ['first_name, .. ] notation.

1 like
RomainLanz's avatar

@JarekTkaczyk Mmmh, that's not false. I'll change my code. Are you sure to return mapped field before the official __get method?

JarekTkaczyk's avatar

@RomainLanz Since it is custom behaviour I would rather want it (as a developer who defines it in my model) to run before the default behaviour, so yes, before the parent::__get($key)

1 like
RomainLanz's avatar

@JarekTkaczyk That could be a good new feature to add on the next release of the plugin (user->picture_path -> user->profile->picture->path).

If we look at your $mappedFields array, I need to iterate over each relation to know if the field is mapped. I think that it's better to keep my definition.

protected $mappedFields = [
    'first_name' => 'profile.first_name',
    'picture_path' => 'profile.picture.path',
];

-- @JarekTkaczyk I'll try to do all of that stuff until tomorrow. Thanks for your help!

RomainLanz's avatar

@JarekTkaczyk Nice one, I'm impatient to see the next add-on!

Why not use a dependency on my package (and btw send a PR to allow implicit mapping and do some refactoring of my dirty code) for the Mappable system?

Curdal's avatar

@JarekTkaczyk Hey great package, but I have encountered not really a problem but a bug of sorts. Frankly I don't even know how to define it. But it goes as follows.

The mapping works great when directly called on in the view eg. $client->first_name which is a mapped to a "morphOne" relationship on a People model.

But when trying to combine this with the "Illuminate\Html" package's form model binding method it just doesn't work. I still have to define a accessor before it populates the field. Any suggestions?

JarekTkaczyk's avatar

@Curdal 99% that the reason for this is isset check that is called during form model binding. Please post relevant example in the github and I'll look into it tonight.

Please or to participate in this conversation.