Last active
January 5, 2023 10:14
-
-
Save chrispage1/67cd7623b1ddf85e83d34f7695cb8fe0 to your computer and use it in GitHub Desktop.
Pure PHP policy management. A basic implementation replicating the functionality of Laravel's policy management
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* A PHP trait which allows us to re-use logic | |
* across classes. Note that in 'User', we are calling 'use HasPolicies;' | |
* This allows the User class to utilise these methods. | |
*/ | |
trait HasPolicies { | |
/** | |
* Checks if the user has ALL the passed abilities | |
* | |
* @param string|array $abilities | |
* @param mixed $model | |
* | |
* @return bool | |
*/ | |
public function can($abilities, $model = null): bool | |
{ | |
// make sure abilities is an array | |
$abilities = is_array($abilities) ? $abilities : [$abilities]; | |
// retrieve all of our abilities from our helper method | |
$can = $this->hasAbilities($abilities, $model); | |
// check our filtered abilities matches our original count, | |
// i.e. we have all of the available abilities | |
return count(array_filter($can)) === count($abilities); | |
} | |
/** | |
* Checks if the user has one or more of the passed abilities | |
* | |
* @param string|array $abilities | |
* @param mixed $model | |
* | |
* @return bool | |
*/ | |
public function canAny($abilities, $model = null): bool | |
{ | |
// make sure abilities is an array | |
$abilities = is_array($abilities) ? $abilities : [$abilities]; | |
// retrieve all of our abilities from our helper method | |
$can = $this->hasAbilities($abilities, $model); | |
// check our filtered abilities has at least one | |
// remaining value, to match the 'canAny' criteria | |
return count(array_filter($can, fn($value) => !$value)) > 0; | |
} | |
/** | |
* Returns an array of abilities, with the value being true or false, | |
* dependent on whether the user has the appropriate permissions. | |
* | |
* @param array<bool> $abilities | |
* @param Model|null $model | |
* | |
* @return array | |
*/ | |
private function hasAbilities(array $abilities, ?Model $model = null): array { | |
// create an empty can array | |
$can = []; | |
// determine our policy name | |
$policy = new (get_class($this) . 'Policy'); | |
// loop through our abilities and check them against our policy | |
foreach ($abilities as $ability) { | |
$can[$ability] = $policy->{$ability}($this, $model); | |
} | |
return $can; | |
} | |
} | |
/** | |
* An empty model class. We're not really | |
* using this, just purely for demonstration | |
*/ | |
class Model { | |
// | |
} | |
/** | |
* A user class which simply uses the 'HasPolicies' | |
* trait, giving access to our policy methods within the user. | |
*/ | |
class User { | |
use HasPolicies; | |
} | |
/** | |
* A policy object. Ultimately just a list of methods | |
* that return true or false. Every method accepts the User object | |
* and conditionally a Model object which would allow us to apply | |
* more complex conditional logic. | |
*/ | |
class UserPolicy { | |
public function read(User $user, ?Model $model): bool | |
{ | |
// apply conditional logic here | |
return true; | |
} | |
public function update(User $user, ?Model $model): bool | |
{ | |
// apply conditional logic here | |
return false; | |
} | |
} | |
// create a new instance of our user. | |
$user = new User(); | |
// a user CAN read the model | |
var_dump($user->can(['read'], new Model())); // returns true | |
// a user CANNOT read AND update the model | |
var_dump($user->can(['read', 'update'], new Model())); // returns false | |
// a user CAN read OR update the model | |
var_dump($user->canAny(['read', 'update'], new Model())); // returns true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment