Search code examples
laraveleloquentrelationships

Laravel/Eloquent save validation with parent/child relationships


I have a parent model which must never exist without at least one associated hasMany relation. I am attempting to write validation for the model to ensure this never happens.

public static function boot()
{
    parent::boot();
        // reject model with no children
        if (count($workflow->children) === 0) {
            throw new RuntimeException("need at least one child");
        }
    });
}

But now I have a chicken and egg problem. I can't write the child record without the parent id. And I can't write the parent record until the save validation passes. But when I try to associate the children and call ->push() on the parent model, I get various issues depending on how I try to associate the two.

The following causes a FK constraint failure on the child column which references the parent:

$parent->children->add($child);
$child->parent()->associate($parent);
$parent->push();

Is Laravel just too stupid to handle this (seemingly reasonable) use case?


Solution

  • Use Laravel's DB::beginTransaction(), DB::commit() and DB::rollback() functions to prevent any stray information getting saved to the database. Take the following rough example:

    public function save(){
      DB::beginTransaction();
      try {
        $parent = new Parent();
        $parent->save();
        $child = new Child();
        $parent->children()->save($child);
        DB::commit();
      } catch(Exception $e()) {
        Log::error($e->getMessage());
        DB::rollback();
      }
    }
    

    Basically, using a try { ... } catch() { ... } block with DB::beginTransaction(), DB::commit() and DB::rollback(), you can try saving something to the database, and if no exceptions are caught, DB::commit() the changes. If something goes horribly, terribly wrong, and exception will be caught, the error message will be logged and the changes will be discarded using DB::rollback().

    Hope that helps!