Jump to content

Migrations


LostKobrakai

Recommended Posts

  • 2 months later...

I'm making extensive use of migrations for a project and had a question regarding writing migrations for matrix fields.

When creating a new FieldtypeRepeaterMatrix field, I usually need to create two migrations. The first actually creates the blank matrix field, and then the second populates it with types/fields. This is because the template associated with the matrix is not actually created when the initial save() method is called when creating the field. Does anyone know if it is possible to force PW to create this template via the API?

Wanted to also say how useful this module is. Would love to see migrations built into the core one day as it's totally changed how we use PW. I've been thinking about submitting a pull request with some new migration classes I've been building, would be great to have some reusable templates for complex situations like this.

EDIT: I just realized I could probably just create the template as I would any other template on the initial field setup. But if there is an alternative save() method that would let PW handle the template creation that would still seem easier!

  • Like 2
Link to comment
Share on other sites

Replying to myself here in case anyone else was looking for a similar migration. This seems to work for setting up repeater fields via a single migration file. Not sure if the method I've used for generating the repeater template is sensible but it will do for now:

 

<?php

class Migration_2017_10_01_10_10_10 extends FieldMigration {

	public static $description = "<b>+FIELD</b> Custom Matrix (custom_matrix)";

	protected function getFieldName(){ return 'custom_matrix'; }

	protected function getFieldType(){ return 'FieldtypeRepeaterMatrix'; }

	protected function getMatrixTemplateName() {
		return FieldtypeRepeater::templateNamePrefix . $this->getFieldName();
	}

	protected function getMatrixTypes() {
		return [
			[
				'name' => 'foo',
				'label' => 'Foo',
				'head' => '{matrix_label}',
				'fields' => [
					'title' => [
						'label' => 'Title (Optional)',
						'required' => 0,
					],
					'markdown',
				],
			], [
				'name' => 'bar',
				'label' => 'Bar',
				'head' => '{matrix_label}',
				'fields' => [
					'title' => [
						'label' => 'Title (Required)',
						'required' => 0,
					],
					'image',
				],
			],
		];
	}

	protected function addFieldToRepeaterList($matrixField, $field) {
		$repeaterFields = $matrixField->repeaterFields;
		if (!is_array($repeaterFields)) $repeaterFields = [];
		if (!in_array($field->id, $repeaterFields)) {
			$repeaterFields[] = $field->id;
		}
		$matrixField->repeaterFields = $repeaterFields;
	}

	protected function addMatrixType($matrixField, $index, $config) {
		$templateName = $this->getMatrixTemplateName();
		$prefix = 'matrix' . ($index + 1) . '_';

		$matrixField[$prefix.'name'] = $config['name'];
		$matrixField[$prefix.'label'] = $config['label'];
		$matrixField[$prefix.'head'] = $config['head'];
		$matrixField[$prefix.'sort'] = $index;

		$fields = [];
		foreach ($config['fields'] as $key => $property) {
			$field = (!is_numeric($key))
				? $this->fields->get($key)
				: $this->fields->get($property);

			$fields[] = $field->id;
			$this->addFieldToRepeaterList($matrixField, $field);
			$this->insertIntoTemplate($templateName, $field);

			if (!is_numeric($key)) {
				$this->editInTemplateContext($templateName, $field, function(Field $f) use ($property) {
					foreach ($property as $prop => $val) {
						$f->$prop = $val;
					}
				});
			}
		}

		$matrixField[$prefix.'fields'] = $fields;
	}

	protected function fieldSetup(Field $f){
		$f->label = 'Custom Matrix';
		$f->tags = 'matrix';
		$f->required = 1;
		$f->icon = 'fa-bars';

		// Do this to generate a template file for the repeater
		// @TODO Find a better way to do this
		$fieldtype = $f->type;
		$numOldReady = $fieldtype->countOldReadyPages($f);

		// Setup matrix stuff
		$matrixTypes = $this->getMatrixTypes();
		foreach ($matrixTypes as $i => $matrixData) {
			$this->addMatrixType($f, $i, $matrixData);
		}

    // Insert into template
    $template = 'my-template';
		$this->insertIntoTemplate($template, $f);
	}

}

 

  • Like 1
Link to comment
Share on other sites

  • 3 weeks later...

Was about to ask about FiledtypeTable migrations but decided to try myself and it seems to work fine this way, dropping it here in case someone's in need:

 

