2

I'm trying to create a two-way relationship between my custom entity (package_date) and a content type (package) - the relationship is between a specific field on package and package_date (i.e. it's not an entity reference).

I've got as far being able to add a relationship from a view for package_date, but adding this only allows me to add the linked field to the view, not any other fields from package_date - and on saving the view, I also get the following error:

No entity type for field field_package_id on view package_dates in 
Drupal\views\Plugin\views\HandlerBase->getEntityType() 
(line 697 of core\modules\views\src\Plugin\views\HandlerBase.php)

My package_date entity definition looks like this:

namespace Drupal\my_module\Entity;

use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityChangedTrait;

/**
 * Defines the PackageDate entity.
 *
 * @ingroup package_date
 *
 * @ContentEntityType(
 *   id = "package_date",
 *   label = @Translation("Package Date"),
 *   handlers = {
 *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
 *     "views_data" = "Drupal\views\EntityViewsData",
 *   },
 *   base_table = "package_date",
 *   entity_keys = {
 *     "id" = "id",
 *     "uuid" = "uuid",
 *   },
 *   admin_permission = "administer package_date entity",
 *   fieldable = true,
 * )
 */

class PackageDate extends ContentEntityBase {

    use EntityChangedTrait;

  /**
   * {@inheritdoc}
   *
   * Define the field properties here.
   *
   */
  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {

        // Standard field, used as unique if primary index.
        $fields['id'] = BaseFieldDefinition::create('integer')
            ->setLabel(t('ID'))
            ->setDescription(t('The ID of the Term entity.'))
            ->setReadOnly(TRUE);

        // Standard field, unique outside of the scope of the current project.
        $fields['uuid'] = BaseFieldDefinition::create('uuid')
            ->setLabel(t('UUID'))
            ->setDescription(t('The UUID of the Contact entity.'))
            ->setReadOnly(TRUE);

    $fields['package_id'] = BaseFieldDefinition::create('integer')
      ->setLabel(t('Package ID'))
      ->setDisplayOptions('view', array(
        'label' => 'above',
        'type' => 'integer',
        'weight' => 0,
      ))
      ->setDisplayConfigurable('view', TRUE);

        $fields['start_datetime'] = BaseFieldDefinition::create('datetime')
        ->setLabel('Package Start Time')
        ->setDisplayOptions('view', array(
            'label' => 'above',
                'type' => 'datetime',
                'weight' => 1,
        ))
        ->setSetting('datetime_type', 'datetime')
        ->setSetting('timezone_override', '');

        $fields['end_datetime'] = BaseFieldDefinition::create('datetime')
        ->setLabel('Package End Time')
        ->setDisplayOptions('view', array(
            'label' => 'above',
                'type' => 'datetime',
                'weight' => 2,
        ))
        ->setSetting('datetime_type', 'datetime')
        ->setSetting('timezone_override', '');

    $fields['price'] = BaseFieldDefinition::create('decimal')
      ->setLabel(t('Package Price'))
      ->setSettings(array(
          'precision' => 10,
          'scale' => 4,
          'decimal_separator' => '.',
      ))
      ->setDisplayOptions('view', array(
        'label' => 'above',
        'type' => 'decimal',
        'weight' => 0,
      ))
      ->setDisplayConfigurable('view', TRUE);

    $fields['capacity'] = BaseFieldDefinition::create('integer')
      ->setLabel(t('Capacity'))
      ->setDisplayOptions('view', array(
        'label' => 'above',
        'type' => 'integer',
        'weight' => 0,
      ))
      ->setDisplayConfigurable('view', TRUE);

    $fields['availability'] = BaseFieldDefinition::create('integer')
      ->setLabel(t('Availability'))
      ->setDisplayOptions('view', array(
        'label' => 'above',
        'type' => 'integer',
        'weight' => 0,
      ))
      ->setDisplayConfigurable('view', TRUE);

    $fields['created'] = BaseFieldDefinition::create('created')
      ->setLabel(t('Created'))
      ->setDescription(t('The time that the entity was created.'));

    $fields['changed'] = BaseFieldDefinition::create('changed')
      ->setLabel(t('Changed'))
      ->setDescription(t('The time that the entity was last edited.'));

    return $fields;
  }
}

