sebdesign/laravel-state-machine

Filtering transitions based on a model property.

stephan-v opened this issue ยท 3 comments

Reading the documentation I see that policies can be used to filter transitions based user roles for example.

I am wondering if there is an easier way when you simply want to check a property on a model though.

I have a Stop model which can have a requires_identification property set to true or false. If it is set to true I modify the transitions.

'callbacks' => [
    'guard' => [
        'guard_on_verifying' => [
            'on' => 'verify',
            'can' => 'verify',
        ],
    ],
],

Using the above code I am checking a StopPolicy class which holds the following function:

public function verify(User $user, Stop $stop)
{
    return $stop->requires_identification;
}

This works but it makes no sense because I am not doing user authorization. I am just checking a property on my model. Is there an easy way to improve this so I can just check a requirement on my model directly to ensure if the transition is allowed or not?

Thanks a lot.

Yes indeed, there is a simpler way for these kind of checks.

Using gates/policies is more useful when you want to do user authorizations.
But in your case, you need a simple guard callback. Here are some ways you can achieve this:

1. Using closures

'callbacks' => [
    'guard' => [
        'guard_on_verifying' => [
            'on' => 'verify',
            'do' => fn (Stop $stop) => $stop->requires_identification,
            'args' => ['object'],
        ],
    ],
],

The do key is the callback that checks the property, and the args key is the arguments that will be passed to the callback. In this example object will be evaluated to the Stop instance that is inside the state machine.

2. Using a method on the model

Although I don't really like closures in configuration files, you could create a method on the Stop model, for example:

public function requiresIdentification(): bool
{
    return $this->requires_identification;
}
'callbacks' => [
    'guard' => [
        'guard_on_verifying' => [
            'on' => 'verify',
            'do' => ['object', 'requiresIdentification'],
            'args' => ['object'],
        ],
    ],
],

3. Using the property accessor

If you don't want to define a method on the model, you can call the property accessor on the model (getXAttribute), even if you haven't define an accessor. The accessor will return the value of the X Property.

'callbacks' => [
    'guard' => [
        'guard_on_verifying' => [
            'on' => 'verify',
            'do' => ['object', 'getRequiresIdentificationAttribute'],
            'args' => ['object'],
        ],
    ],
],

4. Using boolval

The native boolval function takes an argument and returns its boolean value. In this example, the callback is the boolval function, and the args is object.requires_identification which will evaluate the value of the property on the model.

'callbacks' => [
    'guard' => [
        'guard_on_verifying' => [
            'on' => 'verify',
            'do' => 'boolval',
            'args' => ['object.requires_identification'],
        ],
    ],
],

In the example 4, you can also use the value or the with helper functions in Laravel, instead of boolval, which return the given argument as is.

Thanks a lot man, just what I am looking for. It's amazing to see how flexible this is. Keep up the good work.