In a previous section we exposed our players and teams tables to Views, as well as made the team name a possible string filter to limit the resulting players by team. But this was not the best way we could have accomplished this because site builders may not necessarily know all the teams that are in the database, nor their exact names. So we can create our own ViewsFilter to turn it into a selection of teams the user can choose from. Kind of like a taxonomy term filter. So let's see how it's done.
First, we need to alter our data definition for the team name field to change the plugin ID that will be used for the filtering (inside hook_views_data()):
'filter' => array( 'id' => 'team_filter', ),
Now we just have to create that plugin. And naturally, it goes in the Plugin/views/filter namespace of our module:
namespace Drupal\sports\Plugin\views\filter; use Drupal\Core\Database\Connection; use Drupal\views\Plugin\views\filter\InOperator; use Drupal\views\ViewExecutable; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Filter class which filters by the available teams. * * @ViewsFilter("team_filter") */ class TeamFilter extends InOperator { /** * @var \Drupal\Core\Database\Connection */ protected $database; /** * Constructs a TeamFilter plugin object. * * @param array $configuration * A configuration array containing information about the plugin instance. * @param string $plugin_id * The plugin_id for the plugin instance. * @param mixed $plugin_definition * The plugin implementation definition. * @param \Drupal\Core\Database\Connection $database * The database connection. */ public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->database = $database; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $configuration, $plugin_id, $plugin_definition, $container->get('database') ); } /** * {@inheritdoc} */ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) { parent::init($view, $display, $options); $this->valueTitle = t('Teams'); $this->definition['options callback'] = [$this, 'getTeams']; } /** * Generates the list of teams that can be used in the filter. */ public function getTeams() { $result = $this->database->query("SELECT name FROM {teams}")->fetchAllAssoc('name'); if (!$result) { return []; } $teams = array_keys($result); return array_combine($teams, $teams); } }
First and foremost, we see the annotation is in place to make this a plugin. Similar to the Views fields. Then, we use dependency injection to get our hands on the database connection service. Nothing new so far. However, you will notice that we extend from the InOperator class which provides the base functionality for a Views filter that allows an IN type of filter. For example, ... WHERE name IN(name1, name2). So we extend from there to inherit much of this logic that applies to Views.
Then, we override the init() method (which initializes the plugin) in order to set the available values that site builders can choose from (the team names) and a title for the resulting form element. But we do so by specifying an options callback that will be used to retrieve the options at the right moment. This callback is a method on our class called getTeams() which returns an array of all the team names. This array needs to be keyed by the value to use in the query filter. And that is pretty much it. We don't need to worry about the options form or anything like that. The base class does it all for us.
Now, site builders can add this filter and choose a team (or more) to filter by, in an inclusive way. For example, to show the players that belong to a respective team:
Even if it's not that obvious, one last thing we need to do is define the configuration schema for our filter. You may be wondering why we are not creating any custom options. The answer is that when the user adds the filter and chooses a team to filter by, Drupal doesn't know what data type that value is. So, we need to tell it that it's a string. Inside our sports.schema.yml file, we can have this:
views.filter.team_filter: type: views_filter label: 'The teams to filter by' mapping: value: type: sequence label: 'Teams' sequence: type: string label: 'Team'
Similar to the Views field, we have a dynamic schema definition for the filter, of the type views_filter. In the mapping we override the value field (which has already been defined by the views_filter data type). In our case, this is a sequence (an array with unimportant keys) whose individual values are strings.
Another way we can achieve the same (or similar) is like this:
views.filter_value.team_filter: type: sequence label: 'Teams' sequence: type: string label: 'Team'
This is because, in the definition of the value key found in the views_filter schema, the type is set to views.filter_value.[%parent.plugin_id]. This means that we can simply define the views.filter_value.team_filter data type ourselves for it to use. If you remember, this is very similar to what we did ourselves in Chapter 12, JavaScript and Ajax API. So, we can just define that missing bit as our sequence, rather than overriding the entire thing to change one small bit.
The existing Views filter classes provide a great deal of capability for either using them directly for custom data or extending to complement our own specificities. So I recommend you check out all the existent filter plugins. However, the main concept of a filter is the alteration of the query being run by Views, which can be done inside the query() method of the plugin class. There, we can add extra conditions to the query based on what we need. You can check out this method on the FilterPluginBase class which simply adds a condition (using the addWhere() method on the query object) based on the configured value and operator.