My relationship definition looks like this:

function my_module_views_data() {
  $data['package_date']['table']['group'] = t('Package Dates');

  $data['package_date']['table']['join'] = array(
    // Index this array by the table name to which this table refers.
    // 'left_field' is the primary key in the referenced table.
    // 'field' is the foreign key in this table.
    'node__field_package_id' => array(
      'left_field' => 'field_package_id_value',
      'field' => 'package_id',
    ),
  );

  $data['package_date']['package_id'] = array(
    'title' => t('Package ID'),
    'relationship' => array(
      'base' => 'node__field_package_id', // The name of the table to join with.
      'base field' => 'field_package_id_value', // The name of the field on the joined table.
      // 'field' => 'package_id' -- see hook_views_data_alter(); not needed here.
      'handler' => 'views_handler_relationship',
      'label' => t('Relationship between Package and Package Dates'),
      'title' => t('Package to Package Dates Relationship'),
      'id' => 'standard',
    ),
  );

  return $data;
}

My view definition, which shows the package_date entity looks like:

langcode: en
status: true
dependencies:
  config:
    - system.menu.admin
  module:
    - datetime
    - pve
    - user
id: _package_dates
label: 'Package Dates'
module: views
description: 'Display current cache of  Package Dates'
tag: ''
base_table: _package_date
base_field: id
core: 8.x
display:
  default:
    display_plugin: default
    id: default
    display_title: Master
    position: 0
    display_options:
      access:
        type: perm
        options:
          perm: 'access site reports'
      cache:
        type: tag
        options: {  }
      query:
        type: views_query
        options:
          disable_sql_rewrite: false
          distinct: false
          replica: false
          query_comment: ''
          query_tags: {  }
      exposed_form:
        type: basic
        options:
          submit_button: Apply
          reset_button: false
          reset_button_label: Reset
          exposed_sorts_label: 'Sort by'
          expose_sort_order: true
          sort_asc_label: Asc
          sort_desc_label: Desc
      pager:
        type: full
        options:
          items_per_page: 50
          offset: 0
          id: 0
          total_pages: null
          tags:
            previous: ‹‹
            next: ››
            first: '« First'
            last: 'Last »'
          expose:
            items_per_page: false
            items_per_page_label: 'Items per page'
            items_per_page_options: '5, 10, 25, 50'
            items_per_page_options_all: false
            items_per_page_options_all_label: '- All -'
            offset: false
            offset_label: Offset
          quantity: 9
      style:
        type: table
      row:
        type: fields
      fields:
        _package_id:
          table: _package_date
          field: _package_id
          id: _package_id
          entity_type: null
          entity_field: _package_id
          plugin_id: field
          relationship: none
          group_type: group
          admin_label: ''
          label: 'Package ID'
          exclude: false
          alter:
            alter_text: false
            text: ''
            make_link: false
            path: ''
            absolute: false
            external: false
            replace_spaces: false
            path_case: none
            trim_whitespace: false
            alt: ''
            rel: ''
            link_class: ''
            prefix: ''
            suffix: ''
            target: ''
            nl2br: false
            max_length: 0
            word_boundary: true
            ellipsis: true
            more_link: false
            more_link_text: ''
            more_link_path: ''
            strip_tags: false
            trim: false
            preserve_tags: ''
            html: false
          element_type: ''
          element_class: ''
          element_label_type: ''
          element_label_class: ''
          element_label_colon: true
          element_wrapper_type: ''
          element_wrapper_class: ''
          element_default_classes: true
          empty: ''
          hide_empty: false
          empty_zero: false
          hide_alter_empty: true
          click_sort_column: value
          type: number_integer
          settings: {  }
          group_column: value
          group_columns: {  }
          group_rows: true
          delta_limit: 0
          delta_offset: 0
          delta_reversed: false
          delta_first_last: false
          multi_type: separator
          separator: ', '
          field_api_classes: false
        start_datetime:
          id: start_datetime
          table: _package_date
          field: start_datetime
          relationship: none
          group_type: group
          admin_label: ''
          label: 'Package Start Time'
          exclude: false
          alter:
            alter_text: false
            text: ''
            make_link: false
            path: ''
            absolute: false
            external: false
            replace_spaces: false
            path_case: none
            trim_whitespace: false
            alt: ''
            rel: ''
            link_class: ''
            prefix: ''
            suffix: ''
            target: ''
            nl2br: false
            max_length: 0
            word_boundary: true
            ellipsis: true
            more_link: false
            more_link_text: ''
            more_link_path: ''
            strip_tags: false
            trim: false
            preserve_tags: ''
            html: false
          element_type: ''
          element_class: ''
          element_label_type: ''
          element_label_class: ''
          element_label_colon: true
          element_wrapper_type: ''
          element_wrapper_class: ''
          element_default_classes: true
          empty: ''
          hide_empty: false
          empty_zero: false
          hide_alter_empty: true
          click_sort_column: value
          type: datetime_default
          settings:
            timezone_override: ''
            format_type: medium
          group_column: value
          group_columns: {  }
          group_rows: true
          delta_limit: 0
          delta_offset: 0
          delta_reversed: false
          delta_first_last: false
          multi_type: separator
          separator: ', '
          field_api_classes: false
          entity_type: _package_date
          entity_field: start_datetime
          plugin_id: field
        end_datetime:
          id: end_datetime
          table: _package_date
          field: end_datetime
          relationship: none
          group_type: group
          admin_label: ''
          label: 'Package End Time'
          exclude: false
          alter:
            alter_text: false
            text: ''
            make_link: false
            path: ''
            absolute: false
            external: false
            replace_spaces: false
            path_case: none
            trim_whitespace: false
            alt: ''
            rel: ''
            link_class: ''
            prefix: ''
            suffix: ''
            target: ''
            nl2br: false
            max_length: 0
            word_boundary: true
            ellipsis: true
            more_link: false
            more_link_text: ''
            more_link_path: ''
            strip_tags: false
            trim: false
            preserve_tags: ''
            html: false
          element_type: ''
          element_class: ''
          element_label_type: ''
          element_label_class: ''
          element_label_colon: true
          element_wrapper_type: ''
          element_wrapper_class: ''
          element_default_classes: true
          empty: ''
          hide_empty: false
          empty_zero: false
          hide_alter_empty: true
          click_sort_column: value
          type: datetime_default
          settings:
            timezone_override: ''
            format_type: medium
          group_column: value
          group_columns: {  }
          group_rows: true
          delta_limit: 0
          delta_offset: 0
          delta_reversed: false
          delta_first_last: false
          multi_type: separator
          separator: ', '
          field_api_classes: false
          entity_type: _package_date
          entity_field: end_datetime
          plugin_id: field
        availability:
          id: availability
          table: _package_date
          field: availability
          relationship: none
          group_type: group
          admin_label: ''
          label: Availability
          exclude: false
          alter:
            alter_text: false
            text: ''
            make_link: false
            path: ''
            absolute: false
            external: false
            replace_spaces: false
            path_case: none
            trim_whitespace: false
            alt: ''
            rel: ''
            link_class: ''
            prefix: ''
            suffix: ''
            target: ''
            nl2br: false
            max_length: 0
            word_boundary: true
            ellipsis: true
            more_link: false
            more_link_text: ''
            more_link_path: ''
            strip_tags: false
            trim: false
            preserve_tags: ''
            html: false
          element_type: ''
          element_class: ''
          element_label_type: ''
          element_label_class: ''
          element_label_colon: true
          element_wrapper_type: ''
          element_wrapper_class: ''
          element_default_classes: true
          empty: ''
          hide_empty: false
          empty_zero: false
          hide_alter_empty: true
          click_sort_column: value
          type: number_integer
          settings:
            thousand_separator: ''
            prefix_suffix: true
          group_column: value
          group_columns: {  }
          group_rows: true
          delta_limit: 0
          delta_offset: 0
          delta_reversed: false
          delta_first_last: false
          multi_type: separator
          separator: ', '
          field_api_classes: false
          entity_type: _package_date
          entity_field: availability
          plugin_id: field
        capacity:
          id: capacity
          table: _package_date
          field: capacity
          relationship: none
          group_type: group
          admin_label: ''
          label: Capacity
          exclude: false
          alter:
            alter_text: false
            text: ''
            make_link: false
            path: ''
            absolute: false
            external: false
            replace_spaces: false
            path_case: none
            trim_whitespace: false
            alt: ''
            rel: ''
            link_class: ''
            prefix: ''
            suffix: ''
            target: ''
            nl2br: false
            max_length: 0
            word_boundary: true
            ellipsis: true
            more_link: false
            more_link_text: ''
            more_link_path: ''
            strip_tags: false
            trim: false
            preserve_tags: ''
            html: false
          element_type: ''
          element_class: ''
          element_label_type: ''
          element_label_class: ''
          element_label_colon: true
          element_wrapper_type: ''
          element_wrapper_class: ''
          element_default_classes: true
          empty: ''
          hide_empty: false
          empty_zero: false
          hide_alter_empty: true
          click_sort_column: value
          type: number_integer
          settings:
            thousand_separator: ''
            prefix_suffix: true
          group_column: value
          group_columns: {  }
          group_rows: true
          delta_limit: 0
          delta_offset: 0
          delta_reversed: false
          delta_first_last: false
          multi_type: separator
          separator: ', '
          field_api_classes: false
          entity_type: _package_date
          entity_field: capacity
          plugin_id: field
        created:
          id: created
          table: _package_date
          field: created
          relationship: none
          group_type: group
          admin_label: ''
          label: Created
          exclude: false
          alter:
            alter_text: false
            text: ''
            make_link: false
            path: ''
            absolute: false
            external: false
            replace_spaces: false
            path_case: none
            trim_whitespace: false
            alt: ''
            rel: ''
            link_class: ''
            prefix: ''
            suffix: ''
            target: ''
            nl2br: false
            max_length: 0
            word_boundary: true
            ellipsis: true
            more_link: false
            more_link_text: ''
            more_link_path: ''
            strip_tags: false
            trim: false
            preserve_tags: ''
            html: false
          element_type: ''
          element_class: ''
          element_label_type: ''
          element_label_class: ''
          element_label_colon: true
          element_wrapper_type: ''
          element_wrapper_class: ''
          element_default_classes: true
          empty: ''
          hide_empty: false
          empty_zero: false
          hide_alter_empty: true
          click_sort_column: value
          type: timestamp
          settings:
            date_format: medium
            custom_date_format: ''
            timezone: ''
          group_column: value
          group_columns: {  }
          group_rows: true
          delta_limit: 0
          delta_offset: 0
          delta_reversed: false
          delta_first_last: false
          multi_type: separator
          separator: ', '
          field_api_classes: false
          entity_type: _package_date
          entity_field: created
          plugin_id: field
        changed:
          id: changed
          table: _package_date
          field: changed
          relationship: none
          group_type: group
          admin_label: ''
          label: Changed
          exclude: false
          alter:
            alter_text: false
            text: ''
            make_link: false
            path: ''
            absolute: false
            external: false
            replace_spaces: false
            path_case: none
            trim_whitespace: false
            alt: ''
            rel: ''
            link_class: ''
            prefix: ''
            suffix: ''
            target: ''
            nl2br: false
            max_length: 0
            word_boundary: true
            ellipsis: true
            more_link: false
            more_link_text: ''
            more_link_path: ''
            strip_tags: false
            trim: false
            preserve_tags: ''
            html: false
          element_type: ''
          element_class: ''
          element_label_type: ''
          element_label_class: ''
          element_label_colon: true
          element_wrapper_type: ''
          element_wrapper_class: ''
          element_default_classes: true
          empty: ''
          hide_empty: false
          empty_zero: false
          hide_alter_empty: true
          click_sort_column: value
          type: timestamp
          settings:
            date_format: medium
            custom_date_format: ''
            timezone: ''
          group_column: value
          group_columns: {  }
          group_rows: true
          delta_limit: 0
          delta_offset: 0
          delta_reversed: false
          delta_first_last: false
          multi_type: separator
          separator: ', '
          field_api_classes: false
          entity_type: _package_date
          entity_field: changed
          plugin_id: field
      filters: {  }
      sorts: {  }
      title: 'Package Dates'
      header: {  }
      footer: {  }
      empty:
        area_text_custom:
          id: area_text_custom
          table: views
          field: area_text_custom
          relationship: none
          group_type: group
          admin_label: ''
          empty: true
          tokenize: false
          content: '<p>There is currently no data to display.</p>'
          plugin_id: text_custom
      relationships:
        _package_id:
          id: _package_id
          table: _package_date
          field: _package_id
          relationship: none
          group_type: group
          admin_label: 'Relationship between Package and Package Dates'
          required: false
          entity_type: _package_date
          entity_field: _package_id
          plugin_id: standard
      arguments: {  }
      display_extenders: {  }
    cache_metadata:
      max-age: 0
      contexts:
        - 'languages:language_content'
        - 'languages:language_interface'
        - url.query_args
        - user.permissions
      tags: {  }
  _package_dates_report:
    display_plugin: page
    id: _package_dates_report
    display_title: 'Package Dates'
    position: 1
    display_options:
      display_extenders: {  }
      path: admin/-package-dates
      menu:
        type: normal
        title: 'Package Dates'
        description: ''
        expanded: false
        parent: system.admin_reports
        weight: 0
        context: '0'
        menu_name: admin
      display_description: ''
    cache_metadata:
      max-age: 0
      contexts:
        - 'languages:language_content'
        - 'languages:language_interface'
        - url.query_args
        - user.permissions
      tags: {  }

