Problem/Motivation

EntityViewsData has the following code around line 252:

    // Load all typed data definitions of all fields. This should cover each of
    // the entity base, revision, data tables.
    $field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityType->id());
    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
    if ($table_mapping = $this->storage->getTableMapping($field_definitions)) {

The problem is that it asks for FieldDefinitionInterface[] and immediately treats it as FieldStorageDefinitionInterface[] by calling $this->storage->getTableMapping() which expects the latter. Further down the code it correctly treats the retrieved data as FDI[] when it calls $this->mapFieldDefinition().

So why is this a problem? Because it is only working by happy accident. We know that we are getting BaseFieldDefinition objects, which we know implement both FDI and FSDI. The problem becomes clear when we try to patch EntityViewsData to actually take care of bundle fields as well, as demonstrated in #2898635: bundleFieldDefinitions() are not added in EntityViewsData

Consider the following code be inserted right after the setting of $field_definitions:

// Add any bundle fields defined in code.
if (isset($this->entityType->getKeys()['bundle'])) {
  $bundle_field_definitions = [];
  foreach ($this->entityManager->getBundleInfo($this->entityType->id()) as $bundle_id => $bundle_info) {
    $bundle_field_definitions += $this->entityManager->getFieldDefinitions($this->entityType->id(), $bundle_id);
  }
  $field_definitions += $bundle_field_definitions;
}

Now we might get BaseFieldDefinitions from the above, but we could also get FieldConfig items. Which is perfectly fine because both getFieldDefinitions() and getBaseFieldDefinitions() should return FDI[] and then be treated as such. Except, because we treat the returned data as both FDI[] and FSDI[], the above addition will lead to a crash because FieldConfig (among others) does not implement FSDI.

Proposed resolution

Properly treat the base field definitions as FDI[] only and rewrite EntityViewsData to properly retrieve the FSDI when needed.

Remaining tasks

  • Write a patch
  • Figure out whether this breaks BC

API changes

None, unless we have to change function signatures to ask for the right interface.

Data model changes

None.

Issue priority explanation

Marked as major because it is both a massive code smell and it blocks a useful patch that would allow bundle fields to show up in views.

Issue fork drupal-2930736

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

kristiaanvandeneynde created an issue. See original summary.

kristiaanvandeneynde’s picture

Issue summary: View changes
kristiaanvandeneynde’s picture

kristiaanvandeneynde’s picture

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

joachim’s picture

Title: EntityViewsData assumes BaseFielDefinitions where it should use FieldDefinitionInterface » EntityViewsData assumes BaseFieldDefinitions where it should use FieldDefinitionInterface

It gets messier: further down the code, we stop assuming that we have a FSDI, and go off to load them all:

  protected function mapFieldDefinition($table, $field_name, FieldDefinitionInterface $field_definition, TableMappingInterface $table_mapping, &$table_data) {
    // Create a dummy instance to retrieve property definitions.
    $field_column_mapping = $table_mapping->getColumnNames($field_name);
    $field_schema = $this->getFieldStorageDefinitions()[$field_name]->getSchema();

We have a $field_definition at that point, so why not just call getFieldStorageDefinition() on it?

joachim’s picture

Status: Active » Needs review
StatusFileSize
new4.08 KB

Here's a go at a patch.

Given an FDI, we can get its FSDI with getFieldStorageDefinition().

For BaseFieldDefinitions, since they implement both, that method trivially returns the same object, but that's ok.

Status: Needs review » Needs work
joachim’s picture

Invalid argument supplied for foreach()

/var/www/html/vendor/symfony/phpunit-bridge/DeprecationErrorHandler.php:105
/var/www/html/core/modules/views/src/EntityViewsData.php:267
/var/www/html/core/modules/views/tests/src/Unit/EntityViewsDataTest.php:194

How can we have something that's not an array here????

    $field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityType->id());

    $field_storage_definitions = [];
    foreach ($field_definitions as $field_name => $field_definition) {
joachim’s picture

Status: Needs work » Needs review
StatusFileSize
new4.6 KB

Trying a quick fix in the entity field manager -- should be a separate issue if it's indeed the problem.

Status: Needs review » Needs work
joachim’s picture

Status: Needs work » Needs review
StatusFileSize
new4.81 KB

Figured it out -- the mocked entity manager should be returning an empty array for baseFieldDefinitions().

Status: Needs review » Needs work
joachim’s picture

StatusFileSize
new13.38 KB

I went down the rest of the class and changed all the calls to FDI that actually are methods on FSDI.

Tests are still failing. I am pretty sure it's due to the mocking in the test now being insufficient, however, I am finding the test code extremely hard to read and figure out!

yogeshmpawar’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work
mglaman’s picture

I think the test failures actually helped uncover a bug.

it is failing here

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $settings = $field_definition->getSettings();
    $target_type_info = \Drupal::entityManager()->getDefinition($settings['target_type']);

    $target_id_data_type = 'string';
    if ($target_type_info->entityClassImplements(FieldableEntityInterface::class)) {
      $id_definition = \Drupal::entityManager()->getBaseFieldDefinitions($settings['target_type'])[$target_type_info->getKey('id')];
      if ($id_definition->getType() === 'integer') {
        $target_id_data_type = 'integer';
      }
    }

The undefined is the `uid` field on the user entity as a base field.

mglaman’s picture

Confirmed. When working in\Drupal\Tests\views\Unit\EntityViewsDataTest::testBaseTableFields the base fields are never actually able to be retrieved from the mocked entity manager.

The problem is due to the fact the method is already mocked in setUp

    // This should return at least an array.
    $this->entityManager->method('getBaseFieldDefinitions')
      ->willReturn([]);
mglaman’s picture

Assigned: Unassigned » mglaman

Rerolling to get tests passing.

mglaman’s picture

Assigned: mglaman » Unassigned
Status: Needs work » Needs review
StatusFileSize
new19.78 KB
new8.94 KB

Here is a patch, worked on getting passing tests. It was mostly due to mocked data in the test itself. That, to me, shows this fixed some kind of bug if the mocking was incorrect in the test.

mglaman’s picture

I don't know if we can make this change as it currently stands. I tried to install Commerce and it broke \Drupal\commerce\CommerceEntityViewsData.

+++ b/core/modules/views/src/EntityViewsData.php
@@ -524,7 +538,7 @@ protected function mapSingleFieldViewsData($table, $field_name, $field_type, $co
     $process_method = 'processViewsDataFor' . Container::camelize($field_type);
     if (method_exists($this, $process_method)) {
-      $this->{$process_method}($table, $field_definition, $views_field, $column_name);
+      $this->{$process_method}($table, $field_definition, $field_storage_definition, $views_field, $column_name);
     }

Anything implementing this method will have a breaking change. I don't think we need to explicitly pass the storage definition, here, since it can be retrieved from the field definition?

mglaman’s picture

Assigned: Unassigned » mglaman
Status: Needs review » Needs work

Simplifying the patch. This is all we cared about from the initial summary

+
+    $field_storage_definitions = array_map(function (FieldDefinitionInterface $definition) {
+      return $definition->getFieldStorageDefinition();
+    }, $field_definitions);
+
     /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
-    if ($table_mapping = $this->storage->getTableMapping($field_definitions)) {
+    if ($table_mapping = $this->storage->getTableMapping($field_storage_definitions)) {

\Drupal\Core\Entity\EntityFieldManagerInterface::getBaseFieldDefinitions is expected to return FieldDefinitionInterface. Currently it's returning \Drupal\Core\Field\BaseFieldDefinition which satisfies both interfaces, so as original reporter mentioned: it just happened to work.

mglaman’s picture

Assigned: mglaman » Unassigned
Status: Needs work » Needs review
StatusFileSize
new10.2 KB
new11.35 KB

This should, hopefully, be a more concise patch.

mglaman’s picture

+++ b/core/modules/views/src/EntityViewsData.php
@@ -53,6 +54,8 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
+   * @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.x.
+   *

@@ -104,6 +107,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
+   *
+   * @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.x.

I wonder if this needs explaination that it is no longer used, and that is why it is deprecated? That anyone extending this class should retrieve the field storage definition from the field definition itself?

joachim’s picture

> I wonder if this needs explaination that it is no longer used, and that is why it is deprecated?

IIRC a class such as this is considered internal, so anyone extending this should expect changes.

But from a core maintenance POV, it's a good idea to mark this, so that anyone making changes to this class in future knows they should avoid using these variables.

mglaman’s picture

IIRC a class such as this is considered internal, so anyone extending this should expect changes.

It is not flagged @internal and is a handler "views_data" = "Drupal\views\EntityViewsData",

I'm assuming the message should be "get the field storage definition from the field definition, not this method." But I feel the case is small on anyone having used that method outside of core. But that is a maintainer call.

joachim’s picture

Turns out there's guidance at https://www.drupal.org/core/d8-bc-policy (though that document itself is not definitive...)

Entity handlers
Particular entity handlers should not be considered part of the public API. The interfaces which define those entity handlers though are part of the supported API.

EntityViewsData is not a particular entity handler, but a default base class to be used generically. Therefore, this applies:

Anything not marked @internal or covered by the below should not break BC without a specific reason.

lendude’s picture

I wonder if this needs explaination that it is no longer used, and that is why it is deprecated?

As far as I know, 'Yes, it does'.
Internal or not, anything marked @deprecated still needs the explanation about what to do when you currently use this and a trigger_error(). It also needs a CR and the trigger error and the deprecation need a reference to that CR.

https://www.drupal.org/core/deprecation

That, to me, shows this fixed some kind of bug if the mocking was incorrect in the test.

While its a nice way to prove it's a bug, don't we want so more explicit test for this?

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

andypost’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

for 8.8.x deprecation format

vacho’s picture

StatusFileSize
new10.9 KB

Only patch reroll

vacho’s picture

Issue tags: -Needs reroll
vacho’s picture

About deprecation message:
I review this issue 3024461

The text should be:
@deprecated in drupal:8.8.0 and is removed from drupal:9.0.0

But @andypost suggest only "@deprecated" ? why?

catch’s picture

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

Status: Needs work » Needs review
Issue tags: +Bug Smash Initiative
StatusFileSize
new10.83 KB

Re-rolled, with fixed deprecation messages, and s/entityManager/entityTypeManager/g.

joachim’s picture

This should be postponed on #3116481: Convert EntityViewsDataTest from a unit test to a kernel test, as that rewrites the tests.

catch’s picture

StatusFileSize
new1.6 KB
new10.61 KB

In general we would not postpone a major bug fix on a test refactoring unless it was impossible to fix without it, but also I don't think there's any risk of this getting committed before that one. Trying to clean up the deprecation messages a bit more, and added a change record.

Status: Needs review » Needs work

The last submitted patch, 42: 2930736-42.patch, failed testing. View results

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new3.97 KB

Re-rolled now that patch is in.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new144 bytes

The Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

joachim’s picture

Status: Needs work » Needs review

Made a MR on 10.1.

lendude’s picture

Status: Needs review » Needs work

Back to NW for the update to the deprecation messages

ravi.shankar made their first commit to this issue’s fork.

ravi.shankar’s picture

Status: Needs work » Needs review

Updated deprecation messages.

lendude’s picture

Status: Needs review » Reviewed & tested by the community

Per discussion around #26 if we should expand the deprecation message, I don't think we need it in the deprecation message, but I expanded the CR to at least provide some sort of example on how to replace this.

Looks nice and minimal otherwise.

catch’s picture

This looks like good clean-up.

My only question is that both this and #2898635: bundleFieldDefinitions() are not added in EntityViewsData which depends on it don't come with any test coverage. This issue is a good net reduction in code, and I'm sure the code that's executed is covered by existing tests, but at some point do we need something to show that bundle field definitions work so it doesn't eventually get regressed. Maybe that's for #2898635: bundleFieldDefinitions() are not added in EntityViewsData though?

mglaman’s picture

The problem is that Drupal doesn't have a bundle field paradigm yet, so I'm not sure how to test without adding that (like BundleFieldDefinition from Entity API module.)

Can we add the test coverage be added once Drupal adds that support? IMO #2898635: bundleFieldDefinitions() are not added in EntityViewsData needs another blocker – bringing BundleFieldDefinition into core which verifes this feature. Maybe that issue can handle that.

catch’s picture

Added some related issues on #2898635: bundleFieldDefinitions() are not added in EntityViewsData which are all heading in that direction, which also confirms we have plenty of other issues to add test coverage meaning I think it's fine for this to go in without and unblock them.

amateescu’s picture

larowlan’s picture

Status: Reviewed & tested by the community » Needs review

Left a comment on the MR

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative

Only moving to NW for the open thread in the MR.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

rodrigoaguilera made their first commit to this issue’s fork.

rodrigoaguilera’s picture

Status: Needs work » Needs review

DeprecatedServicePropertyTrait is used for when there is a \Drupal::service() replacement call
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Dependenc...

So deprecation with no replacement I think is the appropriate thing to do here.

I rebased against 11.x and created a new MR.
Hopefully this can be merged for Drupal 11.2.0

rodrigoaguilera changed the visibility of the branch 2930736-10.1-entity-views-data-assumptions to hidden.

smustgrave’s picture

Status: Needs review » Needs work

11.2 is very feasible.

Probably should add deprecation test coverage though

rodrigoaguilera’s picture

Isn't a bit too much adding deprecation tests for a protected method?

I'm curious about how to mark them so they will be removed along the deprecated code.

I tried to look for examples but couldn't find much. I guess it would be about setting up an artificial code path with a class that extends from EntityViewsData that calls the deprecated method.

nicxvan’s picture

I'm not sure about needing deprecations for protected methods.

However to answer your second question, the nature of the test will necessitate its removal when the method is removed. The test will no longer see the deprecation so it will fail when you remove the function.

You then have to remove the deprecation test, it takes care of itself.

kristiaanvandeneynde’s picture

Yeah we could have a deprecation test here and then remove it when the deprecation is removed.

Generally speaking, I would agree that we don't need a deprecation test here, but we have way too much public API surface in core and I've been bitten before where it turned out we should have kept in mind that some people might be extending these classes.

Having said that, MR looks good to me and so does the CR. So that's great :)

rodrigoaguilera’s picture

Status: Needs work » Needs review

Added test for the deprecation so back to NR

kristiaanvandeneynde’s picture

Status: Needs review » Reviewed & tested by the community

Awesome, thanks!

quietone’s picture

No unanswered questions here, updated credit and hid files.

longwave’s picture

Status: Reviewed & tested by the community » Fixed

The suggested changes make sense to me, and if anything it makes the code more readable anyway.

Committed 42fc80c and pushed to 11.x. Thanks!

Also published the change record.

  • longwave committed 42fc80c1 on 11.x
    Issue #2930736 by rodrigoaguilera, joachim, catch, mglaman, vacho, ravi....
longwave’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

pixiekat’s picture

Is there a backport for 10.6 for this? None of the patches work and upgrading from D9 is broken.