The Entity Pilot exists plugin - dealing with conflicts

Any content-staging workflow is going to have conflicts. In the simplest form a conflict exists when trying to import content that already exists. In more complex cases conflict might occur between revisions.

Having a solid API to detect and handle conflicts is key to any content-staging workflow. Read on to find out how Entity Pilot handles conflicts via an extendable plugin system.

The exists plugin manager

In order to keep things decoupled, detecting and handling conflicts in Entity Pilot is handled by the ExistPluginManager (\Drupal\entity_pilot\ExistsPluginManager).

The Entity Pilot CustomsInterface (\Drupal\entity_pilot\CustomsInterface) is responsible for screening, previewing and approving incoming content entities and hence depends on the ExistsPluginManager. For each incoming content entity the CustomsInterface delegates detection of a matching pre-existing content entity to the ExistsPluginManager. The ExistsPluginManager then employess a Chain of Responsibility (COR) to each EntityPilotExists (\Drupal\entity_pilot\Annotation\EntityPilotExists) plugin defined in the system. If any of these plugins return a matching existing content entity, the CustomsInterface uses this to calculate and display the differences to existing content. This re-uses Drupal 8 core's diff formatter service, added to core to allow viewing a diff of config entities but available for re-use by contributed modules right out of the box.

The exist plugin interface

Each EntityPilotExists plugin implements ExistsPluginInterface (\Drupal\entity_pilot\ExistsPluginInterface) which consists of two methods as follows:.

public function exists(EntityManagerInterface $entity_manager, EntityInterface $passenger)
This method passes each plugin the EntityManager service (\Drupal\Core\Entity\EntityManagerInterface) and the incoming passenger, which is an unsaved EntityInterface object. The incoming passenger is converted from the incoming normalized representation into a denormalized Entity by the CustomsInterface and then handed to the plugin to determine if it matches an existing entity in the system.
public function preApprove(EntityInterface $incoming, EntityInterface $existing);
This method is run just before the incoming entity is saved and passes both the incoming entity and the existing entity. This gives the plugin instance the opportunity to modify values in the incoming entity to match the existing entity. For example this would allow a plugin built to handle forward revisions the ability to change the status of the incoming entity, or set the new revision state or log message.

Detecting duplicates by UUID

The primary exists plugin provided by Entity Pilot module is concerned with finding matching entities by UUID. Each UUID by nature represents a distinct entity so if an incoming entity has the same UUID as and existing one, they are considered to be the same entity. In this instance the preApprove method takes care of lining up the serial IDs so that the incoming entity replaces the existing one.

Other unique keys

For some of Drupal core's built-in content entities there are other unique keys in the data schema. For example the User entity (\Drupal\user\Entity\User) has a unique key on the name field. This prevents two users having the same username. A similar constraint exists for email address. Entity Pilot provides an EntityPilotExistsPlugin for detecting matching users on this basis, notifying the CustomsInterface and ultimately allowing the user the ability to view the differences between the two versions of the entity.

Extension points

If your content workflow contains complex requirements then the ExistsPluginInterface represents the ideal extension point for your custom solution to interact with Entity Pilot and ensure that your requirements are met.