On the above, to me it looks like the relationships property is the wrong way around, but I'm not sure.

I'd also like to be able to create a view for the package content type, and link this to the package_date entity - when trying to though, I don't have a relationship I can add.

I expect I'm missing one or more things to get this working, but I'm not sure what - at the very least, I think I need an inverse of the relationship defining, but I'm pretty sure that's not the only issue.

Update Here is what I have currently, after following Neograph734's suggestions:

    function pve_views_data_alter(&$data) {
        $data['optimo_package_date']['optimo_package_id'] = array(
        'title' => t('Optimo Package ID'),
        'relationship' => array(
          'base' => 'node_field_data', // The name of the table to join with.
          'base field' => 'field_optimo_package_id', // The name of the field on the joined table.
          // 'field' => 'optimo_package_id' -- see hook_views_data_alter(); not needed here.
          'handler' => 'views_handler_relationship',
          'label' => t('Relationship between Event Package and Optimo Package Dates'),
          'title' => t('Event Package to Optimo Package Dates Relationship'),
          'field' => [
            // ID of field handler plugin to use.
            'id' => 'numeric',
          ],
          'sort' => [
            // ID of sort handler plugin to use.
            'id' => 'standard',
          ],
          'filter' => [
            // ID of filter handler plugin to use.
            'id' => 'numeric',
          ],
          'id' => 'standard'
        ),
      );
    }

