class CommandHelper in Search API 8
Provides functionality to be used by CLI tools.
Hierarchy
- class \Drupal\search_api\Utility\CommandHelper implements \Psr\Log\LoggerAwareInterface uses \Psr\Log\LoggerAwareTrait
Expanded class hierarchy of CommandHelper
3 files declare their use of CommandHelper
- CommandHelperTest.php in tests/
src/ Kernel/ System/ CommandHelperTest.php - SearchApiCommands.php in src/
Commands/ SearchApiCommands.php - search_api.drush.inc in ./
search_api.drush.inc - Drush commands for Search API.
File
- src/
Utility/ CommandHelper.php, line 25
Namespace
Drupal\search_api\UtilityView source
class CommandHelper implements LoggerAwareInterface {
use LoggerAwareTrait;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The storage for search index entities.
*
* @var \Drupal\search_api\Entity\SearchApiConfigEntityStorage
*/
protected $indexStorage;
/**
* The storage for search server entities.
*
* @var \Drupal\search_api\Entity\SearchApiConfigEntityStorage
*/
protected $serverStorage;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The event dispatcher.
*
* @var \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher|null
*/
protected $eventDispatcher;
/**
* A callable for translating strings.
*
* @var callable
*/
protected $translationFunction;
/**
* Constructs a CommandHelper object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
* @param string|callable $translation_function
* (optional) A callable for translating strings.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* Thrown if the "search_api_index" or "search_api_server" entity types'
* storage handlers couldn't be loaded.
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* Thrown if the "search_api_index" or "search_api_server" entity types are
* unknown.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, $translation_function = 'dt') {
$this->entityTypeManager = $entity_type_manager;
$this->indexStorage = $entity_type_manager
->getStorage('search_api_index');
$this->serverStorage = $entity_type_manager
->getStorage('search_api_server');
$this->moduleHandler = $module_handler;
$this->eventDispatcher = $event_dispatcher;
$this->translationFunction = $translation_function;
}
/**
* Lists all search indexes.
*
* @return array
* An associative array, keyed by search index ID, each value an associative
* array with the following keys:
* - id: The ID of the search index.
* - name: The human readable name of the search index.
* - server: The ID of the server associated with the search index.
* - serverName: The human readable name of the server associated with the
* search index.
* - types: An array of entity type IDs that are tracked in the index.
* - typeNames: An array of human readable entity type labels that are
* tracked in the index.
* - status: Either "enabled" or "disabled".
* - limit: The number of items that are processed in a single cron run.
*
* @throws \Drupal\search_api\SearchApiException
* Thrown if an index has a server which couldn't be loaded.
*/
public function indexListCommand() {
$indexes = $this
->loadIndexes();
if (!$indexes) {
return [];
}
$rows = [];
$none = '(' . $this
->t('none') . ')';
$enabled = $this
->t('enabled');
$disabled = $this
->t('disabled');
foreach ($indexes as $index) {
$types = [];
$type_names = [];
foreach ($index
->getDatasources() as $datasource) {
$types[] = $datasource
->getEntityTypeId();
$type_names[] = (string) $datasource
->label();
}
$rows[$index
->id()] = [
'id' => $index
->id(),
'name' => $index
->label(),
'server' => $index
->getServerId() ?: $none,
'serverName' => $index
->getServerId() ? $index
->getServerInstance()
->label() : $none,
'types' => $types,
'typeNames' => $type_names,
'status' => $index
->status() ? $enabled : $disabled,
'limit' => (int) $index
->getOption('cron_limit'),
];
}
return $rows;
}
/**
* Lists all search indexes with their status.
*
* @param string[]|null $indexId
* (optional) An array of search index IDs, or NULL to list the status of
* all indexes.
*
* @return array
* An associative array, keyed by search index ID, each value an associative
* array with the following keys:
* - id: The ID of the search index.
* - name: The human readable name of the search index.
* - complete: a percentage of indexation.
* - indexed: The amount of indexed items.
* - total: The total amount of items.
*
* @throws \Drupal\search_api\SearchApiException
* Thrown if one of the affected indexes had an invalid tracker set.
*/
public function indexStatusCommand(array $indexId = NULL) {
$indexes = $this
->loadIndexes($indexId);
if (!$indexes) {
return [];
}
$rows = [];
foreach ($indexes as $index) {
$indexed = $index
->getTrackerInstance()
->getIndexedItemsCount();
$total = $index
->getTrackerInstance()
->getTotalItemsCount();
$complete = '-';
if ($total > 0) {
$complete = 100 * round($indexed / $total, 3) . '%';
}
$rows[$index
->id()] = [
'id' => $index
->id(),
'name' => $index
->label(),
'complete' => $complete,
'indexed' => $indexed,
'total' => $total,
];
}
return $rows;
}
/**
* Enables one or more disabled search indexes.
*
* @param array $index_ids
* (optional) An array of machine names of indexes to enable. If omitted all
* indexes will be enabled.
*
* @throws \Drupal\search_api\ConsoleException
* Thrown if no indexes could be loaded.
*/
public function enableIndexCommand(array $index_ids = NULL) {
if (!$this
->getIndexCount()) {
throw new ConsoleException($this
->t('There are no indexes defined. Please create an index before trying to enable it.'));
}
$indexes = $this
->loadIndexes($index_ids);
if (!$indexes) {
throw new ConsoleException($this
->t('You must specify at least one index to enable.'));
}
foreach ($indexes as $index) {
if (!$index
->status()) {
$this
->setIndexState($index);
}
}
}
/**
* Disables one or more enabled search indexes.
*
* @param array $index_ids
* (optional) An array of machine names of indexes to disable. If omitted
* all indexes will be disabled.
*
* @throws \Drupal\search_api\ConsoleException
* Thrown if no indexes could be loaded.
*/
public function disableIndexCommand(array $index_ids = NULL) {
if (!$this
->getIndexCount()) {
throw new ConsoleException($this
->t('There are no indexes defined. Please create an index before trying to disable it.'));
}
$indexes = $this
->loadIndexes($index_ids);
if (!$indexes) {
throw new ConsoleException($this
->t('You must specify at least one index to disable.'));
}
foreach ($indexes as $index) {
if ($index
->status()) {
$this
->setIndexState($index, FALSE);
}
}
}
/**
* Indexes items on one or more indexes.
*
* @param string[]|null $indexIds
* (optional) An array of index IDs, or NULL if we should index items for
* all enabled indexes.
* @param int|null $limit
* (optional) The maximum number of items to index, or NULL to index all
* items.
* @param int|null $batchSize
* (optional) The maximum number of items to process per batch, or NULL to
* index all items at once.
*
* @return bool
* TRUE if indexing for any index was queued, FALSE otherwise.
*
* @throws \Drupal\search_api\ConsoleException
* Thrown if an indexing batch process could not be created.
* @throws \Drupal\search_api\SearchApiException
* Thrown if one of the affected indexes had an invalid tracker set.
*/
public function indexItemsToIndexCommand(array $indexIds = NULL, $limit = NULL, $batchSize = NULL) {
$indexes = $this
->loadIndexes($indexIds);
if (!$indexes) {
return FALSE;
}
$batchSet = FALSE;
foreach ($indexes as $index) {
if (!$index
->status() || $index
->isReadOnly()) {
continue;
}
$tracker = $index
->getTrackerInstance();
$remaining = $tracker
->getTotalItemsCount() - $tracker
->getIndexedItemsCount();
if (!$remaining) {
$this->logger
->info($this
->t("The index @index is up to date.", [
'@index' => $index
->label(),
]));
continue;
}
else {
$arguments = [
'@remaining' => $remaining,
'@limit' => $limit ?: $this
->t('all'),
'@index' => $index
->label(),
];
$this->logger
->info($this
->t("Found @remaining items to index for @index. Indexing @limit items.", $arguments));
}
// If we pass NULL, it would be used as "no items". -1 is the correct way
// to index all items.
$current_limit = $limit ?: -1;
// Get the batch size to use for this index (in case none was specified in
// the command).
$currentBatchSize = $batchSize;
if (!$currentBatchSize) {
$cron_limit = $index
->getOption('cron_limit');
$currentBatchSize = $cron_limit ?: \Drupal::configFactory()
->get('search_api.settings')
->get('default_cron_limit');
}
// Get the number of items to index.
$current_limit += 0;
if (!is_int($current_limit) || $current_limit <= 0) {
$current_limit = $remaining;
}
$arguments = [
'@index' => $index
->label(),
'@limit' => $current_limit,
'@batch_size' => $currentBatchSize,
];
$this->logger
->info($this
->t("Indexing a maximum number of @limit items (@batch_size items per batch run) for the index '@index'.", $arguments));
// Create the batch.
try {
IndexBatchHelper::create($index, $currentBatchSize, $current_limit);
$batchSet = TRUE;
} catch (SearchApiException $e) {
throw new ConsoleException($this
->t("Couldn't create a batch, please check the batch size and limit parameters."));
}
}
return $batchSet;
}
/**
* Resets the tracker for an index, optionally filtering on entity types.
*
* @param string[]|null $indexIds
* (optional) An array of index IDs, or NULL if we should reset the trackers
* of all indexes.
* @param string[] $entityTypes
* (optional) An array of entity types for which to reset the tracker.
*
* @return bool
* TRUE if any index was affected, FALSE otherwise.
*
* @throws \Drupal\search_api\SearchApiException
* Thrown if one of the affected indexes had an invalid tracker set, or some
* other internal error occurred.
*/
public function resetTrackerCommand(array $indexIds = NULL, array $entityTypes = []) {
$indexes = $this
->loadIndexes($indexIds);
if (!$indexes) {
return FALSE;
}
foreach ($indexes as $index) {
if (!$index
->status()) {
continue;
}
if (!empty($entityTypes)) {
$datasources = $index
->getDatasources();
$reindexed_datasources = [];
foreach ($datasources as $datasource_id => $datasource) {
if (in_array($datasource
->getEntityTypeId(), $entityTypes)) {
$index
->getTrackerInstance()
->trackAllItemsUpdated($datasource_id);
$reindexed_datasources[] = $datasource
->label();
}
}
$description = 'This hook is deprecated in search_api:8.x-1.14 and is removed from search_api:2.0.0. Please use the "search_api.reindex_scheduled" event instead. See https://www.drupal.org/node/3059866';
$this->moduleHandler
->invokeAllDeprecated($description, 'search_api_index_reindex', [
$index,
FALSE,
]);
$event_name = SearchApiEvents::REINDEX_SCHEDULED;
$event = new ReindexScheduledEvent($index, FALSE);
$this->eventDispatcher
->dispatch($event_name, $event);
$arguments = [
'!index' => $index
->label(),
'!datasources' => implode(', ', $reindexed_datasources),
];
$this->logger
->info($this
->t('The following datasources of !index were successfully scheduled for reindexing: !datasources.', $arguments));
}
else {
$index
->reindex();
$this->logger
->info($this
->t('!index was successfully scheduled for reindexing.', [
'!index' => $index
->label(),
]));
}
}
return TRUE;
}
/**
* Rebuilds the tracker for an index.
*
* @param string[]|null $indexIds
* (optional) An array of index IDs, or NULL if we should reset the trackers
* of all indexes.
*
* @return bool
* TRUE if any index was affected, FALSE otherwise.
*/
public function rebuildTrackerCommand(array $indexIds = NULL) {
$indexes = $this
->loadIndexes($indexIds);
if (!$indexes) {
return FALSE;
}
foreach ($indexes as $index) {
if ($index
->status()) {
$index
->rebuildTracker();
$this->logger
->info($this
->t('The tracking information for search index %name will be rebuilt.', [
'%name' => $index
->label(),
]));
}
}
return TRUE;
}
/**
* Deletes all items from one or more indexes.
*
* @param string[]|null $indexIds
* (optional) An array of index IDs, or NULL if we should delete all items
* from all indexes.
*
* @return bool
* TRUE when the clearing was successful, FALSE when no indexes were found.
*
* @throws \Drupal\search_api\SearchApiException
* Thrown if one of the affected indexes had an invalid tracker set, or some
* other internal error occurred.
*/
public function clearIndexCommand(array $indexIds = NULL) {
$indexes = $this
->loadIndexes($indexIds);
if (!$indexes) {
return FALSE;
}
foreach ($indexes as $index) {
if ($index
->status()) {
$index
->clear();
$this->logger
->info($this
->t('@index was successfully cleared.', [
'@index' => $index
->label(),
]));
}
}
return TRUE;
}
/**
* Returns an array of results.
*
* @param string $indexId
* The index to search in.
* @param string|null $keyword
* (optional) The word to search for.
*
* @return array
* An array of results, each of which is represented by an associative
* array with the following keys:
* - id: The internal ID of the item.
* - label: The label of the item, or NULL if it could not be determined.
*
* @throws \Drupal\search_api\ConsoleException
* Thrown if searching failed for any reason.
* @throws \Drupal\search_api\SearchApiException
* Thrown if no search query could be created for the given index, for
* example because it is disabled or its server could not be loaded.
*/
public function searchIndexCommand($indexId, $keyword = NULL) {
$indexes = $this
->loadIndexes([
$indexId,
]);
if (empty($indexes[$indexId])) {
throw new ConsoleException($this
->t('@index was not found'));
}
$query = $indexes[$indexId]
->query();
if ($keyword !== NULL) {
$query
->keys($keyword);
}
$query
->range(0, 10);
try {
$results = $query
->execute();
} catch (SearchApiException $e) {
throw new ConsoleException($e
->getMessage(), 0, $e);
}
$rows = [];
foreach ($results
->getResultItems() as $item) {
try {
$label = $item
->getDatasource()
->getItemLabel($item
->getOriginalObject());
} catch (SearchApiException $e) {
$label = NULL;
}
$rows[] = [
'id' => $item
->getId(),
'label' => $label,
];
}
return $rows;
}
/**
* Returns a list of servers created on the page.
*
* @return array
* An associative array, keyed by search server ID, each value an
* associative array with the following keys:
* - id: The ID of the search server.
* - name: The human readable name of the search server.
* - status: The enabled status of the server.
*
* @throws \Drupal\search_api\ConsoleException
* Thrown if no servers could be loaded.
*/
public function serverListCommand() {
$servers = $this
->loadServers();
if (count($servers) === 0) {
throw new ConsoleException($this
->t('There are no servers present.'));
}
$rows = [];
foreach ($servers as $server) {
$rows[$server
->id()] = [
'id' => $server
->id(),
'name' => $server
->label(),
'status' => $server
->status() ? $this
->t('enabled') : $this
->t('disabled'),
];
}
return $rows;
}
/**
* Enables a server.
*
* @param string $serverId
* The server's ID.
*
* @throws \Drupal\search_api\ConsoleException
* Thrown if the server couldn't be loaded.
* @throws \Drupal\Core\Entity\EntityStorageException
* Thrown if an internal error occurred when saving the server.
*/
public function enableServerCommand($serverId) {
$servers = $this
->loadServers([
$serverId,
]);
if (empty($servers)) {
throw new ConsoleException($this
->t('The server could not be loaded.'));
}
/** @var \Drupal\search_api\ServerInterface $server */
$server = $this
->reloadEntityOverrideFree(reset($servers));
$server
->setStatus(TRUE)
->save();
}
/**
* Disables a server.
*
* @param string $serverId
* The server's ID.
*
* @throws \Drupal\search_api\ConsoleException
* Thrown if the server couldn't be loaded.
* @throws \Drupal\Core\Entity\EntityStorageException
* Thrown if an internal error occurred when saving the server.
*/
public function disableServerCommand($serverId) {
$servers = $this
->loadServers([
$serverId,
]);
if (empty($servers)) {
throw new ConsoleException($this
->t('The server could not be loaded.'));
}
/** @var \Drupal\search_api\ServerInterface $server */
$server = $this
->reloadEntityOverrideFree(reset($servers));
$server
->setStatus(FALSE)
->save();
}
/**
* Clears all indexes on a server.
*
* @param string $serverId
* The ID of the server to clear.
*
* @throws \Drupal\search_api\ConsoleException
* Thrown if the server couldn't be loaded.
* @throws \Drupal\search_api\SearchApiException
* Thrown if one of the affected indexes had an invalid tracker set, or some
* other internal error occurred.
*/
public function clearServerCommand($serverId) {
$servers = $this
->loadServers([
$serverId,
]);
if (empty($servers)) {
throw new ConsoleException($this
->t('The server could not be loaded.'));
}
/** @var \Drupal\search_api\ServerInterface $server */
$server = $this
->reloadEntityOverrideFree(reset($servers));
foreach ($server
->getIndexes() as $index) {
$index
->clear();
}
}
/**
* Switches an index to another server.
*
* @param string $indexId
* The ID of the index.
* @param string $serverId
* The ID of the index's new server.
*
* @throws \Drupal\search_api\ConsoleException
* If either the index or the server couldn't be loaded.
*/
public function setIndexServerCommand($indexId, $serverId) {
// Fetch current index and server data.
$index = $this
->loadIndexes([
$indexId,
]);
$server = $this
->loadServers([
$serverId,
]);
$index = reset($index);
$server = reset($server);
if (!$index) {
throw new ConsoleException($this
->t('Invalid index ID "@index_id".', [
'@index_id' => $indexId,
]));
}
if (!$server) {
throw new ConsoleException($this
->t('Invalid server ID "@server_id".', [
'@server_id' => $serverId,
]));
}
// Set the new server on the index.
try {
/** @var \Drupal\search_api\IndexInterface $index */
$index = $this
->reloadEntityOverrideFree($index);
$index
->setServer($server);
$index
->save();
$this->logger
->info($this
->t('Index @index has been set to use server @server and items have been queued for indexing.', [
'@index' => $indexId,
'@server' => $serverId,
]));
} catch (EntityStorageException $e) {
$this->logger
->warning($e
->getMessage());
$this->logger
->warning($this
->t('There was an error setting index @index to use server @server, or this index is already configured to use this server.', [
'@index' => $indexId,
'@server' => $serverId,
]));
}
}
/**
* Returns the indexes with the given IDs.
*
* @param array|null $indexIds
* (optional) The IDs of the search indexes to return, or NULL to load all
* indexes. An array with a single NULL value is interpreted the same way as
* passing NULL.
*
* @return \Drupal\search_api\IndexInterface[]
* An array of search indexes.
*/
public function loadIndexes(array $indexIds = NULL) {
if ($indexIds === [
NULL,
]) {
$indexIds = NULL;
}
return $this->indexStorage
->loadMultiple($indexIds);
}
/**
* Returns the servers with the given IDs.
*
* @param array|null $serverIds
* (optional) The IDs of the search servers to return, or NULL to load all
* servers.
*
* @return \Drupal\search_api\ServerInterface[]
* An array of search servers.
*/
public function loadServers(array $serverIds = NULL) {
return $this->serverStorage
->loadMultiple($serverIds);
}
/**
* Returns the total number of search indexes.
*
* @return int
* The number of search indexes on this site.
*/
public function getIndexCount() {
return count($this
->loadIndexes());
}
/**
* Changes the state of a single index.
*
* @param \Drupal\search_api\IndexInterface $index
* The index to be enabled.
* @param bool $enable
* (optional) TRUE to enable, FALSE to disable the index.
*/
public function setIndexState(IndexInterface $index, $enable = TRUE) {
$state_label = $enable ? $this
->t('enabled') : $this
->t('disabled');
$method = $enable ? 'enable' : 'disable';
if ($index
->status() == $enable) {
$this->logger
->info($this
->t("The index @index is already @desired_state.", [
'@index' => $index
->label(),
'@desired_state' => $state_label,
]));
return;
}
if (!$index
->getServerId()) {
$this->logger
->warning($this
->t("Index @index could not be @desired_state because it is not bound to any server.", [
'@index' => $index
->label(),
'@desired_state' => $state_label,
]));
return;
}
$index = $this
->reloadEntityOverrideFree($index);
$index
->{$method}()
->save();
$this->logger
->info($this
->t("The index @index was successfully @desired_state.", [
'@index' => $index
->label(),
'@desired_state' => $state_label,
]));
}
/**
* Loads an override-free copy of a config entity, for saving.
*
* @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity
* The entity to reload.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The override-free version of the entity, or NULL if it couldn't be
* loaded.
*/
public function reloadEntityOverrideFree(ConfigEntityInterface $entity) {
try {
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $storage */
$storage = $this->entityTypeManager
->getStorage($entity
->getEntityTypeId());
return $storage
->loadOverrideFree($entity
->id());
} catch (InvalidPluginDefinitionException $e) {
return NULL;
}
}
/**
* Translates a string using the set translation method.
*
* @param string $message
* The message to translate.
* @param array $arguments
* (optional) The translation arguments.
*
* @return string
* The translated message.
*/
public function t($message, array $arguments = []) {
return call_user_func_array($this->translationFunction, [
$message,
$arguments,
]);
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
CommandHelper:: |
protected | property | The entity type manager. | |
CommandHelper:: |
protected | property | The event dispatcher. | |
CommandHelper:: |
protected | property | The storage for search index entities. | |
CommandHelper:: |
protected | property | The module handler. | |
CommandHelper:: |
protected | property | The storage for search server entities. | |
CommandHelper:: |
protected | property | A callable for translating strings. | |
CommandHelper:: |
public | function | Deletes all items from one or more indexes. | |
CommandHelper:: |
public | function | Clears all indexes on a server. | |
CommandHelper:: |
public | function | Disables one or more enabled search indexes. | |
CommandHelper:: |
public | function | Disables a server. | |
CommandHelper:: |
public | function | Enables one or more disabled search indexes. | |
CommandHelper:: |
public | function | Enables a server. | |
CommandHelper:: |
public | function | Returns the total number of search indexes. | |
CommandHelper:: |
public | function | Indexes items on one or more indexes. | |
CommandHelper:: |
public | function | Lists all search indexes. | |
CommandHelper:: |
public | function | Lists all search indexes with their status. | |
CommandHelper:: |
public | function | Returns the indexes with the given IDs. | |
CommandHelper:: |
public | function | Returns the servers with the given IDs. | |
CommandHelper:: |
public | function | Rebuilds the tracker for an index. | |
CommandHelper:: |
public | function | Loads an override-free copy of a config entity, for saving. | |
CommandHelper:: |
public | function | Resets the tracker for an index, optionally filtering on entity types. | |
CommandHelper:: |
public | function | Returns an array of results. | |
CommandHelper:: |
public | function | Returns a list of servers created on the page. | |
CommandHelper:: |
public | function | Switches an index to another server. | |
CommandHelper:: |
public | function | Changes the state of a single index. | |
CommandHelper:: |
public | function | Translates a string using the set translation method. | |
CommandHelper:: |
public | function | Constructs a CommandHelper object. |