如何同时保存一对多的关系模型

现在有两张表

wikis

id title content
标题 内容

tags

id tag wiki_id
标签 外键

一个wiki拥有多个标签,因此在model类里面建立如下关系

wiki类

public function aliases(){
       return $this->hasMany('Tag');
    }

Tag类

public function wiki(){
       return $this->belongsTo('Wiki');
    }

如何在创建wiki的时候同时保存好tags呢?


WikiController@store

用save()的方式

 //先保存wiki
 $wiki = Wiki::create(Input::only('title','content'));

//再逐个保存tags
 if( !empty( Input::get('tags'))){
      foreach (Input::get('tags') as $tag) {
                $tg = new Tag(compact('tag'));
                $wiki->tags()->save($tg);
      }
 }

用saveMany的方式

 //先保存wiki
 $wiki = Wiki::create(Input::only('title','content'));

//再创建tag的对象集合
 if( !empty( Input::get('tags'))){
      foreach (Input::get('tags') as $tag){
            $tags[] = new Tag(compact('tag'));
      }
$wiki->tags()->saveMany($tags);
}

//一次性保存tags,猜测会比直接foreach要节省资源

从tag的角度保存

 //先保存wiki
$wiki = Wiki::create(Input::only('title','content'));

//逐个保存
 if( !empty( Input::get('tags'))){
      foreach (Input::get('tags') as $tag) {
                $tg = new Tag(compact('tag'));
                $tg->wiki()->associate($wiki);//把$tg关联到父对象$wiki
                $tg->save();
      }
 }

但是这种方式的问题是,必须先保存wiki后再保存tag,但如果希望wiki和tag同时保存,如果tag不成功那么整个操作就回滚掉应该怎么办呢?难道不能使用InnoDB的事务处理机制吗?

尝试了一下push():

从tag的角度2 用push
      $tag = new Tag();
      $tag->tag = 'a tag';

      $tag->wiki = new Wiki();
      $tag->wiki->title = 'a title';
      $tag->wiki->content = 'a content';
      $tag->push();

//结果报错
//   SQLSTATE[42S22]: 
//   Column not found: Unknown column 'wiki' in 'field list' (SQL: 
//        insert into `tags` (`tag`, `wiki`, `updated_at`, `created_at`) 
//        values (a tag,{"title":"a title","content":"a content"},
//        2014-11-05 04:20:30, 2014-11-05 04:20:30))

可以看出laravel 是打算直接把对象转化成字符串,存在表里面。

从wiki的角度用push
          $wiki = new Wiki();
          $wiki->title = 'a name';
          $wiki->content = 'a content';

          $aliases = [
              new Tag(['tag' => 'A new tag.']),
              new Tag(['tag' => 'Another tag.']),
              new Tag(['tag' => 'latest tag.']),
          ];

          $wiki->aliases = $aliases;
          $wiki->push();
//结果报错
//Parameter mismatch, 
//pattern is a string while replacement is an array

没法用数组;

最后一招,用事务处理

依旧是WikiController@store

如果要使用方法很简单,将要执行的内容放到 DB::transaction(function(){})

         DB::transaction(function(){

            $wiki = Wiki::create(Input::only('title','content'));

             if( !empty( Input::get('tags'))){

                 foreach (Input::get('tags') as $tag){
                     $al[] = new Alias(compact('tag'));
                 }

                 $wiki->aliases()->saveMany($al);
             }

         });

         return 'ok';

如果要更优雅的话,可以运用仓库模式创建一个WikiRepository提供save()方法,将这些复杂的数据操作放进去。控制里面直接使用 WikiRepository::save()即可;

一些高级的事务工具:

        //开启事务
        DB::beginTransaction();
        //回滚
        DB::rollback();
         //提交事务
        DB::commit();

具体的可以参考:老外的文章

        //开启事务
        DB::beginTransaction();

         try{
             $wiki = Wiki::create(Input::only('title','content'));
         }catch (ValidationException $e)
         {
             //如果验证失败,回滚
             DB::rollback();
             return Redirect::to('/form')
                 ->withErrors( $e->getErrors() )
                 ->withInput();

         } catch(\Exception $e)
         {
             //如果异常,回滚
             DB::rollback();
             throw $e;
         }

        try{
            foreach( Input::get('tags') as $tag   ){
                 $t = Tag::create([
                    'tag'=>$tag,
                    'wiki_id' =>$wiki->id,//这里引用了wiki的di
                ]);
            }

        }catch (ValidationException $e)
        {
            DB::rollback();
            return Redirect::to('/form')
                ->withErrors( $e->getErrors() )
                ->withInput();
        } catch(\Exception $e)
        {
            DB::rollback();
            throw $e;
        }

        //提交事务
        DB::commit();

        return $wiki;

问题

  • push() 的用法资料太少,有哪个大神可以清楚的讲讲用法?
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 3

没有大神来解释一下?

6年前 评论

saveMany 本身也是foreach 所以用save好

6年前 评论

问一下编辑的时候怎么知道哪些标签是新增的,哪些是更改的,哪些被删除了?,后端要自己计算吗?

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!