Trigger events that can be subscribed to (D9)

Created on 9 March 2019, over 5 years ago
Updated 7 March 2024, 9 months ago

Problem / Motivation

Currently, Workflow API exposes a bunch of hooks for interacting with Workflows. Hooks are procedural by nature, and leave a lot to be desired, especially with complex cases. As I understand it, Drupal 9 will be eliminating hooks, possibly in favor of HookEvent. Even if all hooks aren't deprecated, implementing Events should provide some advantages for other modules to subscribe to Workflow events and obtain relevant information ( such as $entity, $wid, $from_sid, $to_sid, etc ).

Proposed resolution

Register Events for the various Workflow transitions, dispatch the events when the transitions occur. The existing hooks could remain side-by-side with the new events to prevent a breaking change.

Remaining tasks

Define the events and create a dispatcher.

API changes

For D8, all the hooks would stay in place, the event system would basically be an additional layer.

Data model changes

I don't think any data model changes would be necessary to add an event system. I could be wrong, I am just getting into the event system :)

✨ Feature request
Status

Fixed

Version

1.7

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States scottsawyer Atlanta

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡©πŸ‡ͺGermany Rudi Teschner

    The changes in the patch in #7 alone are not enough. For example are the event classes missing

  • πŸ‡©πŸ‡ͺGermany Rudi Teschner

    Here is a version that works for me on a D10 website with D10.1.4 and workflow 8.x-1.7

  • Status changed to Needs review about 1 year ago
  • πŸ‡―πŸ‡΄Jordan Yasser Samman Amman, Jordan
  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 10.1.x + Environment: PHP 8.0 & MySQL 5.7
    last update about 1 year ago
    Composer require failure
  • πŸ‡¨πŸ‡­Switzerland ayalon

    I have tested #10 and using the patch. It works well with Drupal 10. Using this patch since ages.

  • πŸ‡¨πŸ‡­Switzerland ayalon

    Here is an updated patch following the event naming from Patch #7.

    I prefer this approach as it is more flexible.

  • πŸ‡¨πŸ‡­Switzerland ayalon

    For some reasons the patch did not properly export. Please use this file:

  • Status changed to Fixed 10 months ago
  • πŸ‡³πŸ‡±Netherlands johnv

    Thanks, I committed a patch with some changes.

    Only 1 TransactionEvent type was committed. The Event interface allows to only use the $event_name. So adding multiple subevents does not have added value.
    All functions to read attributes of the $transition are not committed, while redundant. You can to $event->getTransition()->getFromSid().
    For that, i assured the WorkflowTransitionInterface was completed here: πŸ“Œ Improve WorkflowTransitionInterface Fixed
    To make the impact of the commit smaller, the following was needed: πŸ“Œ Restructure save() method Active
    This way, the dispatchEvent() function can be inside, not around the save() function. This way, the events are also triggered for ScheduledTransitions.

    I hope these changes do not ruin you project code too much.

  • πŸ‡³πŸ‡±Netherlands johnv

    The EventSubscriber from #16 is added with dummy code.

  • πŸ‡¨πŸ‡¦Canada sagesolutions

    @johnv

    Was it intentional to remove the entity and workflow from the WorkflowTransitionEvent?

    In my eventsubscriber, I used the $event->getEntity() method to get the node.

    +
    +  /**
    +     * The workflow.
    +     *
    +     * @var \Drupal\workflow\Entity\WorkflowInterface
    +     */
    +  protected $workflow;
    +
    +  /**
    +     * The entity.
    +     *
    +     * @var \Drupal\Core\Entity\ContentEntityInterface
    +     */
    +  protected $entity;
    ...
    +  public function __construct(WorkflowTransition $transition, WorkflowInterface $workflow, EntityInterface $entity) {
    +        $this->transition = $transition;
    +        $this->workflow = $workflow;
    +        $this->entity = $entity;
    +      }
    +
    +
    +  /**
    +     * Gets the workflow.
    +     *
    +     * @return \Drupal\workflow\Entity\WorkflowInterface
    +     *   The workflow.
    +     */
    +  public function getWorkflow() {
    +        return $this->workflow;
    +  }
    +
    +  /**
    +     * Gets the entity.
    +     *
    +     * @return \Drupal\Core\Entity\EntityInterface
    +     *   The entity.
    +     */
    +  public function getEntity() {
    +        return $this->entity;
    +  }

    Also, I used a specific event to trigger my subscribed events. For example 'permit_status_workflow.permit_status_workflow_open.post_transition' => 'onOpenTransition',

    Is this still possible? I think this functionality has been removed??

    +  /**
    +   * Dispatches a transition event for the given phase.
    +   *
    +   * @param string $phase
    +   *   The phase: pre_transition OR post_transition.
    +   */
    +  protected function dispatchTransitionEvent($phase) {
    +
    +    $workflow = $this->getWorkflow();
    +
    +    $event = new WorkflowTransitionEvent($this, $workflow, $this->getTargetEntity());
    +    $event_dispatcher = \Drupal::getContainer()->get('event_dispatcher');
    +
    +    $events = [
    +      // For example: 'my_custom_workflow.my_custom_transition.pre_transition'.
    +      $this->getWorkflowId() . '.' . $this->getToSid() . '.' . $phase,
    +      // For example: 'my_custom_workflow.pre_transition'.
    +      $this->getWorkflowId() . '.' . $phase,
    +      // For example: 'workflow.pre_transition'.
    +      'workflow.' . $phase,
    +    ];
    +
    +    foreach ($events as $event_id) {
    +      $event_dispatcher->dispatch($event, $event_id);
    +    }
    +  }
    +
  • Status changed to Needs review 10 months ago
  • πŸ‡³πŸ‡±Netherlands johnv

    For the first block, you can make the following modifications. They will work anyway. As stated, you can get the info using the TransitionInterface:
    perhaps changing the constructor to
    public function __construct(WorkflowTransition $transition, WorkflowInterface $workflow = NULL, EntityInterface $entity = NULL) {}
    or
    public function __construct(WorkflowTransition $transition) {}

    +
    +  /**
    +     * The workflow.
    +     *
    +     * @var \Drupal\workflow\Entity\WorkflowInterface
    +     */
    +  // protected $workflow;
    +
    +  /**
    +     * The entity.
    +     *
    +     * @var \Drupal\Core\Entity\ContentEntityInterface
    +     */
    +  //  protected $entity;
    ...
    +  public function __construct(WorkflowTransition $transition, WorkflowInterface $workflow, EntityInterface $entity) {
    +        $this->transition = $transition;
    + //       $this->workflow = $workflow;
    + //       $this->entity = $entity;
    +      }
    +
    ...
    +  public function getWorkflow() {
    +        return $this->transition->getWorkflow();  // CHANGED
    +  }
    +
    +  public function getEntity() {
    +        return $this->transition->getTargetEntity();  // CHANGED
    +  }
    
    • johnv β†’ committed 1b4f1161 on 8.x-1.x
      Issue #3038853: Remove obsolete WorkflowStorage~getRegisteredEvents()
      
  • πŸ‡³πŸ‡±Netherlands johnv

    @sagesolutions,
    for your second block, yes, you still can do what you need to do. Please see below code suggestion:

    +  /**
    +   * Dispatches a transition event for the given phase.
    +   *
    +   * @param string $phase
    +   *   The phase: pre_transition OR post_transition.
    +   */
    +  protected function dispatchTransitionEvent($phase) {
    +
    +    $events = [
    +      // For example: 'my_custom_workflow.my_custom_transition.pre_transition'.
    +      $this->getWorkflowId() . '.' . $this->getToSid() . '.' . $phase,
    +      // For example: 'my_custom_workflow.pre_transition'.
    +      $this->getWorkflowId() . '.' . $phase,
    +      // For example: 'workflow.pre_transition'.
    +      'workflow.' . $phase,
    +    ];
    +
    +    $transition = $this->getTransition();
    +    foreach ($events as $event_id) {
    +      $transition->dispatchEvent( $event_id);
    +    }
    +  }
    +
    
  • πŸ‡¨πŸ‡¦Canada sagesolutions

    Hi @johnv,
    I like the getEntity() and getWorkflow() methods in the WorkflowTransistionEvent class. I can work with that. Please commit these updates :)

    + public function getWorkflow() {
    + return $this->transition->getWorkflow(); // CHANGED
    + }
    +
    + public function getEntity() {
    + return $this->transition->getTargetEntity(); // CHANGED
    + }

    For the subscribed events, I liked the ability of what was there before. Using the original patch, I can subscribe to each individual workflow change. For example:

      public static function getSubscribedEvents(): array {
        return [
          'permit_status_workflow.permit_status_workflow_creation.post_transition' => 'onCreationTransition',
          'permit_status_workflow.permit_status_workflow_draft.post_transition' => 'onDraftTransition',
          'permit_status_workflow.permit_status_workflow_open.post_transition' => 'onOpenTransition',
          'permit_status_workflow.permit_status_workflow_pending.post_transition' => 'onPendingTransition',
          'permit_status_workflow.permit_status_workflow_approved.post_transition' => 'onApprovedTransition',
          'permit_status_workflow.permit_status_workflow_declined.post_transition' => 'onDeclinedTransition',
        ];
      }
    

    But now, I can only subscribe to the one post transition event. Then in the called function, I need to check the transition to/from and break apart from there. This seems a bit messier than before.

  • Status changed to RTBC 9 months ago
  • πŸ‡¨πŸ‡¦Canada sagesolutions

    I've managed to update my EventSubscriber to work with the new workflow updates. Below is what works for me, maybe it will help others while upgrading.

    /**
       * {@inheritdoc}
       */
      public static function getSubscribedEvents(): array {
        return [
          'workflow.post_transition' => 'onWorkflowPostTransition',
        ];
      }
    
      public function onWorkflowPostTransition(WorkflowTransitionEvent $event): void {
        switch ($event->getTransition()->getToSid()) {
          case "permit_status_workflow_pending" :
            $this->onPendingTransition($event);
            break;
          case "permit_status_workflow_open":
            $this->onOpenTransition($event);
            break;
          case "permit_status_workflow_approved":
            $this->onApprovedTransition($event);
            break;
          case "permit_status_workflow_declined":
            $this->onDeclinedTransition($event);
            break;
        }
      }
    
  • Status changed to Fixed 9 months ago
  • πŸ‡³πŸ‡±Netherlands johnv

    Thanks, I added the code to the EventSubscriber.

    • johnv β†’ committed c3f2a833 on 8.x-1.x
      Issue #3038853: Trigger events that can be subscribed to - example code
      
  • πŸ‡³πŸ‡±Netherlands johnv

    Some example code is added to the workflow_devel module.

    • johnv β†’ committed 60a07dc9 on 8.x-1.x
      Issue #3038853: Trigger events that can be subscribed to - example code
      
    • johnv β†’ committed d0c66941 on 8.x-1.x
      Issue #3038853: Trigger events that can be subscribed to - example code
      
  • Automatically closed - issue fixed for 2 weeks with no activity.

  • πŸ‡³πŸ‡±Netherlands johnv
Production build 0.71.5 2024