Skip to content

How to handle messages inside an action that could be used by caller #328

@chrisgo

Description

@chrisgo

Hi,

Thank you for creating this wonderful package. I have some other implementation of this >15 years ago for the Kohana framework and we are in the process of moving all these into your package so it is much better integrated into Laravel (we are moving our entire codebase into Laravel)

One of the "features" that we were trying to figure out what the best practice or any ideas you may have for handling messages (an array of messages) similar to Flash::success(), ::error(), ::warning(), etc

The reason why we we have "actions" is that most of the code starts out in a Controller somewhere. Then eventually there is a 2nd controller that needs to perform the same action, then maybe a 3rd controller.

An example would be "CreateNewUser" so this would be called from a

  • (1) public-facing "Register" screen, then later
  • (2) let us create an internal controller for our employees to use so they can create a new client user without logging out, then later
  • (3) this external system wants to call our API and then create a new user so that will also come in via a 3rd controller.

So on Day 1, you would most likely just create the code to "RegisterNewUser" which could be the following

  • create record in database
  • tell user to check their email {email} to click on confirm
  • tell user here is you confirmation code (say maybe something like 1234)
  • send the person to mailchimp to add them to newsletter
  • send the person to your CRM system
  • send the person to the accounting software to add the person to add their account

So in your controller, you would probably use

public function create(Request $request)
{
  // do stuff
  Flash::success('Email successfully added to newsletter');
  Flash::success('User successfully added to accounting system');
  return view(...);
}

But what if one of the systems is down, then you would do something like

Flash::error('User was not added to accounting system, server error 500 in other system');

So in the controller, your Flash handler would deal with this however you are already dealing with this

So when the 2nd controller comes, you decide to move this into an "Action" to encapsulate all the steps above

In our implementation, we just kept all the Flash::success(), etc inside the Action class which is NOT GOOD because

  • we have some Flash message leakage when there is an unexpected error .. your next click will show the Flash that was not cleared (still in your session)
  • on the API hit, we have to go and get collect all the Flash messages, convert them to an array then do a json_encode() so that we can return these messages to the API

So we have come up with 3 ways to do this with your package, both are not great but maybe there is some other way

  • We have reviewed the validation stuff which is great for error messages only and we are going to roll that into this messages thing as something like warning or error depending on what it is
  • Read through most of the github issues (open and closed) to see if there is anything we can copy

Option 1:

  • use a trait and then this Trait has an array of messages and some helper functions
class CreateNewUser {
  use AsAction, HasMessages;

  public User $user;

  public string $confirmCode;

  public function handle($name, $email): static // or ... : self ?
  {
    // do stuff  
    $this->user = User::create([...]);
    $this->confirmCode = app(ConfirmCodeGenerationService::class)->generate($this->user);
    $this->success('this is a success'); // from trait
    // go to accounting system
    $this->error('there was a problem with the accounting system'); // from trait
    return $this; // this seems kind of strange
  }
}
  • in the caller controller
class Controller {

  public function create(Request $request) 
  {
    $name = $request->input('name');
    $email = $request->input('email');
    $action = CreateNewUser::run($name, $email);
    $action->toFlash();  // this method is in the trait and just pumps the messages array into their Flash::____ equivalents
    // Then I can do more flash in the controller like
    Flash::info('Confirm code is ' . $action->confirmCode);
    // then if we need the user ID or something
    Log::debug("new user ".$email." created with USER ID: ". $action->user->id);
    return view(...);
  }

Option 2: Use a DTO object like ActionResponse then you can just return that everytime

  • This seems LESS better than option 1 because you have to design this class and now this could return "anything", it just seems like return $this; is better (at least more convenient, you just stuff the class with your public member variables or you can even do a __call() inside a base class to give you some magic methods and make the member variables private)
class CreateNewUser {
  use AsAction;

  public function handle($name, $email): ActionResponse
  { 
    ... do stuff
    $response = new ActionResponse(); // or maybe injected or inherited from a base class
    $response->success('this is a success');
    $response->error('there was a problem with the accounting system');
    return $response;
  }
}

Option 3: return an array (or object by casting the array) of random things

class CreateNewUser {
  use AsAction;

  public function handle($name, $email): array
  { 
    ... do stuff
    return [
       'messages' => [],
       'user' => $user,
    ];
  }
}

Maybe there is a some other "official" or better way?

Thank you so much for reading this.

Thanks,
Chris

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions