lorisleiva / laravel-actions

⚡️ Laravel components that take care of one specific task
https://laravelactions.com
MIT License
2.41k stars 117 forks source link

Handle ValidationException when using asCommand and validating in handle() #228

Open jonagoldman opened 1 year ago

jonagoldman commented 1 year ago

Hi, I'm using v2.5.1 of this package with Laravel v10.1.3.

To ensure data always gets validated no matter how the action is called, I'm using unified attributes to manually validate directly in the handle() method. This works fine when the action is running as a controller, so if when the validation fails, the exception is handled correctly, but when the validation fails running as a command it is not handled.

Full example:

<?php

namespace App\Account\Actions;

use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password as PasswordRule;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
use Lorisleiva\Actions\Concerns\WithAttributes;

class CreateUser
{
    use AsAction;
    use WithAttributes;

    public string $commandSignature = 'register:user';

    public string $commandDescription = 'Register a new user.';

    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'min:3', 'max:192'],
            'email' => ['required', 'max:192', 'email', Rule::unique('users')],
            'password' => ['required', 'confirmed', PasswordRule::default()],
        ];
    }

    public function handle(array $attributes = []): User
    {
        $this->fill($attributes);
        $validated = $this->validateAttributes();
        $user = User::create($validated);
        return $user;
    }

    public function asController(ActionRequest $request)
    {
        $this->fillFromRequest($request);
        $user = $this->handle();
        return $user;
    }

    public function asCommand(Command $command)
    {
        $attributes = [];

        $attributes['name'] = $command->ask('Name?', 'User 1');
        $attributes['email'] = $command->ask('Email?', 'user-1@godesk.wip');
        $attributes['password'] = $command->secret('Password?');
        $attributes['password_confirmation'] = $command->secret('Password Confirmation?');

        $user = $this->handle($attributes);

        $command->info('User registered!');
    }
}

Console result: Screenshot_20230223_203437

I can catch the exception locally inside the asCommand() method:

try {
    $user = $this->handle($attributes);
} catch (\Illuminate\Validation\ValidationException $exception) {
    $command->error($exception->getMessage());
    return Command::INVALID;
}

$command->info('User registered!');
return Command::SUCCESS;

But I wonder if there is a better, cleaner way, and I don't have to do this on every action. I don't know if it needs to be handled inside the package or in the app itself.

Thanks.

Wulfheart commented 1 year ago

+1 for the usage of version numbers. :D

jonagoldman commented 1 year ago

Same results when using a regular Laravel command:

class RegisterUser extends Command
{
    protected $signature = 'register:user';

    protected $description = 'Register a new user.';

    public function handle()
    {
        $attributes = [];

        $attributes['name'] = $this->ask('Name?', 'User 1');
        $attributes['email'] = $this->ask('Email?', 'user-1@godesk.wip');
        $attributes['password'] = $this->secret('Password?');
        $attributes['password_confirmation'] = $this->secret('Password Confirmation?');

        // small tweak to set the attributes in a request so I can use them in the action
        $request = request()->merge($attributes);
        $action = app(RegisterUserAction::class);

        $user = $action->handle($attributes);

        $this->info('User registered!');
    }
}

I guess the console exception handler is not prepared for this types of exceptions...