diff --git a/modules/thunder_gqls/graphql/thunder_menu.base.graphqls b/modules/thunder_gqls/graphql/thunder_menu.base.graphqls index a61951f9a..eadd9339d 100644 --- a/modules/thunder_gqls/graphql/thunder_menu.base.graphqls +++ b/modules/thunder_gqls/graphql/thunder_menu.base.graphqls @@ -1,7 +1,6 @@ type Menu { id: String! name: String! - items: [MenuItem] } diff --git a/modules/thunder_gqls/graphql/thunder_search_api.base.graphqls b/modules/thunder_gqls/graphql/thunder_search_api.base.graphqls new file mode 100644 index 000000000..29db90559 --- /dev/null +++ b/modules/thunder_gqls/graphql/thunder_search_api.base.graphqls @@ -0,0 +1,4 @@ +type SearchApiResult { + items: [Page!] + total: Int! +} diff --git a/modules/thunder_gqls/graphql/thunder_search_api.extension.graphqls b/modules/thunder_gqls/graphql/thunder_search_api.extension.graphqls new file mode 100644 index 000000000..e69de29bb diff --git a/modules/thunder_gqls/src/GraphQL/Buffers/SearchApiResultBuffer.php b/modules/thunder_gqls/src/GraphQL/Buffers/SearchApiResultBuffer.php new file mode 100644 index 000000000..354dfa53f --- /dev/null +++ b/modules/thunder_gqls/src/GraphQL/Buffers/SearchApiResultBuffer.php @@ -0,0 +1,101 @@ +entityTypeManager = $entityTypeManager; + } + + /** + * Add an item to the buffer. + * + * @param string|int|null $index + * The entity type of the given entity ids. + * @param array|int $id + * The entity id(s) to load. + * + * @return \Closure + * The callback to invoke to load the result for this buffer item. + */ + public function add($index, $id) { + $item = new \ArrayObject([ + 'index' => $index, + 'id' => $id, + ]); + + return $this->createBufferResolver($item); + } + + /** + * {@inheritdoc} + */ + protected function getBufferId($item) { + return $item['index']; + } + + /** + * {@inheritdoc} + */ + public function resolveBufferArray(array $buffer) { + $index = reset($buffer)['index']; + $ids = array_map(function (\ArrayObject $item) { + return (array) $item['id']; + }, $buffer); + + $ids = call_user_func_array('array_merge', $ids); + $ids = array_values(array_unique($ids)); + + // Load the buffered entities. + /** @var \Drupal\search_api\IndexInterface $index */ + $index = $this->entityTypeManager + ->getStorage('search_api_index') + ->load($index); + + $resultSet = $index->loadItemsMultiple($ids); + $entities = []; + + foreach ($resultSet as $key => $resultItem) { + if ($resultItem instanceof EntityAdapter) { + $entities[$key] = $resultItem->getEntity(); + } + } + + return array_map(function ($item) use ($entities) { + if (is_array($item['id'])) { + return array_reduce($item['id'], static function ($carry, $current) use ($entities) { + if (!empty($entities[$current])) { + $carry[] = $entities[$current]; + return $carry; + } + + return $carry; + }, []); + } + + return $entities[$item['id']] ?? NULL; + }, $buffer); + } + +} diff --git a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/EntitiesWithTerm.php b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/EntitiesWithTerm.php index 8788b2539..9ac9953ae 100644 --- a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/EntitiesWithTerm.php +++ b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/EntitiesWithTerm.php @@ -4,7 +4,6 @@ use Drupal\graphql\GraphQL\Execution\FieldContext; use Drupal\taxonomy\TermInterface; -use Drupal\thunder_gqls\Wrappers\EntityListResponse; use Drupal\thunder_gqls\Wrappers\EntityListResponseInterface; /** @@ -119,7 +118,7 @@ public function resolve(TermInterface $term, string $type, array $bundles, strin $cacheContext ); - return new EntityListResponse($query); + return $this->entityListResponse($query); } /** diff --git a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityList.php b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityList.php index 3afcc2965..43ea0aa03 100644 --- a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityList.php +++ b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityList.php @@ -3,7 +3,6 @@ namespace Drupal\thunder_gqls\Plugin\GraphQL\DataProducer; use Drupal\graphql\GraphQL\Execution\FieldContext; -use Drupal\thunder_gqls\Wrappers\EntityListResponse; use Drupal\thunder_gqls\Wrappers\EntityListResponseInterface; /** @@ -97,7 +96,7 @@ protected function resolve(string $type, array $bundles, int $offset, int $limit $cacheContext ); - return new EntityListResponse($query); + return $this->entityListResponse($query); } } diff --git a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityListProducerBase.php b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityListProducerBase.php index 610700ab2..c2ad6bbe8 100644 --- a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityListProducerBase.php +++ b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityListProducerBase.php @@ -2,12 +2,13 @@ namespace Drupal\thunder_gqls\Plugin\GraphQL\DataProducer; -use Drupal\Core\Entity\EntityTypeManager; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Session\AccountInterface; use Drupal\graphql\GraphQL\Execution\FieldContext; use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase; +use Drupal\thunder_gqls\Wrappers\EntityListResponse; use GraphQL\Error\UserError; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -21,32 +22,43 @@ abstract class ThunderEntityListProducerBase extends DataProducerPluginBase impl /** * The entity type manager service. * - * @var \Drupal\Core\Entity\EntityTypeManager + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityTypeManager; + protected EntityTypeManagerInterface $entityTypeManager; /** * The current user. * * @var \Drupal\Core\Session\AccountInterface */ - protected $currentUser; + protected AccountInterface $currentUser; + + /** + * The response wrapper service. + * + * @var \Drupal\thunder_gqls\Wrappers\EntityListResponse + */ + protected EntityListResponse $responseWrapper; /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self { - return new static( + $instance = new static( $configuration, $plugin_id, $plugin_definition, $container->get('entity_type.manager'), $container->get('current_user') ); + + $instance->setResponseWrapper($container->get('thunder_gqls.entity_list_response_wrapper')); + + return $instance; } /** - * EntityLoad constructor. + * ThunderEntityListProducerBase constructor. * * @param array $configuration * The plugin configuration array. @@ -54,21 +66,51 @@ public static function create(ContainerInterface $container, array $configuratio * The plugin id. * @param array $pluginDefinition * The plugin definition array. - * @param \Drupal\Core\Entity\EntityTypeManager $entityTypeManager + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager * The entity type manager service. - * @param \Drupal\Core\Session\AccountInterface $current_user + * @param \Drupal\Core\Session\AccountInterface $currentUser * The current user. */ public function __construct( array $configuration, string $pluginId, array $pluginDefinition, - EntityTypeManager $entityTypeManager, - AccountInterface $current_user, + EntityTypeManagerInterface $entityTypeManager, + AccountInterface $currentUser, ) { parent::__construct($configuration, $pluginId, $pluginDefinition); + $this->setEntityTypeManager($entityTypeManager); + $this->setCurrentUser($currentUser); + } + + /** + * Set the entity type manager service. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * The entity type manager service. + */ + public function setEntityTypeManager(EntityTypeManagerInterface $entityTypeManager): void { $this->entityTypeManager = $entityTypeManager; - $this->currentUser = $current_user; + } + + /** + * Set the current user. + * + * @param \Drupal\Core\Session\AccountInterface $currentUser + * The current user. + */ + public function setCurrentUser(AccountInterface $currentUser): void { + $this->currentUser = $currentUser; + } + + /** + * Set the response wrapper service. + * + * @param \Drupal\thunder_gqls\Wrappers\EntityListResponse $responseWrapper + * The response wrapper service. + */ + public function setResponseWrapper(EntityListResponse $responseWrapper): void { + $this->responseWrapper = $responseWrapper; } /** @@ -163,10 +205,7 @@ protected function query( $query->range($offset, $limit); $storage = $this->entityTypeManager->getStorage($type); - $entityType = $storage->getEntityType(); - - $cacheContext->addCacheTags($entityType->getListCacheTags()); - $cacheContext->addCacheContexts($entityType->getListCacheContexts()); + $cacheContext->addCacheableDependency($storage->getEntityType()); return $query; } @@ -202,4 +241,17 @@ protected function createPublishedCondition(string $type, array $conditions) { ]; } + /** + * The entity list response. + * + * @param \Drupal\Core\Entity\Query\QueryInterface $query + * The entity query. + * + * @return \Drupal\thunder_gqls\Wrappers\EntityListResponse + * The entity list response. + */ + protected function entityListResponse(QueryInterface $query): EntityListResponse { + return $this->responseWrapper->setQuery($query); + } + } diff --git a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderSearchApi.php b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderSearchApi.php new file mode 100644 index 000000000..d26ecc9c6 --- /dev/null +++ b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderSearchApi.php @@ -0,0 +1,122 @@ + 'status', + 'value' => TRUE, + 'operator' => '=', + ], + [ + 'field' => 'search_api_language', + 'value' => $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(), + 'operator' => '=', + ], + ]; + + // Add default sorts. + $sortBy = $sortBy ?: [ + [ + 'field' => 'search_api_relevance', + 'direction' => QueryInterface::SORT_DESC, + ], + ]; + + $query = $this->buildBaseQuery( + $limit, + $offset, + $index, + $sortBy, + $conditions, + $search, + $cacheContext + ); + + return $this->searchApiResponse($query); + } + +} diff --git a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderSearchApiProducerBase.php b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderSearchApiProducerBase.php new file mode 100644 index 000000000..e568b6971 --- /dev/null +++ b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderSearchApiProducerBase.php @@ -0,0 +1,181 @@ +setEntityTypeManager($container->get('entity_type.manager')); + $instance->setLanguageManager($container->get('language_manager')); + $instance->setResponseWrapper($container->get('thunder_gqls.search_api_response_wrapper')); + + return $instance; + } + + /** + * Set the entity type manager service. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * The entity type manager service. + */ + public function setEntityTypeManager(EntityTypeManagerInterface $entityTypeManager): void { + $this->entityTypeManager = $entityTypeManager; + } + + /** + * Set the language manager service. + * + * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager + * The language manager service. + */ + public function setLanguageManager(LanguageManagerInterface $languageManager): void { + $this->languageManager = $languageManager; + } + + /** + * Set the response wrapper service. + * + * @param \Drupal\thunder_gqls\Wrappers\SearchApiResponse $responseWrapper + * The response wrapper service. + */ + public function setResponseWrapper(SearchApiResponse $responseWrapper): void { + $this->responseWrapper = $responseWrapper; + } + + /** + * Build base search api query. + * + * @param int $limit + * Limit of the query. + * @param int $offset + * Offset of the query. + * @param string $index + * Id of the search api index. + * @param array|null $sortBy + * List of sorts. + * @param array|null $conditions + * List of conditions to filter the result. + * @param string|null $search + * Query Search. + * @param \Drupal\graphql\GraphQL\Execution\FieldContext $cacheContext + * The caching context related to the current field. + * + * @return \Drupal\search_api\Query\QueryInterface|null + * The query interface. + * + * @throws \Drupal\search_api\SearchApiException + */ + protected function buildBaseQuery( + int $limit, + int $offset, + string $index, + ?array $sortBy, + ?array $conditions, + ?string $search, + FieldContext $cacheContext, + ): ?QueryInterface { + + // Make sure offset is zero or positive. + $offset = max($offset, 0); + + // Make sure limit is positive and cap the max items. + if ($limit <= 0) { + $limit = 10; + } + if ($limit > static::MAX_ITEMS) { + throw new UserError( + sprintf('Exceeded maximum query limit: %s.', static::MAX_ITEMS) + ); + } + + $searchIndex = Index::load($index); + if (!$searchIndex) { + return NULL; + } + + $query = $searchIndex->query(); + + foreach ($conditions as $condition) { + $query->addCondition($condition['field'], $condition['value'], $condition['operator']); + } + + foreach ($sortBy as $sort) { + $direction = $sort['direction'] ?? QueryInterface::SORT_ASC; + $query->sort($sort['field'], $direction); + } + + if (!empty($search)) { + $query->keys($search); + } + + $query->range($offset, $limit); + $cacheContext->addCacheableDependency($searchIndex); + + return $query; + } + + /** + * The search api response. + * + * @param \Drupal\search_api\Query\QueryInterface $query + * The search api query. + * + * @return \Drupal\thunder_gqls\Wrappers\SearchApiResponse + * The search api response. + */ + protected function searchApiResponse(QueryInterface $query): SearchApiResponse { + return $this->responseWrapper->setQuery($query); + } + +} diff --git a/modules/thunder_gqls/src/Plugin/GraphQL/SchemaExtension/ThunderSearchApiSchemaExtension.php b/modules/thunder_gqls/src/Plugin/GraphQL/SchemaExtension/ThunderSearchApiSchemaExtension.php new file mode 100644 index 000000000..dc9c2fbeb --- /dev/null +++ b/modules/thunder_gqls/src/Plugin/GraphQL/SchemaExtension/ThunderSearchApiSchemaExtension.php @@ -0,0 +1,39 @@ +addFieldResolverIfNotExists('SearchApiResult', 'total', + $this->builder->callback(function (SearchApiResponse $result) { + return $result->total(); + }) + ); + + $this->addFieldResolverIfNotExists('SearchApiResult', 'items', + $this->builder->callback(function (SearchApiResponse $result) { + return $result->items(); + }) + ); + } + +} diff --git a/modules/thunder_gqls/src/Traits/ResolverHelperTrait.php b/modules/thunder_gqls/src/Traits/ResolverHelperTrait.php index d09fbca9e..86d66c1e9 100644 --- a/modules/thunder_gqls/src/Traits/ResolverHelperTrait.php +++ b/modules/thunder_gqls/src/Traits/ResolverHelperTrait.php @@ -4,6 +4,7 @@ use Drupal\graphql\GraphQL\Resolver\ResolverInterface; use Drupal\graphql\GraphQL\ResolverBuilder; +use Drupal\graphql\GraphQL\ResolverRegistryInterface; /** * Helper functions for field resolvers. @@ -15,14 +16,14 @@ trait ResolverHelperTrait { * * @var \Drupal\graphql\GraphQL\ResolverBuilder */ - protected $builder; + protected ResolverBuilder $builder; /** * ResolverRegistryInterface. * * @var \Drupal\graphql\GraphQL\ResolverRegistryInterface */ - protected $registry; + protected ResolverRegistryInterface $registry; /** * Add field resolver to registry, if it does not already exist. diff --git a/modules/thunder_gqls/src/Wrappers/EntityListResponse.php b/modules/thunder_gqls/src/Wrappers/EntityListResponse.php index d2502ac23..4b25e5044 100644 --- a/modules/thunder_gqls/src/Wrappers/EntityListResponse.php +++ b/modules/thunder_gqls/src/Wrappers/EntityListResponse.php @@ -2,29 +2,66 @@ namespace Drupal\thunder_gqls\Wrappers; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\Query\QueryInterface; +use Drupal\graphql\GraphQL\Buffers\EntityBuffer; use GraphQL\Deferred; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * The thunder entity list response class. */ -class EntityListResponse implements EntityListResponseInterface { +class EntityListResponse implements EntityListResponseInterface, ContainerInjectionInterface { /** * The query interface. * * @var \Drupal\Core\Entity\Query\QueryInterface */ - protected $query; + protected QueryInterface $query; + + /** + * The entity buffer. + * + * @var \Drupal\graphql\GraphQL\Buffers\EntityBuffer + */ + protected EntityBuffer $buffer; /** * EntityListResponse constructor. * + * @param \Drupal\Core\Entity\Query\QueryInterface|\Drupal\graphql\GraphQL\Buffers\EntityBuffer $buffer + * The query or buffer parameter. + */ + public function __construct(QueryInterface|EntityBuffer $buffer) { + if ($buffer instanceof QueryInterface) { + // phpcs:ignore + @trigger_error('Calling the constructor with a query parameter is deprecated in Thunder 7.3.3 and will be removed in Thunder 8.0. Use service injection and ::setQuery() instead.', E_USER_DEPRECATED); + $this->setQuery($buffer); + // phpcs:ignore + $buffer = \Drupal::service('graphql.buffer.entity'); + } + $this->buffer = $buffer; + } + + /** + * {@inheritDoc} + */ + public static function create(ContainerInterface $container): self { + return new static( + $container->get('graphql.buffer.entity'), + ); + } + + /** + * Set query. + * * @param \Drupal\Core\Entity\Query\QueryInterface $query - * The query interface. + * The query. */ - public function __construct(QueryInterface $query) { + public function setQuery(QueryInterface $query): EntityListResponse { $this->query = $query; + return $this; } /** @@ -36,7 +73,7 @@ public function __construct(QueryInterface $query) { public function total(): int { $query = clone $this->query; $query->range(NULL, NULL)->count(); - return intval($query->execute()); + return (int) $query->execute(); } /** @@ -51,8 +88,7 @@ public function items() { return []; } - $buffer = \Drupal::service('graphql.buffer.entity'); - $callback = $buffer->add($this->query->getEntityTypeId(), array_values($result)); + $callback = $this->buffer->add($this->query->getEntityTypeId(), array_values($result)); return new Deferred(fn() => $callback()); } diff --git a/modules/thunder_gqls/src/Wrappers/SearchApiResponse.php b/modules/thunder_gqls/src/Wrappers/SearchApiResponse.php new file mode 100644 index 000000000..612c56d03 --- /dev/null +++ b/modules/thunder_gqls/src/Wrappers/SearchApiResponse.php @@ -0,0 +1,273 @@ +get('thunder_gqls.buffer.search_api_result'), + $container->get('entity_field.manager'), + ); + } + + /** + * Set query. + * + * @param \Drupal\search_api\Query\QueryInterface $query + * The query. + */ + public function setQuery(QueryInterface $query): SearchApiResponse { + $this->query = $query; + return $this; + } + + /** + * Set Facet mapping. + * + * @param array $facetMapping + * The facet mapping. + */ + public function setFacetMapping(array $facetMapping): SearchApiResponse { + $this->facetMapping = $facetMapping; + return $this; + } + + /** + * Set bundle. + * + * @param string $bundle + * The bundle. + */ + public function setBundle(string $bundle): SearchApiResponse { + $this->bundle = $bundle; + return $this; + } + + /** + * Set facets. + * + * @param array $facets + * The facets. + */ + public function setFacets(array $facets): SearchApiResponse { + $this->facets = $facets; + return $this; + } + + /** + * {@inheritdoc} + * + * @throws \Drupal\search_api\SearchApiException + */ + public function facets(): array { + if (!$this->facets || !$this->facetMapping) { + return []; + } + + if (!$this->result) { + $this->result = $this->query->execute(); + } + + $facets = []; + + $facetData = $this->result->getExtraData('search_api_facets'); + foreach ($facetData as $facetFieldId => $facetResults) { + $facets[] = [ + 'key' => $this->facetMapping[$facetFieldId], + 'values' => $this->processFacetResults($this->facets[$facetFieldId], $facetResults), + ]; + } + + return $facets; + } + + /** + * Get search result items. + * + * @return array|\GraphQL\Deferred + * The search result items. + * + * @throws \Drupal\search_api\SearchApiException + */ + public function items(): array|Deferred { + if (!$this->result) { + $this->result = $this->query->execute(); + } + + $ids = array_map(static function ($item) { + return $item->getId(); + }, $this->result->getResultItems()); + + $ids = array_unique($ids); + + if (empty($ids)) { + return []; + } + + $callback = $this->buffer->add( + $this->query->getIndex()->id(), + array_values($ids) + ); + + return new Deferred(function () use ($callback) { + return $callback(); + }); + } + + /** + * Returns the total results. + * + * @return int + * The total results. + * + * @throws \Drupal\search_api\SearchApiException + */ + public function total(): int { + $query = clone $this->query; + $query->range(0, NULL); + $result = $query->execute(); + + return (int) $result->getResultCount(); + } + + /** + * Handles processing of facet values. + * + * @param \Drupal\facets\Entity\Facet $facet + * The facet to process. + * @param array $facetResults + * The facet results. + * + * @return array + * The processed facet results. + */ + private function processFacetResults( + Facet $facet, + array $facetResults, + ): array { + // First process facet results which contain filter like filter=""9"". + // @see Drupal\facets\Plugin\facets\query_type\SearchApiString#build(). + foreach ($facetResults as $i => $facetResult) { + $facetResult['filter'] = $facetResult['filter'] ?? ''; + + if ($facetResult['filter'][0] === '"') { + $facetResult['filter'] = substr($facetResult['filter'], 1); + } + if ($facetResult['filter'][strlen($facetResult['filter']) - 1] === '"') { + $facetResult['filter'] = substr($facetResult['filter'], 0, -1); + } + + $facetResults[$i] = $facetResult; + } + + return $this->processFacetResultsFromFieldConfig($facet, $facetResults); + } + + /** + * Populates label for facet values from allowed options field config. + * + * @param \Drupal\facets\Entity\Facet $facet + * The facet. + * @param array $facetResults + * The facet results. + * + * @return array + * The processed facet results. + */ + private function processFacetResultsFromFieldConfig( + Facet $facet, + array $facetResults, + ): array { + if (!$this->bundle) { + return $facetResults; + } + + $fieldName = $facet->getFieldIdentifier(); + $fieldConfig = $this->entityFieldManager->getFieldDefinitions('node', $this->bundle); + + if (isset($fieldConfig[$fieldName])) { + $allowedValues = options_allowed_values($fieldConfig[$fieldName]->getFieldStorageDefinition()); + + // Use order of allowedValues. + foreach ($facetResults as $key => $facetResult) { + $facetResults[$key]['label'] = $allowedValues[$facetResult['filter']] ?? $facetResult['filter']; + $facetResults[$key]['value'] = $facetResult['filter']; + } + + $allowedValueKeys = array_keys($allowedValues); + usort($facetResults, function ($a, $b) use ($allowedValueKeys) { + $indexA = array_search($a['filter'], $allowedValueKeys, TRUE); + $indexB = array_search($b['filter'], $allowedValueKeys, TRUE); + + return $indexA < $indexB ? -1 : 1; + }); + } + + return $facetResults; + } + +} diff --git a/modules/thunder_gqls/src/Wrappers/SearchApiResponseInterface.php b/modules/thunder_gqls/src/Wrappers/SearchApiResponseInterface.php new file mode 100644 index 000000000..82d2158aa --- /dev/null +++ b/modules/thunder_gqls/src/Wrappers/SearchApiResponseInterface.php @@ -0,0 +1,18 @@ +logWithRole('administrator'); + + $this->drupalGet('admin/config/search/search-api/index/content'); + $this->submitForm([], 'Index now'); + $this->assertSession()->statusCodeEquals(200); + $this->checkForMetaRefresh(); + + $options = [ + 'index' => 'content', + 'search' => 'the', + 'limit' => 10, + 'offset' => 0, + ]; + + $result = $this->executeDataProducer('thunder_search_api', $options); + $this->assertEquals(3, $result->total()); + + $items = $result->items(); + $items->runQueue(); + $this->assertEquals('Burda Launches Open-Source CMS Thunder', $items->result[0]->getTitle()); + + // Change sort order. + $options['sortBy'] = [ + [ + 'field' => 'search_api_relevance', + 'direction' => QueryInterface::SORT_ASC, + ], + ]; + + $this->container->get('kernel')->rebuildContainer(); + $result = $this->executeDataProducer('thunder_search_api', $options); + + $items = $result->items(); + $items->runQueue(); + $this->assertEquals('Legal notice', $items->result[0]->getTitle()); + + // Get articles only. + $options['conditions'] = [ + [ + 'field' => 'type', + 'value' => 'article', + 'operator' => '=', + ], + ]; + + $this->container->get('kernel')->rebuildContainer(); + $result = $this->executeDataProducer('thunder_search_api', $options); + + $items = $result->items(); + $items->runQueue(); + $this->assertEquals('Come to DrupalCon New Orleans', $items->result[0]->getTitle()); + + } + +} diff --git a/modules/thunder_gqls/thunder_gqls.services.yml b/modules/thunder_gqls/thunder_gqls.services.yml new file mode 100644 index 000000000..863299712 --- /dev/null +++ b/modules/thunder_gqls/thunder_gqls.services.yml @@ -0,0 +1,12 @@ +services: + _defaults: + autowire: true + thunder_gqls.buffer.search_api_result: + class: Drupal\thunder_gqls\GraphQL\Buffers\SearchApiResultBuffer + Drupal\thunder_gqls\GraphQL\Buffers\SearchApiResultBuffer: '@thunder_gqls.buffer.search_api_result' + thunder_gqls.search_api_response_wrapper: + class: Drupal\thunder_gqls\Wrappers\SearchApiResponse + thunder_gqls.entity_list_response_wrapper: + autowire: false + class: Drupal\thunder_gqls\Wrappers\EntityListResponse + arguments: [ '@graphql.buffer.entity' ] diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3fcf0a4db..576ad8cca 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,55 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_article/src/Form/NodeRevisionRevertDefaultForm.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_article/src/Plugin/Derivative/DynamicLocalTasks.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/EntityLinks.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/FocalPoint.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/MenuLinksActiveTrail.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/MetaTags.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityListProducerBase.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntitySubRequestBase.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderImage.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderRedirect.php - - message: "#^Access to an undefined property Drupal\\\\Core\\\\Entity\\\\ContentEntityInterface\\:\\:\\$field_teaser_media\\.$#" count: 1 @@ -70,11 +20,6 @@ parameters: count: 4 path: modules/thunder_gqls/tests/src/Kernel/DataProducer/EntityLinksTest.php - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_taxonomy/src/ThunderTaxonomyPermissions.php - - message: "#^Access to an undefined property Drupal\\\\Core\\\\Entity\\\\EntityInterface\\:\\:\\$status\\.$#" count: 1 @@ -225,11 +170,6 @@ parameters: count: 1 path: tests/src/TestSuites/ThunderTestSuite.php - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: tests/src/TestSuites/ThunderTestSuite.php - - message: "#^Call to method id\\(\\) on an unknown class Drupal\\\\entity_browser\\\\Entity\\\\EntityBrowser\\.$#" count: 1 diff --git a/phpstan.neon b/phpstan.neon index 654ea7304..a604bc45c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,8 +1,11 @@ parameters: customRulesetUsed: true - checkGenericClassInNonGenericObjectType: false - checkMissingIterableValueType: false reportUnmatchedIgnoredErrors: true level: 6 + ignoreErrors: + # new static() is a best practice in Drupal, so we cannot fix that. + - "#^Unsafe usage of new static#" + - identifier: missingType.generics + - identifier: missingType.iterableValue includes: - ./phpstan-baseline.neon