<?php

class Migration_2017_10_23_12_24_20 extends FieldMigration {

	public static $description = "Add table field";

	protected function getFieldName(){ return 'booking_advanced_fields'; }

	protected function getFieldType(){ return 'Table'; }

	protected function fieldSetup(Field $f){
		$f->label = 'Booking Form Extra Fields';
		$f->collapsed = Inputfield::collapsedNever;
        $this->insertIntoTemplate('home', $f, "booking_advanced", true);
		//Booking advanced is a checkbox in this case, hence the showIf setup to make the table appear if required
        $this->editInTemplateContext('home', $f, function(Field $f) {
            $f->set("showIf","booking_advanced=1");
        });
        $f->set("maxCols", 4);
        
        $f->set("col1name", "column1");
        $f->set("maxCols", 4);
        $f->set("col1type", "text");
        $f->set("col1sort", "1");
        $f->set("col1label", "Column 1 Label");
        $f->set("col1width", 50);
        $f->set("col1settings", "textformatters=TextformatterEntities\nplaceholder=\ndefault=\nmaxLength=2048" );
        
        $f->set("col2name", "column2");
        $f->set("col2type", "text");
        $f->set("col2sort", "2");
        $f->set("col2label", "Column 2 Label");
        $f->set("col2width", 50);
        $f->set("col2settings", "textformatters=TextformatterEntities\nplaceholder=\ndefault=\nmaxLength=2048" );
        $f->save();

        $table_module = wire("modules")->get("FieldtypeTable");

        $table_module->_checkSchema($f, true);
        
	}

}

 

  • Like 2
Link to comment
Share on other sites

  • 6 months later...

@simonsays Something like that:

class Migration_xxxx_xx_xx_xx_xx_xx extends FieldMigration {

	public static $description = "Create field";

	protected function getFieldName(){ return 'field_name'; }

	protected function getFieldType(){ return 'FieldtypeOptions'; }

	protected function fieldSetup(Field $f){
		$f->label = 'Label';
		$f->inputfieldClass = 'InputfieldRadios';
		$options = <<< _END
34
36
38
40
42
44
46
48
_END;
		$f->type->manager->setOptionsString($f, $options, true);
	}
}

 

  • Like 2
Link to comment
Share on other sites

1 hour ago, LostKobrakai said:

@simonsays Something like that:


class Migration_xxxx_xx_xx_xx_xx_xx extends FieldMigration {

	public static $description = "Create field";

	protected function getFieldName(){ return 'field_name'; }

	protected function getFieldType(){ return 'FieldtypeOptions'; }

	protected function fieldSetup(Field $f){
		$f->label = 'Label';
		$f->inputfieldClass = 'InputfieldRadios';
		$options = <<< _END
34
36
38
40
42
44
46
48
_END;
		$f->type->manager->setOptionsString($f, $options, true);
	}
}

 

Awesome, thanks @LostKobrakai! Saved me a lot of effort and I also became slightly smarter in the process ;)

Link to comment
Share on other sites

  • 2 weeks later...

@LostKobrakai

When I try to install module using your migration, extra permission declared for this module in getModuleInfo() is not installed automatically.

When I install module via admin, permission is added OK.

Is this a bug? Or was it done like that intentionally?

Edited by simonsays
Link to comment
Share on other sites

It's certainly not intentional.

https://github.com/LostKobrakai/Migrations/blob/master/classes/ModuleMigration.php

There's what a module migration does. I'd expect that $this->modules->install($name) would also take care about installing any permissions of modules. — I just quickly checked the source and it seems this method should handle that. 

  • Like 2
Link to comment
Share on other sites

On 5/14/2018 at 5:57 PM, LostKobrakai said:

It's certainly not intentional.

https://github.com/LostKobrakai/Migrations/blob/master/classes/ModuleMigration.php

There's what a module migration does. I'd expect that $this->modules->install($name) would also take care about installing any permissions of modules. — I just quickly checked the source and it seems this method should handle that. 

Weird, failed first 2 times, but works perfectly now. Logs did not find anything. Guess, something with my localhost...

Link to comment
Share on other sites

@LostKobrakai

Do you happen to have a good example of adding a repeater field (just the field, no data)?

Found this old thread, which introduces a fairly complex approach, but it is rather old, so I was hoping, that something has changed over the last 4 years.

Looked through FieldtypeRepeater module, but did not find any helpful methods.

