1
votes

I'm creating a new field type that contains a quote, name, company, and an optional headshot.

My code is below

modules/mymodule/src/FieldType/Testimonial.php

<?php

namespace Drupal\dods\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface as StorageDefinition;

/**
 * Plugin implementation of the 'Testimonial' field type.
 *
 * @FieldType(
 *   id = "Testimonial",
 *   label = @Translation("Testimonial"),
 *   description = @Translation("Stores Testimonial Information."),
 *   category = @Translation("Custom"),
 *   default_widget = "TestimonialDefaultWidget",
 *   default_formatter = "TestimonialDefaultFormatter"
 * )
 *
 * Class Testimonial
 * @package Drupal\dods\Plugin\Field\FieldType
 */
class Testimonial extends FieldItemBase
{

/**
 * Field type properties definition.
 *
 * Inside this method we defines all the fields (properties) that our
 * custom field type will have.
 *
 * @param StorageDefinition $storage
 * @return array
 */
public static function propertyDefinitions(StorageDefinition $storage)
{
    $properties['testimonial_quote'] = DataDefinition::create('string')->setLabel(t('Quote'));
    $properties['testimonial_name'] = DataDefinition::create('string')->setLabel(t('Name'));
    $properties['testimonial_company'] = DataDefinition::create('string')->setLabel(t('Company'));
    $properties['testimonial_headshot'] = DataDefinition::create('integer')->setLabel(t('Headshot'));

    return $properties;
}

/**
 * Field type schema definition.
 *
 * Inside this method we defines the database schema used to store data for
 * our field type.
 * @param StorageDefinition $storage
 * @return array
 */
public static function schema(StorageDefinition $storage)
{
    $schema['columns']['testimonial_quote'] = ['type' => 'text'];
    $schema['columns']['testimonial_name'] = ['type' => 'char', 'length' => 100];
    $schema['columns']['testimonial_company'] = ['type' => 'char', 'length' => 100];
    $schema['columns']['testimonial_headshot'] = ['type' => 'int'];

    return $schema;
}

/**
 * Define when the field type is empty.
 *
 * This method is important and used internally by Drupal. Take a moment
 * to define when the field type must be considered empty.
 *
 * @return bool
 */
public function isEmpty()
{
    return empty($this->get('testimonial_quote')->getValue()) && empty($this->get('testimonial_name')->getValue()) && empty($this->get('testimonial_company')->getValue());
}

}

and my widget looks like this

modules/mymodule/src/Plugin/Field/FieldWidget/TestimonialDefaultWidget.php

<?php

namespace Drupal\dods\Plugin\Field\FieldWidget;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Plugin implementation of the 'TestimonialDefaultWidget' widget.
 *
 * @FieldWidget(
 *   id = "TestimonialDefaultWidget",
 *   label = @Translation("Testimonial Information"),
 *   field_types = {
 *     "Testimonial"
 *   }
 * )
 *
 * Class TestimonialDefaultWidget
 * @package Drupal\dods\Plugin\Field\FieldWidget
 */
class TestimonialDefaultWidget extends WidgetBase
{

/**
 * Define the form for the field type.
 *
 * Inside this method we can define the form used to edit the field type.
 *
 * @param FieldItemListInterface $items
 * @param int $delta
 * @param array $element
 * @param array $form
 * @param FormStateInterface $formState
 * @return array
 */
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $formState)
{
    $element['testimonial_quote'] = [
        '#type' => 'textarea',
        '#title' => t('Quote'),
        '#default_value' => isset($items[$delta]->testimonial_quote) ? $items[$delta]->testimonial_quote : null,
        '#empty_value' => '',
        '#placeholder' => t('Quote'),
    ];
    $element['testimonial_name'] = [
        '#type' => 'textfield',
        '#title' => t('Name'),
        '#default_value' => isset($items[$delta]->testimonial_name) ? $items[$delta]->testimonial_name : null,
        '#empty_value' => '',
        '#placeholder' => t('Name'),
    ];
    $element['testimonial_company'] = [
        '#type' => 'textfield',
        '#title' => t('Company'),
        '#default_value' => isset($items[$delta]->testimonial_company) ? $items[$delta]->testimonial_company : null,
        '#empty_value' => '',
        '#placeholder' => t('Company'),
    ];

    $element['testimonial_headshot'] = array(
        '#type' => 'managed_file',
        '#upload_validators' => array(
            'file_validate_extensions' => array('gif png jpg jpeg'),
            'file_validate_size' => array(25600000),
        ),
        '#required' => false
    );

    return $element;
}

}

It all looksd fine but when ever i try to save anything I get this error

This value should be of the correct primitive type.

Any ideas would be appreciated I'm hitting a brick wall :(

UPDATE

Feels a little hacky but to get this too work I had to do the following

add in formElement()

$element['testimonial_headshot_revision'] = [
        '#type' => 'hidden',
        '#default_value' => isset($items[$delta]->testimonial_headshot) ? $items[$delta]->testimonial_headshot : null
    ];

add in a new function in the same file like so

/**
 * {@inheritdoc}
 */
public function massageFormValues(array $values, array $form, FormStateInterface $form_state)
{
    foreach ($values as &$value) {
        if (count($value['testimonial_headshot'])) {
            foreach ($value['testimonial_headshot'] as $fid) {
                $value['testimonial_headshot'] = $fid;
            }
        } else {
            $value['testimonial_headshot'] = $value['testimonial_headshot_revision'] !== '' ? $value['testimonial_headshot_revision'] : '0';
        }

    }

    return $values;
}
1
Same problem I have faced. - JayKandari

1 Answers

1
votes

That is because you define testimonial_headshot as int but provide a file upload widget. As a tip in general: whenever you program "new" stuff, see if you can find something that's similar to what you're trying to do and then just extend/modify it.

Example: To me, your new field is basically a image field with some extra textfields. So what you could do is look at the ImageItem Field Type and extend it with testimonial_quote etc.

So your new field type could look like this:

class TestimonialItem extends ImageItem

And then:

public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    // get the image item properties
    $properties = parent::propertyDefinitions($field_definition);

    // if you don't need some properties of the parent do this:
    unset($properties['some_property_of_parent']);

    // add your thing
    $properties['testimonial_quote'] = DataDefinition::create('string')
      ->setLabel(t('Quote'))
      ->setDescription(t("This is the quote of the testimonial"));

    // etc.

    return $properties;
}

This is the main idea, do the same for the schema() method and also for the widget class. Grab the parent and extend it.

Hope this helps.