Skip to content

Instantly share code, notes, and snippets.

@chrispage1
Last active January 5, 2023 10:14
Show Gist options
  • Save chrispage1/67cd7623b1ddf85e83d34f7695cb8fe0 to your computer and use it in GitHub Desktop.
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
<?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