Lot's of only display-related stuff just needs the $entity objects to function, so the field handler isn't in any way back-end specific except for one point: It has to retrieve the entity object and this is currently back-end specific.

Thus, I'd propose that we should a unified way for field handlers to retrieve the according entity for a result-row + relationship. That way one could finally write back-end agnostic, display-related fields or display-row plugins - such as we try to do in #1077148: add an entity-view row plugin.

In #1077148 we are currently implementing a guessing method, which basically seems to work. But as only the back-end can know how the results map to entities, I think we should let the query-backend figure that out. So search-api-views which has already pre-loaded entities, can return them and other backends can care about loading them.

Here, a possible way to do it based on drunken_monkey's suggestion in #1077148-8: add an entity-view row plugin:

public function pre_render($results) {
  // Load entities for rendering.
  if (method_exists($this->view->query, 'getResultEntities')) {
    $this->entities = $this->view->query->getResultEntities($results, $relationship);
    return;
  }
}

Thoughts?

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

drunken monkey’s picture

+1 for this, would be a great help.

(Also, my name has no underscore. :P)

dawehner’s picture

This sounds in general like a good idea. It could be used for the plugin_row_node for example.

One thing i'm wondering is what should be done with entities in relationships.
For example the node author.

Additional this method would have to find out whether the current base table has entities at all, and return an empty array if it's not.

drunken monkey’s picture

Additional this method would have to find out whether the current base table has entities at all, and return an empty array if it's not.

I'd think that a base table that doesn't contain entities won't have any handlers that would use them, anyways. Also, normal handlers would need some information as to what the entity ID field is – if that information isn't present, they'd have to assume that the results aren't entities.

fago’s picture

Ideally, I think the solution would cover relationships too. I.e. a field handler making use of the function would have to pass the relation it is configured for to function, which then figures out the table of the relation-ship, its entity-type and finally the entity.

For that, I think it would make sense to "officially" support a 'entity type' in the views-table definition (like we have done it for the linked issue), so the lookup of the entity-type is easy and query-backends can use that to map their tables to entity-types.

>(Also, my name has no underscore. :P)
I beg your pardon, Mr. monkey!

drunken monkey’s picture

For that, I think it would make sense to "officially" support a 'entity type' in the views-table definition (like we have done it for the linked issue), so the lookup of the entity-type is easy and query-backends can use that to map their tables to entity-types.

But that doesn't help query backends figure out the ID field, does it? (OK, they could then use entity_get_info() with the known type to find it out, but the field name isn't guaranteed to identically map to a databasecolumn, so couldn't be completely relied upon.)

fago’s picture

sry, for being imprecise - I meant only for the base-tables. So "search_api_node" would be declared to contain nodes. Well, this shouldn't matter to field-handler as the handler can use the suggested function anyway, but standardizing on that allows re-using parts of the entity-detection logic across back-end and establishes a uniform way for developers to mark their base-tables accordingly.

Anyway, that's just beside. The main thing we should go for is query::getResultEntities()

drunken monkey’s picture

I just realized the method, as proposed, wouldn't allow one to access the entity associated with a result row from within a field handler, would it? For this, we'd probably need a method that returns an entity given a $values result row.
In my opinion, this should be equally possible. Or is there no use case for having this unified across query backends?

(Also: Isn't this a feature request, not a task?)

drunken monkey’s picture

Hm, looked at the code in the Entity API issue again, and I see you solve this by calling entity_id() on the result row to get the ID (and then use that to access the corresponding loaded entity).
While this should work in most cases, I'm not sure we can rely on this being present, and in the same property that the entity object uses, when we call this on a result row instead of an entity.
However, it should work for my use case (as I have the ID anyways), so that's just a remark on something to consider.

fago’s picture

>In my opinion, this should be equally possible. Or is there no use case for having this unified across query backends?

Yep, that sounds good. So I'd suggest handing a "views_handler_field_entity" or so, which provides a method for easy access of the entity per row but internally uses the query backend's getResultEntities() to load all entities in a single call - so we can still benefit from multiple entity loading.

fago’s picture

Status: Active » Needs review
FileSize
2.41 KB

I'd imagine something like this, completely untested.

Thoughts?

merlinofchaos’s picture

+      $info = entity_get_info($entity_type);
+      $id_key = $info['entity keys']['id'];
+      // Assume the id has been added using the default-alias.
+      $id_alias = !empty($relationship) ? $relationship . '_' . $id_key : $id_key;

I don't think this is safe, since we can't really guarantee that the alias was constructed this way. I guess in the vast majority of cases it will be, and I might be being a little too conservative here, but I think we either:

1) need to cycle through the fields array looking for our field and testing if field and table actually match
2) have add_field create a secondary array that allows you to look up aliases by table + field. Something like $this->field_aliases[$table][$field] = $alias;

