Change record status: 
Project: 
Introduced in branch: 
8.x-3.x
Introduced in version: 
8.x-3.2
Description: 


Note: this change record does not apply to a sub-theme that has not explicitly created its own @BootstrapAlter("theme_suggestions") implementation. If you do not see this in your sub-theme, then you can easily ignore this, unless you're curious about the pure awesomeness below!


Prior to 8.x-3.2, all theme hook suggestions were added directly in ThemeSuggestions::alter via a very ugly switch block.

This caused a tremendous amount of pain when just a few theme hook suggestion needed to be added or removed in a sub-theme. Because the entire alter method had to be overridden and logic from the base theme had to be maintained, it was nearly impossible to tell what was "custom". See the following before and after examples:

Before


namespace Drupal\THEMENAME\Plugin\Alter;

use Drupal\bootstrap\Annotation\BootstrapAlter;
use Drupal\bootstrap\Plugin\Alter\ThemeSuggestions as BootstrapThemeSuggestions;
use Drupal\bootstrap\Utility\Unicode;
use Drupal\bootstrap\Utility\Variables;

/**
 * Implements hook_theme_suggestions_alter().
 *
 * @BootstrapAlter("theme_suggestions")
 */
class ThemeSuggestions extends BootstrapThemeSuggestions {

  /**
   * {@inheritdoc}
   */
  public function alter(&$suggestions, &$context1 = NULL, &$hook = NULL) {
    $variables = Variables::create($context1);

    switch ($hook) {
      case 'links':
        if (Unicode::strpos($variables['theme_hook_original'], 'links__dropbutton') !== FALSE) {
          // Handle dropbutton "subtypes".
          // @see \Drupal\bootstrap\Plugin\Prerender\Dropbutton::preRenderElement()
          if ($suggestion = Unicode::substr($variables['theme_hook_original'], 17)) {
            $suggestions[] = 'bootstrap_dropdown' . $suggestion;
          }
          $suggestions[] = 'bootstrap_dropdown';
        }
        break;

      case 'fieldset':
      case 'details':
      case 'my_custom_element':
        if ($variables->element && $variables->element->getProperty('bootstrap_panel', TRUE)) {
          $suggestions[] = 'bootstrap_panel';
        }
        break;

      case 'input':
        if ($variables->element && $variables->element->isButton()) {
          if ($variables->element->getProperty('dropbutton')) {
            $suggestions[] = 'input__button__dropdown';
          }
          else {
            $suggestions[] = $variables->element->getProperty('split') ? 'input__button__split' : 'input__button';
          }
        }
        elseif ($variables->element && !$variables->element->isType(['checkbox', 'hidden', 'radio', 'my_custom_element'])) {
          $suggestions[] = 'input__form_control';
        }

        $suggestions[] = 'my_suggestion';
        if (Unicode::strpos($variables['theme_hook_original'], '__search') !== FALSE) {
          $suggestions[] = 'my_suggestion__search';
          $suggestions[] = 'something_else';
        }
        break;

      // Add the "user" entity theme hook suggestions.
      // @see https://www.drupal.org/node/2828634
      // @see https://www.drupal.org/node/2808481
      // @todo Remove/refactor once core issue is resolved.
      case 'user':
        $this->addEntitySuggestions($suggestions, $variables, 'user');
        break;
    }

  }

}

After

There is now dynamic method detection logic in place so individual theme hooks can be separated into their own (overridable) methods (named like alterThemeHookSuggestion, where ThemeHookSuggestion is the camel cased equivalent).

There is also two new protected overridable properties:

  • protected $bootstrapPanelTypes = ['details', 'fieldset'] - Indicates which elements should receive bootstrap_panel suggestions.
  • protected $ignoreFormControlTypes = ['checkbox', 'hidden', 'radio'] - Indicates which input elements should not receive the input__form_control suggestion.

namespace Drupal\THEMENAME\Plugin\Alter;

use Drupal\bootstrap\Annotation\BootstrapAlter;
use Drupal\bootstrap\Plugin\Alter\ThemeSuggestions as BootstrapThemeSuggestions;

/**
 * Implements hook_theme_suggestions_alter().
 *
 * @BootstrapAlter("theme_suggestions")
 */
class ThemeSuggestions extends BootstrapThemeSuggestions {

  /**
   * {@inheritdoc}
   */
  protected $bootstrapPanelTypes = ['details', 'fieldset', 'my_custom_element'];

  /**
   * {@inheritdoc}
   */
  protected $ignoreFormControlTypes = ['checkbox', 'hidden', 'radio', 'my_custom_element'];

  /**
   * Dynamic alter method for "input".
   */
  protected function alterInput() {
    parent::alterInput();
    // Automatically adds my_suggestion__search.
    $this->addSuggestion('my_suggestion');
  }

  /**
   * Dynamic alter method for "input__search".
   */
  protected function alterInputSearch() {
    $this->addSuggestion('something_else');
  }

}

Deprecated methods

The following two methods have been deprecated (essentially renamed and unnecessary arguments removed). However, for BC they have remained in case any code relies on them. They should be updated to their new counterparts ASAP as they will eventually be removed.

  • ThemeSuggestions::addEntitySuggestions - Use ThemeSuggestions::addSuggestionsForEntity instead.
  • ThemeSuggestions::getEntity - Use ThemeSuggestions::getEntityObject instead.
Impacts: 
Themers