Eloquent morphOne relationship doesn't limit to one relation
Asked Answered
R

4

8

I am having a issue with an Eloquent morphOne relationship where it is creating new entries rather than updating the one that already exists.

Basically I have a number of models (for example, let's say Person and Building) that both need a location, so I have created a Location model:

class Location extends Eloquent {

    public function locationable()
    {
        return $this->morphTo();
    }

}

Then in my other models I have this:

class Person extends Eloquent {

    // ...

    /**
     * Get the person's location
     * 
     * @return Location
     */
    public function location()
    {
        return $this->morphOne('Location', 'locationable');
    }

    // ...
class Building extends Eloquent {

    // ...

    /**
     * Get the building's location
     * 
     * @return Location
     */
    public function location()
    {
        return $this->morphOne('Location', 'locationable');
    }

    // ...

When I run the following test code, it creates the location entry fine, but if I repeat it, it creates more entries.

$person = Person::first();

$loc = new Location;

$loc->lat = "123";
$loc->lng = "321";

$person->location()->save($loc);

Am I doing something wrong here? I would have expected morphOne to constrain this to one entry per type, so the last entry in the table below should not exist:

+---------------------+--------------------------+
|  locationable_id    |   locationable_type      |
+---------------------+--------------------------+
|  2                  |  Building                |
|  3                  |  Building                |
|  2                  |  Person                  |
|  2                  |  Building                |
+---------------------+--------------------------+
Roveover answered 11/7, 2014 at 9:42 Comment(2)
No, you're not doing it wrong. It's just how it works. Polymorphic relations are good for easy stuff, but the further you go, the more bugs you find.Affinity
So do you suggest to avoid them wherever possible? Doesn't make sense for me to do anything else but create a polymorphic relation. 1) Someone suggests doing a simple if else check to see if the orderable_type is empty then create, otherwise update, what is your opinion on this? 2) i.imgur.com/gPmh1OK.png Opinion on this? Would love to hear it. Thank you man.Efficient
K
6

Maybe I'm late for the party, but this worked for me!

# Do we have location already?
if($person->location) {
    return $person->location()->update($location);
}
return $person->location()->create($location);
Kevin answered 15/10, 2015 at 15:34 Comment(1)
I usually put this in a method on the model: saveLocation(Location $location){ ... } for a nice and easy apiAlpenglow
A
3

Actually, by calling

$loc = new Location;

you are, by definition, creating a new location!

Calling

$person->location()->save($loc);

doesn't help either.

IF you want to update a location, you need to find the location, update it's values and then save it. Independently from the parent model:

$person = Person::first();

$loc = $person->location;

$loc->lat = "123";
$loc->lng = "321";

$loc->save();

Done.

Alathia answered 1/11, 2014 at 0:21 Comment(0)
I
3

Based on my very current experiences, this solution should be works

Creation or replace old by new Location model instance:

$person = Person::first();

$loc = new Location;

$loc->lat = "123";
$loc->lng = "321";

$loc->save();
$person->location()->associate($loc);
$person->save();

Update:

$person = Person::first();

$person->location->lat = "123";
$person->location->lng = "321";

$person->location->save();
$person->save();

I spent many days for finding out this solution. Hope they would document it much better officially.

Inlay answered 29/11, 2015 at 11:17 Comment(0)
M
0

The best way to do create or update things on either a hasOne or morphOne related model is to use the "updateOrCreate" function.

In my case, A client can have one address so I do the following and works for me

$client->address()->updateOrCreate([], $addressData);

The same thing can also be applied in case of hasOne relationship like a user can only have one profile so I do the following.

$user->profile()->updateOrCreate([], $data);

User.php

class User {
    /**
     * Get the user profile information associated with the user.
     */
    public function profile()
    {
        return $this->hasOne('App\UserProfile');
    }
}

Client.php

class Client {
    /**
     * Get all of the client's addresses.
     */
    public function address()
    {
        return $this->morphOne('App\Address', 'addressadder');
    }
}
Marciano answered 27/7, 2020 at 6:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.