You are here

class DefaultFacetManager in Facets 8

The facet manager.

The manager is responsible for interactions with the Search backend, such as altering the query, it is also responsible for executing and building the facet. It is also responsible for running the processors.

Hierarchy

Expanded class hierarchy of DefaultFacetManager

9 files declare their use of DefaultFacetManager
DefaultFacetsSummaryManager.php in modules/facets_summary/src/FacetsSummaryManager/DefaultFacetsSummaryManager.php
DependentFacetProcessor.php in src/Plugin/facets/processor/DependentFacetProcessor.php
DependentFacetProcessorTest.php in tests/src/Unit/Plugin/processor/DependentFacetProcessorTest.php
FacetBlock.php in src/Plugin/Block/FacetBlock.php
FacetForm.php in src/Form/FacetForm.php

... See full list

1 string reference to 'DefaultFacetManager'
facets.services.yml in ./facets.services.yml
facets.services.yml
1 service uses DefaultFacetManager
facets.manager in ./facets.services.yml
Drupal\facets\FacetManager\DefaultFacetManager

File

src/FacetManager/DefaultFacetManager.php, line 24

Namespace

Drupal\facets\FacetManager
View source
class DefaultFacetManager {
  use StringTranslationTrait;

  /**
   * The query type plugin manager.
   *
   * @var \Drupal\facets\QueryType\QueryTypePluginManager
   *   The query type plugin manager.
   */
  protected $queryTypePluginManager;

  /**
   * The facet source plugin manager.
   *
   * @var \Drupal\facets\FacetSource\FacetSourcePluginManager
   */
  protected $facetSourcePluginManager;

  /**
   * The processor plugin manager.
   *
   * @var \Drupal\facets\Processor\ProcessorPluginManager
   */
  protected $processorPluginManager;

  /**
   * An array of facets that are being rendered.
   *
   * @var \Drupal\facets\FacetInterface[]
   *
   * @see \Drupal\facets\FacetInterface
   * @see \Drupal\facets\Entity\Facet
   */
  protected $facets = [];

  /**
   * An array of all entity ids in the active resultset which are a child.
   *
   * @var string[]
   */
  protected $childIds = [];

  /**
   * The entity storage for facets.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface|object
   */
  protected $facetStorage;

  /**
   * A static cache of already processed facets.
   *
   * @var \Drupal\facets\FacetInterface[]
   */
  protected $processedFacets;

  /**
   * Constructs a new instance of the DefaultFacetManager.
   *
   * @param \Drupal\facets\QueryType\QueryTypePluginManager $query_type_plugin_manager
   *   The query type plugin manager.
   * @param \Drupal\facets\FacetSource\FacetSourcePluginManager $facet_source_manager
   *   The facet source plugin manager.
   * @param \Drupal\facets\Processor\ProcessorPluginManager $processor_plugin_manager
   *   The processor plugin manager.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type plugin manager.
   */
  public function __construct(QueryTypePluginManager $query_type_plugin_manager, FacetSourcePluginManager $facet_source_manager, ProcessorPluginManager $processor_plugin_manager, EntityTypeManagerInterface $entity_type_manager) {
    $this->queryTypePluginManager = $query_type_plugin_manager;
    $this->facetSourcePluginManager = $facet_source_manager;
    $this->processorPluginManager = $processor_plugin_manager;
    $this->facetStorage = $entity_type_manager
      ->getStorage('facets_facet');
  }

  /**
   * Allows the backend to add facet queries to its native query object.
   *
   * This method is called by the implementing module to initialize the facet
   * display process.
   *
   * @param mixed $query
   *   The backend's native query object.
   * @param string $facetsource_id
   *   The facet source ID to process.
   */
  public function alterQuery(&$query, $facetsource_id) {

    /** @var \Drupal\facets\FacetInterface[] $facets */
    foreach ($this
      ->getFacetsByFacetSourceId($facetsource_id) as $facet) {

      /** @var \Drupal\facets\QueryType\QueryTypeInterface $query_type_plugin */
      $query_type_plugin = $this->queryTypePluginManager
        ->createInstance($facet
        ->getQueryType(), [
        'query' => $query,
        'facet' => $facet,
      ]);
      $query_type_plugin
        ->execute();
    }
  }

  /**
   * Returns enabled facets for the searcher associated with this FacetManager.
   *
   * @return \Drupal\facets\FacetInterface[]
   *   An array of enabled facets.
   */
  public function getEnabledFacets() {
    return $this->facetStorage
      ->loadMultiple();
  }

