Skip to main content

Overview

GAC uses adapters to interact with your database and cache systems. This design allows you to use the built-in adapters or create custom implementations for your specific infrastructure.

Database Adapter

The database adapter handles all queries to retrieve permissions, restrictions, roles, and module data.

Using Built-in Database Adapter

If you already have a PDO connection, pass it directly:
use DancasDev\GAC\GAC;

// Your existing PDO connection
$pdo = new PDO(
    'mysql:host=localhost;dbname=my_database',
    'username',
    'password'
);

// Initialize GAC with PDO
$gac = new GAC();
$gac->setDatabase($pdo);
This approach is recommended if you’re using PDO elsewhere in your application to avoid multiple connections.

Database Adapter Interface

The built-in DatabaseAdapter class (src/Adapters/DatabaseAdapter.php:9) implements these methods:
interface DatabaseAdapterInterface {
    // Get roles assigned to an entity
    public function getRoles(string $entityType, string|int $entityId): array;
    
    // Get permissions for an entity and its roles
    public function getPermissions(string $entityType, string|int $entityId, array $roleIds = []): array;
    
    // Get restrictions for an entity and its roles
    public function getRestrictions(string $entityType, string|int $entityId, array $roleIds = []): array;
    
    // Get module data by category or module IDs
    public function getModulesData(array $categoryIds = [], array $moduleIds = []): array;
    
    // Get entities (users/clients) associated with specific roles
    public function getEntitiesByRoles(array $roleIds): array;
}

Example: Database Adapter Queries

Here’s how the built-in adapter retrieves roles (src/Adapters/DatabaseAdapter.php:49):
public function getRoles(string $entityType, string|int $entityId): array {
    $query = 'SELECT b.id, b.code, a.priority';
    $query .= ' FROM `gac_role_entity` AS a INNER JOIN `gac_role` AS b ON a.role_id = b.id';
    $query .= ' WHERE a.entity_type = :entity_type AND a.entity_id = :entity_id';
    $query .= ' AND a.is_disabled = \'0\' AND b.is_disabled = \'0\'';
    $query .= ' AND a.deleted_at IS NULL AND b.deleted_at IS NULL';
    $query .= ' ORDER BY a.priority ASC';
    
    $query = $this->connection->prepare($query);
    $query->bindParam(':entity_type', $entityType, PDO::PARAM_INT);
    $query->bindParam(':entity_id', $entityId, PDO::PARAM_INT);
    $query->execute();

    return $query->fetchAll(PDO::FETCH_ASSOC);
}

Cache Adapter

The cache adapter stores processed permissions and restrictions to improve performance. It significantly reduces database queries.

Using Built-in Cache Adapter

The default cache adapter stores data as JSON files:
use DancasDev\GAC\GAC;

$gac = new GAC();
$gac->setDatabase($pdo);

// Simple setup with defaults
$gac->setCache();
// Cache key prefix: 'gac'
// TTL: 1800 seconds (30 minutes)
// Directory: vendor/dancasdev/gac/src/writable/

Cache Adapter Interface

The built-in CacheAdapter class (src/Adapters/CacheAdapter.php:8) implements:
interface CacheAdapterInterface {
    // Retrieve cached value by key
    public function get(string $key): mixed;
    
    // Store value in cache with TTL
    public function save(string $key, mixed $data, ?int $ttl = 60): bool;
    
    // Delete specific cache key
    public function delete(string $key): bool;
    
    // Delete cache keys matching a pattern
    public function deleteMatching(string $pattern): int;
    
    // Clear all cache
    public function clean(): bool;
}

How Caching Works

When you call getPermissions() or getRestrictions(), GAC:
  1. Generates a unique cache key based on entity type and ID
  2. Checks if cached data exists and is valid (not expired)
  3. If cache hit: returns cached data
  4. If cache miss: queries database, processes data, stores in cache, returns data
Cache Key Format (src/GAC.php:49):
public function getCacheKey(string $type): string {
    if ($type == 'restrictions_global') {
        return $this->cachekey . '_r_global';
    }
    else {
        $type = substr($type, 0, 1);
        return $this->cachekey . '_' . $type . '_' . $this->entityType . '_' . $this->entityId;
    }
}
Examples:
  • User #123 permissions: gac_p_1_123
  • Client #456 restrictions: gac_r_2_456
  • Global restrictions: gac_r_global

Cache TTL Management

You can set different TTL values per entity:
$gac = new GAC();
$gac->setDatabase($pdo);
$gac->setCache('myapp', 1800); // Default 30 minutes

// For a specific entity, use different TTL
$gac->setEntity('user', 123);
$gac->setCacheTtl(7200); // 2 hours for this entity
$permissions = $gac->getPermissions();

