2

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.

1 Answer 1

5

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.

Sign up to request clarification or add additional context in comments.

7 Comments

That does what I need as far as returning a new Address object, but when I'm creating an object with $account->address()->save(new Address($params)), it doesn't work. It creates both the account and the address, but the accounts.act_id does get set to addresses.add_id.
Show the code and what the issue is. It has nothing to do with method call $account->address() so it can't interfere with its save method. Btw I changed getNewAddress to avoid explicitly calling new Address.
And, are you sure the relations are OK? hasOne means, that Address table holds foreign key related to Account table primary key. Your tables are hard to read, but it seems that you have it wrong.
While I did state this is a one-to-one relationship, it's actually setup as a many-to-one. Several accounts can point to the same address. So, the accounts table just has a foreign key to the address.add_id column. I do the same thing with two other tables and it works fine. But, that foreign key is required, so I don't run into the issue that I originally proposed. I'm updating my post with more info.
Yes, that's what I thought. So the relations are wrong, you need Account belongsTo Address, Address hasMany Account. I'll adjust the code for this setup in a moment.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.