Search code examples
migrationcraftcms

Updating Handles for MatrixBlock "Field Layout Fields" in Craft CMS Migrations


After reading this excellent Medium article, I've been stoked on Migrations in CraftCMS. They've been super useful in importing the same changes across the 10 or so developers who work on our site.

When trying to change the handles on individual fields within block types within matrix blocks (whew) via migrations, I came across a hurdle. The field itself can easily have its "handle" attribute updated, but the columns table for that matrix block's content (matrixcontent_xxxxx) do not get updated to reflect any updated handles. The association between the Matrix Block & its associated Matrix Content table only exists in the info column in the field record for that Matrix Block.

If the Matrix Block's field is updated via the CMS, the change is reflected, so it's gotta be somewhere in Craft's source, but it's not apparent through the Craft CMS Class Reference.

Any ideas?


Edited to add the migration snippet:

    public function safeUp()
    {      
      // Strings for labels, etc.
      $matrix_block_instructions = "instructions";
      $block_label  = "Video";
      $block_handle = "video";
      $field_handle = "video_url";
      $field_label  = "Video URL";
      $field_instructions = "Add a YouTube or Facebook video URL.";

      // Fetching the MatrixBlock fields used on the entries themselves
      $video_gallery_1 = Craft::$app->fields->getFieldByHandle("handle_1");
      $video_gallery_2 = Craft::$app->fields->getFieldByHandle("handle_2");
      $video_gallery_3 = Craft::$app->fields->getFieldByHandle("handle_3");
      $galleries = [$video_gallery_1, $video_gallery_2, $video_gallery_3];


      foreach( $galleries as $gallery ) {
        // Fetching the record for this specific MatrixBlock field.
        $gallery_record = \craft\records\Field::find()->where(
          ['id' => $gallery->id]
        )->one();

        // Fetching the record for this specific MatrixBlockType
        $gallery_block_id = $gallery->getBlockTypes()[0]->id;
        $gallery_block = \craft\records\MatrixBlockType::find()->where(
          ['id' => $gallery_block_id]
        )->one();

        // Assigning standard labels for the MatrixBlockType
        $gallery_block->name   = $block_label;
        $gallery_block->handle = $block_handle;
        $gallery_block->update();

        // Getting the specific ... 1 ... field to edit
        $field_group   = \craft\records\FieldLayout::find()->where(
          ['id' => $gallery_block->fieldLayoutId]
        )->one();
        $field_layout_field = $field_group->fields[0];
        $field = $field_layout_field->field;
        $field = \craft\records\Field::find()->where(
          ['id' => $field->id]
        )->one();

        // Assigning standard labels for the Label
        $field->name         = $field_label;
        $field->handle       = $field_handle;
        $field->instructions = $field_instructions;
        $field->update();

        // Updating the MatrixBlock record with new instructions
        $gallery_record->refresh();
        $gallery_record->instructions = $matrix_block_instructions;
        $gallery_record->update();
}

Solution

  • OK, so my apologies if anyone was stoked on figuring this out, but my approach above was kind of a crazy person's approach, but I've found my own solution.

    The key here is that I should have been interacting with craft\fields\MatrixBlock and the craft\fields\PlainText objects, not craft\records\Field objects. There's a method within \craft\services\Fields for saving the field that requires a FieldInterface implemented. This is actually the default classes returned, and I was making more work for myself in the code!

    Within that foreach loop, this worked out:

            // Fetching the record for this specific MatrixBlock field.
            $gallery->instructions = $matrix_block_instructions;
    
            // Update the MatrixBlockType
            $gallery_block_id = $gallery->getBlockTypes()[0]->id;
            $gallery_block = \craft\records\MatrixBlockType::find()->where(
              ['id' => $gallery_block_id]
            )->one();
            $gallery_block->name   = $block_label;
            $gallery_block->handle = $block_handle;
            $gallery_block->update();
    
            // Update the actual field.
            $field = $gallery->blockTypeFields[0];
            $field->name         = $field_label;
            $field->handle       = $field_handle;
            $field->instructions = $field_instructions;
    
            Craft::$app->getFields()->saveField( $field );
            Craft::$app->getFields()->saveField( $gallery );
    

    Thanks for looking at this, and sorry for being a crazy person.