Problem/Motivation

To solve #2842222: D7 Plain text fields incorrectly migrated to D8 as Text (formatted), we'd like to have an easy way to check if a config or content entity exists.

Proposed resolution

Create a process plugin that returns false or if it exists return the id of entity.

Remaining tasks

Do it.

User interface changes

API changes

Data model changes

Comments

heddn created an issue. See original summary.

heddn’s picture

As a starting place, look at the entity_lookup process plugin in migrate_plus. The difference is we'd like make this much more explicit and have less magic.

vasi’s picture

Here's a first stab at this, if someone wants to take it farther.

TODO:

* Better docs: What should transform() return? True/false? The entity/null?
* Write tests


namespace Drupal\migrate\Plugin\migrate\process;

use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Checks whether an entity with a given ID exists.
 *
 * Available configuration keys:
 * - entity_type: The entity type.
 *
 * Examples:
 *
 * @code
 * process:
 *   check_entity:
 *     # Imports this row only if a certain vocabulary exists.
 *     - plugin: entity_exists
 *       source: vocab_name
 *       entity_type: vocabulary
 *     - plugin: skip_on_empty
 *       method: row
 * @endcode
 *
 * @MigrateProcessPlugin(
 *   id = "entity_exists"
 * )
 */
class EntityExists extends ProcessPluginBase implements ContainerFactoryPluginInterface {

  /**
   * The entity storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $storage;

  /**
   * EntityExists constructor.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $storage) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->storage = $storage;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('entity_type.manager')->getStorage($configuration['entity_type'])
    );
  }

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    return $this->storage->load($value);
  }

}
rakesh.gectcr’s picture

Assigned: Unassigned » rakesh.gectcr

Thanks @Vasi,

was talking @heddn

<?php

namespace Drupal\migrate\Plugin\migrate\process;


use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;

/**
 * This plugin confirm the given entity is exists or not.
 *
 * @MigrateProcessPlugin(
 *  id = "entity_exists"
 * )
 *
 * Example usage with configuration:
 * @code
 *   field_tags:
 *     plugin: entity_exists
 *     source: tid
 *     entity_type: taxonomy_term
 * @endcode
 */
class EntityExists extends ProcessPluginBase {

  /** @var \Drupal\Core\Entity\EntityTypeManagerInterface */
  protected $entityTypeManager;

  /** @var \Drupal\migrate\Plugin\MigrationInterface */
  protected $migration;
  
  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {

    \Drupal::entityTypeManager()->getStorage($this->configuration['entity_type'])->load($this->configuration['source']);

    return parent::transform($value, $migrate_executable, $row, $destination_property); // TODO: Change the autogenerated stub
  }

}

Let me try to roll the patch ASAP

rakesh.gectcr’s picture

StatusFileSize
new2.39 KB

Uploaded the first patch, Ones the plugin is reviewed and finalised, will write the test as well . :)

rakesh.gectcr’s picture