I'm only interested in getting data into my view, via relationship, not with attaching data automatically - so I don't think I need the join part I originally had.

To clarify what the fields are - the field_optimo_package_id is a multiple value field, but it is not an entity reference - it simply holds multiple integer values. The optimo_package_id is an integer field in my custom entity.

  • 2
    Keen to know how this would work, I have posted similar questions, but no answers on those yet. – amit Apr 18 '17 at 22:27
  • 1
    Since optimo_package_date is your own custom entity, you can use hook_views_data instead of hook_views_data_alter. hook_views_data_alter would only be required to change node's implementation of hook_views_data (eg. to allow a node to reference your custom entity). In a hook_views_data_alter implementation you have to specify the field (which is currently missing). If I get it right the date entity contains a reference to the node. In that case the 'base field' should be the identifier of the node, which is nid. Otherwise the reference structure is not clear to me. – Neograph734 Apr 25 '17 at 14:17
  • 1
    I changed back to using hook_views_data but no change to the behaviour unfortunately. The relationship between my package_date entity and the package content type is represented by an integer field in package_date and a multiple value integer field in package - as stated in my question, it's not an entity relationship. – user34185 Apr 25 '17 at 14:33
  • 1
    @BrynJ, apologies I missed that. I assumed you had one integer field referring to a node id. I have no idea how to approach this with two fields unfortunately... – Neograph734 Apr 25 '17 at 17:51
