Skip to content

Commit

Permalink
Add argument/option autocompletion to field commands (#5678)
Browse files Browse the repository at this point in the history
  • Loading branch information
DieterHolvoet authored Aug 7, 2023
1 parent 2156b56 commit 1d39b54
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 32 deletions.
9 changes: 7 additions & 2 deletions src/Commands/field/EntityTypeBundleAskTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,19 @@
*/
trait EntityTypeBundleAskTrait
{
protected function askEntityType(): ?string
protected function getFieldableEntityTypes(): array
{
$entityTypeDefinitions = array_filter(
return array_filter(
$this->entityTypeManager->getDefinitions(),
function (EntityTypeInterface $entityType) {
return $entityType->entityClassImplements(FieldableEntityInterface::class);
}
);
}

protected function askEntityType(): ?string
{
$entityTypeDefinitions = $this->getFieldableEntityTypes();
$choices = [];

foreach ($entityTypeDefinitions as $entityTypeDefinition) {
Expand Down
10 changes: 10 additions & 0 deletions src/Commands/field/FieldBaseInfoCommands.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\DependencyInjection\ContainerInterface;

class FieldBaseInfoCommands extends DrushCommands
Expand Down Expand Up @@ -66,6 +68,7 @@ public static function create(ContainerInterface $container): self
#[CLI\FilterDefaultField(field: 'field_name')]
#[CLI\Usage(name: 'field:base-info taxonomy_term', description: 'List all base fields.')]
#[CLI\Usage(name: 'field:base-info', description: 'List all base fields and fill in the remaining information through prompts.')]
#[CLI\Complete(method_name_or_callable: 'complete')]
#[CLI\Version(version: '11.0')]
public function info(?string $entityType = null, array $options = [
'format' => 'table',
Expand All @@ -78,4 +81,11 @@ public function info(?string $entityType = null, array $options = [

return $this->getRowsOfFieldsByFieldDefinitions($fieldDefinitions);
}

public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->getCompletionName() === 'entityType') {
$suggestions->suggestValues(array_keys($this->getFieldableEntityTypes()));
}
}
}
27 changes: 27 additions & 0 deletions src/Commands/field/FieldBaseOverrideCreateCommands.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use Drupal\Core\Field\Entity\BaseFieldOverride;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\DependencyInjection\ContainerInterface;
Expand Down Expand Up @@ -58,6 +60,7 @@ public static function create(ContainerInterface $container): self
#[CLI\Usage(name: 'field:base-field-override-create', description: 'Create a base field override by answering the prompts.')]
#[CLI\Usage(name: 'field:base-field-override-create taxonomy_term tag', description: 'Create a base field override and fill in the remaining information through prompts.')]
#[CLI\Usage(name: 'field:base-field-override-create taxonomy_term tag --field-name=name --field-label=Label --is-required=1', description: 'Create a base field override in a completely non-interactive way.')]
#[CLI\Complete(method_name_or_callable: 'complete')]
#[CLI\Version(version: '11.0')]
public function baseOverrideCreateField(?string $entityType = null, ?string $bundle = null, array $options = [
'field-name' => InputOption::VALUE_REQUIRED,
Expand Down Expand Up @@ -118,6 +121,30 @@ public function baseOverrideCreateField(?string $entityType = null, ?string $bun
$this->logResult($baseFieldOverride);
}

public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->getCompletionType() === CompletionInput::TYPE_ARGUMENT_VALUE) {
if ($input->getCompletionName() === 'entityType') {
$suggestions->suggestValues(array_keys($this->getFieldableEntityTypes()));
}

if ($input->getCompletionName() === 'bundle') {
$entityTypeId = $input->getArgument('entityType');
$bundleInfo = $this->entityTypeBundleInfo->getBundleInfo($entityTypeId);

$suggestions->suggestValues(array_keys($bundleInfo));
}
}

if ($input->getCompletionType() === CompletionInput::TYPE_OPTION_VALUE) {
if ($input->getCompletionName() === 'field-name') {
$entityTypeId = $input->getArgument('entityType');
$definitions = $this->entityFieldManager->getBaseFieldDefinitions($entityTypeId);
$suggestions->suggestValues(array_keys($definitions));
}
}
}

protected function askFieldName(string $entityType): ?string
{
/** @var BaseFieldDefinition[] $definitions */
Expand Down
63 changes: 60 additions & 3 deletions src/Commands/field/FieldCreateCommands.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
use Drupal\field\FieldStorageConfigInterface;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\DependencyInjection\ContainerInterface;
Expand Down Expand Up @@ -103,6 +105,7 @@ public function setContentTranslationManager(ContentTranslationManagerInterface
#[CLI\Usage(name: self::CREATE, description: 'Create a field by answering the prompts.')]
#[CLI\Usage(name: 'field-create taxonomy_term tag', description: 'Create a field and fill in the remaining information through prompts.')]
#[CLI\Usage(name: 'field-create taxonomy_term tag --field-name=field_tag_label --field-label=Label --field-type=string --field-widget=string_textfield --is-required=1 --cardinality=2', description: 'Create a field in a completely non-interactive way.')]
#[CLI\Complete(method_name_or_callable: 'complete')]
#[CLI\Version(version: '11.0')]
public function fieldCreate(?string $entityType = null, ?string $bundle = null, array $options = [
'field-name' => InputOption::VALUE_REQUIRED,
Expand Down Expand Up @@ -199,11 +202,65 @@ public function fieldCreate(?string $entityType = null, ?string $bundle = null,
$this->logResult($field);
}

public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->getCompletionType() === CompletionInput::TYPE_ARGUMENT_VALUE) {
if ($input->getCompletionName() === 'entityType') {
$suggestions->suggestValues(array_keys($this->getFieldableEntityTypes()));
}

if ($input->getCompletionName() === 'bundle') {
$entityTypeId = $input->getArgument('entityType');
$bundleInfo = $this->entityTypeBundleInfo->getBundleInfo($entityTypeId);

$suggestions->suggestValues(array_keys($bundleInfo));
}
}

if ($input->getCompletionType() === CompletionInput::TYPE_OPTION_VALUE) {
if ($input->getCompletionName() === 'existing-field-name') {
$entityTypeId = $input->getArgument('entityType');
$bundle = $input->getArgument('bundle');
$showMachineNames = (bool) $input->getOption('show-machine-names');

if ($entityTypeId && $bundle) {
$choices = $this->getExistingFieldStorageOptions($entityTypeId, $bundle, $showMachineNames);
$suggestions->suggestValues(array_keys($choices));
}
}

if ($input->getCompletionName() === 'field-name') {
$fieldLabel = $input->getOption('field-label');
$bundle = $input->getArgument('bundle');

if ($fieldLabel && $bundle) {
$suggestion = $this->generateFieldName($fieldLabel, $bundle);
$suggestions->suggestValue($suggestion);
}
}

if ($input->getCompletionName() === 'field-type') {
$fieldTypes = $this->fieldTypePluginManager->getDefinitions();
$suggestions->suggestValues(array_keys($fieldTypes));
}

if ($input->getCompletionName() === 'field-widget') {
$fieldType = $input->getOption('field-type');

if ($fieldType) {
$fieldWidgets = $this->widgetPluginManager->getOptions($fieldType);
$suggestions->suggestValues(array_keys($fieldWidgets));
}
}
}
}

protected function askExistingFieldName(): ?string
{
$entityType = $this->input->getArgument('entityType');
$bundle = $this->input->getArgument('bundle');
$choices = $this->getExistingFieldStorageOptions($entityType, $bundle);
$showMachineNames = $this->input->getOption('show-machine-names');
$choices = $this->getExistingFieldStorageOptions($entityType, $bundle, $showMachineNames);

if (empty($choices)) {
return null;
Expand Down Expand Up @@ -509,7 +566,7 @@ protected function fieldStorageExists(string $fieldName, string $entityType): bo
return isset($fieldStorageDefinitions[$fieldName]);
}

protected function getExistingFieldStorageOptions(string $entityType, string $bundle): array
protected function getExistingFieldStorageOptions(string $entityType, string $bundle, bool $showMachineNames): array
{
$fieldTypes = $this->fieldTypePluginManager->getDefinitions();
$options = [];
Expand All @@ -521,7 +578,7 @@ protected function getExistingFieldStorageOptions(string $entityType, string $bu
// - field storages that should not be added via user interface,
// - field storages that already have a field in the bundle.
$fieldType = $fieldStorage->getType();
$label = $this->input->getOption('show-machine-names')
$label = $showMachineNames
? $fieldTypes[$fieldType]['id']
: $fieldTypes[$fieldType]['label'];

Expand Down
14 changes: 14 additions & 0 deletions src/Commands/field/FieldDefinitionCommands.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use Drupal\Core\Field\WidgetPluginManager;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\DependencyInjection\ContainerInterface;

final class FieldDefinitionCommands extends DrushCommands
Expand Down Expand Up @@ -90,6 +92,7 @@ public function types(array $options = ['format' => 'yaml']): UnstructuredListDa
),
]
#[CLI\FilterDefaultField(field: 'id')]
#[CLI\Complete(method_name_or_callable: 'complete')]
public function widgets(array $options = ['format' => 'yaml', 'field-type' => self::REQ]): UnstructuredListData
{
$processor = static fn(array $definition): array => [
Expand Down Expand Up @@ -126,6 +129,7 @@ public function widgets(array $options = ['format' => 'yaml', 'field-type' => se
),
]
#[CLI\FilterDefaultField(field: 'id')]
#[CLI\Complete(method_name_or_callable: 'complete')]
public function formatters(array $options = ['format' => 'yaml', 'field-type' => self::REQ]): UnstructuredListData
{
$processor = static fn(array $definition): array => [
Expand All @@ -143,6 +147,16 @@ public function formatters(array $options = ['format' => 'yaml', 'field-type' =>
return new UnstructuredListData($definitions);
}

public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->getCompletionType() === CompletionInput::TYPE_OPTION_VALUE) {
if ($input->getCompletionName() === 'field-type') {
$fieldTypes = $this->typePluginManager->getDefinitions();
$suggestions->suggestValues(array_keys($fieldTypes));
}
}
}

/**
* Filters definitions by applicable field types.
*/
Expand Down
101 changes: 74 additions & 27 deletions src/Commands/field/FieldDeleteCommands.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use Drupal\field\FieldConfigInterface;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\DependencyInjection\ContainerInterface;

Expand Down Expand Up @@ -54,6 +56,7 @@ public static function create(ContainerInterface $container): self
#[CLI\Usage(name: 'field-delete taxonomy_term tag', description: 'Delete a field and fill in the remaining information through prompts.')]
#[CLI\Usage(name: 'field-delete taxonomy_term tag --field-name=field_tag_label', description: 'Delete a field in a non-interactive way.')]
#[CLI\Usage(name: 'field-delete taxonomy_term --field-name=field_tag_label --all-bundles', description: 'Delete a field from all bundles.')]
#[CLI\Complete(method_name_or_callable: 'complete')]
#[CLI\Version(version: '11.0')]
public function delete(?string $entityType = null, ?string $bundle = null, array $options = [
'field-name' => InputOption::VALUE_REQUIRED,
Expand Down Expand Up @@ -125,42 +128,43 @@ public function delete(?string $entityType = null, ?string $bundle = null, array
field_purge_batch(10);
}

protected function askExisting(string $entityType, ?string $bundle): ?string
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
/** @var FieldConfigInterface[] $fieldConfigs */
$fieldConfigs = $this->entityTypeManager
->getStorage('field_config')
->loadByProperties([
'entity_type' => $entityType,
]);
if ($input->getCompletionType() === CompletionInput::TYPE_ARGUMENT_VALUE) {
if ($input->getCompletionName() === 'entityType') {
$suggestions->suggestValues(array_keys($this->getFieldableEntityTypes()));
}

if ($fieldConfigs === []) {
throw new \InvalidArgumentException(
dt("Entity type '!entityType' has no fields.", [
'!entityType' => $entityType,
])
);
if ($input->getCompletionName() === 'bundle') {
$entityTypeId = $input->getArgument('entityType');
$bundleInfo = $this->entityTypeBundleInfo->getBundleInfo($entityTypeId);

$suggestions->suggestValues(array_keys($bundleInfo));
}
}

if ($bundle !== null) {
/** @var FieldConfigInterface[] $fieldConfigs */
$fieldConfigs = $this->entityTypeManager
->getStorage('field_config')
->loadByProperties([
'entity_type' => $entityType,
'bundle' => $bundle,
]);
if ($input->getCompletionType() === CompletionInput::TYPE_OPTION_VALUE) {
if ($input->getCompletionName() === 'field-name') {
$entityTypeId = $input->getArgument('entityType');

if ($fieldConfigs === []) {
throw new \InvalidArgumentException(
dt("Bundle '!bundle' has no fields.", [
'!bundle' => $bundle,
])
);
if ($entityTypeId) {
$bundle = $input->getArgument('bundle');
$fieldNames = array_map(
fn (FieldConfig $fieldConfig) => $fieldConfig->get('field_name'),
$this->getFieldConfigs($entityTypeId, $bundle),
);

$suggestions->suggestValues($fieldNames);
}
}
}
}

protected function askExisting(string $entityType, ?string $bundle): ?string
{
$fieldConfigs = $this->getFieldConfigs($entityType, $bundle);
$choices = [];

foreach ($fieldConfigs as $fieldConfig) {
$label = $this->input->getOption('show-machine-names')
? $fieldConfig->get('field_name')
Expand Down Expand Up @@ -209,6 +213,49 @@ protected function askBundle(): ?string
return $answer;
}

/**
* Returns all field configs for the given entity type and bundle.
*
* @return FieldConfigInterface[]
*/
protected function getFieldConfigs(string $entityType, ?string $bundle): array
{
/** @var FieldConfigInterface[] $fieldConfigs */
$fieldConfigs = $this->entityTypeManager
->getStorage('field_config')
->loadByProperties([
'entity_type' => $entityType,
]);

if ($fieldConfigs === []) {
throw new \InvalidArgumentException(
dt("Entity type '!entityType' has no fields.", [
'!entityType' => $entityType,
])
);
}

if ($bundle !== null) {
/** @var FieldConfigInterface[] $fieldConfigs */
$fieldConfigs = $this->entityTypeManager
->getStorage('field_config')
->loadByProperties([
'entity_type' => $entityType,
'bundle' => $bundle,
]);

if ($fieldConfigs === []) {
throw new \InvalidArgumentException(
dt("Bundle '!bundle' has no fields.", [
'!bundle' => $bundle,
])
);
}
}

return $fieldConfigs;
}

protected function deleteFieldConfig(FieldConfigInterface $fieldConfig): void
{
$fieldStorage = $fieldConfig->getFieldStorageDefinition();
Expand Down
Loading

0 comments on commit 1d39b54

Please sign in to comment.