2 is the most efficient, isn't really that impactful and could be useful in other ways later on.

drunken monkey’s picture

Status: Needs review » Needs work

One small thing I noticed:

+++ b/plugins/views_plugin_query_default.inc
@@ -1455,6 +1455,50 @@ class views_plugin_query_default extends views_plugin_query {
+          $base_table_alias = $relationship->alias;

$base_table_alias isn't used afterwards.

Otherwise this looks good to me, apart from the issue Earl mentioned.

Powered by Dreditor.

fago’s picture

Status: Needs work » Needs review

ad #11:
Yes, option 2) makes sense to me. Let's try that.

ad #12:
Thanks, fixed that.

Attached is a re-rolled patch that also includes the start of a base entity field handler, which cares about retrieving the entity. However, I'm facing the problem of how to get the entity object best from pre_render() into render(). (We need pre_render to be able to benefit from multiple entity loading.) Attached patch changes $values, but that doesn't fly as $values is "global" to all field handlers.

Options to solve this:
(a) Write the entity into $row->entity or $row->$relation_ship_entity. To prevent naming overlaps maybe better $row->__entity or so.
(b) Assume the id_field has been added and read $row->$id_field_alias to get the id. Then use the id to look up the entity.
(c) Extend render() to also pass the key of the currently displayed result. So it could be used to look up the entity.

(a) really is a ugly work-a-round.
(b) would impose the existence of the add_field() method for all query backends, what currently is not the case. Would it be feasible to add this to the default query backend?
(c) seems straightforward and makes sense to me. However I fear we cannot change the default function signature of render(), as all already existing, extended field handler would generic php strict warnings then...
Maybe we could just pass it always and just change the function signature for the base entity handler?

Thoughts?

fago’s picture

FileSize
4.29 KB

forgot the patch...

drunken monkey’s picture

Hm, yes, that problem is a bit of a puzzler. I'd vote for b) – I always wondered why that method wasn't in the base query class. After all, the vast majority of backends will use such a method, and there are already some methods (or parameters) that will have to be ignored by some backends.

fago’s picture

FileSize
11.86 KB

There is already a solution for our problem! I stumbled over it over there: #1209678-2: Add a Views field handler to display rendered entities - thanks to Damien :)

So I took a stab on this and made a finally working version of this.

ad #11:

2) have add_field create a secondary array that allows you to look up aliases by table + field. Something like $this->field_aliases[$table][$field] = $alias;

I've implemented that + added the 'entity type' to all base tables.

As I needed something to test this I used this to fix #1139754: Use the real entity status instead of dummy to check access in edit link. Thus the attached patch also incorporates this fix. Attached patch works for me, including relationships. I only tested it with the default back-end though (as no other one implements this yet...).

drunken monkey’s picture

Status: Needs review » Needs work

Ah, great that this got resolved!

+++ b/handlers/views_handler_field_entity.inc
@@ -0,0 +1,54 @@
+    if (get_class($this->query) != 'views_plugin_query_default') {
+      return;
+    }

This doesn't look like a good idea to me. If someone used just a slight extension to the default query class, this would "inexplicably" stop working. You should at least use instanceof – or, better still, just leave it out altogether. Query backends not supporting this just shouldn't use the field handler, right?
E.g., the maintainer of the Search API (whoever that is) might also want to use this, without overriding the method. At the very least, overriding should be made easier by factoring out the generic part of the method.

