blacksmith-8.x-1.x-dev/src/Blacksmith/EntityImporter/FieldFormatter/EntityReferenceFieldFormatter.php
src/Blacksmith/EntityImporter/FieldFormatter/EntityReferenceFieldFormatter.php
<?php namespace Drupal\blacksmith\Blacksmith\EntityImporter\FieldFormatter; use Drupal; use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Component\Utility\Random; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\blacksmith\BlacksmithItem; use Exception; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\blacksmith\Blacksmith\EntityImporter\EntityImporterFactory; use Drupal\blacksmith\Exception\BlacksmithImportSkip; /** * Class EntityReferenceFieldFormatter. * * @package Drupal\blacksmith\Blacksmith\EntityImporter\FieldFormatter */ class EntityReferenceFieldFormatter extends FieldFormatterBase implements ContainerInjectionInterface { /** * Entity type manager service. * * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ protected $entityTypeManager; /** * Blacksmith entity importer. * * @var \Drupal\blacksmith\Blacksmith\EntityImporter\EntityImporter */ protected $entityImporter; /** * The type of entity the field refers to. * * @var string */ protected $targetEntityType; /** * The storage of the entity type referred by the field. * * @var \Drupal\Core\Entity\EntityStorageInterface */ protected $targetEntityTypeStorage; /** * The bundles that the field can create. * * @var array */ protected $targetBundles; /** * Random string generator service. * * @var \Drupal\Component\Utility\Random */ protected $randomService; /** * {@inheritdoc} */ public function __construct(FieldDefinitionInterface $fieldDefinition, EntityTypeManagerInterface $entityTypeManager, EntityImporterFactory $entityImporter) { parent::__construct($fieldDefinition); $this->entityTypeManager = $entityTypeManager; $this->randomService = new Random(); // @todo Find out why instance of ConfigEntityBase doesn't work. if (method_exists($fieldDefinition, 'getSetting')) { $this->targetEntityType = $fieldDefinition->getSetting('target_type'); $this->targetBundles = $fieldDefinition->getSetting('handler_settings')['target_bundles'] ?? []; if ($this->targetBundles) { $this->targetBundles = array_keys($this->targetBundles); } try { $this->targetEntityTypeStorage = $entityTypeManager->getStorage($this->targetEntityType); } catch (InvalidPluginDefinitionException | PluginNotFoundException $exception) { $this->logger->error($exception->getMessage()); } } // Set the entity importer. try { if ($this->targetEntityType) { // @todo Find a way to do Dependency Injection without a recursive service issue. $this->entityImporter = $entityImporter->create($this->targetEntityType); } } catch (BlacksmithImportSkip $exception) { $this->logger->error(($exception->getMessage())); } } /** * {@inheritDoc} */ public static function create(ContainerInterface $container, FieldDefinitionInterface $fieldDefinition = NULL) { return new static( $fieldDefinition, $container->get('entity_type.manager'), $container->get('blacksmith.entity_importer.factory') ); } /** * {@inheritdoc} * * @throws \Drupal\blacksmith\Exception\BlacksmithInvalidItemConfiguration */ public function formatUniqueValue($value) { if (is_int($value)) { return $value; } // @todo Find a more suitable way to check if the value is a sub-entity. // Maybe get the ID? if (is_array($value)) { $value['entity_type'] = $value['entity_type'] ?? $this->targetEntityType; // If the bundle isn't specified and the field only allows one bundle // consider the bundle as already set. if (!isset($value['bundle']) && count($this->targetBundles) === 1) { $value['bundle'] = $value['bundle'] ?? reset($this->targetBundles); } // @todo Find out a way to get the parent's langcode, status and author // Check if the sub-entity's bundle is set and allowed by the field. if (!in_array($value['bundle'], $this->targetBundles, FALSE) && !empty($this->targetEntityTypeStorage->getEntityType()->getKey('bundle'))) { $this->messenger()->addWarning("Missing 'bundle' key in sub entity."); return NULL; } // @todo Yeah... That's no good. The sub entities should not require a // specific Blacksmith ID. Only the top level items should have one. if (!isset($value['id'])) { $value['id'] = $this->randomService->name(32); } // Try to find existing content instead of creating it for nothing. if ($this->targetEntityType === 'taxonomy_term' && $term = $this->findTerm($value)) { return $term; } if ($this->targetEntityType === 'user' && $user = $this->findUser($value)) { return $user; } // @todo Find a way to get the parent's selector and add it as the group. $item = new BlacksmithItem($value, 'todo'); try { $entity = $this->entityImporter->import($item); return $this->formatEntity($entity); } catch (EntityStorageException | Exception $exception) { Drupal::messenger()->addWarning($exception->getMessage()); return NULL; } } // Make sure that the $value is a array at this point. elseif (is_string($value)) { $labelKey = $this->targetEntityTypeStorage->getEntityType()->getKey('label') ?: 'name'; $value = [$labelKey => $value]; if (count($this->targetBundles) === 1) { $value['bundle'] = reset($this->targetBundles); } elseif (!empty($this->targetEntityTypeStorage->getEntityType()->getKey('bundle'))) { // @todo Proper error and warning handling. $this->messenger()->addWarning('You need to specify the bundle of the ' . $this->targetEntityType); } return self::formatUniqueValue($value); } return parent::formatUniqueValue($value); } /** * The way the entity is referenced in the database. * * @param \Drupal\Core\Entity\EntityInterface $entity * The sub entity that was just created. * * @return mixed * The way the entity is saved the database. */ protected function formatEntity(EntityInterface $entity) { return $entity->id(); } /** * Finds a user based on it's username. * * @param mixed $value * A unique value from the Blacksmith file. * * @return int|null * A user's unique identifier or NULL if it doesn't exist. */ protected function findUser($value) : ?int { $uid = array_keys($this->targetEntityTypeStorage->loadByProperties(['name' => $value['name']])); if (empty($uid)) { return NULL; } if (is_array($uid)) { $uid = reset($uid); } return $uid; } /** * Looks for an existing taxonomy term with specific values. * * @param array $value * Taxonomy values used to find an existing its counterpart. * * @return int|null * The found taxonomy term if one is found. */ protected function findTerm(array $value) : ?int { $tid = array_keys($this->targetEntityTypeStorage->loadByProperties([ 'name' => $value['name'], 'vid' => $value['bundle'], ])); if (empty($tid)) { return NULL; } if (is_array($tid)) { $tid = reset($tid); } return $tid; } }