title: Yii 2 nested forms
body: <p>Firstly sorry for my bad english!</p>
  
  <p>I'm using the <a href="https://github.com/wbraganca/yii2-dynamicform" rel="nofollow noreferrer">Yii2-dynamicforms extension</a> to nested forms in Yii 2.<br>
  Following <a href="https://github.com/wbraganca/yii2-dynamicform/wiki/Nested-Forms-Example" rel="nofollow noreferrer">this guide</a>, I was able to make Create Action work properly, but Update Action partially.</p>
  
  <p>Let me explain using my case...
  <a href="https://i.stack.imgur.com/oS1nf.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/oS1nf.png" alt="EER Model"></a></p>
  
  <p>I can create a Mission (missao) with many Groups (grupo) and many Slots for each group;<br>
  On Update, I can add some Slots for existing Groups, but when I try to add a new Group and new slots for this new Group, I got an error <code>Undefined offset: 2 // for example</code></p>
  
  <p>The error occurs near this section of my Update Action in controller:  </p>
  
  <pre><code>// get Slots data from POST
  $newSlotIds = [];
  $loadsData['_csrf'] =  Yii::$app-&gt;request-&gt;post()['_csrf'];
  for ($i=0; $i &lt; count($modelsGrupos); $i++) {
      $loadsData['MissaoSlot'] =  Yii::$app-&gt;request-&gt;post()['MissaoSlot'][$i];
      // ERROR POINTS TO THE LINE BELOW
      $modelsSlots[$i] = Model::createMultiple(MissaoSlot::classname(), $modelsSlots[$i], $loadsData);
      Model::loadMultiple($modelsSlots[$i], $loadsData);
      $newSlotIds = array_merge($newSlotIds, ArrayHelper::getColumn($loadsData['MissaoSlot'], 'id'));
   }
  </code></pre>
  
  <p>On Yii log, i see this:    </p>
  
  <pre><code>Failed to set unsafe attribute 'id' in 'app\models\MissaoGrupo'.
  /var/www/html/qg_armapoint/modules/admin/controllers/MissaoController.php (134)
  </code></pre>
  
  <p>The line 134:<br>
  <code>Model::loadMultiple($modelsGrupos, Yii::$app-&gt;request-&gt;post());</code></p>
  
  <p>I have reviewed all my code following <a href="https://github.com/wbraganca/yii2-dynamicform/wiki/Nested-Forms-Example" rel="nofollow noreferrer">his example</a> and didn't find the error.<br>
  Can anyone help me, please?</p>
  
  <p><strong>My Update Action in Controller:</strong></p>
  
  <pre><code>use app\models\Missao;
  use app\models\MissaoGrupo;
  use app\models\MissaoSlot;
  use app\base\Model;
  use yii\helpers\ArrayHelper;
  
  /**
  ........
  */
  public function actionUpdate($id)
  {
      // retrieve existing Missao (mission) data
      $model = $this-&gt;findModel($id);
  
      // retrieve existing Grupos (groups) data
      $oldGrupoIds = MissaoGrupo::find()-&gt;select('id')-&gt;where(['missao_id' =&gt; $id])-&gt;asArray()-&gt;all();
      $oldGrupoIds = ArrayHelper::getColumn($oldGrupoIds,'id');
      $modelsGrupos = MissaoGrupo::findAll(['id' =&gt; $oldGrupoIds]);
      $modelsGrupos = (empty($modelsGrupos)) ? [new MissaoGrupo] : $modelsGrupos;
  
      // retrieve existing Slots data
      $oldSlotIds = [];
      foreach ($modelsGrupos as $i =&gt; $modelGrupo) {
          $oldSlots = MissaoSlot::findAll(['grupo_id' =&gt; $modelGrupo-&gt;id]);
          $modelsSlots[$i] = $oldSlots;
          $oldSlotIds = array_merge($oldSlotIds, ArrayHelper::getColumn($oldSlots, 'id'));
          $modelsSlots[$i] = (empty($modelsSlots[$i])) ? [new MissaoSlot()] : $modelsSlots[$i];
      }
  
      // handle POST
      if ($model-&gt;load(Yii::$app-&gt;request-&gt;post())) {
  
          // get Groups data from POST
          $modelsGrupos = Model::createMultiple(MissaoGrupo::classname(), $modelsGrupos);
          Model::loadMultiple($modelsGrupos, Yii::$app-&gt;request-&gt;post());
          $newGrupoIds = ArrayHelper::getColumn($modelsGrupos, 'id');
  
          // get Slots data from POST
          $newSlotIds = [];
          $loadsData['_csrf'] =  Yii::$app-&gt;request-&gt;post()['_csrf'];
          for ($i=0; $i &lt; count($modelsGrupos); $i++) {
              $loadsData['MissaoSlot'] =  Yii::$app-&gt;request-&gt;post()['MissaoSlot'][$i];
              $modelsSlots[$i] = Model::createMultiple(MissaoSlot::classname(), $modelsSlots[$i], $loadsData);
              Model::loadMultiple($modelsSlots[$i], $loadsData);
              $newSlotIds = array_merge($newSlotIds, ArrayHelper::getColumn($loadsData['MissaoSlot'], 'id'));
          }
  
          // delete removed data
          $delSlotIds = array_diff($oldSlotIds, $newSlotIds);
          if (! empty($delSlotIds)) MissaoSlot::deleteAll(['id' =&gt; $delSlotIds]);
          $delGrupoIds = array_diff($oldGrupoIds, $newGrupoIds);
          if (! empty($delGrupoIds)) MissaoGrupo::deleteAll(['id' =&gt; $delGrupoIds]);
  
          // validate all models
          $valid = $model-&gt;validate();
          $valid = $this-&gt;validaMissao($modelsGrupos, $modelsSlots) &amp;&amp; $valid;
  
          if ($valid) { 
              if ($this-&gt;saveMissao($model, $modelsGrupos, $modelsSlots)) {
                  return $this-&gt;redirect(['view', 'id' =&gt; $model-&gt;id]);
              }
          }
      }
  
      return $this-&gt;render('update', [
          'model' =&gt; $model,
          'modelsGrupos' =&gt; $modelsGrupos,
          'modelsSlots' =&gt; $modelsSlots,
      ]);
  }
  
  protected function saveMissao($modelMissao, $modelsGrupos, $modelsSlots)
  {
      $transaction = \Yii::$app-&gt;db-&gt;beginTransaction();
      try {
          if ($go = $modelMissao-&gt;save(false)) {
  
              foreach ($modelsGrupos as $i =&gt; $modelGrupo) {
  
                  $modelGrupo-&gt;missao_id = $modelMissao-&gt;id;
                  if ($go = $modelGrupo-&gt;save(false)) {
  
                      foreach ($modelsSlots[$i] as $j =&gt; $modelSlot) {
  
                          $modelSlot-&gt;grupo_id = $modelGrupo-&gt;id;
                          if (! ($go = $modelSlot-&gt;save(false))) {
                              $transaction-&gt;rollBack();
                              break;
                          }
  
                      }
  
                  }
              }
  
          }
          if ($go) {
              $transaction-&gt;commit();
          }
      } catch (Exception $e) {
          $transaction-&gt;rollBack();
      }
  
      return $go;
  }
  </code></pre>
  
  <p><strong>app\base\Model</strong>    </p>
  
  <p>
  
  <pre><code>namespace app\base;
  
  use Yii;
  use yii\helpers\ArrayHelper;
  
  class Model extends \yii\base\Model
  {
      /**
       * Creates and populates a set of models.
       *
       * @param string $modelClass
       * @param array $multipleModels
       * @return array
       */
      public static function createMultiple($modelClass, $multipleModels = [], $data = null)
      {
          $model    = new $modelClass;
          $formName = $model-&gt;formName();
          $post     = empty($data) ? Yii::$app-&gt;request-&gt;post($formName) : $data[$formName];
          $models   = [];
  
          if (! empty($multipleModels)) {
              $keys = array_keys(ArrayHelper::map($multipleModels, 'id', 'id'));
              $multipleModels = array_combine($keys, $multipleModels);
          }
  
          if ($post &amp;&amp; is_array($post)) {
              foreach ($post as $i =&gt; $item) {
                  if (isset($item['id']) &amp;&amp; !empty($item['id']) &amp;&amp; isset($multipleModels[$item['id']])) {
                      $models[] = $multipleModels[$item['id']];
                  } else {
                      $models[] = new $modelClass;
                  }
              }
          }
  
          unset($model, $formName, $post);
  
          return $models;
      }
  }    
  </code></pre>
  
  <p><strong>The view</strong></p>
  
  <pre><code>&lt;div role="tabpanel" class="tab-pane" id="aba-slots"&gt;
          &lt;br/&gt;
          &lt;div class="panel panel-default"&gt;
              &lt;div class="panel-body"&gt;
                  &lt;?php DynamicFormWidget::begin([
                      'widgetContainer' =&gt; 'dynamicform_wrapper',
                      'widgetBody' =&gt; '.container-grupos',
                      'widgetItem' =&gt; '.grupo',
                      //'limit' =&gt; 4,
                      'min' =&gt; 1,
                      'insertButton' =&gt; '.add-grupo',
                      'deleteButton' =&gt; '.remove-grupo',
                      'model' =&gt; $modelsGrupos[0],
                      'formId' =&gt; 'frm_missao',
                      'formFields' =&gt; [
                          'nome',
                      ],
                  ]); ?&gt;
                  &lt;div class="panel panel-default"&gt;
                      &lt;div class="panel-heading"&gt;Grupos / Slots&lt;/div&gt;
                      &lt;div class="panel-body container-grupos"&gt;
                          &lt;?php foreach ($modelsGrupos as $i =&gt; $modelGrupo): ?&gt;
                          &lt;div class="row grupo"&gt; &lt;!-- Item Grupo - INICIO --&gt;
                              &lt;table class="table"&gt;
                                  &lt;tr&gt;
                                      &lt;th style="width:5%"&gt;#&lt;/th&gt;
                                      &lt;th&gt;Grupo&lt;/th&gt;
                                      &lt;th&gt;Slot&lt;/th&gt;
                                  &lt;/tr&gt;
                                  &lt;tr&gt;
                                      &lt;td&gt;&lt;button class="btn btn-danger remove-grupo"&gt;&lt;i class="fa fa-minus"&gt;&lt;/i&gt;&lt;/button&gt;&lt;/td&gt;
                                      &lt;td class="col-md-4"&gt;
                                          &lt;?php
                                          // necessary for update action.
                                          if (! $modelGrupo-&gt;isNewRecord) {
                                              echo Html::activeHiddenInput($modelGrupo, "[{$i}]id");
                                          }
                                          ?&gt;
                                          &lt;?= $form-&gt;field($modelGrupo, "[{$i}]nome")-&gt;textInput(['maxlength' =&gt; true, 'placeholder' =&gt; 'Nome do Grupo'])-&gt;label(false) ?&gt;
                                      &lt;/td&gt;
                                      &lt;td&gt;&lt;!-- Slots --&gt;
                                          &lt;?php DynamicFormWidget::begin([
                                              'widgetContainer' =&gt; 'dynamicform_inner',
                                              'widgetBody' =&gt; '.container-slots',
                                              'widgetItem' =&gt; '.slot',
                                              //'limit' =&gt; 10,
                                              'min' =&gt; 1,
                                              'insertButton' =&gt; '.add-slot',
                                              'deleteButton' =&gt; '.remove-slot',
                                              'model' =&gt; $modelsSlots[$i][0],
                                              'formId' =&gt; 'frm_missao',
                                              'formFields' =&gt; [
                                                  'nome',
                                              ],
                                          ]); ?&gt;
                                          &lt;table class="table container-slots"&gt;
                                              &lt;?php foreach ($modelsSlots[$i] as $j =&gt; $modelSlot): ?&gt;
                                              &lt;tr class="slot"&gt;
                                                  &lt;td&gt;
                                                      &lt;?php
                                                      // necessary for update action.
                                                      if (! $modelSlot-&gt;isNewRecord) {
                                                          echo Html::activeHiddenInput($modelSlot, "[{$i}][{$j}]id");
                                                      }
                                                      ?&gt;
                                                      &lt;?= $form-&gt;field($modelSlot, "[{$i}][{$j}]nome")-&gt;textInput(['maxlength' =&gt; true, 'placeholder' =&gt; 'Nome do Slot'])-&gt;label(false) ?&gt;
                                                  &lt;/td&gt;
                                                  &lt;td style="width:5%"&gt;&lt;button class="btn btn-danger btn-xs remove-slot"&gt;&lt;i class="fa fa-minus"&gt;&lt;/i&gt;&lt;/button&gt;&lt;/td&gt;
                                              &lt;/tr&gt;
                                              &lt;?php endforeach; ?&gt;
                                              &lt;tfoot&gt;
                                              &lt;tr&gt;
                                                  &lt;td colspan="2"&gt;&lt;button class="btn btn-info btn-xs add-slot"&gt;&lt;i class="fa fa-plus"&gt;&lt;/i&gt; Novo Slot&lt;/button&gt;&lt;/td&gt;
                                              &lt;/tr&gt;
                                              &lt;/tfoot&gt;
                                          &lt;/table&gt;
                                          &lt;?php DynamicFormWidget::end(); ?&gt;
                                      &lt;/td&gt;&lt;!--Slots - fim --&gt;
                                  &lt;/tr&gt;
                              &lt;/table&gt;
                          &lt;/div&gt; &lt;!-- Item Grupo - FIM --&gt;
                          &lt;?php endforeach; ?&gt;
                      &lt;/div&gt;
                      &lt;div class="panel-footer"&gt;
                          &lt;button class="btn btn-primary btn-xs add-grupo"&gt;&lt;i class="fa fa-plus"&gt;&lt;/i&gt; Novo Grupo&lt;/button&gt;
                      &lt;/div&gt;
                  &lt;/div&gt;
                  &lt;?php DynamicFormWidget::end(); ?&gt;
              &lt;/div&gt;
          &lt;/div&gt;
      &lt;/div&gt;
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/LyPKc.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/LyPKc.png" alt="enter image description here"></a></p>
  
  <p>Thank You!</p>
  
  <hr>
  
  <h1>EDIT: Added MissaoGrupo Model</h1>
  
  <p>
  
  <pre><code>namespace app\models;
  
  use Yii;
  
  /**
   * This is the model class for table "{{%missao_grupo}}".
   *
   * @property integer $id
   * @property string $nome
   * @property integer $missao_id
   *
   * @property Missao $missao
   * @property MissaoSlot[] $missaoSlots
   */
  class MissaoGrupo extends \yii\db\ActiveRecord
  {
      /**
       * @inheritdoc
       */
      public static function tableName()
      {
          return '{{%missao_grupo}}';
      }
  
      /**
       * @inheritdoc
       */
      public function rules()
      {
          return [
              [['nome'], 'required'],
              [['missao_id'], 'integer'],
              [['nome'], 'string', 'max' =&gt; 255]
          ];
      }
  
      /**
       * @inheritdoc
       */
      public function attributeLabels()
      {
          return [
              'id' =&gt; 'ID',
              'nome' =&gt; 'Grupo',
              'missao_id' =&gt; 'Missao ID',
          ];
      }
  
      /**
       * @return \yii\db\ActiveQuery
       */
      public function getMissao()
      {
          return $this-&gt;hasOne(Missao::className(), ['id' =&gt; 'missao_id']);
      }
  
      /**
       * @return \yii\db\ActiveQuery
       */
      public function getMissaoSlots()
      {
          return $this-&gt;hasMany(MissaoSlot::className(), ['grupo_id' =&gt; 'id']);
      }
  }
  </code></pre>