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.