1
votes

I'm using some hard coded statuses in my app to track the status of an order. For an example

1 - draft
2 - placed
3 - completed

Currently I'm keeping them in a database table as order statuses. In the app level I'm using these status to validate the order updates. Such as

  • If order status = 1, it cannot be completed.
  • If order status = 2, it cannot be deleted.
  • If order status > 2, it cannot be edited.

What I'm not sure is in the future requirements there can be new statuses between current statuses. Like

1 - draft
2 - placed
3 - partially completed 
4 - completed

How to prevent code rewriting in these kind of situations, I would have to rewrite all the conditions in the future if there are several statuses in between the current statuses.

3
Spatie's Model States could certainly help you with that.Dan
Generally, i'd use constants in my code to represent each step and all my code would reference the constants. The value of the constant could change in the future and the code wouldnt care.Wesley Smith
@WesleySmith so there is like less code rewrites if I use the constants. But there are some rewrite to do.Tharindu
You could have your routine into a function so you only have that function to change if things change in the futureuser1620090
Why dont you use design patterns to overcome this problem. Looki into php interfaces this might help you with scaling your applicationDeepesh Thapa

3 Answers

3
votes

You can make a Constant Class and define a constant variable in it as below:

class Constant
{
    const DRAFT = 'draft';
    const PLACED = 'placed';
}

and then use in blade file as below:

if($orderStatus == Constant::DRAFT){
  //success
}

1
votes

I would refactor to enums. Maybe check out https://github.com/spatie/enum or make a custom implementation with constants on the models and enum fields in the database.

0
votes

I would abstract this out to a service. How you write this service is up to you, my example below is using a static class and functions. You might want to make it insatiable and use dependency injection.

You will need to store the states in some way. I will start with a static array, then describe how you might pass this off to the database.

Static permitter class

interface HasStatusInterface
{
    function getStatus(): string
}


class ObjectPermitter
{
    const DRAFT = 'draft';
    const PLACED = 'placed';
    const COMPLETED = 'completed';

    const EDIT = 'edit';
    const DELETE = 'delete';

    const PERMISSIONS = [
        Models\Order::class => [
            self::DRAFT => [
                self::EDIT => true,
                self::DELETE => true,
            ],
            self::PLACED => [
                self::EDIT => true,
                self::DELETE => false,
            ],
            self::COMPLETED => [
                self::EDIT => false,
                self::DELETE => false,
            ],
        ],
    ];

    public static function can(HasStatusInterface $object, string $verb): bool
    {
        if (!in_array(get_class($object), array_keys(self::PERMISSIONS))) {
            return false; // or other sensible default
        }

        if (!in_array($object->getStatus(), array_keys(self::PERMISSIONS[get_class($object)])) {
            return false; // or other sensible default
        }

        if (!in_array($verb, array_keys(self::PERMISSIONS[get_class($object)][$object->getStatus()])) {
            return false; // or other sensible default
        }

        return self::PERMISSIONS[get_class($object)][$object->getStatus()][$verb];
    }
}

You can then check the ability to do things to the object with

if (ObjectPermitter::can($object, ObjectPermitter::EDIT)) {
    // yes, you can
}

Now, that is a lot of static setup for this and means you have to push code each time you want to change the way your behaviour works, but you should be able to see how easy it is to extend for new model types and new statuses.

Database

The other way would be to move all this to the database.

id | status
--------------
1  | draft
2  | placed
3  | completed

status_id | document_class | verb
------------------------------------
1         | Models\Order   | edit
1         | Models\Order   | delete
2         | Models\Order   | edit

Now, your can function can do a lookup on the database

SELECT COUNT(*) AS c
FROM status
    INNER JOIN status_permission ON status.id = status_permission.status_id
WHERE status.status = :1
    AND status_permission.document_class = :2
    AND status_permission.verb = :3

Where your calling code fills in

:1 $object->getStatus()
:2 get_class($object)
:3 self::EDIT, or 'edit'

Then your function can return $results[0]['c'] == 1;