1
+25

You have actually provided both relationships correctly;

  • $data['package_date']['table']['join'] provides an automatic reference from the external entity to this one; when the other entity is loaded, this one is always available.

  • $data['package_date']['package_id']['relationship'] provides a relationship that needs to be added manually via the views UI.

(If you want them to behave similar, you would have to use hook_views_data_alter()) to add one of the above definitions (automatic or manual) to the node entity.)

What I think your problem here (mostly based on Drupal 7 logic), is that you are referring the wrong table. When looking at the complete list of invocations of hook_views_data() and for instance look at book_views_data(), you will see that all those implementations look like this:

$data['book']['table']['join'] = [
  'node_field_data' => [
    'left_field' => 'nid',
    'field' => 'nid',
  ],
];

All those joins use the node_field_data table, not the field specific table node__field_package_id you are using:

$data['package_date']['table']['join'] = array(
  // Index this array by the table name to which this table refers.
  // 'left_field' is the primary key in the referenced table.
  // 'field' is the foreign key in this table.
  'node__field_package_id' => array(
    'left_field' => 'field_package_id_value',
    'field' => 'package_id',
  ),
);

By providing the value for left_field, you are already telling Views to which field you want to use, you you should not access that field directly. I suppose it should work once you change node__field_package_id to node_field_data.

  • 1
    I made the changes as suggested, but although it changed the behaviour - I no longer get the error on saving, after adding the desired fields (and I can actually see all fields that I might need to select) - it still doesn't work. As soon as the relationship is added, I get no data in my view - this is despite not requiring the relationship for the result. Any ideas? – user34185 Apr 25 '17 at 13:15
  • 1
    Just checking again, field_package_id_value in the join should not be a value; so not use 5 for package 5. It should be the name of the database column. I am sorry I missed that the previous time. It probably needs to be field_package_id and the package id probably just nid. If that does not work, please edit the new code to the bottom of your question so I have better insight in what you have so far. – Neograph734 Apr 25 '17 at 13:22
  • 1
    I've edited as suggested but same result - on trying to open the view directly (rather than previewing) the following error is displayed: QLSTATE[42S22]: Column not found: 1054 Unknown column 'package_date.Array' in 'on clause': SELECT COUNT(*) AS expression FROM (SELECT 1 AS expression FROM {package_date} package_date LEFT JOIN {node_field_data} node_field_data_package_date ON package_date.Array = node_field_data_package_date.field_package_id – user34185 Apr 25 '17 at 13:56
  • 1
    That would imply a syntax error in your relationship definition. Edit the question and append the relationships as they are now to the bottom. Ideally also expose the database structure of your custom entity if you can. – Neograph734 Apr 25 '17 at 13:59
  • 1
    I've added some more info into the question, thanks. – user34185 Apr 25 '17 at 14:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy