Troncoso
Troncoso

Reputation: 2463

Laravel - Set Default Value for Relation Models

I have a table accounts:

act_id,
act_name,
act_address

And I have a table addresses:

add_id,
add_street1,
<other fields you'd expect in an address table>

accounts.act_address is a foreign key to addresses.add_id. In Laravel, I have my Account model:

use LaravelBook\Ardent\Ardent;
use Illuminate\Database\Eloquent\SoftDeletingTrait;

class Account extends Ardent
{
    use SoftDeletingTrait;

    protected $table = 'accounts';

    protected $primaryKey = 'act_id';

    public static $rules = array
    (
        'act_name' => 'required|unique:accounts'
    );

    protected $fillable = array
    (
        'act_name'
    );

    public function address()
    {
        return $this->hasOne('Address', 'add_id', 'act_address');
    }
}

As you can see, I have my one-to-one relationship setup here. (Of course, the Address model has a 'belongsTo' as well). This all works.

The thing is, the address foreign key is nullable, as accounts don't require addresses. So, if I try to access the Account->address when it doesn't have one, I'll get a 'trying to access property of non-object' error.

What I'd like to do is set Account->address to a new Address object (all fields empty), if the account record doesn't have one set.

What I've been able to do is either create a second method in the model:

public function getAddress()
{
    return empty($this->address) ? new Address() : $this->address;
}

Or, add it on the fly:

if (empty($account->address))
    $account->address = new Address();

The first solution is really close, but I'd really like to keep the functionality of accessing address as a property instead of a method.

So, my question is:
How can I have Account->address return new Address() if Account->address is empty/null?

Oh, and I tried overriding the $attributes like so:

protected $attributes = array
(
    'address' => new Address()
);

But that throws an error.

Upvotes: 2

Views: 10424

Answers (1)

Jarek Tkaczyk
Jarek Tkaczyk

Reputation: 81157

Use accessor:

Edit: Since it is belongsTo not hasOne relation, it is a bit tricky - you can't associate a model to non-existing one, for the latter has no id:

public function getAddressAttribute()
{
    if ( ! array_key_exists('address', $this->relations)) $this->load('address');

    $address = ($this->getRelation('address')) ?: $this->getNewAddress();

    return $address;
}

protected function getNewAddress()
{
    $address = $this->address()->getRelated();

    $this->setRelation('address', $address);

    return $address;
}

However, now you need this:

$account->address->save();
$account->address()->associate($account->address);

which is not very convenient. You can alternatively save newly instantiated address in getNewAddress method, or override Account save method, to do the association automatically. Anyway for this relation I'm not sure if it makes sense to do it.. For hasOne it would play nice.


Below is the way how it should look like for hasOne relation:

public function getAddressAttribute()
{
    if ( ! array_key_exists('address', $this->relations)) $this->load('address');

    $address = ($this->getRelation('address')) ?: $this->getNewAddress();

    return $address;
}

protected function getNewAddress()
{
    $address = $this->address()->getRelated();

    $this->associateNewAddress($address);

    return $address;
}

protected function associateNewAddress($address)
{
    $foreignKey = $this->address()->getPlainForeignKey();

    $address->{$foreignKey} = $this->getKey();

    $this->setRelation('address', $address);
}

You could do all this in single accessor, but this is the way it 'should' look like.

Upvotes: 5

Related Questions