+++ b/handlers/views_handler_field_entity.inc
@@ -0,0 +1,54 @@
+  /**
+   * Load the entities for all fields that are about to be displayed.
+   */
+  function pre_render(&$values) {

Should probably be "all rows", not "all fields". At least that's what I'd think.

Maybe I can come up with some code in the Search API to test this, too. I just have to provide the get_result_entities() method in the query plugin and then extend the views_handler_field_entity class for the field handlers, right?

Powered by Dreditor.

fago’s picture

Status: Needs work » Needs review
FileSize
11.87 KB

ok, I've improved the patch such that views_handler_field_entity::query() is reusable and uses instanceof for its check.

>Should probably be "all rows", not "all fields". At least that's what I'd think.
Also fixed that.

>Maybe I can come up with some code in the Search API to test this, too. I just have to provide the get_result_entities() method in the query plugin and then extend the views_handler_field_entity class for the field handlers, right?

Yes, that would be great!

drunken monkey’s picture

ok, I've improved the patch such that views_handler_field_entity::query() is reusable and uses instanceof for its check.

Like this, I'll still have to copy the whole method, only changing that single line. Why not just completely remove the line, as I argued above? Or use method_exists(). Or move the check (or the rest of the code) to a helper method so others don't have to override the whole method.

drunken monkey’s picture

FileSize
0 bytes

Version preferred by me.

drunken monkey’s picture

fago’s picture

hm, with my patch in #18 you could just do:

function query() {
  parent::query();
  $this->field_alias = $this->query->your_own_add_field($this->table_alias, $this->base_field, '');
}

So the extra-method doesn't really make it simpler?

Also, the instanceof check is more reliable than method_exists(), as any extended class must have a compatible version of the method, while the existence of the method doesn't say it is compatible, or just implemented accidentally.

fago’s picture

>+ * For query plugins not using the add_field() method, this method will have
>+ * to be overridden.

Well, it depends. For some back-ends the id might be there anyway, so they would have to do just nothing.

fago’s picture

Stop - that all should be possible without having to override the entity-handler - else you won't be able to re-use existing handlers like the node-edit links again.
So, the method_exists() variant would help us here. It would be cleaner with an interface, but I don't think it is really necessary, nor would it fit to Views' way of using php oop. ;)

drunken monkey’s picture

Status: Needs review » Needs work

Stop - that all should be possible without having to override the entity-handler - else you won't be able to re-use existing handlers like the node-edit links again.

Ah, you're right – that was the whole point, wasn't it? OK, then let's do it with method_exists(). I'd say, the chances of someone providing the method with a completely different purpose are really slim. And even then, they'd just have to take care of that in their field handlers and couldn't use them as-is.

#22 was right, too, of course, overlooked that.

fago’s picture

Status: Needs work » Needs review
FileSize
11.87 KB

ok, done so.

bojanz’s picture

Oh, yes. Knowing the entity type of a views table would be great (especially for use cases such as VBO).

fago’s picture

Patch is imho ready - any feedback?

It'd be rather important to have that committed, so we can move on writing handlers based upon that. What's still missing is the "discovery" part, e.g. if a module provides a field based upon this handler we need a way to make it available to other backends too. But we can care about this in a follow-up once this made it in.

JoeMcGuire’s picture

Nice work I've not had a chance to look at it all fully yet but attached is the patch re-rolled against latest dev.

Quick thought: is this a candidate for a break; after we've found the relationship?

+    if (!empty($relationship)) {
+      foreach ($this->view->relationship as $current) {
+        if ($current->alias == $relationship) {
+          $base_table = $current->definition['base'];
+          $base_table_alias = $relationship;
+        }
+      }
+    }
+    $table_data = views_fetch_data($base_table);
fago’s picture

Yep, we can add a break there.

dawehner’s picture

+++ b/handlers/views_handler_field_entity.incundefined
@@ -0,0 +1,53 @@
+
+  protected $entity_type, $entities;
+  protected $table_alias, $base_field;

I'm wondering whether this stuff stuff should be protected, perhaps this kind of informations can be used on custom theming etc.

Changed this and moved table_alias to views_handler as it is used there as well.

+++ b/plugins/views_plugin_query_default.incundefined
@@ -1421,6 +1438,49 @@ class views_plugin_query_default extends views_plugin_query {
+    if (!empty($relationship)) {
+      foreach ($this->view->relationship as $current) {
+        if ($current->alias == $relationship) {
+          $base_table = $current->definition['base'];
+          $base_table_alias = $relationship;
+        }
+      }

This is a quite common pattern, so it might be possible to abstract this, but this is out of scope of this patch.

mh86’s picture

subscribing

dawehner’s picture

Status: Needs review » Fixed

Thanks for working on this big task!
Another step to move d7 stuff into views, as there are still parts which aren't supported yet.

Commited to 7.x-3.x

bitsgrecco’s picture

subscribing

fago’s picture

Awesome! :)

Status: Fixed » Closed (fixed)

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

Dave Reid’s picture

FYI we should really start collecting API changes like this somehow for developers. I created a change record (http://drupal.org/node/add/changenotice) for this issue which can now be seen at the top of the issue.

dawehner’s picture

Perhaps things like this should be added to the changelog.txt again.

bojanz’s picture

Let's keep the changelog dead. Drupal.org now has facilities for this (that Dave Reid just showed us), so we can and should use that.