Link to comment
Share on other sites

  • 6 months later...

@LostKobrakai

I have this code for repeater migration, however, I noticed something. Not sure if it is a bug or the way it should be.

<?php

class Migration_2018_11_18_00_00_00_RepeaterAccordionField extends FieldMigration {

    public static $description = "Add repeater for accordion";

    protected function getFieldName() {
        return 'repeater_accordion';
    }

    protected function getFieldType() {
        return 'FieldtypeRepeater';
    }

    protected function fieldSetup(Field $f) {

        $f->label = 'Repeater accordion';
        $f->collapsed = Inputfield::collapsedNever;

        $repeaterFieldGroup = new Fieldgroup();
        $repeaterFieldGroup->name = 'repeater_' . $this->getFieldName();

        //Add fields to fieldgroup - add others as necessary
        $repeaterFieldGroup->append($this->fields->get('title'));
        $repeaterFieldGroup->append($this->fields->get('content_columns'));

        $repeaterFieldGroup->save();

        $repeaterTemplate = new Template();
        $repeaterTemplate->name = 'repeater_' . $this->getFieldName();
        $repeaterTemplate->flags = 8;
        $repeaterTemplate->noChildren = 1;
        $repeaterTemplate->noParents = 1;
        $repeaterTemplate->noGlobal = 1;
        $repeaterTemplate->slashUrls = 1;
        $repeaterTemplate->fieldgroup = $repeaterFieldGroup;

        $repeaterTemplate->save();

        $repeaterPage = "for-field-{$f->id}";
        $f->parent_id = $this->pages->get("name=$repeaterPage")->id;
        $f->template_id = $repeaterTemplate->id;
        $f->repeaterReadyItems = 3;
        
        //Add fields to the repeater - add others as necessary
        $f->repeaterFields = $this->fields->get('title');
        $f->repeaterFields = $this->fields->get('content_columns');

        $f->save();
    }

}

If I rollback the migration, it deletes the field. But does not affect the fieldgroup.

So, when I try to run the migration for the second time - it throws me an integrity violation error (originates from fieldgroups table).

Is this the expected behaviour?

Edited by simonsays
specify
Link to comment
Share on other sites

You can look into the FieldMigration class to see what it does on migration/rollback. It should just remove the field itself. If that doesn‘t clean up the fieldgroup than it‘s expected behaviour. You can still override/add to that by putting your own upgrade/downgrade functions like e.g. I did it here: https://github.com/LostKobrakai/MigrationSnippets/blob/master/Reverse_Template_Migration_Type.php

Link to comment
Share on other sites

16 hours ago, LostKobrakai said:

You can look into the FieldMigration class to see what it does on migration/rollback. It should just remove the field itself. If that doesn‘t clean up the fieldgroup than it‘s expected behaviour. You can still override/add to that by putting your own upgrade/downgrade functions like e.g. I did it here: https://github.com/LostKobrakai/MigrationSnippets/blob/master/Reverse_Template_Migration_Type.php

It was just a quick question to confirm, whether repeater field downgrade works out of the box or requires additional extending.

Thanks for the reply.

Link to comment
Share on other sites

  • 1 month later...

As hinted in the topic on RockMigrations I'll deprecate my module in favor of it. I've added noted to github and this topic:

Quote

This module is deprecated in favor of RockMigrations. It'll continue to work and I might fix some smaller incompatibilities if they're reported, but no major development will happen on this anymore.

 

Link to comment
Share on other sites

  • 1 month later...

I've heard from a few people in the last weeks, which seemed not to happy about by decision in regards to this module and especially the suggestion of RockMigrations as an alternative. Therefore I'll leave a few more thought.

The module has been in it's current state for years. The last commit from my side was from Juli 2017. Just like the module continued to work till today it'll likely do in the future. There might be things to change with new PHP versions or PW versions – but maintaining working condition should be a fairly simple job.

Given that I don't have any active projects anymore using the module (to the most part even for processwire in general) I personally can't and won't deal with keeping the module maintained. My suggestion for supporting RockMigration was mostly based on the fact that it's way more actively maintained and I feel it's better to group efforts behind a single project – especially given the processwire community being on the smaller end – but if RockMigration doesn't work for someone and this module does, feel free to use it. I'd be happy for anyone to pick this up and maintain it going forward, it just won't be me.

/cc @Sascha Nos @elabx

  • Like 4
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...