Skip to content

Commit

Permalink
field-create: Make it easier for field types extending entity referen…
Browse files Browse the repository at this point in the history
…ce to add target type/bundles options (#5556)

* Move entity reference related logic to hook implementations

* Remove ensureOption method

* Fix cs
  • Loading branch information
DieterHolvoet authored Jun 2, 2023
1 parent a902fb1 commit 1dee70f
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 111 deletions.
110 changes: 0 additions & 110 deletions src/Drupal/Commands/field/FieldCreateCommands.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,6 @@ public function create(?string $entityType = null, ?string $bundle = null, array
$this->ensureOption('is-translatable', [$this, 'askTranslatable'], false);
$this->ensureOption('cardinality', [$this, 'askCardinality'], true);

if ($this->input->getOption('field-type') === 'entity_reference') {
$this->ensureOption('target-type', [$this, 'askReferencedEntityType'], true);
}

$this->createFieldStorage();
}

Expand Down Expand Up @@ -363,81 +359,6 @@ protected function askCardinality(): int

return $limit;
}

protected function askReferencedEntityType(): string
{
$definitions = $this->entityTypeManager->getDefinitions();
$choices = [];

foreach ($definitions as $name => $definition) {
$label = $this->input->getOption('show-machine-names')
? $name
: sprintf('%s: %s', $definition->getGroupLabel()->render(), $definition->getLabel());
$choices[$name] = $label;
}

return $this->io()->choice('Referenced entity type', $choices);
}

protected function askReferencedBundles(FieldDefinitionInterface $fieldDefinition): array
{
$choices = [];
$bundleInfo = $this->entityTypeBundleInfo->getBundleInfo(
$fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')
);

if (empty($bundleInfo)) {
return [];
}

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

$question = (new ChoiceQuestion('Referenced bundles', $choices))
->setMultiselect(true);

return $this->io()->askQuestion($question) ?: [];
}

protected function askBundle(): ?string
{
$entityTypeId = $this->input->getArgument('entityType');
$entityTypeDefinition = $this->entityTypeManager->getDefinition($entityTypeId);
$bundleEntityType = $entityTypeDefinition->getBundleEntityType();
$bundleInfo = $this->entityTypeBundleInfo->getBundleInfo($entityTypeId);
$choices = [];

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

if ($fieldName = $this->input->getOption('existing-field-name')) {
$bundleInfo = array_filter($bundleInfo, function (string $bundle) use ($entityTypeId, $fieldName) {
return !$this->entityTypeManager->getStorage('field_config')->load("$entityTypeId.$bundle.$fieldName");
}, ARRAY_FILTER_USE_KEY);
}

if (!$bundleEntityType && count($bundleInfo) === 1) {
// eg. User
return $entityTypeId;
}

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;
}