Status: Active » Needs review
Issue tags: +Baltimore2017
phenaproxima’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/migrate/src/Plugin/migrate/process/EntityExists.php
    @@ -0,0 +1,78 @@
    +class EntityExists extends ProcessPluginBase {
    

    This needs to implement ContainerFactoryPluginInterface.

  2. +++ b/core/modules/migrate/src/Plugin/migrate/process/EntityExists.php
    @@ -0,0 +1,78 @@
    +   *   The plugin_id for the plugin instance.
    

    Should be just "The plugin ID".

  3. +++ b/core/modules/migrate/src/Plugin/migrate/process/EntityExists.php
    @@ -0,0 +1,78 @@
    +    /**
    +     * If the given entity exits will return the entity_id otherwise will return null.
    +     */
    

    This needs to use // comment style, not /* */ style.

  4. +++ b/core/modules/migrate/src/Plugin/migrate/process/EntityExists.php
    @@ -0,0 +1,78 @@
    +    if (!empty($this->storage->load($value))) {
    +      return $value;
    +    } else {
    +      return null;
    +    }
    

    We don't want to return a full entity here -- just an indication of whether it exists. So this should be something like this:

    return $this->storage->load($value) instanceof EntityInterface

heddn’s picture

#7.4: Is it generally more useful to return FALSE | entity id? Rather than FALSE | TRUE?

rakesh.gectcr’s picture

Status: Needs work » Needs review
StatusFileSize
new2.01 KB
new2.51 KB

Well, I checked with mike, He is also in the same page of returning Entity_id, so rolled out with returning entity_id | FALSE. and done all the #4

peaton’s picture

Assigned: rakesh.gectcr » peaton

I'm going to work on this!

phenaproxima’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

Looking better and better. I found some nits, and we still need tests here.

  1. +++ b/core/modules/migrate/src/Plugin/migrate/process/EntityExists.php
    @@ -0,0 +1,80 @@
    + * This plugin confirm the given entity is exists or not.
    

    Nit: Should be "...checks if a given entity exists."

  2. +++ b/core/modules/migrate/src/Plugin/migrate/process/EntityExists.php
    @@ -0,0 +1,80 @@
    + * @MigrateProcessPlugin(
    + *  id = "entity_exists"
    + * )
    

    This needs to be final thing in the doc comment, as far as I know, or the annotation will not work.

  3. +++ b/core/modules/migrate/src/Plugin/migrate/process/EntityExists.php
    @@ -0,0 +1,80 @@
    + * Example usage with configuration:
    + * @code
    + *   field_tags:
    + *     plugin: entity_exists
    + *     source: tid
    + *     entity_type: taxonomy_term
    + * @endcode
    

    This should come before the @MigrateProcessPlugin annotation.

  4. +++ b/core/modules/migrate/src/Plugin/migrate/process/EntityExists.php
    @@ -0,0 +1,80 @@
    +   *   The plugin ID
    

    nit: Missing a period.

  5. +++ b/core/modules/migrate/src/Plugin/migrate/process/EntityExists.php
    @@ -0,0 +1,80 @@
    +  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
    +    return new static(
    +      $configuration,
    +      $plugin_id,
    +      $plugin_definition,
    +      $container->get('entity_type.manager')->getStorage($configuration['entity_type'])
    +    );
    +  }
    +
    

    If $this->configuration['entity_type'] is empty, this will fail really hard. Let's check first if that configuration value is set, and throw an InvalidArgumentException if not.

  6. +++ b/core/modules/migrate/src/Plugin/migrate/process/EntityExists.php
    @@ -0,0 +1,80 @@
    +    /* If the given entity exits will return the entity_id otherwise will return null. */
    +    if (!empty($this->storage->load($value))) {
    +      // return the entity id.
    +      return $value;
    +    }
    +    else {
    +      return FALSE;
    +    }
    

    I'd rather return the canonical entity ID, not the input value. Can this be something like:

    $entity = $this->storage->load($value);
    if ($entity instanceof EntityInterface) {
      return $entity->id();
    }
    else {
      return FALSE;
    }
    
  7. +++ b/core/modules/migrate/src/Plugin/migrate/process/EntityExists.php
    @@ -0,0 +1,80 @@
    +}
    \ No newline at end of file
    

    Missing a newline at the end of the file.

peaton’s picture

Status: Needs work » Needs review
StatusFileSize
new2.57 KB
new2.23 KB

Addressed the feedback in #11

phenaproxima’s picture

Status: Needs review » Needs work

The patch looks perfect to me now...but it needs tests :)

peaton’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new4.54 KB
new2.98 KB

Fixed syntax errors in patch. Added tests.

phenaproxima’s picture

Status: Needs review » Needs work

This patch looks flawless. Thanks, @peaton!

I would, however, request one small change:

+++ b/core/modules/migrate/src/Plugin/migrate/process/EntityExists.php
@@ -0,0 +1,85 @@
+    if (empty($configuration['entity_type'])) {
+      throw new \InvalidArgumentException('An entity type ID is required');
+    }

So I know I asked for this to be added originally, but I take that back now. This check is completely pointless -- the configuration value is never used except in create() to get an instance of EntityStorageInterface, which is required by the constructor anyway. If something calls create() with an empty entity_type configuration value, the entity type manager will blow up because the entity type ID will not be valid. So there is no reason at all to throw this exception...let's remove this bit.

peaton’s picture

Status: Needs work » Needs review
StatusFileSize
new4.41 KB
new701 bytes

Ok, removed!

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

This looks great. Preemptively RTBC.

catch’s picture

Status: Reviewed & tested by the community » Fixed

Looks great. Committed/pushed to 8.4.x and cherry-picked to 8.3.x. Thanks!

  • catch committed 4cdb87f on 8.4.x
    Issue #2872793 by peaton, rakesh.gectcr, phenaproxima, heddn, vasi:...

  • catch committed f24d3be on 8.3.x
    Issue #2872793 by peaton, rakesh.gectcr, phenaproxima, heddn, vasi:...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.