// Reset to default for next entity
$gac->setEntity('user', 456);
$gac->setCacheTtl(1800); // Back to 30 minutes
$permissions = $gac->getPermissions();

Complete Setup Example

Here’s a complete initialization with both adapters:
use DancasDev\GAC\GAC;

// Initialize GAC
$gac = new GAC();

// Setup database adapter
$gac->setDatabase([
    'host' => $_ENV['DB_HOST'],
    'username' => $_ENV['DB_USER'],
    'password' => $_ENV['DB_PASS'],
    'database' => $_ENV['DB_NAME']
]);

// Setup cache adapter
$gac->setCache(
    key: 'myapp_gac',
    ttl: 3600,
    dir: __DIR__ . '/../storage/cache/gac'
);

// Set the entity (user or client)
$gac->setEntity('user', $_SESSION['user_id']);

// Now you can use GAC methods
$permissions = $gac->getPermissions();
$restrictions = $gac->getRestrictions();

Environment-based Configuration

$gac = new GAC();
$gac->setDatabase([
    'host' => 'localhost',
    'username' => 'root',
    'password' => 'root',
    'database' => 'dev_database'
]);

// Shorter TTL for testing
$gac->setCache('dev_gac', 300); // 5 minutes

Dependency Injection

If you’re using a framework with DI, create a service:
// app/Services/GACService.php
namespace App\Services;

use DancasDev\GAC\GAC;
use PDO;

class GACService {
    private GAC $gac;
    
    public function __construct(PDO $pdo) {
        $this->gac = new GAC();
        $this->gac->setDatabase($pdo);
        $this->gac->setCache(
            key: config('gac.cache_key'),
            ttl: config('gac.cache_ttl'),
            dir: storage_path('cache/gac')
        );
    }
    
    public function forUser(int $userId): GAC {
        $this->gac->setEntity('user', $userId);
        return $this->gac;
    }
    
    public function forClient(int $clientId): GAC {
        $this->gac->setEntity('client', $clientId);
        return $this->gac;
    }
}
Then inject it:
// In your controller
public function __construct(private GACService $gacService) {}

public function index() {
    $gac = $this->gacService->forUser(auth()->id());
    $permissions = $gac->getPermissions();
    
    if (!$permissions->has('users')) {
        abort(403);
    }
    
    // ... rest of your code
}

Error Handling

use DancasDev\GAC\Exceptions\DatabaseAdapterException;

try {
    $gac = new GAC();
    $gac->setDatabase([
        'host' => 'invalid-host',
        'username' => 'user',
        'password' => 'pass',
        'database' => 'db'
    ]);
} catch (DatabaseAdapterException $e) {
    // Handle connection error
    error_log('GAC Database Error: ' . $e->getMessage());
    // Fallback to read-only mode or show maintenance page
}
use DancasDev\GAC\Exceptions\CacheAdapterException;

try {
    $gac = new GAC();
    $gac->setDatabase($pdo);
    $gac->setCache('app', 3600, '/readonly/path');
} catch (CacheAdapterException $e) {
    // Handle cache directory error
    error_log('GAC Cache Error: ' . $e->getMessage());
    // Continue without cache (will still work, just slower)
}
use DancasDev\GAC\Exceptions\DatabaseAdapterException;

class InvalidAdapter {}

try {
    $gac = new GAC();
    $gac->setDatabase(new InvalidAdapter());
} catch (DatabaseAdapterException $e) {
    // Error: Invalid implementation: The database adapter must implement DatabaseAdapterInterface
    echo $e->getMessage();
}

Verifying Setup

Test your adapter configuration:
use DancasDev\GAC\GAC;

$gac = new GAC();
$gac->setDatabase($pdo);
$gac->setCache();
$gac->setEntity('user', 1);

// Test database connection
try {
    $permissions = $gac->getPermissions(fromCache: false); // Force DB query
    echo "Database adapter: OK\n";
} catch (Exception $e) {
    echo "Database adapter: FAILED - " . $e->getMessage() . "\n";
}

// Test cache
try {
    $permissions = $gac->getPermissions(fromCache: true);
    echo "Cache adapter: OK\n";
    
    // Verify cache was written
    $cached = $gac->getPermissions(fromCache: true);
    echo "Cache read: OK\n";
} catch (Exception $e) {
    echo "Cache adapter: FAILED - " . $e->getMessage() . "\n";
}

// Clear cache
$gac->clearCache();
echo "Cache cleared: OK\n";

Next Steps

Checking Permissions

Learn how to check permissions in your application

Custom Adapters

Create custom database and cache adapters

Build docs developers (and LLMs) love