2

I can have many 'settings' fields for a fieldType and they're saved as a serialized string in the database.

Is it possible to have more than one field for the fieldType itself? It looks like there's only one column in craft_content. So I'm not sure you can.

Is the preferred way to create your own table and store multiple field data there? And would that be using prepValueFromPost()?

2
  • You can create multiple fieldTypes in one plugin, each with there own settings.
    – a-am
    Commented Nov 19, 2016 at 20:59
  • I don't want to define 2 fieldTypes. I need 1 fieldType with 2 fields, in this case 2 text fields. Commented Nov 20, 2016 at 2:11

1 Answer 1

3

Nope – a single field can only have a single column in the craft_content table.

If you need your FieldType to save complex data, you have two options:

The easiest approach – by far – is to simply have Craft serialize the data before it's saved to the craft_content table. Simply return AttributeType::Mixed from your FieldType class' defineContentAttribute() method:

public function defineContentAttribute()
{
    return AttributeType::Mixed;
}

With the above, your FieldType's value will be an array. This means that when you save the data, you'll need to use the array syntax for the input fields' name attributes; e.g. if you had two "sub fields" called foo and bar, your input fields should look like this:

<input type="text" name="myFieldHandle[foo]" />
<input type="text" name="myFieldHandle[bar]" />

To output the sub fields in a template, you'd do something like this:

{{ entry.myFieldHandle.foo }}
{{ entry.myFieldHandle.bar }}

The other approach for saving complex values is to have your FieldType use a custom database table. Explaining this in any great detail is impossible without knowing a lot more about your particular use case and a good overview is already given in Ben Parizek's great answer to a similar question – but here's the gist of it:

First, you'll need to create a Record class, containing your custom FieldType's columns (sub fields) and their attribute types. Whenever your plugin is installed, Craft will create custom database tables based on your plugin's Records (which means that if your plugin is already installed, you'll need to uninstall and install it again to actually have Craft create the tables – if this is a publicly available plugin you should probably write a Migration to ensure the table is created whereever the plugin is already installed).

Note that your custom table should probably have foreign keys for the element and field ID's, to create the necessary relations between the field data, the field and the actual element (e.g. entry).

To actually save the data to your custom table, you'll need to add a onAfterElementSave hook to your FieldType class. This hook is run whenever an element with a field using your custom FieldType is saved. The field and element ID's (and the actual data to save), is available via the FieldType's model and element properties, respectively:

public function onAfterElementSave()
{

    $element = $this->element; // The element being saved
    $elementId = $this->element->id; // The ID for the element being saved
    $fieldModel = $this->model; // The field's model
    $fieldHandle = $field->model->handle; // The field's handle
    $data = $this->element->getContent()->getAttribute($fieldHandle); // The data you want to save

    ...

    // Some custom logic to save the data

}

To save the data via the onAfterElementSave hook, you'll most likely need to use the DbCommand class to build a custom query.

In order to populuate your field (i.e. pull the data) for use in templates and the getInputHtml() method, you'll also need to write a custom query. A good place to do this is in your FieldType's prepValue() method, i.e. something like this:

public function prepValue($value) {
    $value = null;
    $fieldId = $this->model->id;
    if ($this->element->id) {
        // Then element has been saved before and there might be existing data – try to pull it
        $value = craft()->db->createCommand()
            ->select('foo, bar')
            ->from('yourcustomdatabasetable')
            ->where([
                'fieldId' => $this->model->id,
                'elementId' => $this->element->id,
            ])
            ->queryAll();
    }
    return $value;
}

Finally, to save some overhead its a good idea to return false from your FieldType class' defineContentAttribute method (this basically tells Craft to not save anything to the craft_content table):

public function defineContentAttribute()
{
    return false;
}

Note that there are other aspects to consider as well, such as offloading the business logic in saving and retrieving the data to a service layer (a good idea), and using Models to pass data between records, the service layer and your templates (a very good idea), but it's a bit out of scope for an SE answer like this. In any case, I hope the above will get you up and running. In any case, if you simply need to two text fields, I'd go with the first approach – it's way easier.

1
  • Thank so much @Mats. That's given me all the information I need. I'll need to run some DB queries on data from 1 of the fields. I'll dive in to a Model. Knowing the correct methods will be a time save as well :) Commented Nov 21, 2016 at 16:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.