diff --git a/config/schema/facets.facetsource.schema.yml b/config/schema/facets.facetsource.schema.yml new file mode 100644 index 0000000..962703e --- /dev/null +++ b/config/schema/facets.facetsource.schema.yml @@ -0,0 +1,16 @@ +facets.facet_source.*: + type: config_entity + label : 'Facet Source' + mapping: + uuid: + type: string + label: 'UUID' + id: + type: string + label: 'ID' + name: + type: label + label: Name' + filterKey: + type: string + label: 'Filter key' diff --git a/core_search_facets/src/Plugin/CoreSearchFacetSourceInterface.php b/core_search_facets/src/Plugin/CoreSearchFacetSourceInterface.php index fa16be5..24486a2 100644 --- a/core_search_facets/src/Plugin/CoreSearchFacetSourceInterface.php +++ b/core_search_facets/src/Plugin/CoreSearchFacetSourceInterface.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\core_search_facets\Plugin\FacetSourceInterface. + * Contains \Drupal\core_search_facets\Plugin\CoreSearchFacetSourceInterface. */ namespace Drupal\core_search_facets\Plugin; @@ -14,7 +14,7 @@ use Drupal\facets\FacetInterface; * * A facet source is used to abstract the data source where facets can be added * to. A good example of this is a search api view. There are other possible - * facet data sources, these all implement the FacetSourceInterface. + * facet data sources, these all implement the FacetSourcePluginInterface. */ interface CoreSearchFacetSourceInterface { diff --git a/core_search_facets/src/Plugin/facets/facet_source/CoreNodeSearchFacetSource.php b/core_search_facets/src/Plugin/facets/facet_source/CoreNodeSearchFacetSource.php index 08849f5..e1b516a 100644 --- a/core_search_facets/src/Plugin/facets/facet_source/CoreNodeSearchFacetSource.php +++ b/core_search_facets/src/Plugin/facets/facet_source/CoreNodeSearchFacetSource.php @@ -12,10 +12,9 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\core_search_facets\Plugin\CoreSearchFacetSourceInterface; use Drupal\facets\FacetInterface; -use Drupal\facets\FacetSource\FacetSourceInterface; use Drupal\facets\FacetSource\FacetSourcePluginBase; +use Drupal\facets\FacetSource\FacetSourcePluginInterface; use Drupal\field\Entity\FieldConfig; -use Drupal\field\Entity\FieldStorageConfig; use Drupal\search\SearchPageInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -175,7 +174,7 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea /** * {@inheritdoc} */ - public function buildConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet, FacetSourceInterface $facet_source) { + public function buildConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet, FacetSourcePluginInterface $facet_source) { $form['field_identifier'] = [ '#type' => 'select', diff --git a/facets.routing.yml b/facets.routing.yml index 576dd64..5202aa7 100644 --- a/facets.routing.yml +++ b/facets.routing.yml @@ -41,3 +41,12 @@ entity.facets_facet.display_form: _entity_form: 'facets_facet.display' requirements: _entity_access: 'facets_facet.edit' + + +entity.facets_facetsource.edit_form: + path: '/admin/config/search/facets/facet-sources/{source_id}/edit' + defaults: + _controller: '\Drupal\facets\Controller\FacetSourceController::facetSourceConfigForm' + _title: 'Edit facet source configuration' + requirements: + _entity_create_access: 'facets_facet' diff --git a/src/Annotation/FacetsFacetSource.php b/src/Annotation/FacetsFacetSource.php index bfe425c..7389e31 100644 --- a/src/Annotation/FacetsFacetSource.php +++ b/src/Annotation/FacetsFacetSource.php @@ -13,7 +13,7 @@ use Drupal\Component\Annotation\Plugin; * Defines a Facets facet source annotation. * * @see \Drupal\facets\FacetSource\FacetSourcePluginManager - * @see \Drupal\facets\FacetSource\FacetSourceInterface + * @see \Drupal\facets\FacetSource\FacetSourcePluginInterface * @see \Drupal\facets\FacetSource\FacetSourcePluginBase * @see plugin_api * diff --git a/src/Controller/FacetSourceController.php b/src/Controller/FacetSourceController.php new file mode 100644 index 0000000..578e7c9 --- /dev/null +++ b/src/Controller/FacetSourceController.php @@ -0,0 +1,31 @@ +formBuilder()->getForm('\Drupal\facets\Form\FacetSourceEditForm'); + } + +} diff --git a/src/Entity/Facet.php b/src/Entity/Facet.php index 889a074..5330a54 100644 --- a/src/Entity/Facet.php +++ b/src/Entity/Facet.php @@ -131,7 +131,7 @@ class Facet extends ConfigEntityBase implements FacetInterface { /** * The facet source belonging to this facet. * - * @var \Drupal\facets\FacetSource\FacetSourceInterface + * @var \Drupal\facets\FacetSource\FacetSourcePluginInterface * * @see getFacetSource() */ @@ -187,6 +187,14 @@ class Facet extends ConfigEntityBase implements FacetInterface { protected $widget_plugin_manager; /** + * The facet source config object. + * + * @var \Drupal\Facets\FacetSourceInterface + * The facet source config object. + */ + protected $facetSourceConfig; + + /** * {@inheritdoc} */ public function __construct(array $values, $entity_type) { @@ -371,6 +379,40 @@ class Facet extends ConfigEntityBase implements FacetInterface { } /** + * {@inheritdoc} + */ + public function getFacetSourceConfig() { + // Return the facet source config object, if it's already set on the facet. + if ($this->facetSourceConfig instanceof FacetSource) { + return $this->facetSourceConfig; + } + + $storage = \Drupal::entityTypeManager()->getStorage('facets_facet_source'); + $source_id = str_replace(':', '__', $this->facet_source_id); + + // Load and return the facet source config object from the storage. + $facet_source = $storage->load($source_id); + if ($facet_source instanceof FacetSource) { + $this->facetSourceConfig = $facet_source; + return $this->facetSourceConfig; + } + + // We didn't have a facet source config entity yet for this facet source + // plugin, so we create on on the fly; trough magic. + $facet_source = new FacetSource( + [ + 'id' => $source_id, + 'name' => $this->facet_source_id, + ], + 'facets_facet_source' + ); + $facet_source->save(); + + $this->facetSourceConfig = $facet_source; + return $this->facetSourceConfig; + } + + /** * Retrieves all processors supported by this facet. * * @return \Drupal\facets\Processor\ProcessorInterface[] @@ -458,7 +500,7 @@ class Facet extends ConfigEntityBase implements FacetInterface { // Create our settings for this facet source.. $config = isset($this->facetSourcePlugins[$name]) ? $this->facetSourcePlugins[$name] : []; - /* @var $facet_source \Drupal\facets\FacetSource\FacetSourceInterface */ + /* @var $facet_source \Drupal\facets\FacetSource\FacetSourcePluginInterface */ $facet_source = $facet_source_plugin_manager->createInstance($name, $config); $this->facetSourcePlugins[$name] = $facet_source; } diff --git a/src/Entity/FacetSource.php b/src/Entity/FacetSource.php new file mode 100644 index 0000000..3d3f7ad --- /dev/null +++ b/src/Entity/FacetSource.php @@ -0,0 +1,98 @@ +id; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setFilterKey($filter_key) { + $this->filterKey = $filter_key; + } + + /** + * {@inheritdoc} + */ + public function getFilterKey() { + return $this->filterKey; + } + +} diff --git a/src/FacetInterface.php b/src/FacetInterface.php index 7e39f2a..58c1cd1 100644 --- a/src/FacetInterface.php +++ b/src/FacetInterface.php @@ -200,18 +200,26 @@ interface FacetInterface extends ConfigEntityInterface { /** * Returns the plugin instance of a facet source. * - * @return \Drupal\facets\FacetSource\FacetSourceInterface + * @return \Drupal\facets\FacetSource\FacetSourcePluginInterface * The plugin instance for the facet source. */ public function getFacetSource(); /** + * Returns the facet source configuration object. + * + * @return \Drupal\facets\FacetSourceInterface + * A facet source configuration object. + */ + public function getFacetSourceConfig(); + + /** * Load the facet sources for this facet. * * @param bool|TRUE $only_enabled * Only return enabled facet sources. * - * @return \Drupal\facets\FacetSource\FacetSourceInterface[] + * @return \Drupal\facets\FacetSource\FacetSourcePluginInterface[] * An array of facet sources. */ public function getFacetSources($only_enabled = TRUE); diff --git a/src/FacetListBuilder.php b/src/FacetListBuilder.php index 3469a79..8ac209b 100644 --- a/src/FacetListBuilder.php +++ b/src/FacetListBuilder.php @@ -10,6 +10,7 @@ namespace Drupal\facets; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Config\Entity\ConfigEntityListBuilder; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Link; /** * Builds a listing of facet entities. @@ -134,7 +135,9 @@ class FacetListBuilder extends ConfigEntityListBuilder { 'status' => array( 'data' => '', ), - 'operations' => array(), + 'operations' => array( + 'data' => Link::createFromRoute('Configure', 'entity.facets_facetsource.edit_form', ['source_id' => $facet_source['id']])->toRenderable(), + ), ), 'class' => array('facet-source'), ); @@ -153,7 +156,7 @@ class FacetListBuilder extends ConfigEntityListBuilder { '#markup' => $this->t( 'You currently have no facet sources defined. You should start by adding a facet source before creating facets.
An example of a facet source is a view based on Search API or a Search API page. - Other modules can also implement a facet source by providing a plugin that implements the FacetSourceInterface.' + Other modules can also implement a facet source by providing a plugin that implements the FacetSourcePluginInterface.' ), ]; } diff --git a/src/FacetManager/DefaultFacetManager.php b/src/FacetManager/DefaultFacetManager.php index 254ee04..29bd459 100644 --- a/src/FacetManager/DefaultFacetManager.php +++ b/src/FacetManager/DefaultFacetManager.php @@ -96,7 +96,7 @@ class DefaultFacetManager { * * @var string * - * @see \Drupal\facets\FacetSource\FacetSourceInterface + * @see \Drupal\facets\FacetSource\FacetSourcePluginInterface */ protected $facetSourceId; @@ -211,7 +211,7 @@ class DefaultFacetManager { $processor_definition = $processor->getPluginDefinition(); if (is_array($processor_definition['stages']) && array_key_exists(ProcessorInterface::STAGE_PRE_QUERY, $processor_definition['stages'])) { /** @var PreQueryProcessorInterface $pre_query_processor */ - $pre_query_processor = $this->processorPluginManager->createInstance($processor->getPluginDefinition()['id']); + $pre_query_processor = $this->processorPluginManager->createInstance($processor->getPluginDefinition()['id'], ['facet' => $facet]); if (!$pre_query_processor instanceof PreQueryProcessorInterface) { throw new InvalidProcessorException(new FormattableMarkup("The processor @processor has a pre_query definition but doesn't implement the required PreQueryProcessorInterface interface", ['@processor' => $processor_configuration['processor_id']])); } @@ -275,7 +275,7 @@ class DefaultFacetManager { $processor_definition = $this->processorPluginManager->getDefinition($processor->getPluginDefinition()['id']); if (is_array($processor_definition['stages']) && array_key_exists(ProcessorInterface::STAGE_BUILD, $processor_definition['stages'])) { /** @var BuildProcessorInterface $build_processor */ - $build_processor = $this->processorPluginManager->createInstance($processor->getPluginDefinition()['id']); + $build_processor = $this->processorPluginManager->createInstance($processor->getPluginDefinition()['id'], ['facet' => $facet]); if (!$build_processor instanceof BuildProcessorInterface) { throw new InvalidProcessorException(new FormattableMarkup("The processor @processor has a build definition but doesn't implement the required BuildProcessorInterface interface", ['@processor' => $processor['processor_id']])); } @@ -308,7 +308,7 @@ class DefaultFacetManager { */ public function updateResults() { // Get an instance of the facet source. - /** @var \drupal\facets\FacetSource\FacetSourceInterface $facet_source_plugin */ + /** @var \drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source_plugin */ $facet_source_plugin = $this->facetSourcePluginManager->createInstance($this->facetSourceId); $facet_source_plugin->fillFacetsWithResults($this->facets); diff --git a/src/FacetSource/FacetSourceInterface.php b/src/FacetSource/FacetSourceInterface.php deleted file mode 100644 index aef8c7a..0000000 --- a/src/FacetSource/FacetSourceInterface.php +++ /dev/null @@ -1,110 +0,0 @@ -getEntity()->getFacetSourceId(); if (!is_null($facet_source_id) && $facet_source_id !== '') { - /** @var \Drupal\facets\FacetSource\FacetSourceInterface $facet_source */ + /** @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source */ $facet_source = $this->getFacetSourcePluginManager()->createInstance($facet_source_id); if ($config_form = $facet_source->buildConfigurationForm([], $form_state, $this->getEntity(), $facet_source)) { diff --git a/src/Form/FacetSourceEditForm.php b/src/Form/FacetSourceEditForm.php new file mode 100644 index 0000000..56facd5 --- /dev/null +++ b/src/Form/FacetSourceEditForm.php @@ -0,0 +1,90 @@ +getStorage('facets_facet_source'); + + // Make sure we remove colons from the source id, those are disallowed in + // the entity id. + $source_id = $this->getRequest()->get('source_id'); + $source_id = str_replace(':', '__', $source_id); + + $facet_source = $facet_source_storage->load($source_id); + + if ($facet_source instanceof FacetSource) { + $this->setEntity($facet_source); + } + else { + + // We didn't have a facet source config entity yet for this facet source + // plugin, so we create on on the fly; trough magic. + $facet_source = new FacetSource( + [ + 'id' => $source_id, + 'name' => $this->getRequest()->get('source_id'), + ], + 'facets_facet_source' + ); + $facet_source->save(); + $this->setEntity($facet_source); + } + + // Set the module handler. This is usually handled for use, but because of + // the overridden __construct, we have to do this explicitly. + $this->setModuleHandler(\Drupal::moduleHandler()); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'facet_source_edit_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + + /** @var \Drupal\facets\FacetSourceInterface $facet_source */ + $facet_source = $this->getEntity(); + + // Filter key setting. + $form['filterKey'] = [ + '#type' => 'textfield', + '#title' => $this->t('Filter key'), + '#size' => 20, + '#maxlength' => 255, + '#default_value' => $facet_source->getFilterKey(), + '#description' => $this->t( + 'The key used in the url to identify the facet source. + When using multiple facet sources you should make sure each facet source has a different filter key.' + ), + ]; + + // The parent's form build method will add a save button. + return parent::buildForm($form, $form_state); + } + +} diff --git a/src/Plugin/facets/facet_source/SearchApiBaseFacetSource.php b/src/Plugin/facets/facet_source/SearchApiBaseFacetSource.php index b7f86d5..74e98c8 100644 --- a/src/Plugin/facets/facet_source/SearchApiBaseFacetSource.php +++ b/src/Plugin/facets/facet_source/SearchApiBaseFacetSource.php @@ -10,10 +10,9 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\facets\Exception\InvalidQueryTypeException; use Drupal\facets\FacetInterface; +use Drupal\facets\FacetSource\FacetSourcePluginInterface; use Drupal\search_api\Backend\BackendInterface; -use Drupal\facets\FacetSource\FacetSourceInterface; use Drupal\facets\FacetSource\FacetSourcePluginBase; -use Drupal\search_api\FacetsQueryTypeMappingInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -66,7 +65,7 @@ abstract class SearchApiBaseFacetSource extends FacetSourcePluginBase { /** * {@inheritdoc} */ - public function buildConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet, FacetSourceInterface $facet_source) { + public function buildConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet, FacetSourcePluginInterface $facet_source) { $form['field_identifier'] = [ '#type' => 'select', diff --git a/src/Processor/UrlProcessorPluginBase.php b/src/Processor/UrlProcessorPluginBase.php index ee8af0b..99d5875 100644 --- a/src/Processor/UrlProcessorPluginBase.php +++ b/src/Processor/UrlProcessorPluginBase.php @@ -7,6 +7,7 @@ namespace Drupal\facets\Processor; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\facets\Exception\InvalidProcessorException; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -53,6 +54,18 @@ abstract class UrlProcessorPluginBase extends ProcessorPluginBase implements Url public function __construct(array $configuration, $plugin_id, $plugin_definition, Request $request) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->request = $request; + + if (!isset($configuration['facet'])) { + throw new InvalidProcessorException(); + } + + /** @var \Drupal\facets\FacetInterface $facet */ + $facet = $configuration['facet']; + + /** @var \Drupal\facets\FacetSourceInterface $facet_source_config */ + $facet_source_config = $facet->getFacetSourceConfig(); + + $this->filterKey = $facet_source_config->getFilterKey() ?: 'f'; } /** diff --git a/src/Tests/FacetSourceTest.php b/src/Tests/FacetSourceTest.php new file mode 100644 index 0000000..3beef1d --- /dev/null +++ b/src/Tests/FacetSourceTest.php @@ -0,0 +1,40 @@ +drupalLogin($this->adminUser); + + // Test the overview. + $this->drupalGet('admin/config/search/facets'); + $this->assertLink($this->t('Configure')); + $this->clickLink($this->t('Configure')); + + // Test the edit page. + $this->assertField('filterKey'); + $this->drupalPostForm(NULL, array('filterKey' => 'fq'), $this->t('Save')); + + // Test that saving worked. + $this->assertField('filterKey'); + $this->assertRaw('fq'); + } + +} diff --git a/tests/src/Unit/Plugin/processor/QueryStringUrlProcessorTest.php b/tests/src/Unit/Plugin/processor/QueryStringUrlProcessorTest.php index 2315ddf..d23054b 100644 --- a/tests/src/Unit/Plugin/processor/QueryStringUrlProcessorTest.php +++ b/tests/src/Unit/Plugin/processor/QueryStringUrlProcessorTest.php @@ -8,6 +8,7 @@ namespace Drupal\Tests\facets\Unit\Plugin\Processor; use Drupal\facets\Entity\Facet; +use Drupal\facets\Entity\FacetSource; use Drupal\facets\Plugin\facets\processor\QueryStringUrlProcessor; use Drupal\facets\Result\Result; use Drupal\Tests\UnitTestCase; @@ -49,6 +50,8 @@ class QueryStringUrlProcessorTest extends UnitTestCase { new Result('duck', 'Duck', 15), new Result('alpaca', 'Alpaca', 25), ]; + + $this->setContainer(); } /** @@ -62,7 +65,7 @@ class QueryStringUrlProcessorTest extends UnitTestCase { $request = new Request(); $request->query->set('f', ['test:badger']); - $this->processor = new QueryStringUrlProcessor([], 'query_string', [], $request); + $this->processor = new QueryStringUrlProcessor(['facet' => $facet], 'query_string', [], $request); $this->processor->preQuery($facet); $this->assertEquals(['badger'], $facet->getActiveItems()); @@ -79,7 +82,7 @@ class QueryStringUrlProcessorTest extends UnitTestCase { $request = new Request(); $request->query->set('f', ['test:badger', 'test:mushroom', 'donkey:kong']); - $this->processor = new QueryStringUrlProcessor([], 'query_string', [], $request); + $this->processor = new QueryStringUrlProcessor(['facet' => $facet], 'query_string', [], $request); $this->processor->preQuery($facet); $this->assertEquals(['badger', 'mushroom'], $facet->getActiveItems()); @@ -95,7 +98,7 @@ class QueryStringUrlProcessorTest extends UnitTestCase { $request = new Request(); $request->query->set('f', []); - $this->processor = new QueryStringUrlProcessor([], 'query_string', [], $request); + $this->processor = new QueryStringUrlProcessor(['facet' => $facet], 'query_string', [], $request); $results = $this->processor->build($facet, []); $this->assertEmpty($results); } @@ -111,9 +114,7 @@ class QueryStringUrlProcessorTest extends UnitTestCase { $request = new Request(); $request->query->set('f', []); - $this->setContainer(); - - $this->processor = new QueryStringUrlProcessor([], 'query_string', [], $request); + $this->processor = new QueryStringUrlProcessor(['facet' => $facet], 'query_string', [], $request); $results = $this->processor->build($facet, $this->originalResults); /** @var \Drupal\facets\Result\ResultInterface $r */ @@ -137,9 +138,7 @@ class QueryStringUrlProcessorTest extends UnitTestCase { $request = new Request(); $request->query->set('f', ['king:kong']); - $this->setContainer(); - - $this->processor = new QueryStringUrlProcessor([], 'query_string', [], $request); + $this->processor = new QueryStringUrlProcessor(['facet' => $facet], 'query_string', [], $request); $results = $this->processor->build($facet, $original_results); /** @var \Drupal\facets\Result\ResultInterface $r */ @@ -155,6 +154,46 @@ class QueryStringUrlProcessorTest extends UnitTestCase { } /** + * Test that the facet source configuration filter key override works. + */ + public function testFacetSourceFilterKeyOverride() { + $facet_source = new FacetSource(['filterKey' => 'ab'], 'facets_facet_source'); + + // Override the container with the new facet source. + $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface'); + $storage->expects($this->once()) + ->method('load') + ->willReturn($facet_source); + $em = $this->getMockBuilder('\Drupal\Core\Entity\EntityTypeManagerInterface') + ->disableOriginalConstructor() + ->getMock(); + $em->expects($this->any()) + ->method('getStorage') + ->willReturn($storage); + + $container = \Drupal::getContainer(); + $container->set('entity_type.manager', $em); + \Drupal::setContainer($container); + + $facet = new Facet([], 'facet'); + $facet->setFieldIdentifier('test'); + $facet->setFacetSourceId('facet_source__dummy'); + + $request = new Request(); + $request->query->set('ab', []); + + $this->processor = new QueryStringUrlProcessor(['facet' => $facet], 'query_string', [], $request); + $results = $this->processor->build($facet, $this->originalResults); + + /** @var \Drupal\facets\Result\ResultInterface $r */ + foreach ($results as $r) { + $this->assertInstanceOf('\Drupal\facets\Result\ResultInterface', $r); + $this->assertEquals('route:test?ab[0]=test%3A' . $r->getRawValue(), $r->getUrl()->toUriString()); + } + + } + + /** * Set the container for use in unit tests. */ protected function setContainer() { @@ -170,21 +209,31 @@ class QueryStringUrlProcessorTest extends UnitTestCase { ] ); - $fsi = $this->getMockBuilder('\Drupal\facets\FacetSource\FacetSourceInterface') + $fsi = $this->getMockBuilder('\Drupal\facets\FacetSource\FacetSourcePluginInterface') ->disableOriginalConstructor() ->getMock(); $fsi->method('getPath') ->willReturn('search/test'); - $manager = $this->getMockBuilder('Drupal\facets\FacetSource\FacetSourcePluginManager') + $manager = $this->getMockBuilder('\Drupal\facets\FacetSource\FacetSourcePluginManager') ->disableOriginalConstructor() ->getMock(); $manager->method('createInstance') ->willReturn($fsi); + $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface'); + $em = $this->getMockBuilder('\Drupal\Core\Entity\EntityTypeManagerInterface') + ->disableOriginalConstructor() + ->getMock(); + $em->expects($this->any()) + ->method('getStorage') + ->willReturn($storage); + $container = new ContainerBuilder(); $container->set('router.no_access_checks', $router); $container->set('plugin.manager.facets.facet_source', $manager); + $container->set('entity_type.manager', $em); + $container->set('entity.manager', $em); \Drupal::setContainer($container); }