3 namespace Drupal\views\Plugin\views\join;
5 use Drupal\Core\Plugin\PluginBase;
8 * @defgroup views_join_handlers Views join handler plugins
10 * Handler plugins for Views table joins.
12 * Handler plugins help build the view query object. Join handler plugins
15 * Views join handlers extend \Drupal\views\Plugin\views\join\JoinPluginBase.
16 * They must be annotated with \Drupal\views\Annotation\ViewsJoin annotation,
17 * and they must be in namespace directory Plugin\views\join.
19 * Here are some examples of configuration for the join plugins.
23 * LEFT JOIN {two} ON one.field_a = two.field_b
25 * Use this configuration:
27 * $configuration = array(
29 * 'field' => 'field_b',
30 * 'left_table' => 'one',
31 * 'left_field' => 'field_a',
34 * $join = Views::pluginManager('join')->createInstance('standard', $configuration);
36 * Note that the default join type is a LEFT join when 'type' is not supplied in
37 * the join plugin configuration.
41 * INNER JOIN {two} ON one.field_a = two.field_b AND one.field_c = 'some_val'
43 * Use this configuration:
45 * $configuration = array(
48 * 'field' => 'field_b',
49 * 'left_table' => 'one',
50 * 'left_field' => 'field_a',
54 * 'left_field' => 'field_c',
55 * 'value' => 'some_val',
59 * $join = Views::pluginManager('join')->createInstance('standard', $configuration);
64 * INNER JOIN {two} ON one.field_a = two.field_b AND two.field_d = 'other_val'
66 * Use this configuration:
68 * $configuration = array(
71 * 'field' => 'field_b',
72 * 'left_table' => 'one',
73 * 'left_field' => 'field_a',
77 * 'field' => 'field_d',
78 * 'value' => 'other_val',
82 * $join = Views::pluginManager('join')->createInstance('standard', $configuration);
87 * INNER JOIN {two} ON one.field_a = two.field_b AND one.field_c = two.field_d
89 * Use this configuration:
91 * $configuration = array(
94 * 'field' => 'field_b',
95 * 'left_table' => 'one',
96 * 'left_field' => 'field_a',
100 * 'left_field' => 'field_c',
101 * 'field' => 'field_d',
105 * $join = Views::pluginManager('join')->createInstance('standard', $configuration);
108 * Here is an example of a more complex join:
110 * class JoinComplex extends JoinPluginBase {
111 * public function buildJoin($select_query, $table, $view_query) {
112 * // Add an additional hardcoded condition to the query.
113 * $this->extra = 'foo.bar = baz.boing';
114 * parent::buildJoin($select_query, $table, $view_query);
119 * @ingroup views_plugins
124 * Represents a join and creates the SQL necessary to implement the join.
126 * Extensions of this class can be used to create more interesting joins.
128 class JoinPluginBase extends PluginBase implements JoinPluginInterface {
131 * The table to join (right table).
138 * The field to join on (right field).
145 * The table we join to.
152 * The field we join to.
159 * An array of extra conditions on the join.
161 * Each condition is either a string that's directly added, or an array of
163 * - table(optional): If not set, current table; if NULL, no table. If you
164 * specify a table in cached configuration, Views will try to load from an
165 * existing alias. If you use realtime joins, it works better.
166 * - field(optional): Field or formula. In formulas we can reference the
167 * right table by using %alias.
168 * - left_field(optional): Field or formula. In formulas we can reference
169 * the left table by using %alias.
170 * - operator(optional): The operator used, Defaults to "=".
171 * - value: Must be set. If an array, operator will be defaulted to IN.
172 * - numeric: If true, the value will not be surrounded in quotes.
174 * @see SelectQueryInterface::addJoin()
181 * The join type, so for example LEFT (default) or INNER.
188 * The configuration array passed by initJoin.
192 * @see \Drupal\views\Plugin\views\join\JoinPluginBase::initJoin()
194 public $configuration = [];
197 * How all the extras will be combined. Either AND or OR.
201 public $extraOperator;
204 * Defines whether a join has been adjusted.
206 * Views updates the join object to set the table alias instead of the table
207 * name. Once views has changed the alias it sets the adjusted value so it
208 * does not have to be updated anymore. If you create your own join object
209 * you should set the adjusted in the definition array to TRUE if you already
210 * know the table alias.
214 * @see \Drupal\views\Plugin\HandlerBase::getTableJoin()
215 * @see \Drupal\views\Plugin\views\query\Sql::adjustJoin()
216 * @see \Drupal\views\Plugin\views\relationship\RelationshipPluginBase::query()
221 * Constructs a Drupal\views\Plugin\views\join\JoinPluginBase object.
223 public function __construct(array $configuration, $plugin_id, $plugin_definition) {
224 parent::__construct($configuration, $plugin_id, $plugin_definition);
225 // Merge in some default values.
228 'extra_operator' => 'AND'
230 $this->configuration = $configuration;
232 if (!empty($configuration['table'])) {
233 $this->table = $configuration['table'];
236 $this->leftTable = $configuration['left_table'];
237 $this->leftField = $configuration['left_field'];
238 $this->field = $configuration['field'];
240 if (!empty($configuration['extra'])) {
241 $this->extra = $configuration['extra'];
244 if (isset($configuration['adjusted'])) {
245 $this->adjusted = $configuration['adjusted'];
248 $this->extraOperator = strtoupper($configuration['extra_operator']);
249 $this->type = $configuration['type'];
255 public function buildJoin($select_query, $table, $view_query) {
256 if (empty($this->configuration['table formula'])) {
257 $right_table = $this->table;
260 $right_table = $this->configuration['table formula'];
263 if ($this->leftTable) {
264 $left = $view_query->getTableInfo($this->leftTable);
265 $left_field = "$left[alias].$this->leftField";
268 // This can be used if left_field is a formula or something. It should be used only *very* rarely.
269 $left_field = $this->leftField;
272 $condition = "$left_field = $table[alias].$this->field";
275 // Tack on the extra.
276 if (isset($this->extra)) {
277 if (is_array($this->extra)) {
279 foreach ($this->extra as $info) {
280 // Do not require 'value' to be set; allow for field syntax instead.
284 // Figure out the table name. Remember, only use aliases provided
285 // if at all possible.
287 if (!array_key_exists('table', $info)) {
288 $join_table = $table['alias'] . '.';
290 elseif (isset($info['table'])) {
291 // If we're aware of a table alias for this table, use the table
292 // alias instead of the table name.
293 if (isset($left) && $left['table'] == $info['table']) {
294 $join_table = $left['alias'] . '.';
297 $join_table = $info['table'] . '.';
301 // Convert a single-valued array of values to the single-value case,
302 // and transform from IN() notation to = notation
303 if (is_array($info['value']) && count($info['value']) == 1) {
304 $info['value'] = array_shift($info['value']);
306 if (is_array($info['value'])) {
307 // We use an SA-CORE-2014-005 conformant placeholder for our array
308 // of values. Also, note that the 'IN' operator is implicit.
309 // @see https://www.drupal.org/node/2401615.
310 $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
311 $placeholder = ':views_join_condition_' . $select_query->nextPlaceholder() . '[]';
312 $placeholder_sql = "( $placeholder )";
315 // With a single value, the '=' operator is implicit.
316 $operator = !empty($info['operator']) ? $info['operator'] : '=';
317 $placeholder = $placeholder_sql = ':views_join_condition_' . $select_query->nextPlaceholder();
319 // Set 'field' as join table field if available or set 'left field' as
320 // join table field is not set.
321 if (isset($info['field'])) {
322 $join_table_field = "$join_table$info[field]";
323 // Allow the value to be set either with the 'value' element or
324 // with 'left_field'.
325 if (isset($info['left_field'])) {
326 $placeholder_sql = "$left[alias].$info[left_field]";
329 $arguments[$placeholder] = $info['value'];
332 // Set 'left field' as join table field is not set.
334 $join_table_field = "$left[alias].$info[left_field]";
335 $arguments[$placeholder] = $info['value'];
337 // Render out the SQL fragment with parameters.
338 $extras[] = "$join_table_field $operator $placeholder_sql";
342 if (count($extras) == 1) {
343 $condition .= ' AND ' . array_shift($extras);
346 $condition .= ' AND (' . implode(' ' . $this->extraOperator . ' ', $extras) . ')';
350 elseif ($this->extra && is_string($this->extra)) {
351 $condition .= " AND ($this->extra)";
355 $select_query->addJoin($this->type, $right_table, $table['alias'], $condition, $arguments);