Overview
GAC’s adapter system allows you to integrate with any database or caching solution. This guide shows you how to create custom implementations of theDatabaseAdapterInterface and CacheAdapterInterface.
Database Adapter Interface
To create a custom database adapter, implement theDatabaseAdapterInterface from src/Adapters/DatabaseAdapterInterface.php:5.
Required Methods
namespace DancasDev\GAC\Adapters;
interface DatabaseAdapterInterface {
public function getRoles(string $entityType, string|int $entityId): array;
public function getPermissions(string $entityType, string|int $entityId, array $roleIds = []): array;
public function getRestrictions(string $entityType, string|int $entityId, array $roleIds = []): array;
public function getModulesData(array $categoryIds = [], array $moduleIds = []): array;
public function getEntitiesByRoles(array $roleIds): array;
}
Method Specifications
getRoles()
getRoles()
Retrieve roles assigned to an entity.Parameters:Example Implementation:
$entityType- Entity type (‘1’ = user, ‘2’ = client)$entityId- Entity identifier
[
[
'id' => 1,
'code' => 'system_administrator',
'priority' => 0
],
[
'id' => 2,
'code' => 'manager',
'priority' => 1
]
]
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);
}
getPermissions()
getPermissions()
Retrieve permissions for an entity and its roles.Parameters:Example Implementation:
$entityType- Entity type (‘1’ = user, ‘2’ = client)$entityId- Entity identifier$roleIds- Array of role IDs to include
[
[
'id' => 1,
'from_entity_type' => '0', // 0=role, 1=user, 2=client
'from_entity_id' => 1,
'to_entity_type' => '1', // 0=category, 1=module
'to_entity_id' => 3,
'feature' => '0,1,2,3', // Comma-separated features
'level' => '1' // 0=low, 1=normal, 2=high
]
]
public function getPermissions(string $entityType, string|int $entityId, array $roleIds = []): array {
if($entityType === '0') {
return [];
}
$query = 'SELECT id, from_entity_type, from_entity_id, to_entity_type, to_entity_id, feature, level';
$query .= ' FROM `gac_module_access` WHERE ((`from_entity_type` = ? AND `from_entity_id` = ?)';
foreach ($roleIds as $key => $id) {
$query .= ' OR (`from_entity_type` = \'0\' AND `from_entity_id` = ?)';
}
$query .= ') AND `deleted_at` IS NULL AND `is_disabled` = \'0\'';
$query .= ' ORDER BY `from_entity_type` DESC';
$query = $this->connection->prepare($query);
$query->execute(array_merge([$entityType, $entityId], $roleIds));
return $query->fetchAll(PDO::FETCH_ASSOC);
}
getRestrictions()
getRestrictions()
Retrieve restrictions for an entity and its roles.Parameters:Example Implementation:
$entityType- Entity type (‘1’ = user, ‘2’ = client, ‘3’ = global)$entityId- Entity identifier$roleIds- Array of role IDs to include
[
[
'id' => 1,
'entity_type' => '1',
'entity_id' => 123,
'category_code' => 'by_branch',
'type_code' => 'allow',
'data' => '{"l": ["5", "12"]}'
]
]
public function getRestrictions(string $entityType, string|int $entityId, array $roleIds = []): array {
if($entityType === '0') {
return [];
}
$query = 'SELECT a.id, a.entity_type, a.entity_id, c.code AS category_code, b.code AS type_code, a.data';
$query .= ' FROM `gac_restriction` AS a';
$query .= ' INNER JOIN `gac_restriction_method` AS b ON a.restriction_method_id = b.id';
$query .= ' INNER JOIN `gac_restriction_category` AS c ON b.restriction_category_id = c.id';
$query .= ' WHERE ((a.entity_type = ? AND a.entity_id = ?)';
foreach ($roleIds as $key => $id) {
$query .= ' OR (a.entity_type = \'0\' AND a.entity_id = ?)';
}
$query .= ') AND a.deleted_at IS NULL AND b.deleted_at IS NULL AND c.deleted_at IS NULL';
$query .= ' AND a.is_disabled = \'0\' AND b.is_disabled = \'0\' AND c.is_disabled = \'0\'';
$query .= ' ORDER BY a.entity_type DESC';
$query = $this->connection->prepare($query);
$query->execute(array_merge([$entityType, $entityId], $roleIds));
return $query->fetchAll(PDO::FETCH_ASSOC);
}
getModulesData()
getModulesData()
Retrieve module data by category or module IDs.Parameters:Example Implementation:
$categoryIds- Array of category IDs$moduleIds- Array of module IDs
[
[
'id' => 3,
'module_category_id' => 1,
'code' => 'users',
'is_developing' => '0'
]
]
public function getModulesData(array $categoryIds = [], array $moduleIds = []): array {
$hasModules = !empty($moduleIds);
$hasCategories = !empty($categoryIds);
if (!$hasModules && !$hasCategories) {
return [];
}
$query = 'SELECT a.id, a.module_category_id, a.code, a.is_developing';
$query .= ' FROM gac_module AS a INNER JOIN gac_module_category AS b ON a.module_category_id = b.id';
$query .= ' WHERE (';
if (!empty($categoryIds)) {
$query .= 'a.module_category_id IN (' . implode(',', $categoryIds) . ')';
}
if ($hasModules) {
$query .= ' OR a.id IN (' . implode(',', $moduleIds) . ')';
}
$query .= ') AND a.deleted_at IS NULL AND b.deleted_at IS NULL';
$query .= ' AND a.is_disabled = \'0\' AND b.is_disabled = \'0\'';
$query = $this->connection->prepare($query);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
getEntitiesByRoles()
getEntitiesByRoles()
Get all users and clients associated with specific roles.Parameters:Example Implementation:
$roleIds- Array of role IDs
[
[
'id' => 1,
'role_id' => 5,
'entity_type' => '1', // 1=user, 2=client
'entity_id' => 123
]
]
function getEntitiesByRoles(array $roleIds): array {
if (empty($roleIds)) {
return [];
}
$query = 'SELECT id, role_id, entity_type, entity_id';
$query .= ' FROM `gac_role_entity`';
$query .= ' WHERE role_id IN (' . implode(',', $roleIds) . ')';
$query .= ' AND is_disabled = \'0\' AND deleted_at IS NULL';
$query = $this->connection->prepare($query);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
}
Custom Database Adapter Examples
PostgreSQL Adapter
use DancasDev\GAC\Adapters\DatabaseAdapterInterface;
use DancasDev\GAC\Exceptions\DatabaseAdapterException;
class PostgreSQLAdapter implements DatabaseAdapterInterface {
private $connection;
public function __construct(array $config) {
try {
$dsn = "pgsql:host={$config['host']};dbname={$config['database']}";
$this->connection = new PDO($dsn, $config['username'], $config['password']);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (\Throwable $th) {
throw new DatabaseAdapterException('PostgreSQL connection failed: ' . $th->getMessage(), 0, $th);
}
}
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 = $1 AND a.entity_id = $2';
$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';
$stmt = $this->connection->prepare($query);
$stmt->execute([$entityType, $entityId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Implement other methods...
}
MongoDB Adapter
use DancasDev\GAC\Adapters\DatabaseAdapterInterface;
use MongoDB\Client;
class MongoDBAdapter implements DatabaseAdapterInterface {
private $db;
public function __construct(array $config) {
$client = new Client($config['uri']);
$this->db = $client->{$config['database']};
}
public function getRoles(string $entityType, string|int $entityId): array {
$pipeline = [
[
'$match' => [
'entity_type' => $entityType,
'entity_id' => (int)$entityId,
'is_disabled' => '0',
'deleted_at' => null
]
],
[
'$lookup' => [
'from' => 'gac_role',
'localField' => 'role_id',
'foreignField' => 'id',
'as' => 'role'
]
],
['$unwind' => '$role'],
[
'$match' => [
'role.is_disabled' => '0',
'role.deleted_at' => null
]
],
[
'$project' => [
'_id' => 0,
'id' => '$role.id',
'code' => '$role.code',
'priority' => 1
]
],
['$sort' => ['priority' => 1]]
];
$result = $this->db->gac_role_entity->aggregate($pipeline);
return $result->toArray();
}
// Implement other methods...
}
Doctrine ORM Adapter
use DancasDev\GAC\Adapters\DatabaseAdapterInterface;
use Doctrine\ORM\EntityManagerInterface;
class DoctrineAdapter implements DatabaseAdapterInterface {
private EntityManagerInterface $em;
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
public function getRoles(string $entityType, string|int $entityId): array {
$qb = $this->em->createQueryBuilder();
$qb->select('r.id', 'r.code', 're.priority')
->from('App\Entity\RoleEntity', 're')
->innerJoin('re.role', 'r')
->where('re.entityType = :entityType')
->andWhere('re.entityId = :entityId')
->andWhere('re.isDisabled = :disabled')
->andWhere('r.isDisabled = :disabled')
->andWhere('re.deletedAt IS NULL')
->andWhere('r.deletedAt IS NULL')
->setParameter('entityType', $entityType)
->setParameter('entityId', $entityId)
->setParameter('disabled', '0')
->orderBy('re.priority', 'ASC');
return $qb->getQuery()->getArrayResult();
}
// Implement other methods...
}
Cache Adapter Interface
To create a custom cache adapter, implement theCacheAdapterInterface from src/Adapters/CacheAdapterInterface.php:5.
Required Methods
namespace DancasDev\GAC\Adapters;
interface CacheAdapterInterface {
public function get(string $key): mixed;
public function save(string $key, mixed $data, ?int $ttl = 60): bool;
public function delete(string $key): bool;
public function deleteMatching(string $pattern): int;
public function clean(): bool;
}
Method Specifications
get()
get()
Retrieve cached value by key.Parameters:
$key- Cache key
null if not found/expiredpublic function get(string $key): mixed {
$file = $this->cacheDir . DIRECTORY_SEPARATOR . $key;
if (!file_exists($file)) {
return null;
}
$data = json_decode(file_get_contents($file), true);
if (empty($data) || (!empty($data['t']) && $data['t'] < time())) {
$this->delete($key);
return null;
}
return $data['v'] ?? null;
}
save()
save()
Store value in cache with TTL.Parameters:
$key- Cache key$data- Data to cache (any type)$ttl- Time to live in seconds (null = no expiration)
true on success, false on failurepublic function save(string $key, $data, int|null $ttl = 60): bool {
$file = $this->cacheDir . DIRECTORY_SEPARATOR . $key;
$dataToSave = [
't' => empty($ttl) ? null : time() + $ttl,
'v' => $data
];
return file_put_contents($file, json_encode($dataToSave)) !== false;
}
delete()
delete()
Delete specific cache key.Parameters:
$key- Cache key to delete
true on success, false if key doesn’t existpublic function delete(string $key): bool {
$file = $this->cacheDir . DIRECTORY_SEPARATOR . $key;
if (file_exists($file)) {
return unlink($file);
}
return false;
}
deleteMatching()
deleteMatching()
Delete all keys matching a glob pattern.Parameters:
$pattern- Glob pattern (e.g.,gac_p_*,myapp_*_123)
public function deleteMatching(string $pattern): int {
$deletedCount = 0;
$files = glob($this->cacheDir . DIRECTORY_SEPARATOR . $pattern);
foreach ($files as $file) {
if (is_file($file) && unlink($file)) {
$deletedCount++;
}
}
return $deletedCount;
}
clean()
clean()
Clear all cache entries.Returns:
true on success, false on failurepublic function clean(): bool {
$files = glob($this->cacheDir . DIRECTORY_SEPARATOR . '*');
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
}
}
return true;
}
Custom Cache Adapter Examples
Redis Adapter
use DancasDev\GAC\Adapters\CacheAdapterInterface;
use DancasDev\GAC\Exceptions\CacheAdapterException;
class RedisCacheAdapter implements CacheAdapterInterface {
private $redis;
private $prefix;
public function __construct(array $config) {
$this->redis = new \Redis();
if (!$this->redis->connect($config['host'], $config['port'] ?? 6379)) {
throw new CacheAdapterException('Redis connection failed');
}
if (isset($config['password'])) {
$this->redis->auth($config['password']);
}
$this->redis->select($config['database'] ?? 0);
$this->prefix = $config['prefix'] ?? 'gac:';
}
public function get(string $key): mixed {
$data = $this->redis->get($this->prefix . $key);
return $data ? json_decode($data, true) : null;
}
public function save(string $key, mixed $data, ?int $ttl = 60): bool {
$encoded = json_encode($data);
$fullKey = $this->prefix . $key;
if ($ttl) {
return $this->redis->setex($fullKey, $ttl, $encoded);
}
return $this->redis->set($fullKey, $encoded);
}
public function delete(string $key): bool {
return $this->redis->del($this->prefix . $key) > 0;
}
public function deleteMatching(string $pattern): int {
$keys = $this->redis->keys($this->prefix . $pattern);
return !empty($keys) ? $this->redis->del(...$keys) : 0;
}
public function clean(): bool {
return $this->redis->flushDB();
}
}
// Usage
$redisAdapter = new RedisCacheAdapter([
'host' => '127.0.0.1',
'port' => 6379,
'password' => 'your_password',
'database' => 0,
'prefix' => 'myapp:gac:'
]);
$gac->setCache('myapp', 3600, $redisAdapter);
Memcached Adapter
use DancasDev\GAC\Adapters\CacheAdapterInterface;
use DancasDev\GAC\Exceptions\CacheAdapterException;
class MemcachedAdapter implements CacheAdapterInterface {
private $memcached;
private $prefix;
public function __construct(array $config) {
$this->memcached = new \Memcached();
$this->memcached->addServer(
$config['host'] ?? '127.0.0.1',
$config['port'] ?? 11211
);
$this->prefix = $config['prefix'] ?? 'gac_';
}
public function get(string $key): mixed {
$data = $this->memcached->get($this->prefix . $key);
return $this->memcached->getResultCode() === \Memcached::RES_SUCCESS ? $data : null;
}
public function save(string $key, mixed $data, ?int $ttl = 60): bool {
return $this->memcached->set(
$this->prefix . $key,
$data,
$ttl ?? 0
);
}
public function delete(string $key): bool {
return $this->memcached->delete($this->prefix . $key);
}
public function deleteMatching(string $pattern): int {
// Memcached doesn't support pattern matching
// You'd need to maintain a key registry
return 0;
}
public function clean(): bool {
return $this->memcached->flush();
}
}
APCu Adapter
use DancasDev\GAC\Adapters\CacheAdapterInterface;
class APCuAdapter implements CacheAdapterInterface {
private $prefix;
public function __construct(string $prefix = 'gac_') {
if (!extension_loaded('apcu')) {
throw new \RuntimeException('APCu extension not loaded');
}
$this->prefix = $prefix;
}
public function get(string $key): mixed {
$success = false;
$data = apcu_fetch($this->prefix . $key, $success);
return $success ? $data : null;
}
public function save(string $key, mixed $data, ?int $ttl = 60): bool {
return apcu_store($this->prefix . $key, $data, $ttl ?? 0);
}
public function delete(string $key): bool {
return apcu_delete($this->prefix . $key);
}
public function deleteMatching(string $pattern): int {
$iterator = new \APCUIterator('/^' . preg_quote($this->prefix . $pattern, '/') . '/');
$count = 0;
foreach ($iterator as $entry) {
if (apcu_delete($entry['key'])) {
$count++;
}
}
return $count;
}
public function clean(): bool {
return apcu_clear_cache();
}
}
Custom Restriction Classes
Extend the baseRestriction class to create custom restriction types.
IP Address Restriction
use DancasDev\GAC\Restrictions\Restriction;
class ByIp extends Restriction {
protected array $methods = [
'allow' => 'allowIp',
'deny' => 'denyIp'
];
public function allowIp(array $internalData, array $externalData): bool {
// Validate data integrity
if (!$this->validateDataIntegrity($internalData, ['ips' => ['array']])) {
return false;
}
if (!$this->validateDataIntegrity($externalData, ['ip' => ['string']])) {
return false;
}
$userIp = $externalData['ip'];
foreach ($internalData['ips'] as $allowedIp) {
if ($this->ipMatches($userIp, $allowedIp)) {
return true;
}
}
return false;
}
public function denyIp(array $internalData, array $externalData): bool {
if (!$this->validateDataIntegrity($internalData, ['ips' => ['array']])) {
return false;
}
if (!$this->validateDataIntegrity($externalData, ['ip' => ['string']])) {
return false;
}
$userIp = $externalData['ip'];
foreach ($internalData['ips'] as $deniedIp) {
if ($this->ipMatches($userIp, $deniedIp)) {
return false;
}
}
return true;
}
private function ipMatches(string $ip, string $range): bool {
// Support CIDR notation
if (strpos($range, '/') !== false) {
list($subnet, $mask) = explode('/', $range);
$ip_long = ip2long($ip);
$subnet_long = ip2long($subnet);
$mask_long = -1 << (32 - (int)$mask);
return ($ip_long & $mask_long) === ($subnet_long & $mask_long);
}
// Exact match
return $ip === $range;
}
}
// Register the restriction
use DancasDev\GAC\Restrictions\Restrictions;
Restrictions::register('by_ip', ByIp::class);
// Database setup
/*
INSERT INTO gac_restriction_category (name, code, created_at)
VALUES ('By IP Address', 'by_ip', UNIX_TIMESTAMP());
INSERT INTO gac_restriction_method (restriction_category_id, name, code, created_at)
VALUES (last_insert_id(), 'Allow IPs', 'allow', UNIX_TIMESTAMP());
INSERT INTO gac_restriction (entity_type, entity_id, restriction_method_id, data, created_at)
VALUES ('1', 123, last_insert_id(), '{"ips": ["192.168.1.0/24", "10.0.0.1"]}', UNIX_TIMESTAMP());
*/
// Usage
$restrictions = $gac->getRestrictions();
$ipRestriction = $restrictions->get('by_ip');
if ($ipRestriction) {
$isAllowed = $ipRestriction->run(['ip' => $_SERVER['REMOTE_ADDR']]);
if (!$isAllowed) {
die('Your IP address is not allowed.');
}
}
Geolocation Restriction
use DancasDev\GAC\Restrictions\Restriction;
class ByCountry extends Restriction {
protected array $methods = [
'allow' => 'allowCountries',
'deny' => 'denyCountries'
];
public function allowCountries(array $internalData, array $externalData): bool {
if (!$this->validateDataIntegrity($internalData, ['countries' => ['array']])) {
return false;
}
if (!$this->validateDataIntegrity($externalData, ['country' => ['string']])) {
return false;
}
return in_array($externalData['country'], $internalData['countries']);
}
public function denyCountries(array $internalData, array $externalData): bool {
if (!$this->validateDataIntegrity($internalData, ['countries' => ['array']])) {
return false;
}
if (!$this->validateDataIntegrity($externalData, ['country' => ['string']])) {
return false;
}
return !in_array($externalData['country'], $internalData['countries']);
}
}
// Register
Restrictions::register('by_country', ByCountry::class);
// Usage with GeoIP
$geoip = geoip_record_by_name($_SERVER['REMOTE_ADDR']);
$country = $geoip['country_code'] ?? 'XX';
$restrictions = $gac->getRestrictions();
$countryRestriction = $restrictions->get('by_country');
if ($countryRestriction && !$countryRestriction->run(['country' => $country])) {
die('Service not available in your country.');
}
Using Custom Adapters
Complete Example
use DancasDev\GAC\GAC;
use App\Adapters\PostgreSQLAdapter;
use App\Adapters\RedisCacheAdapter;
use App\Restrictions\ByIp;
use DancasDev\GAC\Restrictions\Restrictions;
// Initialize custom adapters
$dbAdapter = new PostgreSQLAdapter([
'host' => 'localhost',
'database' => 'myapp',
'username' => 'postgres',
'password' => 'password'
]);
$cacheAdapter = new RedisCacheAdapter([
'host' => '127.0.0.1',
'port' => 6379,
'database' => 0,
'prefix' => 'myapp:gac:'
]);
// Register custom restrictions
Restrictions::register('by_ip', ByIp::class);
// Setup GAC
$gac = new GAC();
$gac->setDatabase($dbAdapter);
$gac->setCache('myapp', 3600, $cacheAdapter);
$gac->setEntity('user', $userId);
// Use GAC normally
$permissions = $gac->getPermissions();
$restrictions = $gac->getRestrictions();
// Check IP restriction
$ipRestriction = $restrictions->get('by_ip');
if ($ipRestriction && !$ipRestriction->run(['ip' => $_SERVER['REMOTE_ADDR']])) {
http_response_code(403);
die('Access denied from your IP address.');
}
Testing Custom Adapters
Database Adapter Test
use PHPUnit\Framework\TestCase;
class PostgreSQLAdapterTest extends TestCase {
private $adapter;
protected function setUp(): void {
$this->adapter = new PostgreSQLAdapter([
'host' => 'localhost',
'database' => 'test_db',
'username' => 'test',
'password' => 'test'
]);
}
public function testGetRoles() {
$roles = $this->adapter->getRoles('1', 123);
$this->assertIsArray($roles);
$this->assertNotEmpty($roles);
$this->assertArrayHasKey('id', $roles[0]);
$this->assertArrayHasKey('code', $roles[0]);
$this->assertArrayHasKey('priority', $roles[0]);
}
public function testGetPermissions() {
$permissions = $this->adapter->getPermissions('1', 123, [1, 2]);
$this->assertIsArray($permissions);
foreach ($permissions as $perm) {
$this->assertArrayHasKey('id', $perm);
$this->assertArrayHasKey('feature', $perm);
$this->assertArrayHasKey('level', $perm);
}
}
}
Cache Adapter Test
class RedisCacheAdapterTest extends TestCase {
private $adapter;
protected function setUp(): void {
$this->adapter = new RedisCacheAdapter([
'host' => '127.0.0.1',
'port' => 6379,
'database' => 15 // Use separate test database
]);
$this->adapter->clean();
}
public function testSaveAndGet() {
$data = ['test' => 'value'];
$this->assertTrue($this->adapter->save('test_key', $data, 60));
$this->assertEquals($data, $this->adapter->get('test_key'));
}
public function testDelete() {
$this->adapter->save('test_key', ['data'], 60);
$this->assertTrue($this->adapter->delete('test_key'));
$this->assertNull($this->adapter->get('test_key'));
}
public function testDeleteMatching() {
$this->adapter->save('gac_p_1', ['data1'], 60);
$this->adapter->save('gac_p_2', ['data2'], 60);
$this->adapter->save('gac_r_1', ['data3'], 60);
$deleted = $this->adapter->deleteMatching('gac_p_*');
$this->assertEquals(2, $deleted);
$this->assertNull($this->adapter->get('gac_p_1'));
$this->assertNull($this->adapter->get('gac_p_2'));
$this->assertNotNull($this->adapter->get('gac_r_1'));
}
}
Best Practices
Error Handling
Error Handling
Always throw appropriate exceptions:
use DancasDev\GAC\Exceptions\DatabaseAdapterException;
use DancasDev\GAC\Exceptions\CacheAdapterException;
// In database adapter
try {
$result = $this->connection->query($sql);
} catch (\Throwable $e) {
throw new DatabaseAdapterException(
'Query failed: ' . $e->getMessage(),
0,
$e
);
}
// In cache adapter
if (!$this->redis->connect($host, $port)) {
throw new CacheAdapterException('Redis connection failed');
}
Connection Pooling
Connection Pooling
Reuse connections when possible:
class PostgreSQLAdapter implements DatabaseAdapterInterface {
private static $sharedConnection;
public function __construct(array $config) {
if (self::$sharedConnection === null) {
self::$sharedConnection = new PDO(...);
}
$this->connection = self::$sharedConnection;
}
}
Lazy Loading
Lazy Loading
Don’t connect until needed:
class RedisCacheAdapter implements CacheAdapterInterface {
private $redis;
private $config;
private $connected = false;
public function __construct(array $config) {
$this->config = $config;
}
private function connect() {
if (!$this->connected) {
$this->redis = new \Redis();
$this->redis->connect($this->config['host'], $this->config['port']);
$this->connected = true;
}
}
public function get(string $key): mixed {
$this->connect();
return $this->redis->get($key);
}
}
Type Safety
Type Safety
Use strict types and return type declarations:
declare(strict_types=1);
class MyAdapter implements DatabaseAdapterInterface {
public function getRoles(string $entityType, string|int $entityId): array {
// Implementation
}
}
Next Steps
Setup Database
Review database schema and customization
Cache Management
Learn advanced caching strategies