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;
}
if (!in_array($object->getStatus(), array_keys(self::PERMISSIONS[get_class($object)])) {
return false;
}
if (!in_array($verb, array_keys(self::PERMISSIONS[get_class($object)][$object->getStatus()])) {
return false;
}
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)) {
}
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;