Browse Source

optimized

main
astanley 4 weeks ago
parent
commit
018b5f2906
  1. 328
      src/Controller/MetadataProfileController.php

328
src/Controller/MetadataProfileController.php

@ -119,6 +119,20 @@ class MetadataProfileController extends ControllerBase {
*/
protected array $usageCounts = [];
/**
* Cached reverse map of field name → Search API indexed field entries.
*
* @var array<string, array<string, array>>|null
*/
protected ?array $searchApiIndexMap = NULL;
/**
* All loaded facet entities, keyed by facet machine name.
*
* @var array<string, \Drupal\facets\FacetInterface>|null
*/
protected ?array $allFacets = NULL;
/**
* Constructs a MetadataProfileController object.
*
@ -180,6 +194,89 @@ class MetadataProfileController extends ControllerBase {
$container->get('entity_type.manager'),
);
}
/**
* Builds a reverse map of field name → indexed field entries across all
* Search API indexes.
*
* Map structure:
* @code
* [
* 'field_abstract' => [
* 'search_api.index.default.field_abstract' => [
* 'search_api_field_name' => 'field_abstract',
* 'search_api_field_label' => 'Abstract',
* 'property_path' => 'field_abstract',
* 'type' => 'text',
* 'index_name' => 'Default',
* 'fields_included' => [],
* 'has_facet' => FALSE,
* 'facets' => [],
* ],
* ],
* ]
* @endcode
*
* @return array
* The reverse map, also stored in $this->searchApiIndexMap.
*/
protected function buildSearchApiIndexMap(): array {
if ($this->searchApiIndexMap !== NULL) {
return $this->searchApiIndexMap;
}
$this->searchApiIndexMap = [];
if (!$this->moduleHandler()->moduleExists('search_api')) {
return $this->searchApiIndexMap;
}
// Load all facets once so getSearchApiField() can use them without
// hitting the DB on every call.
$this->allFacets = $this->moduleHandler()->moduleExists('facets')
? $this->entityTypeManager()->getStorage('facets_facet')->loadMultiple()
: [];
// Build a facet lookup keyed by field_identifier for O(1) access.
// $facetsByFieldId['field_abstract'] = [facet, facet, ...]
$facetsByFieldId = [];
foreach ($this->allFacets as $facet_id => $facet) {
$facetsByFieldId[$facet->get('field_identifier')][] = [
'field_identifier' => $facet->get('field_identifier'),
'facet_name' => $facet->getName(),
'facet_machine_name' => $facet_id,
'facet_source' => $facet->getFacetSourceId(),
'url_alias' => $facet->getUrlAlias(),
'block_visible' => $this->getBlockVisible($facet),
];
}
// Iterate every index config exactly once.
foreach ($this->configFactory->listAll('search_api.index') as $index_config_name) {
$index_config = $this->config($index_config_name);
foreach ($index_config->get('field_settings') as $sa_field_name => $sa_field_setting) {
$indexed_field_key = $index_config->getName() . '.' . $sa_field_name;
// Resolve which source field(s) this indexed field maps back to.
$source_field_names = $this->resolveSourceFieldNames(
$sa_field_setting,
$index_config
);
foreach ($source_field_names as $source_field_name) {
$this->searchApiIndexMap[$source_field_name][$indexed_field_key] =
$this->buildSearchApiFieldEntry(
$sa_field_name,
$sa_field_setting,
$index_config,
$facetsByFieldId
);
}
}
}
return $this->searchApiIndexMap;
}
/**
* Displays the metadata profile page for the current bundle.
@ -266,7 +363,7 @@ class MetadataProfileController extends ControllerBase {
'#suffix' => ')',
],
'description' => [
'#plain_text' => $description,
'#markup' => $description,
'#prefix' => '<p>',
'#suffix' => '</p>',
],
@ -369,91 +466,180 @@ class MetadataProfileController extends ControllerBase {
}
/**
* Builds Search API metadata for a field definition.
* Returns Search API metadata for a single field definition.
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
* This is now a pure lookup into the pre-built index map rather than a
* nested scan of every index and every field setting.
*
* @return array|false
* An associative array containing:
* - in_search_api: bool
* - has_facet: bool
* - fields: array
* Or FALSE if Search API module is not enabled.
* {@inheritdoc}
*/
private function getSearchApi($field_definition) {
private function getSearchApi(FieldDefinitionInterface $field_definition): array|false {
if (!$this->moduleHandler()->moduleExists('search_api')) {
return FALSE;
}
$search_api = [
'in_search_api' => FALSE,
'fields' => [],
// Ensure the map has been built (idempotent).
$map = $this->buildSearchApiIndexMap();
$field_name = $field_definition->getName();
$indexed_fields = $map[$field_name] ?? [];
$has_facet = !empty(array_filter(
array_column($indexed_fields, 'has_facet')
));
return [
'in_search_api' => !empty($indexed_fields),
'has_facet' => $has_facet,
'fields' => $indexed_fields,
];
$storage = $field_definition->getFieldStorageDefinition();
if ($storage instanceof BaseFieldDefinition) {
$field_id = $this->entityTypeBundleOf . '.' . $storage->getName();
}
/**
* Resolves the source field machine names that a Search API field setting
* maps back to on the original entity.
*
* This consolidates the scattered if/elseif chain that previously lived
* inside getSearchApi() into a single, testable method.
*
* @param array $field_setting
* A single entry from $index_config->get('field_settings').
* @param \Drupal\Core\Config\ImmutableConfig $index_config
* The parent index config object (needed for processor settings).
*
* @return string[]
* Zero or more source field machine names (e.g. ['field_abstract']).
*/
private function resolveSourceFieldNames(
array $field_setting,
\Drupal\Core\Config\ImmutableConfig $index_config
): array {
$property_path = $field_setting['property_path'] ?? '';
// --- Aggregated fields --------------------------------------------------
if ($property_path === 'aggregated_field') {
// Each entry looks like 'entity:node/field_abstract'.
$sources = [];
foreach ($field_setting['configuration']['fields'] ?? [] as $ref) {
// Strip the 'entity:node/' prefix to get the bare field name.
$parts = explode('/', $ref, 2);
if (isset($parts[1])) {
$sources[] = $parts[1];
}
}
return $sources;
}
else {
$field_id = 'field.storage.' . $storage->get('id');
// --- EDTF year ----------------------------------------------------------
if ($property_path === 'edtf_year') {
$processor_fields = $index_config
->get('processor_settings.edtf_year_only.fields') ?? [];
return $this->resolveEdtfSourceNames($processor_fields);
}
// --- EDTF date ----------------------------------------------------------
if ($property_path === 'edtf_dates') {
$processor_fields = $index_config
->get('processor_settings.edtf_date_processor.fields') ?? [];
return $this->resolveEdtfSourceNames($processor_fields);
}
$search_api_indexes = $this->configFactory->listAll('search_api.index');
foreach ($search_api_indexes as $index) {
$index_config = $this->config($index);
// Loop over fields.
foreach ($index_config->get('field_settings') as $search_api_field_name => $search_api_field_setting) {
$indexed_field_name = $index_config->getName() . '.' . $search_api_field_name;
// Get Aggregated Fields.
if ($search_api_field_setting['property_path'] == 'aggregated_field') {
if (in_array('entity:node/' . $field_definition->getName(), $search_api_field_setting['configuration']['fields'])) {
$search_api['fields'][$indexed_field_name] = $this->getSearchApiField($search_api_field_name, $search_api_field_setting, $index_config);
}
}
// Get EDTF fields.
else {
if ($field_definition->getType() == 'edtf') {
// Get EDTF year.
if ($search_api_field_setting['property_path'] == 'edtf_year') {
$edtf_year_fields = $index_config->get('processor_settings')['edtf_year_only']['fields'] ?: [];
if (in_array(str_replace('.', '|', $field_definition->id()), $edtf_year_fields)) {
$search_api['fields'][$indexed_field_name] = $this->getSearchApiField($search_api_field_name, $search_api_field_setting, $index_config);
}
}
// Get EDTF Date
else {
if ($search_api_field_setting['property_path'] == 'edtf_dates') {
$edtf_date_fields = $index_config->get('processor_settings')['edtf_date_processor']['fields'] ?: [];
if (in_array(str_replace('.', '|', $field_definition->id()), $edtf_date_fields)) {
$search_api['fields'][$indexed_field_name] = $this->getSearchApiField($search_api_field_name, $search_api_field_setting, $index_config);
}
}
}
}
}
// Check dependencies for a dependency on this field.
if (isset($search_api_field_setting['dependencies']) and isset($search_api_field_setting['dependencies']['config'])) {
$field_dependencies = $search_api_field_setting['dependencies']['config'];
if (in_array($field_id, $field_dependencies)) {
$search_api['fields'][$indexed_field_name] = $this->getSearchApiField($search_api_field_name, $search_api_field_setting, $index_config);
}
}
// Check if the property path equals this field.
if (isset($search_api_field_setting['datasource_id']) and $search_api_field_setting['property_path'] == $field_definition->getName()) {
$search_api['fields'][$indexed_field_name] = $this->getSearchApiField($search_api_field_name, $search_api_field_setting, $index_config);
// --- Dependency-declared fields -----------------------------------------
$config_deps = $field_setting['dependencies']['config'] ?? [];
if (!empty($config_deps)) {
$sources = [];
foreach ($config_deps as $dep) {
// dep looks like 'field.storage.node.field_abstract'
$parts = explode('.', $dep);
$candidate = end($parts);
if (str_starts_with($candidate, 'field_')) {
$sources[] = $candidate;
}
}
if (count($search_api['fields']) > 0) {
$search_api['in_search_api'] = TRUE;
}
if (in_array(TRUE, array_column($search_api['fields'], 'has_facet'), TRUE)) {
$search_api['has_facet'] = TRUE;
if (!empty($sources)) {
return $sources;
}
else {
$search_api['has_facet'] = FALSE;
}
// --- Direct property_path match -----------------------------------------
if (isset($field_setting['datasource_id']) && str_starts_with($property_path, 'field_')) {
return [$property_path];
}
return [];
}
/**
* Converts EDTF processor field references to bare field machine names.
*
* EDTF processor fields are stored as 'node|field_abstract' (pipe-delimited).
*
* @param string[] $processor_fields
* Raw entries from an EDTF processor's 'fields' setting.
*
* @return string[]
* Bare field machine names.
*/
private function resolveEdtfSourceNames(array $processor_fields): array {
$names = [];
foreach ($processor_fields as $ref) {
// Format is 'entity_type|field_name' (pipe-separated after str_replace).
$parts = explode('|', $ref, 2);
if (isset($parts[1])) {
$names[] = $parts[1];
}
}
return $search_api;
return $names;
}
/**
* Builds one Search API field entry including resolved facet data.
*
* Replaces the original getSearchApiField() — same output shape, but
* receives pre-loaded facets instead of loading them itself.
*
* @param string $field_setting_name
* The Search API field machine name.
* @param array $field_setting
* The raw field setting array from index config.
* @param \Drupal\Core\Config\ImmutableConfig $index_config
* The parent index config.
* @param array $facets_by_field_id
* Pre-loaded facets keyed by field_identifier.
*
* @return array
* Structured Search API field entry.
*/
private function buildSearchApiFieldEntry(
string $field_setting_name,
array $field_setting,
\Drupal\Core\Config\ImmutableConfig $index_config,
array $facets_by_field_id
): array {
$property_path = $field_setting['property_path'] ?? '';
// Resolve included fields for aggregate / EDTF paths.
$fields_included = match ($property_path) {
'aggregated_field' => $field_setting['configuration']['fields'] ?? [],
'edtf_year' => $index_config->get('processor_settings.edtf_year_only.fields') ?? [],
'edtf_dates' => $index_config->get('processor_settings.edtf_date_processor.fields') ?? [],
default => [],
};
// O(1) facet lookup from the pre-built map.
$facets = $facets_by_field_id[$field_setting_name] ?? [];
$has_facet = !empty($facets);
return [
'search_api_field_name' => $field_setting_name,
'search_api_field_label' => $field_setting['label'] ?? '',
'property_path' => $property_path,
'type' => $field_setting['type'] ?? '',
'index_name' => $index_config->get('name'),
'fields_included' => $fields_included,
'has_facet' => $has_facet,
'facets' => $facets,
];
}
/**

Loading…
Cancel
Save