protected function createField(): FieldConfigInterface
{
$values = [
Expand All @@ -460,33 +381,6 @@ protected function createField(): FieldConfigInterface
$field = $this->entityTypeManager
->getStorage('field_config')
->create($values);

if ($this->input->getOption('field-type') === 'entity_reference') {
$targetType = $this->input->getOption('target-type');
$targetTypeDefinition = $this->entityTypeManager->getDefinition($targetType);
// For the 'target_bundles' setting, a NULL value is equivalent to "allow
// entities from any bundle to be referenced" and an empty array value is
// equivalent to "no entities from any bundle can be referenced".
$targetBundles = null;

if ($targetTypeDefinition->hasKey('bundle')) {
if ($referencedBundle = $this->input->getOption('target-bundle')) {
$this->validateBundle($targetType, $referencedBundle);
$referencedBundles = [$referencedBundle];
} else {
$referencedBundles = $this->askReferencedBundles($field);
}

if (!empty($referencedBundles)) {
$targetBundles = array_combine($referencedBundles, $referencedBundles);
}
}

$settings = $field->getSetting('handler_settings') ?? [];
$settings['target_bundles'] = $targetBundles;
$field->setSetting('handler_settings', $settings);
}

$field->save();

return $field;
Expand All @@ -502,10 +396,6 @@ protected function createFieldStorage(): FieldStorageConfigInterface
'translatable' => true,
];

if ($targetType = $this->input->getOption('target-type')) {
$values['settings']['target_type'] = $targetType;
}

// Command files may customize $values as desired.
$handlers = $this->getCustomEventHandlers('field-create-field-storage');
foreach ($handlers as $handler) {
Expand Down
151 changes: 151 additions & 0 deletions src/Drupal/Commands/field/FieldEntityReferenceHooks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php

namespace Drush\Drupal\Commands\field;

use Consolidation\AnnotatedCommand\AnnotationData;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drush\Commands\DrushCommands;
use Drush\Drupal\Commands\field\EntityTypeBundleValidationTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ChoiceQuestion;

class FieldEntityReferenceHooks extends DrushCommands
{
use EntityTypeBundleValidationTrait;

/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;

/**
* The entity type bundle info service.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;

/**
* Constructs a new EntityReferenceRevisionsHooks object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entityTypeBundleInfo
* The entity type bundle info service.
*/
public function __construct(
EntityTypeManagerInterface $entityTypeManager,
EntityTypeBundleInfoInterface $entityTypeBundleInfo
) {
$this->entityTypeManager = $entityTypeManager;
$this->entityTypeBundleInfo = $entityTypeBundleInfo;
}

/**
* @hook on-event field-create-field-storage
*/
public function hookFieldStorage(array $values, InputInterface $input): array
{
if ($input->getOption('field-type') === 'entity_reference') {
$values['settings']['target_type'] = $this->getTargetType($input);
}

return $values;
}

/**
* @hook on-event field-create-field-config
*/
public function hookFieldConfig(array $values, InputInterface $input): array
{
if ($input->getOption('field-type') === 'entity_reference') {
$values['settings']['handler_settings']['target_bundles'] = $this->getTargetBundles($input);
}

return $values;
}

protected function getTargetType(InputInterface $input): string
{
$value = $input->getOption('target-type');

if ($value === null && $input->isInteractive()) {
$value = $this->askReferencedEntityType();
}

if ($value === null) {
throw new \InvalidArgumentException(dt('The %optionName option is required.', [
'%optionName' => 'target-type',
]));
}

$input->setOption('target-type', $value);

return $value;
}

protected function getTargetBundles(InputInterface $input): ?array
{
$targetType = $input->getOption('target-type');
$targetTypeDefinition = $this->entityTypeManager->getDefinition($targetType);
// For the 'target_bundles' setting, a NULL value is equivalent to "allow
// entities from any bundle to be referenced" and an empty array value is
// equivalent to "no entities from any bundle can be referenced".
$targetBundles = null;

if ($targetTypeDefinition->hasKey('bundle')) {
if ($referencedBundle = $input->getOption('target-bundle')) {
$this->validateBundle($targetType, $referencedBundle);
$referencedBundles = [$referencedBundle];
} else {
$referencedBundles = $this->askReferencedBundles($targetType);
}

if (!empty($referencedBundles)) {
$targetBundles = array_combine($referencedBundles, $referencedBundles);
}
}

return $targetBundles;
}

protected function askReferencedEntityType(): string
{
$definitions = $this->entityTypeManager->getDefinitions();
$choices = [];

foreach ($definitions as $name => $definition) {
$label = $this->input->getOption('show-machine-names')
? $name
: sprintf('%s: %s', $definition->getGroupLabel()->render(), $definition->getLabel());
$choices[$name] = $label;
}

return $this->io()->choice('Referenced entity type', $choices);
}

protected function askReferencedBundles(string $targetType): ?array
{
$choices = [];
$bundleInfo = $this->entityTypeBundleInfo->getBundleInfo($targetType);

if (empty($bundleInfo)) {
return [];
}

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

$question = (new ChoiceQuestion('Referenced bundles', $choices))
->setMultiselect(true);

return $this->io()->askQuestion($question) ?: null;
}
}
9 changes: 8 additions & 1 deletion src/Drupal/Commands/field/drush.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,11 @@ services:
- '@entity_type.bundle.info'
- '@entity_field.manager'
tags:
- { name: drush.command }
- { name: drush.command }
field.entity-reference.hooks:
class: \Drush\Drupal\Commands\field\FieldEntityReferenceHooks
arguments:
- '@entity_type.manager'
- '@entity_type.bundle.info'
tags:
- { name: drush.command }

0 comments on commit 1dee70f

Please sign in to comment.