Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add field:info command #4928

Merged
merged 6 commits into from
Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/Drupal/Commands/core/AskBundleTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Drush\Drupal\Commands\core;

use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\Console\Input\InputInterface;

/**
* @property InputInterface $input
* @property EntityTypeBundleInfoInterface $entityTypeBundleInfo
* @property EntityTypeManagerInterface $entityTypeManager
*/
trait AskBundleTrait
{
protected function askBundle(): ?string
{
$entityTypeId = $this->input->getArgument('entityType');
$entityTypeDefinition = $this->entityTypeManager->getDefinition($entityTypeId);
$bundleEntityType = $entityTypeDefinition->getBundleEntityType();
$bundleInfo = $this->entityTypeBundleInfo->getBundleInfo($entityTypeId);
$choices = [];

if (empty($bundleInfo)) {
if ($bundleEntityType) {
throw new \InvalidArgumentException(
t('Entity type with id \':entityType\' does not have any bundles.', [':entityType' => $entityTypeId])
);
}

return null;
}

foreach ($bundleInfo as $bundle => $data) {
$label = $this->input->getOption('show-machine-names') ? $bundle : $data['label'];
$choices[$bundle] = $label;
}

if (!$answer = $this->io()->choice('Bundle', $choices)) {
throw new \InvalidArgumentException(t('The bundle argument is required.'));
}

return $answer;
}
}
59 changes: 59 additions & 0 deletions src/Drupal/Commands/core/FieldDefinitionRowsOfFieldsTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace Drush\Drupal\Commands\core;

use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;

trait FieldDefinitionRowsOfFieldsTrait
{
public function renderArray($key, $value, FormatterOptions $options)
{
if (is_array($value)) {
return implode(', ', $value);
}

return $value;
}

public function renderBoolean($key, $value, FormatterOptions $options)
{
if (is_bool($value)) {
return $value ? '✔' : '';
}

return $value;
}

protected function getRowsOfFieldsByFieldDefinitions(array $fieldDefinitions): RowsOfFields
{
$rows = [];

foreach ($fieldDefinitions as $field) {
$storage = $field->getFieldStorageDefinition();
$handlerSettings = $field->getSetting('handler_settings');

$rows[$field->getName()] = [
'label' => $field->getLabel(),
'description' => $field->getDescription(),
'field_name' => $field->getName(),
'field_type' => $field->getType(),
'required' => $field->isRequired(),
'translatable' => $field->isTranslatable(),
'cardinality' => $storage->getCardinality(),
'default_value' => empty($field->getDefaultValueLiteral()) ? null : $field->getDefaultValueLiteral(),
'default_value_callback' => $field->getDefaultValueCallback(),
'allowed_values' => $storage->getSetting('allowed_values'),
'allowed_values_function' => $storage->getSetting('allowed_values_function'),
'handler' => $field->getSetting('handler'),
'target_bundles' => $handlerSettings['target_bundles'] ?? null,
];
}

$result = new RowsOfFields($rows);
$result->addRendererFunction([$this, 'renderArray']);
$result->addRendererFunction([$this, 'renderBoolean']);

return $result;
}
}
87 changes: 87 additions & 0 deletions src/Drupal/Commands/core/FieldInfoCommands.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace Drush\Drupal\Commands\core;

use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
use Drupal\Core\Entity\EntityTypeBundleInfo;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drush\Commands\DrushCommands;

class FieldInfoCommands extends DrushCommands
{
use AskBundleTrait;
use FieldDefinitionRowsOfFieldsTrait;
use ValidateEntityTypeTrait;

/** @var EntityTypeManagerInterface */
protected $entityTypeManager;
/** @var EntityTypeBundleInfo */
protected $entityTypeBundleInfo;

public function __construct(
EntityTypeManagerInterface $entityTypeManager,
EntityTypeBundleInfo $entityTypeBundleInfo
) {
$this->entityTypeManager = $entityTypeManager;
$this->entityTypeBundleInfo = $entityTypeBundleInfo;
}

/**
* List all configurable fields of an entity bundle
*
* @command field:info
* @aliases field-info,fi
*
* @param string $entityType
* The machine name of the entity type
* @param string $bundle
* The machine name of the bundle
*
* @option show-machine-names
* Show machine names instead of labels in option lists.
*
* @default-fields field_name,required,field_type,cardinality
* @field-labels
* label: Label
* description: Description
* field_name: Field name
* field_type: Field type
* required: Required
* translatable: Translatable
* cardinality: Cardinality
* default_value: Default value
* default_value_callback: Default value callback
* allowed_values: Allowed values
* allowed_values_function: Allowed values function
* handler: Selection handler
* target_bundles: Target bundles
* @filter-default-field field_name
* @table-style default
*
* @usage drush field-info taxonomy_term tag
* List all fields.
* @usage drush field:info
* List all fields and fill in the remaining information through prompts.
*
* @version 11.0
*/
public function info(string $entityType, ?string $bundle = null, array $options = [
'format' => 'table',
]): RowsOfFields
{
$this->validateEntityType($entityType);

$this->input->setArgument('bundle', $bundle = $bundle ?? $this->askBundle());
$this->validateBundle($entityType, $bundle);

$fieldDefinitions = $this->entityTypeManager
->getStorage('field_config')
->loadByProperties([
'entity_type' => $entityType,
'bundle' => $bundle,
]);

return $this->getRowsOfFieldsByFieldDefinitions($fieldDefinitions);
}
}
46 changes: 46 additions & 0 deletions src/Drupal/Commands/core/ValidateEntityTypeTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Drush\Drupal\Commands\core;

use Drupal\Core\Entity\EntityTypeManagerInterface;

