Drupal 8 Views tutorial for developers. Part V — Relationship
In this chapter we shall explore in depth the notion of Views relationships.
To explain the concept of relationships we must return to the notion of base and non-base tables in Views. So imagine you want to have a view that displays data of a node and its author simultaneously. Ahm… views are built on top of one single base table, you can’t have two.
This contradiction is solved through relationships. In a way it is similar to SQL JOIN — you tell views that another base table (from a list of known relationships collected from your currently available tables) should be introduced and views internally construct necessary JOINs to actually have the additional base table included within your SQL.
That way you can actually start building your view on a node base table. Introduce the relationship of ‘node author’ (which implies adding user base table) and then you could use everything user base table has to offer (fields, sorts, filters, etc). Moreover, the actual user data that will be backing it up effectively will be the node author because SQL JOIN will be joining the user table on the node’s author column. Eventually your view still parts from one single base table node and everything sort of ‘unrolls’ from there, i.e. user data within your view stands connected to the starting point — node base table.
Such technique allows to reuse almost entire code independently of the scenario your view is running: displaying user data when it’s actual starting point of a view, or when it’s a JOINed table to some other base table. Same $views_data
information will be used in both cases for the user table and thus same handlers will be used too.
If you remember, in case of contextual filters (arguments) there was additional underlying handler type, argument validator. Something similar happens with relationship. There is a relationship handler which is the one that lays on the surface, so to say. We could say that its primary objective is to figure out what/how new base table should be JOINed. However, to produce actual SQL JOINs, it relies on another handler type, join. Therefore, join handler type is more of a hummer-tool, it just produces SQL JOINs based on pretty low-level specification without much knowledge as to why those JOINs are being generated.
This is the minimal set of theory you have to know about relationships. Now let’s see how we could actually play with them.
Remember our fruit_relations table? It’s about time to make some use of it. We should figure out a relationship that would allow us to reference Sweeter than and Bigger than fruits of the ‘current’ fruit. Make sure to insert enough data into both of your tables so you have actual toys to play with.
Relations in a RDBMS are pretty straightforward and routine: you have a table whose column is a foreign key to another table. Sure, there could be OneToOne, ManyToMany with all the intermediate steps between them. All in all, there is a relationship handler that works perfect for this routine case, it’s called standard (duh). Observe the snippet from hook_views_data() that depicts how to use it:
With this extra data in your hook_views_data() we now can introduce a relationship onto related fruits. As I was saying above, now all of the views handlers we have coded so far automatically become available on the related fruits too!
I would strongly suggest you reading through \Drupal\views\Plugin\views\relationship\RelationshipPluginBase and \Drupal\views\Plugin\views\join\JoinPluginBase to shape reasonable understanding of all the toolset Views module ships out of the box for introducing new relationships. It is extremely robust, in fact, during my entire career I only had to code a custom relationship handler once — https://www.drupal.org/project/webform_views/issues/3008750
Given the high unlikelihood of you having to code custom handlers of these types, I chose not to include this part of exercise into the series.
However, there is something else we could explore. Because there are actually two types of relation (sweeter than and bigger than) our business logic might require us to distinguish between them. This means we need to introduce 2 different relationships for the same physical DB column. If you remember, in the early chapters of this tutorial I mentioned it is possible to assign 2 different handlers of the same type to a DB column by the virtue of having 2 different views columns that both point to the same DB column. Let’s explore this in practice on the case of relationships from fruit_relations table. We will append 2 additional relationships for each possible relation respectively.
¡Voilà!— Now we can specifically reference either sweeter or bigger fruit (or even both!) despite the fact that actual relationship is stored within same column. By using Views ‘virtual’ columns we managed to get by this limitation.
On this print screen I introduced both relationships and included labels of the bigger and sweeter than fruits into the table of results.
Actual SQL executed:
SELECT fruit.label AS fruit_label, fruit.weight AS fruit_weight, fruit.weight_unit AS fruit_weight_unit, fruit_fruit_relations.label AS fruit_fruit_relations_label, fruit_fruit_relations_1.label AS fruit_fruit_relations_1_label
FROM
{fruit} fruit
LEFT JOIN {fruit_relations} fruit_relations ON fruit.id = fruit_relations.fruit_id
LEFT JOIN {fruit} fruit_fruit_relations ON fruit_relations.fruit_id_related = fruit_fruit_relations.id AND fruit_relations.relation = 'bigger'
LEFT JOIN {fruit} fruit_fruit_relations_1 ON fruit_relations.fruit_id_related = fruit_fruit_relations_1.id AND fruit_relations.relation = 'sweeter'
LIMIT 11 OFFSET 0
At last, let’s spend some time catching up one important dimension we have been ignoring so far, Config schema API.
Views are nothing else but Config Entities in Drupal 8 terminology. This means they are subject to configuration schema, i.e. they must report their metadata and comply with it afterwords. At this point it is worth to note that we have been writing custom views handlers throughout this series some of which have been using non-standard options (the properties described within ::defineOptions()
method). This means we have messed up config metadata and, as authors of the new handlers, are to fix it.
To begin with, I suggest you installing the spectacular module Config Inspector that allows you to identify any issues with your current config metadata. Scan your configs using that module and you shall see that any view that uses our custom handlers have errors in the metadata.
Read the awesome drupal.org documentation on the subject: https://www.drupal.org/docs/8/api/configuration-api/configuration-schemametadata if you want to deep-dive into this. As far as Views are concerned, you ought to know the following: because of Views’ modular nature (different handlers applied in different set ups for different views), similar modular approach is used towards views’ metadata. Ultimately, an individual view is a single Config Entity and thus must individually report its metadata. So what it does is that it ‘outsources’ so to say nested values of its metadata to one or another handler’s metadata depending on what actual handlers the view is set up to use.
Therefore, we as developers of views handlers, must focus on describing metadata of all the custom handlers we have coded so far. Once we have their metadata described correctly, Views module will pick it up and will distribute under appropriate keys to each individual view that utilizes any of our custom handlers.
Matters actually do not stand all that bad, if no schema metadata is defined for a particular views handler, Views module falls back onto the most generic metadata for a handler of that type. What does it mean? It means as long as your views handler does not introduce additional options, you have nothing to worry about. Considering all the custom handlers we have coded during this tutorial, there are only 3 of them that make custom use of options. Thus placing the following metadata into your ./config/schema/fruity_views.schema.yml should do the job:
Clear the caches, re-save your view, and navigate to Config Inspector again. At this point you should not see a single error related to your view.
Final words
Ahm… You made it! If you have not dropped out nor fallen asleep throughout the series, then you officially know everything I believe a true Drupal developer should know about Views. I hope it was fun and full of useful discoveries for you.
The final source code of the entire fruity_views module is available in the part-5
branch here.