  /**
   * Returns currently rendered facets filtered by facetsource ID.
   *
   * @param string $facetsource_id
   *   The facetsource ID to filter by.
   *
   * @return \Drupal\facets\FacetInterface[]
   *   An array of enabled facets.
   */
  public function getFacetsByFacetSourceId($facetsource_id) {

    // Immediately initialize the facets.
    $this
      ->initFacets();
    $facets = [];
    foreach ($this->facets as $facet) {
      if ($facet
        ->getFacetSourceId() === $facetsource_id) {
        $facets[] = $facet;
      }
    }
    return $facets;
  }

  /**
   * Initializes facet builds, sets the breadcrumb trail.
   *
   * Facets are built via FacetsFacetProcessor objects. Facets only need to be
   * processed, or built, once The FacetsFacetManager::processed semaphore is
   * set when this method is called ensuring that facets are built only once
   * regardless of how many times this method is called.
   *
   * @param string|null $facetsource_id
   *   The facetsource if of the currently processed facet.
   *
   * @throws \Drupal\facets\Exception\InvalidProcessorException
   *   Thrown when one of the defined processors is invalid.
   */
  public function processFacets($facetsource_id = NULL) {
    if ($facetsource_id === NULL) {
      foreach ($this->facets as $facet) {
        $current_facetsource_id = $facet
          ->getFacetSourceId();
        $this
          ->processFacets($current_facetsource_id);
      }
    }
    $unprocessedFacets = array_filter($this->facets, function ($item) use ($facetsource_id) {

      /* @var \Drupal\facets\FacetInterface $item */
      return !isset($this->processedFacets[$facetsource_id][$item
        ->id()]);
    });

    // All facets were already processed on a previous run, so no need to do so
    // again.
    if (count($unprocessedFacets) === 0) {
      return;
    }
    $this
      ->updateResults($facetsource_id);
    foreach ($unprocessedFacets as $facet) {
      $processor_configs = $facet
        ->getProcessorConfigs();
      foreach ($facet
        ->getProcessorsByStage(ProcessorInterface::STAGE_POST_QUERY) as $processor) {
        $processor_config = $processor_configs[$processor
          ->getPluginDefinition()['id']]['settings'];
        $processor_config['facet'] = $facet;

        /** @var \Drupal\facets\processor\PostQueryProcessorInterface $post_query_processor */
        $post_query_processor = $this->processorPluginManager
          ->createInstance($processor
          ->getPluginDefinition()['id'], $processor_config);
        if (!$post_query_processor instanceof PostQueryProcessorInterface) {
          throw new InvalidProcessorException("The processor {$processor->getPluginDefinition()['id']} has a post_query definition but doesn't implement the required PostQueryProcessor interface");
        }
        $post_query_processor
          ->postQuery($facet);
      }
      $this->processedFacets[$facetsource_id][$facet
        ->id()] = $facet;
    }
  }

  /**
   * Initializes enabled facets.
   *
   * In this method all pre-query processors get called and their contents are
   * executed.
   *
   * @throws \Drupal\facets\Exception\InvalidProcessorException
   *   Thrown if one of the pre query processors is invalid.
   */
  protected function initFacets() {
    if (count($this->facets) > 0) {
      return;
    }
    $this->facets = $this
      ->getEnabledFacets();
    foreach ($this->facets as $facet) {
      foreach ($facet
        ->getProcessorsByStage(ProcessorInterface::STAGE_PRE_QUERY) as $processor) {

        /** @var \Drupal\facets\Processor\PreQueryProcessorInterface $pre_query_processor */
        $pre_query_processor = $facet
          ->getProcessors()[$processor
          ->getPluginDefinition()['id']];
        if (!$pre_query_processor instanceof PreQueryProcessorInterface) {
          throw new InvalidProcessorException("The processor {$processor->getPluginDefinition()['id']} has a pre_query definition but doesn't implement the required PreQueryProcessorInterface interface");
        }
        $pre_query_processor
          ->preQuery($facet);
      }
    }
  }

