I have two types of entities defined:

1) A textbook with a numeric ID (ISBN). Eg: 9780495011668

2) A school with a string ID. Eg: my_university

In panels, I am able to load textbooks by their IDs without trouble, but I can't successfully load schools. I can successfully load schools in views, but I believe that uses a different code path. I'm using the Entity API if that matters, but I don't believe it does.

I believe there is a mistaken assumption in ctools that entity IDs must be numeric. I find no evidence of that requirement in core. There is some discussion that results in the same conclusion here: #1027908: entity_load() should return entities keyed by ID, not name

The following change in plugins/arguments/entity_id.inc fixes this for me. Remove:

  if (!is_numeric($arg)) {
    return FALSE;
  }

from:

/**
 * Discover if this argument gives us the entity we crave.
 */
function ctools_argument_entity_id_context($arg = NULL, $conf = NULL, $empty = FALSE) {
  $entity_type = explode(':', $conf['name']);
  $entity_type = $entity_type[1];
  // If unset it wants a generic, unfilled context.
  if ($empty) {
    return ctools_context_create_empty('entity:' . $entity_type);
  }

  // We can accept either an entity object or a pure id.
  if (is_object($arg)) {
    return ctools_context_create('entity:' . $entity_type, $arg);
  }

  if (!is_numeric($arg)) {
    return FALSE;
  }

  $entity = entity_load($entity_type, array($arg));
  if (!$entity) {
    return FALSE;
  }

  return ctools_context_create('entity:' . $entity_type, $entity[$arg]);
}

I'm not sure, but I suspect something also needs to be changed in plugins/contexts/entity.inc (too many is_numeric calls for my liking).

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

merlinofchaos’s picture

Would you be able, willing to create a patch?

It seems likely that this code derived from the node context originally and that did require numeric entity IDs. Since every entity in core uses numeric IDs, it is unsurprising that such a thing was never caught.

jdleonard’s picture

Yes. It's been a while, so let me go read how to do that :)

Could you take a quick look at plugins/contexts/entity.inc and let me know if you think anything needs to be changed there too? I'm fairly new to ctools. Some of that code is confusing me. Thanks!

merlinofchaos’s picture

Yes, it suffers from the same problem:

  elseif (is_numeric($data)) {
    $id = $data;
    $data = entity_load($entity_type, array($id));
    $data = !empty($data[$id]) ? $data[$id] : FALSE;
  }

Since the checks prior to it are is_array and is_object already, anything else is probably legit as an entity id, so just changing that to an else is probably fine.

jdleonard’s picture

Status: Active » Needs review
FileSize
2.09 KB

Alright, how'd I do? :)

merlinofchaos’s picture

Status: Needs review » Needs work