/**
* @property EntityTypeManagerInterface $entityTypeManager
*/
trait ValidateEntityTypeTrait
{
protected function validateEntityType(string $entityTypeId): void
{
if (!$this->entityTypeManager->hasDefinition($entityTypeId)) {
throw new \InvalidArgumentException(
t("Entity type with id ':entityType' does not exist.", [':entityType' => $entityTypeId])
);
}
}

protected function validateBundle(string $entityTypeId, string $bundle): void
{
if (!$entityTypeDefinition = $this->entityTypeManager->getDefinition($entityTypeId)) {
return;
}

$bundleEntityType = $entityTypeDefinition->getBundleEntityType();

if ($bundleEntityType === null && $bundle === $entityTypeId) {
return;
}

$bundleDefinition = $this->entityTypeManager
->getStorage($bundleEntityType)
->load($bundle);

if (!$bundleDefinition) {
throw new \InvalidArgumentException(
t("Bundle ':bundle' does not exist on entity type with id ':entityType'.", [
':bundle' => $bundle,
':entityType' => $entityTypeId,
])
);
}
}
}
7 changes: 7 additions & 0 deletions src/Drupal/Commands/core/drush.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ services:
- [ setContentTranslationManager, [ '@?content_translation.manager' ] ]
tags:
- { name: drush.command }
field.info.commands:
class: \Drush\Drupal\Commands\core\FieldInfoCommands
arguments:
- '@entity_type.manager'
- '@entity_type.bundle.info'
tags:
- { name: drush.command }
link.hooks:
class: \Drush\Drupal\Commands\core\LinkHooks
arguments:
Expand Down
23 changes: 21 additions & 2 deletions tests/functional/FieldCreateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Unish;

use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Webmozart\PathUtil\Path;

/**
Expand Down Expand Up @@ -35,12 +36,12 @@ public function testFieldCreate()
$this->assertStringNotContainsString('bundle', $this->getErrorOutputRaw());

// New field storage
$this->drush('field:create', ['unish_article', 'alpha'], ['field-label' => 'Test', 'field-name' => 'field_test2', 'field-type' => 'entity_reference', 'field-widget' => 'entity_reference_autocomplete', 'cardinality' => '-1'], null, null, self::EXIT_ERROR);
$this->drush('field:create', ['unish_article', 'alpha'], ['field-label' => 'Test', 'field-name' => 'field_test2', 'field-type' => 'entity_reference', 'field-widget' => 'entity_reference_autocomplete', 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED], null, null, self::EXIT_ERROR);
$this->assertStringContainsString('The target-type option is required.', $this->getErrorOutputRaw());
/// @todo --target-bundle not yet validated.
// $this->drush('field:create', ['unish_article', 'alpha'], ['field-label' => 'Test', 'field-name' => 'field_test3', 'field-type' => 'entity_reference', 'field-widget' => 'entity_reference_autocomplete', 'cardinality' => '-1', 'target-type' => 'unish_article', 'target-bundle' => 'NO-EXIST']);
// $this->assertStringContainsString('TODO', $this->getErrorOutputRaw());
$this->drush('field:create', ['unish_article', 'alpha'], ['field-label' => 'Test', 'field-name' => 'field_test3', 'field-type' => 'entity_reference', 'field-widget' => 'entity_reference_autocomplete', 'cardinality' => '-1', 'target-type' => 'unish_article', 'target-bundle' => 'beta']);
$this->drush('field:create', ['unish_article', 'alpha'], ['field-label' => 'Test', 'field-name' => 'field_test3', 'field-type' => 'entity_reference', 'field-widget' => 'entity_reference_autocomplete', 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, 'target-type' => 'unish_article', 'target-bundle' => 'beta']);
$this->assertStringContainsString("Successfully created field 'field_test3' on unish_article type with bundle 'alpha'", $this->getErrorOutputRaw());
$this->assertStringContainsString("Further customisation can be done at the following url:
http://dev/admin/structure/unish_article_types/manage/alpha/fields/unish_article.alpha.field_test3", $this->getErrorOutputRaw());
Expand All @@ -58,4 +59,22 @@ public function testFieldCreate()
$this->drush('field:create', ['unish_article', 'beta'], ['existing-field-name' => 'field_test3', 'field-label' => 'Body', 'field-widget' => 'text_textarea_with_summary'], null, null, self::EXIT_ERROR);
$this->assertStringContainsString('Field with name \'field_test3\' already exists on bundle \'beta\'', $this->getErrorOutputRaw());
}

public function testFieldInfo()
{
$this->drush('field:create', ['unish_article', 'alpha'], ['field-label' => 'Test', 'field-name' => 'field_test4', 'field-description' => 'baz', 'field-type' => 'entity_reference', 'is-required' => true, 'field-widget' => 'entity_reference_autocomplete', 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, 'target-type' => 'unish_article', 'target-bundle' => 'beta']);
$this->assertStringContainsString("Successfully created field 'field_test4' on unish_article type with bundle 'alpha'", $this->getSimplifiedErrorOutput());

$this->drush('field:info', ['unish_article'], [], null, null, self::EXIT_ERROR);
$this->assertStringContainsString('The bundle argument is required.', $this->getSimplifiedErrorOutput());
$this->drush('field:info', ['unish_article', 'alpha'], ['format' => 'json', 'fields' => '*']);
$json = $this->getOutputFromJSON('field_test4');
$this->assertSame('field_test4', $json['field_name']);
$this->assertTrue($json['required']);
$this->assertSame('entity_reference', $json['field_type']);
$this->assertSame('baz', $json['description']);
$this->assertSame(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, $json['cardinality']);
$this->assertFalse($json['translatable']);
$this->assertArrayHasKey('beta', $json['target_bundles']);
}
}