Search code examples
phplaravellumenpassive-event-listeners

How can I preserve model state across multiple Laravel/Lumen listeners that receive an event?


Using Lumen 5.5, PHP 7.3

My goal is to extend existing functionality on a model while maintaining code cleanliness. Using the Event/Listener pattern made sense but I'm hitting some design issues where I need access to the original state of the model down in the listener. That state doesn't appear to be accessible.

How It Started: this method on a model (sanitized for public viewing) "resets" it by assigning null to some attributes. No problems with this.

    public function reset()
    {
        $this->association_id = null;
        $this->associated_at = null;
        $this->save();
    }

How It's Going: Added an event dispatch so I can add new functionality to the listeners.

    public function reset()
    {
        $this->association_id = null;
        $this->associated_at = null;
        $this->save();

        event(new ResetEvent($this));
    }

Event

class ResetEvent
{
    public $myModel;

    public function __construct($myModel)
    {
        $this->myModel = $myModel;
    }
}

Listener

class ResetListener
{
    public function handle(ResetEvent $event)
    {
        $association_id = $event->myModel->association_id;
        // OR...
        $association_id = $event->myModel->getOriginal('association_id');

        // Do new functionality...
    }
}

CHALLENGE

The listener needs access to the association_id value from the model. Unfortunately, this is no longer available after the save() method is called from the model's reset method. I did attempt using the getOriginal() method on the model from the listener, but the value there was null also. That feels like it should have worked.

Note that this implementation is using synchronous events; there is no external async queueing system involved.

OPTIONS

One option is to change the intention and name from ResetEvent into a ResetRequestEvent and use one listener to perform the attribute modification to the model, and another to perform the new functionality I'm adding. This may or may not lead to race conditions if the model is passed around to the listeners by reference.

Another option could be a way I haven't learned yet to preserve the state of the model as it's passed to the event. Java-style data transfer objects come to mind, but that sounds like dodging the luxuries provided by the framework.

Looking for ideas to further either option.


Solution

  • You can pass additional params to your event. For example:

     public function reset()
     {
        $lastAssociation = $this->association_id;
        $this->association_id = null;
        $this->associated_at = null;
        $this->save();
    
        event(new ResetEvent($this, $lastAssociation));
     }
    

    and then your event would be:

    class ResetEvent
    {
        public $myModel;
        public $lastAssociationId;
    
        public function __construct($myModel, $lastAssociationId)
        {
            $this->myModel = $myModel;
            $this->lastAssociationId = $lastAssociationId;
        }
    }
    

    The last association will be available in your listener as $event->lastAssociationId, hope it helps.