  /**
   * Builds a facet and returns it as a renderable array.
   *
   * This method delegates to the relevant plugins to render a facet, it calls
   * out to a widget plugin to do the actual rendering when results are found.
   * When no results are found it calls out to the correct empty result plugin
   * to build a render array.
   *
   * Before doing any rendering, the processors that implement the
   * BuildProcessorInterface enabled on this facet will run.
   *
   * @param \Drupal\facets\FacetInterface $facet
   *   The facet we should build.
   *
   * @return array
   *   Facet render arrays.
   *
   * @throws \Drupal\facets\Exception\InvalidProcessorException
   *   Throws an exception when an invalid processor is linked to the facet.
   */
  public function build(FacetInterface $facet) {

    // Immediately initialize the facets if they are not initiated yet.
    $this
      ->initFacets();

    // It might be that the facet received here, is not the same as the already
    // loaded facets in the FacetManager.
    // For that reason, get the facet from the already loaded facets in the
    // static cache.
    $facet = $this->facets[$facet
      ->id()];
    if ($facet
      ->getOnlyVisibleWhenFacetSourceIsVisible()) {

      // Block rendering and processing should be stopped when the facet source
      // is not available on the page. Returning an empty array here is enough
      // to halt all further processing.
      $facet_source = $facet
        ->getFacetSource();
      if (is_null($facet_source) || !$facet_source
        ->isRenderedInCurrentRequest()) {
        return [];
      }
    }

    // For clarity, process facets is called each build.
    // The first facet therefor will trigger the processing. Note that
    // processing is done only once, so repeatedly calling this method will not
    // trigger the processing more than once.
    $this
      ->processFacets($facet
      ->getFacetSourceId());

    // Get the current results from the facets and let all processors that
    // trigger on the build step do their build processing.
    // @see \Drupal\facets\Processor\BuildProcessorInterface.
    // @see \Drupal\facets\Processor\SortProcessorInterface.
    $results = $facet
      ->getResults();
    foreach ($facet
      ->getProcessorsByStage(ProcessorInterface::STAGE_BUILD) as $processor) {
      if (!$processor instanceof BuildProcessorInterface) {
        throw new InvalidProcessorException("The processor {$processor->getPluginDefinition()['id']} has a build definition but doesn't implement the required BuildProcessorInterface interface");
      }
      $results = $processor
        ->build($facet, $results);
    }

    // Handle hierarchy.
    if ($results && $facet
      ->getUseHierarchy()) {
      $keyed_results = [];
      foreach ($results as $result) {
        $keyed_results[$result
          ->getRawValue()] = $result;
      }
      $parent_groups = $facet
        ->getHierarchyInstance()
        ->getChildIds(array_keys($keyed_results));
      $keyed_results = $this
        ->buildHierarchicalTree($keyed_results, $parent_groups);

      // Remove children from primary level.
      foreach (array_unique($this->childIds) as $child_id) {
        unset($keyed_results[$child_id]);
      }
      $results = array_values($keyed_results);
    }

    // Trigger sort stage.
    $active_sort_processors = [];
    foreach ($facet
      ->getProcessorsByStage(ProcessorInterface::STAGE_SORT) as $processor) {
      $active_sort_processors[] = $processor;
    }

    // Sort the actual results if we have enabled sort processors.
    if (!empty($active_sort_processors)) {
      $results = $this
        ->sortFacetResults($active_sort_processors, $results);
    }
    $facet
      ->setResults($results);

    // We include this build even if empty, it may contain attached libraries.

    /** @var \Drupal\facets\Widget\WidgetPluginInterface $widget */
    $widget = $facet
      ->getWidgetInstance();
    $build = $widget
      ->build($facet);

    // No results behavior handling. Return a custom text or false depending on
    // settings.
    if (empty($facet
      ->getResults())) {
      $empty_behavior = $facet
        ->getEmptyBehavior();
      if ($empty_behavior && $empty_behavior['behavior'] === 'text') {
        return [
          [
            $build,
            '#type' => 'container',
            '#attributes' => [
              'data-drupal-facet-id' => $facet
                ->id(),
              'class' => [
                'facet-empty',
              ],
            ],
            'empty_text' => [
              // @codingStandardsIgnoreStart
              '#markup' => $this
                ->t($empty_behavior['text']),
            ],
          ],
        ];
      }
      else {

        // If the facet has no results, but it is being rendered trough ajax we
        // should render a container (that is empty). This is because the
        // javascript needs to be able to find a div to replace with the new
        // content.
        return [
          [
            $build,
            '#type' => 'container',
            '#attributes' => [
              'data-drupal-facet-id' => $facet
                ->id(),
              'class' => [
                'facet-empty',
                'facet-hidden',
              ],
            ],
          ],
        ];
      }
    }
    return [
      $build,
    ];
  }

  /**
   * Updates all facets of a given facet source with the results.
   *
   * @param string $facetsource_id
   *   The facet source ID of the currently processed facet.
   */
  public function updateResults($facetsource_id) {
    $facets = $this
      ->getFacetsByFacetSourceId($facetsource_id);
    if ($facets) {

      /** @var \drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source_plugin */
      $facet_source_plugin = $this->facetSourcePluginManager
        ->createInstance($facetsource_id);
      $facet_source_plugin
        ->fillFacetsWithResults($facets);
    }
  }

