Overview
The Restriction class is the base class for all restriction implementations. It provides the core structure and utility methods for validating access based on custom criteria. Extend this class to create custom restriction types.
Constructor
Creates a Restriction instance with restriction data.
public function __construct(array $list)
Nested array of restriction methods and their data:[
'method_name' => [
['i' => 1, 'd' => [/* data */]],
['i' => 2, 'd' => [/* data */]]
]
]
Protected Properties
Restriction data organized by method
Map of method names to class method names. Must be defined in child classes:protected array $methods = [
'before' => 'before',
'after' => 'after',
'in_range' => 'inRange'
];
Stores the last validation error with 'method' and 'restriction' keys
Methods
has()
Checks if a restriction method exists and is configured.
public function has(string $method, bool $includeList = true): bool
The restriction method name (e.g., 'before', 'in_range', 'allow')
Whether to also verify the method exists in the restriction data list
Returns true if method exists and is configured, false otherwise
if ($restriction->has('in_range')) {
// This restriction has date range validation configured
}
// Check if method exists in class (ignore data)
if ($restriction->has('before', false)) {
// The 'before' method is defined in this restriction class
}
run()
Executes all configured restriction validations.
public function run(array $externalData): bool
External context data needed for validation (e.g., current date, IP address, entity ID)
Returns true if all restrictions pass, false if any restriction fails
// Date restriction
$passed = $dateRestriction->run(['date' => time()]);
// Entity restriction
$passed = $entityRestriction->run(['entity' => 'branch-123']);
// IP restriction
$passed = $ipRestriction->run(['ip' => '192.168.1.100']);
if (!$passed) {
$error = $restriction->getError();
echo "Failed: {$error['method']}";
}
getError()
Returns the last validation error.
public function getError(): array
Array containing:
method (string): The method that failed
restriction (array): The restriction data that caused the failure
if (!$restriction->run($context)) {
$error = $restriction->getError();
echo "Failed method: {$error['method']}\n";
echo "Restriction ID: {$error['restriction']['i']}\n";
echo "Restriction data: " . print_r($error['restriction']['d'], true);
}
validateDataIntegrity() (Protected)
Utility method to validate data structure and types.
protected function validateDataIntegrity(array $data, array $list): bool
Expected structure: ['key' => ['type1', 'type2']]Supported types: 'string', 'integer', 'array', 'boolean', 'double', 'NULL'
Returns true if data matches expected structure, false otherwise
// In your restriction method
public function before(array $internalData, array $externalData): bool {
// Validate internal data has 'd' key as string
if (!$this->validateDataIntegrity($internalData, ['d' => ['string']])) {
return false;
}
// Validate external data has 'date' key as integer
if (!$this->validateDataIntegrity($externalData, ['date' => ['integer']])) {
return false;
}
// Validation logic...
return $externalData['date'] < strtotime($internalData['d']);
}
Creating Custom Restrictions
Basic Structure
use DancasDev\GAC\Restrictions\Restriction;
class ByCustomCriteria extends Restriction {
// Define method mappings
protected array $methods = [
'method_name' => 'methodName',
'another_method' => 'anotherMethod'
];
// Implement restriction methods
public function methodName(array $internalData, array $externalData): bool {
// Validate data integrity
if (!$this->validateDataIntegrity($internalData, [
'required_key' => ['string', 'integer']
])) {
return false;
}
if (!$this->validateDataIntegrity($externalData, [
'context_key' => ['string']
])) {
return false;
}
// Implement validation logic
return $externalData['context_key'] === $internalData['required_key'];
}
public function anotherMethod(array $internalData, array $externalData): bool {
// Another validation method
return true;
}
}
Complete Example: IP Whitelist/Blacklist
namespace App\Restrictions;
use DancasDev\GAC\Restrictions\Restriction;
final class ByIpAddress extends Restriction {
protected array $methods = [
'whitelist' => 'whitelist',
'blacklist' => 'blacklist',
'range' => 'ipRange'
];
/**
* Allow only specific IP addresses
*/
public function whitelist(array $internalData, array $externalData): bool {
// Validate internal data
if (!$this->validateDataIntegrity($internalData, ['ips' => ['array']])) {
return false;
}
// Validate external data
if (!$this->validateDataIntegrity($externalData, ['ip' => ['string']])) {
return false;
}
// Check if IP is in whitelist
return in_array($externalData['ip'], $internalData['ips']);
}
/**
* Block specific IP addresses
*/
public function blacklist(array $internalData, array $externalData): bool {
// Validate data
if (!$this->validateDataIntegrity($internalData, ['ips' => ['array']])) {
return false;
}
if (!$this->validateDataIntegrity($externalData, ['ip' => ['string']])) {
return false;
}
// Deny if IP is in blacklist
return !in_array($externalData['ip'], $internalData['ips']);
}
/**
* Allow IP range using CIDR notation
*/
public function ipRange(array $internalData, array $externalData): bool {
// Validate data
if (!$this->validateDataIntegrity($internalData, [
'cidr' => ['string']
])) {
return false;
}
if (!$this->validateDataIntegrity($externalData, ['ip' => ['string']])) {
return false;
}
// Check if IP is in CIDR range
return $this->ipInRange($externalData['ip'], $internalData['cidr']);
}
/**
* Helper: Check if IP is in CIDR range
*/
private function ipInRange(string $ip, string $cidr): bool {
list($subnet, $mask) = explode('/', $cidr);
$ip_long = ip2long($ip);
$subnet_long = ip2long($subnet);
$mask_long = -1 << (32 - (int)$mask);
$subnet_long &= $mask_long;
return ($ip_long & $mask_long) === $subnet_long;
}
}
Registration and Usage
use DancasDev\GAC\Restrictions\Restrictions;
use App\Restrictions\ByIpAddress;
// Register the custom restriction
Restrictions::register('by_ip', ByIpAddress::class);
// Use it
$restrictions = $gac->getRestrictions();
if ($restrictions->has('by_ip')) {
$ipRestriction = $restrictions->get('by_ip');
$userIp = $_SERVER['REMOTE_ADDR'];
$allowed = $ipRestriction->run(['ip' => $userIp]);
if (!$allowed) {
$error = $ipRestriction->getError();
die("Access denied from IP {$userIp}: {$error['method']}");
}
}
Method Implementation Guidelines
Method Signature
All restriction methods must follow this signature:
public function methodName(array $internalData, array $externalData): bool
Data stored in the restriction configuration (from database)
Runtime context data passed when checking the restriction
Implementation Pattern
public function myRestrictionMethod(array $internalData, array $externalData): bool {
// 1. Validate internal data structure
if (!$this->validateDataIntegrity($internalData, [
'required_field' => ['string'],
'optional_field' => ['string', 'NULL']
])) {
return false;
}
// 2. Validate external data structure
if (!$this->validateDataIntegrity($externalData, [
'context_field' => ['integer', 'string']
])) {
return false;
}
// 3. Process/transform data if needed
$processedValue = $this->processData($internalData['required_field']);
// 4. Perform validation logic
if ($externalData['context_field'] !== $processedValue) {
return false; // Restriction fails
}
// 5. Return true if restriction passes
return true;
}
Advanced Example: Geolocation Restriction
namespace App\Restrictions;
use DancasDev\GAC\Restrictions\Restriction;
final class ByGeolocation extends Restriction {
protected array $methods = [
'countries' => 'allowedCountries',
'radius' => 'withinRadius'
];
public function allowedCountries(array $internalData, array $externalData): bool {
// Validate configuration
if (!$this->validateDataIntegrity($internalData, [
'countries' => ['array'],
'mode' => ['string'] // 'allow' or 'deny'
])) {
return false;
}
// Validate runtime data
if (!$this->validateDataIntegrity($externalData, [
'country' => ['string']
])) {
return false;
}
$isInList = in_array(
strtoupper($externalData['country']),
array_map('strtoupper', $internalData['countries'])
);
// Allow or deny based on mode
if ($internalData['mode'] === 'allow') {
return $isInList;
} else {
return !$isInList;
}
}
public function withinRadius(array $internalData, array $externalData): bool {
// Validate configuration
if (!$this->validateDataIntegrity($internalData, [
'lat' => ['double', 'integer'],
'lng' => ['double', 'integer'],
'radius_km' => ['integer', 'double']
])) {
return false;
}
// Validate runtime data
if (!$this->validateDataIntegrity($externalData, [
'lat' => ['double', 'integer'],
'lng' => ['double', 'integer']
])) {
return false;
}
// Calculate distance using Haversine formula
$distance = $this->calculateDistance(
$internalData['lat'], $internalData['lng'],
$externalData['lat'], $externalData['lng']
);
return $distance <= $internalData['radius_km'];
}
private function calculateDistance($lat1, $lng1, $lat2, $lng2): float {
$earthRadius = 6371; // km
$dLat = deg2rad($lat2 - $lat1);
$dLng = deg2rad($lng2 - $lng1);
$a = sin($dLat/2) * sin($dLat/2) +
cos(deg2rad($lat1)) * cos(deg2rad($lat2)) *
sin($dLng/2) * sin($dLng/2);
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
return $earthRadius * $c;
}
}
See Also