Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force create an eloquent model through a relationship

In a small project with Laravel 5.3 and Stripe, I am trying to force create a Subscription on a User through a hasOne relationship:

// User.php
public function subscription() {
    return $this->hasOne('App\Subscription');
}

public function subscribe($data) {
    return $this->subscription()->forceCreate(
        // $data contains some guarded fields;
        // create() will simply ignore them...
    );
}

However, I get :

Call to undefined method Illuminate\Database\Query\Builder::forceCreate()

Even though forceCreate() is a valid Eloquent method.

Any ideas how I can simulate this behavior? Or should I just save a Subscription manually assigning each field? The complication is that certain fields should be kept guarded, e.g. stripe_id.

EDIT

My quick' n' dirty solution:

// User.php @ subscribe($data)
return (new Subscription())
        ->forceFill([
            'user_id' => $this->id,
            // $data with sensitive guarded data
        ])
        ->save();

I'm sure there is a better way though!

like image 909
Alex Avatar asked Oct 24 '25 06:10

Alex


1 Answers

The call to $this->subscriptions() returns an instance of HasMany class, not a Model.

Contrary to create and createMany, there's no forceCreate() implemented in HasOneOrMany class. And thus, there's no first-hand API to use in such situations.

You see? when you call $this->subscriptions()->create(), you're calling the create method on a HasMany instance, not a Model instance. The fact that Model class has a forceCreate method, has nothing to do with this.

You could use the getRelated() method to fetch the related model and then call forceCreate() or unguarded() on that:

public function subscribe($data) {
    return $this->subscription()
                ->getRelated()
                ->forceCreate($data);
}

But there's a serious downside to this approach; it does not set the relation as we're fetching the model out of the relation. To work around it you might say:

public function subscribe($data)
{
    // $data['user_id'] = $this->id;
    // Or more generally:  
    $data[$this->courses()->getPlainForeignKey()] = $this->courses()->getParentKey();

    return $this->courses()
                ->getRelated()
                ->forceCreate($data);
}

Ehh, seems too hacky, not my approach. I prefer unguarding the underlying model, calling create and then reguarding it. Something along the lines of:

public function subscribe($data)
{
    $this->courses()->getRelated()->unguard();

    $created = $this->courses()->create($data);

    $this->courses()->getRelated()->reguard();

    return $created;
}

This way, you don't have to deal with setting the foreign key by hand.

laravel/internals related discussion:
[PROPOSAL] Force create model through a relationship

like image 141
sepehr Avatar answered Oct 26 '25 20:10

sepehr



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!