  /**
   * Returns one of the processed facets.
   *
   * Returns one of the processed facets, this is a facet with filled results.
   * Keep in mind that if you want to have the facet's build processor executed,
   * there needs to be an extra call to the FacetManager::build with the facet
   * returned here as argument.
   *
   * @param \Drupal\facets\FacetInterface $facet
   *   The facet to process.
   *
   * @return \Drupal\facets\FacetInterface|null
   *   The updated facet if it exists, NULL otherwise.
   */
  public function returnProcessedFacet(FacetInterface $facet) {
    $this
      ->processFacets($facet
      ->getFacetSourceId());
    return !empty($this->facets[$facet
      ->id()]) ? $this->facets[$facet
      ->id()] : NULL;
  }

  /**
   * Builds an hierarchical structure for results.
   *
   * When given an array of results and an array which defines the hierarchical
   * structure, this will build the results structure and set all childs.
   *
   * @param \Drupal\facets\Result\ResultInterface[] $keyed_results
   *   An array of results keyed by id.
   * @param array $parent_groups
   *   An array of 'child id arrays' keyed by their parent id.
   *
   * @return \Drupal\facets\Result\ResultInterface[]
   *   An array of results structured hierarchically.
   */
  protected function buildHierarchicalTree(array $keyed_results, array $parent_groups) {
    foreach ($keyed_results as &$result) {
      $current_id = $result
        ->getRawValue();
      if (isset($parent_groups[$current_id]) && $parent_groups[$current_id]) {
        $child_ids = $parent_groups[$current_id];
        $child_keyed_results = [];
        foreach ($child_ids as $child_id) {
          if (isset($keyed_results[$child_id])) {
            $child_keyed_results[$child_id] = $keyed_results[$child_id];
          }
          else {

            // Children could already be built by Facets Summary manager, if
            // they are, just loading them will suffice.
            $children = $keyed_results[$current_id]
              ->getChildren();
            if (!empty($children[$child_id])) {
              $child_keyed_results[$child_id] = $children[$child_id];
            }
          }
        }
        $result
          ->setChildren($child_keyed_results);
        $this->childIds = array_merge($this->childIds, $child_ids);
      }
    }
    return $keyed_results;
  }

  /**
   * Sort the facet results, and recurse to children to do the same.
   *
   * @param \Drupal\facets\Processor\SortProcessorInterface[] $active_sort_processors
   *   An array of sort processors.
   * @param \Drupal\facets\Result\ResultInterface[] $results
   *   An array of results.
   *
   * @return \Drupal\facets\Result\ResultInterface[]
   *   A sorted array of results.
   */
  protected function sortFacetResults(array $active_sort_processors, array $results) {
    uasort($results, function ($a, $b) use ($active_sort_processors) {
      $return = 0;
      foreach ($active_sort_processors as $sort_processor) {
        if ($return = $sort_processor
          ->sortResults($a, $b)) {
          if ($sort_processor
            ->getConfiguration()['sort'] == 'DESC') {
            $return *= -1;
          }
          break;
        }
      }
      return $return;
    });

    // Loop over the results and see if they have any children, if they do, fire
    // a request to this same method again with the children.
    foreach ($results as &$result) {
      if (!empty($result
        ->getChildren())) {
        $children = $this
          ->sortFacetResults($active_sort_processors, $result
          ->getChildren());
        $result
          ->setChildren($children);
      }
    }
    return $results;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DefaultFacetManager::$childIds protected property An array of all entity ids in the active resultset which are a child.
DefaultFacetManager::$facets protected property An array of facets that are being rendered.
DefaultFacetManager::$facetSourcePluginManager protected property The facet source plugin manager.
DefaultFacetManager::$facetStorage protected property The entity storage for facets.
DefaultFacetManager::$processedFacets protected property A static cache of already processed facets.
DefaultFacetManager::$processorPluginManager protected property The processor plugin manager.
DefaultFacetManager::$queryTypePluginManager protected property The query type plugin manager.
DefaultFacetManager::alterQuery public function Allows the backend to add facet queries to its native query object.
DefaultFacetManager::build public function Builds a facet and returns it as a renderable array.
DefaultFacetManager::buildHierarchicalTree protected function Builds an hierarchical structure for results.
DefaultFacetManager::getEnabledFacets public function Returns enabled facets for the searcher associated with this FacetManager.
DefaultFacetManager::getFacetsByFacetSourceId public function Returns currently rendered facets filtered by facetsource ID.
DefaultFacetManager::initFacets protected function Initializes enabled facets.
DefaultFacetManager::processFacets public function Initializes facet builds, sets the breadcrumb trail.
DefaultFacetManager::returnProcessedFacet public function Returns one of the processed facets.
DefaultFacetManager::sortFacetResults protected function Sort the facet results, and recurse to children to do the same.
DefaultFacetManager::updateResults public function Updates all facets of a given facet source with the results.
DefaultFacetManager::__construct public function Constructs a new instance of the DefaultFacetManager.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.