-  elseif (is_numeric($data)) {
+  else {
     $id = $data;
-    $data = entity_load($entity_type, array($id));
-    $data = !empty($data[$id]) ? $data[$id] : FALSE;
   }
 
-  if (is_array($data)) {
-    $data = entity_load($entity_type, array($id));
-    $data = !empty($data[$id]) ? $data[$id] : FALSE;
-  }
+  $data = entity_load($entity_type, array($id));
+  $data = !empty($data[$id]) ? $data[$id] : FALSE;

Unless I misinterpret, that will fail -- you'll entity load, and then entity load again immediately. $data being an array is still an important gate there.

jdleonard’s picture

Status: Needs work » Needs review

I pretty sure it works as intended. The first if/else block sets the ID and the second block loads the entity. The patch removes the entity_load in the first if/else block. The relevant code with the patch is:

  if (is_array($data) && isset($data['id'])) {
    $id = $data['id'];
  }
  elseif (is_object($data)) {
    $ids = entity_extract_ids($entity_type, $data);
    $id = $ids[0];
  }
  else {
    $id = $data;
  }

  $data = entity_load($entity_type, array($id));
  $data = !empty($data[$id]) ? $data[$id] : FALSE;

Are there any conditions under which the value of $data passed into ctools_context_create_entity() is an array without $data['id'] set?

merlinofchaos’s picture

Status: Needs review » Needs work

If $data is an object, meaning an already loaded entity was passed in, you then perform a second entity_load. That is definitely not the right behavior.

jdleonard’s picture

Status: Needs work » Needs review
FileSize
2.86 KB

Right you are. Here's another attempt. I've also done a little renaming to make it easier to follow.

merlinofchaos’s picture

+  if(isset($entities) && !empty($entities)) {

No need for the isset here; !empty() covers it. Also spacing problem: if should have a space.

I don't see any validation for an enitity actually being loaded. It's pretty easy to get invalid entity IDs over time, due to deletions or just bad data, so we need to ensure that a failed entity load returns an empty context.

jdleonard’s picture

FileSize
2.94 KB

There is validation that the entity loads. entity_load() returns an array keyed on entity ID. We're passing a single ID in to entity_load so if the returned array is non-empty, it must contain the desired entity.

Removed unneeded isset(), fixed spacing, and moved return statement out one level to ensure failed entity load results in returning empty context.

Thanks for your help!

jdleonard’s picture

Bumping for review :)

merlinofchaos’s picture

Sorry! Been basically out of commission the last 2 weeks. Super busy. :/

merlinofchaos’s picture

Status: Needs review » Fixed

Look sokay!

Dave Reid’s picture

Side note that entity IDs cannot and should not be non-numeric - this is an assumption coming from core that Entity API provides ways to violate. See #1003788: PostgreSQL: PDOException:Invalid text representation when attempting to load an entity with a string or non-scalar ID as to why this is not allowed.

Dave Reid’s picture

Status: Fixed » Needs work

This needs to be reverted or re-add the numeric checking. To clarify: the core patch #1003788: PostgreSQL: PDOException:Invalid text representation when attempting to load an entity with a string or non-scalar ID once committed the entity_load() function will not work for entities with non-numeric IDs.

merlinofchaos’s picture

Blargh.

merlinofchaos’s picture

Ok, reverted.

fago’s picture

Side note that entity IDs cannot and should not be non-numeric - this is an assumption coming from core that Entity API provides ways to violate.

That's not true. Only fieldable entities require numeric ids, others don't. Also see the docs at hook_entity_info() and #1003788-67: PostgreSQL: PDOException:Invalid text representation when attempting to load an entity with a string or non-scalar ID.

To clarify: the core patch #1003788: PDOException:Invalid text representation when attempting to load an entity with a string ID once committed the entity_load() function will not work for entities with non-numeric IDs.

It's about to fix the default implementation which requires numeric ids - that doesn't mean the whole API does so though. Thus, entity_load() is going to remain working with non-numeric ids.

Dave Reid’s picture

The assumption made that non-numeric IDs will work has already caused problems with other contrib modules - see #1343934: Meta Tags & WYSIWYG Incompatibility?. so sorry, I just don't think that supporting machine names as IDs for entities is a valid assumption with Drupal 7.

fago’s picture

Dave, this is not just about machine names for entities, it's about the restriction in general. Consider integrating Drupal with a REST web service, such that the REST resources are entities. In that case the URL or at least the trailing path of it would have to act as identifier, but needs not be an integer at all. In order to be able to implement use-cases like this it's crucial that the API doesn't pose such restrictions which a remote source might not fulfill.

andrewbelcher’s picture

We encountered this problem where we were building a relationship via a machine name. I've attached the patch that fixed it for us. It's essentially the same as the previous patches, though I've only dealt with context creation, not arguments.

The main differences are that we do a check in entity info to make sure that there is a machine name for the entity type and that the following wont work with machine names as entity_load() keys by numeric ids:

$data = !empty($data[$id]) ? $data[$id] : FALSE;

As such, I'm using:

$data = reset($data);

I think this is fine, as in this case we either have an array with a single item, or an empty array. Reset will return the item or FALSE respectively, so it has the same result. I wonder if it's worth changing to that across the board?

I wonder if it's worth tweaking the rest of the function so that it's agnostic whether the argument is an id or a name, rather than using $id. I've also not done anything to allow it to load via an array/object on machine names.

If it looks like this approach is a good one, let me know and I can write a patch to deal with the rest of is as well.

As for the argument, do we want to have a separate entity_name argument handler, rather than making entity_id cope with machine names?

andrewbelcher’s picture

Status: Needs work » Needs review
FileSize
660 bytes

Oops, forgot to attach the patch! Marked for review for testing purposes, though will want more work before being committed.

tim.plunkett’s picture

FileSize
666 bytes

The reason this issue was reverted (#1003788: PostgreSQL: PDOException:Invalid text representation when attempting to load an entity with a string or non-scalar ID) was never committed.

Profile2 supports non-numeric entity IDs. While added a context via the UI gives you an autocomplete that translates it into a numeric ID, the argument programmatically adds the context with no such allowances.

I haven't revisited the context code, but here's a fix for the argument.

Unlike the above patches, this doesn't need any specific checking, since it just uses reset().

jpstrikesback’s picture

Tested, get's rid of some notices, no side effects including nausea, dizziness, fainting, etc

jpstrikesback’s picture

Issue summary: View changes

Added file names and another place that may need updating

  • merlinofchaos committed 1704f69 on 8.x-2.x
    Issue #1229320 by jdleonard: Allow entities with non numeric primary...
  • merlinofchaos committed 0fe9eb6 on 8.x-2.x
    Revert "Issue #1229320 by jdleonard: Allow entities with non numeric...

Status: Needs review » Needs work

The last submitted patch, 23: ctools-1229320-23.patch, failed testing.

  • merlinofchaos committed 1704f69 on 8.x-3.x
    Issue #1229320 by jdleonard: Allow entities with non numeric primary...
  • merlinofchaos committed 0fe9eb6 on 8.x-3.x
    Revert "Issue #1229320 by jdleonard: Allow entities with non numeric...
annya’s picture

Status: Needs work » Needs review
FileSize
829 bytes

This patch fix non-numeric issue with arguments. Not sure if we need id-regexp functionality as ctools_argument_entity_id_settings_form has never called(and even if it will be called, it doesn't work).

betz’s picture

I am having the same issue.
We are using entities for our backend API, which has non-numerical ID's.
A bit confused about this issue, is there consensus? Will this be allowed?
We are patching Ctools now internally for this in our make file.
It would be nice to know if it's just about submitting a working patch or not to get this pushed upstream.

Tom

Chris Matthews’s picture

The 3 year old patch in #29 to entity_id.inc applied cleanly to the latest ctools 7.x-1.x-dev and (if applicable) still needs review.

Checking patch plugins/arguments/entity_id.inc...
Hunk #1 succeeded at 70 (offset -1 lines).
Applied patch plugins/arguments/entity_id.inc cleanly.