PHP Lesson 7: Microservices, Advanced Patterns & Production Excellence

PHP Lesson 7: Microservices, Advanced Patterns & Production Excellence

Oct 31, 2025 - 13:34
 0  137775
PHP Lesson 7: Microservices, Advanced Patterns & Production Excellence

PHP Lesson 7: Microservices, Advanced Patterns & Production Excellence

Welcome to Lesson 7! We're now diving into microservices architecture, advanced design patterns, and production excellence practices used by top tech companies.

Part 1: Microservices Architecture

1.1 API Gateway & Service Discovery

php
<?php
// gateway/GatewayService.php
class GatewayService {
    private $services;
    private $cache;
    private $circuitBreakers;

    public function __construct() {
        $this->services = [
            'users' => [
                'base_url' => env('USER_SERVICE_URL', 'http://user-service:8001'),
                'health_check' => '/health',
                'timeout' => 5
            ],
            'projects' => [
                'base_url' => env('PROJECT_SERVICE_URL', 'http://project-service:8002'),
                'health_check' => '/health',
                'timeout' => 5
            ],
            'notifications' => [
                'base_url' => env('NOTIFICATION_SERVICE_URL', 'http://notification-service:8003'),
                'health_check' => '/health',
                'timeout' => 5
            ]
        ];

        $this->cache = new RedisCache();
        $this->circuitBreakers = [];
    }

    public function route(Request $request) {
        $path = $request->getPathInfo();
        $serviceName = $this->extractServiceName($path);
        
        if (!isset($this->services[$serviceName])) {
            return $this->errorResponse('Service not found', 404);
        }

        // Check circuit breaker
        if ($this->isCircuitOpen($serviceName)) {
            return $this->fallbackResponse($serviceName, $request);
        }

        try {
            $response = $this->forwardRequest($serviceName, $request);
            
            // Mark success
            $this->recordSuccess($serviceName);
            
            return $response;
            
        } catch (Exception $e) {
            // Mark failure
            $this->recordFailure($serviceName);
            
            return $this->fallbackResponse($serviceName, $request);
        }
    }

    private function forwardRequest($serviceName, $request) {
        $service = $this->services[$serviceName];
        $client = new GuzzleHttp\Client([
            'timeout' => $service['timeout'],
            'base_uri' => $service['base_url']
        ]);

        $options = [
            'headers' => $this->filterHeaders($request->headers->all()),
            'body' => $request->getContent(),
            'query' => $request->query->all()
        ];

        $response = $client->request(
            $request->getMethod(),
            $this->rewritePath($request->getPathInfo()),
            $options
        );

        return new Response(
            $response->getBody(),
            $response->getStatusCode(),
            $this->filterResponseHeaders($response->getHeaders())
        );
    }

    private function extractServiceName($path) {
        $parts = explode('/', trim($path, '/'));
        return $parts[0] ?? 'unknown';
    }

    private function rewritePath($path) {
        $parts = explode('/', trim($path, '/'));
        array_shift($parts); // Remove service name
        return '/' . implode('/', $parts);
    }

    private function isCircuitOpen($serviceName) {
        $key = "circuit_{$serviceName}";
        $state = $this->cache->get($key);
        
        if ($state && $state['state'] === 'open') {
            // Check if we should try again
            if (time() - $state['last_failure'] < 60) { // 60 seconds cooldown
                return true;
            }
        }
        
        return false;
    }

    private function recordFailure($serviceName) {
        $key = "circuit_{$serviceName}";
        $failuresKey = "failures_{$serviceName}";
        
        // Increment failure count
        $failures = $this->cache->increment($failuresKey);
        
        if ($failures >= 5) { // Open circuit after 5 failures
            $this->cache->set($key, [
                'state' => 'open',
                'last_failure' => time()
            ], 300); // 5 minutes
        }
    }

    private function recordSuccess($serviceName) {
        // Reset failure count on success
        $this->cache->delete("failures_{$serviceName}");
        $this->cache->delete("circuit_{$serviceName}");
    }

    private function fallbackResponse($serviceName, $request) {
        // Implement fallback strategies
        $cacheKey = "fallback_{$serviceName}_" . md5($request->getPathInfo());
        
        if ($cached = $this->cache->get($cacheKey)) {
            return new Response($cached, 200, ['Content-Type' => 'application/json']);
        }

        // Default fallback
        return $this->errorResponse('Service temporarily unavailable', 503);
    }
}

// gateway/HealthCheckService.php
class HealthCheckService {
    private $services;

    public function __construct() {
        $this->services = [
            'users' => env('USER_SERVICE_URL') . '/health',
            'projects' => env('PROJECT_SERVICE_URL') . '/health',
            'notifications' => env('NOTIFICATION_SERVICE_URL') . '/health',
            'database' => 'mysql',
            'redis' => 'redis'
        ];
    }

    public function checkAll() {
        $results = [];
        
        foreach ($this->services as $name => $endpoint) {
            $results[$name] = $this->checkService($name, $endpoint);
        }

        $overallStatus = $this->calculateOverallStatus($results);

        return [
            'status' => $overallStatus,
            'timestamp' => time(),
            'services' => $results
        ];
    }

    private function checkService($name, $endpoint) {
        try {
            switch ($name) {
                case 'database':
                    return $this->checkDatabase();
                case 'redis':
                    return $this->checkRedis();
                default:
                    return $this->checkHttpService($endpoint);
            }
        } catch (Exception $e) {
            return [
                'status' => 'down',
                'response_time' => 0,
                'error' => $e->getMessage()
            ];
        }
    }

    private function checkHttpService($url) {
        $start = microtime(true);
        
        $client = new GuzzleHttp\Client(['timeout' => 3]);
        $response = $client->get($url);
        
        $responseTime = round((microtime(true) - $start) * 1000, 2);
        
        return [
            'status' => $response->getStatusCode() === 200 ? 'up' : 'down',
            'response_time' => $responseTime,
            'status_code' => $response->getStatusCode()
        ];
    }

    private function checkDatabase() {
        $start = microtime(true);
        
        try {
            DB::connection()->getPdo();
            $responseTime = round((microtime(true) - $start) * 1000, 2);
            
            return [
                'status' => 'up',
                'response_time' => $responseTime
            ];
        } catch (Exception $e) {
            return [
                'status' => 'down',
                'response_time' => 0,
                'error' => $e->getMessage()
            ];
        }
    }

    private function checkRedis() {
        $start = microtime(true);
        
        try {
            Redis::ping();
            $responseTime = round((microtime(true) - $start) * 1000, 2);
            
            return [
                'status' => 'up',
                'response_time' => $responseTime
            ];
        } catch (Exception $e) {
            return [
                'status' => 'down',
                'response_time' => 0,
                'error' => $e->getMessage()
            ];
        }
    }

    private function calculateOverallStatus($results) {
        foreach ($results as $service) {
            if ($service['status'] === 'down') {
                return 'degraded';
            }
        }
        return 'healthy';
    }
}

1.2 User Service (Microservice Example)

php
<?php
// services/user-service/app/Controllers/UserController.php
class UserController extends Controller {
    private $userRepository;
    private $eventPublisher;

    public function __construct(UserRepository $userRepository, EventPublisher $eventPublisher) {
        $this->userRepository = $userRepository;
        $this->eventPublisher = $eventPublisher;
    }

    public function createUser(Request $request) {
        $validator = Validator::make($request->all(), [
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users',
            'password' => 'required|min:8'
        ]);

        if ($validator->fails()) {
            return response()->json([
                'error' => 'Validation failed',
                'details' => $validator->errors()
            ], 422);
        }

        try {
            DB::beginTransaction();

            $user = $this->userRepository->create([
                'name' => $request->name,
                'email' => $request->email,
                'password' => password_hash($request->password, PASSWORD_DEFAULT),
                'status' => 'active'
            ]);

            // Publish user created event
            $this->eventPublisher->publish('user.created', [
                'user_id' => $user->id,
                'email' => $user->email,
                'name' => $user->name,
                'timestamp' => time()
            ]);

            DB::commit();

            return response()->json([
                'message' => 'User created successfully',
                'user' => $user
            ], 201);

        } catch (Exception $e) {
            DB::rollBack();
            
            Log::error('User creation failed: ' . $e->getMessage());
            
            return response()->json([
                'error' => 'User creation failed'
            ], 500);
        }
    }

    public function getUser($id) {
        $user = $this->userRepository->find($id);
        
        if (!$user) {
            return response()->json([
                'error' => 'User not found'
            ], 404);
        }

        return response()->json([
            'user' => $user
        ]);
    }

    public function updateUser($id, Request $request) {
        $user = $this->userRepository->find($id);
        
        if (!$user) {
            return response()->json([
                'error' => 'User not found'
            ], 404);
        }

        $validator = Validator::make($request->all(), [
            'name' => 'sometimes|string|max:255',
            'email' => 'sometimes|email|unique:users,email,' . $id
        ]);

        if ($validator->fails()) {
            return response()->json([
                'error' => 'Validation failed',
                'details' => $validator->errors()
            ], 422);
        }

        $user = $this->userRepository->update($id, $request->all());

        // Publish user updated event
        $this->eventPublisher->publish('user.updated', [
            'user_id' => $user->id,
            'changes' => $request->all(),
            'timestamp' => time()
        ]);

        return response()->json([
            'message' => 'User updated successfully',
            'user' => $user
        ]);
    }

    public function health() {
        try {
            // Check database connection
            DB::connection()->getPdo();
            
            // Check Redis connection
            Redis::ping();

            return response()->json([
                'status' => 'healthy',
                'timestamp' => time(),
                'service' => 'user-service'
            ]);

        } catch (Exception $e) {
            return response()->json([
                'status' => 'unhealthy',
                'error' => $e->getMessage(),
                'timestamp' => time(),
                'service' => 'user-service'
            ], 503);
        }
    }
}

// services/user-service/app/Services/EventPublisher.php
class EventPublisher {
    private $rabbitMQ;

    public function __construct() {
        $this->rabbitMQ = new AMQPStreamConnection(
            env('RABBITMQ_HOST', 'rabbitmq'),
            env('RABBITMQ_PORT', 5672),
            env('RABBITMQ_USER', 'guest'),
            env('RABBITMQ_PASS', 'guest')
        );
    }

    public function publish($eventType, $data) {
        try {
            $channel = $this->rabbitMQ->channel();
            
            $channel->exchange_declare('user_events', 'topic', false, true, false);
            
            $message = new AMQPMessage(
                json_encode($data),
                ['content_type' => 'application/json']
            );
            
            $channel->basic_publish($message, 'user_events', $eventType);
            
            $channel->close();
            
            Log::info("Event published: {$eventType}", $data);
            
        } catch (Exception $e) {
            Log::error("Failed to publish event: {$eventType} - " . $e->getMessage());
            
            // Fallback to database for event storage
            $this->storeEventInDatabase($eventType, $data);
        }
    }

    private function storeEventInDatabase($eventType, $data) {
        DB::table('event_outbox')->insert([
            'event_type' => $eventType,
            'payload' => json_encode($data),
            'created_at' => now(),
            'status' => 'pending'
        ]);
    }
}

Part 2: Advanced Design Patterns

2.1 CQRS (Command Query Responsibility Segregation)

php
<?php
// patterns/CQRS/Commands/CreateUserCommand.php
class CreateUserCommand {
    public $name;
    public $email;
    public $password;

    public function __construct($name, $email, $password) {
        $this->name = $name;
        $this->email = $email;
        $this->password = $password;
    }
}

// patterns/CQRS/Handlers/CreateUserHandler.php
class CreateUserHandler {
    private $userRepository;
    private $eventBus;

    public function __construct(UserRepository $userRepository, EventBus $eventBus) {
        $this->userRepository = $userRepository;
        $this->eventBus = $eventBus;
    }

    public function handle(CreateUserCommand $command) {
        // Validation
        if ($this->userRepository->findByEmail($command->email)) {
            throw new DomainException('User with this email already exists');
        }

        // Create user
        $user = new User([
            'name' => $command->name,
            'email' => $command->email,
            'password' => password_hash($command->password, PASSWORD_DEFAULT)
        ]);

        $this->userRepository->save($user);

        // Dispatch events
        $this->eventBus->dispatch(new UserWasCreated($user));

        return $user->getId();
    }
}

// patterns/CQRS/Queries/GetUserQuery.php
class GetUserQuery {
    public $userId;

    public function __construct($userId) {
        $this->userId = $userId;
    }
}

// patterns/CQRS/Handlers/GetUserHandler.php
class GetUserHandler {
    private $userReadRepository;

    public function __construct(UserReadRepository $userReadRepository) {
        $this->userReadRepository = $userReadRepository;
    }

    public function handle(GetUserQuery $query) {
        return $this->userReadRepository->findById($query->userId);
    }
}

// patterns/CQRS/ReadModels/UserReadModel.php
class UserReadModel {
    private $db;

    public function __construct(Connection $db) {
        $this->db = $db;
    }

    public function findById($userId) {
        return $this->db->table('user_read_models')
                       ->where('user_id', $userId)
                       ->first();
    }

    public function findByEmail($email) {
        return $this->db->table('user_read_models')
                       ->where('email', $email)
                       ->first();
    }

    public function updateFromEvent(UserWasCreated $event) {
        $this->db->table('user_read_models')->insert([
            'user_id' => $event->getUserId(),
            'name' => $event->getName(),
            'email' => $event->getEmail(),
            'created_at' => $event->getTimestamp(),
            'updated_at' => $event->getTimestamp()
        ]);
    }
}

2.2 Event Sourcing

php
<?php
// patterns/EventSourcing/AggregateRoot.php
abstract class AggregateRoot {
    private $recordedEvents = [];

    protected function recordThat(DomainEvent $event) {
        $this->recordedEvents[] = $event;
        $this->apply($event);
    }

    public function getRecordedEvents() {
        return $this->recordedEvents;
    }

    public function clearRecordedEvents() {
        $this->recordedEvents = [];
    }

    abstract public function aggregateId();
    abstract protected function apply(DomainEvent $event);
}

// patterns/EventSourcing/UserAggregate.php
class UserAggregate extends AggregateRoot {
    private $userId;
    private $name;
    private $email;
    private $status;
    private $version = 0;

    public static function create($userId, $name, $email) {
        $user = new self();
        $user->recordThat(new UserWasCreated($userId, $name, $email));
        return $user;
    }

    public function changeEmail($newEmail) {
        if ($this->email === $newEmail) {
            return;
        }

        $this->recordThat(new UserEmailWasChanged($this->userId, $newEmail));
    }

    public function deactivate() {
        if ($this->status === 'inactive') {
            return;
        }

        $this->recordThat(new UserWasDeactivated($this->userId));
    }

    public function apply(DomainEvent $event) {
        $this->version++;

        switch (get_class($event)) {
            case UserWasCreated::class:
                $this->userId = $event->getUserId();
                $this->name = $event->getName();
                $this->email = $event->getEmail();
                $this->status = 'active';
                break;

            case UserEmailWasChanged::class:
                $this->email = $event->getNewEmail();
                break;

            case UserWasDeactivated::class:
                $this->status = 'inactive';
                break;
        }
    }

    public function aggregateId() {
        return $this->userId;
    }

    public function getVersion() {
        return $this->version;
    }
}

// patterns/EventSourcing/EventStore.php
class EventStore {
    private $db;

    public function __construct(Connection $db) {
        $this->db = $db;
    }

    public function append($aggregateId, array $events, $expectedVersion) {
        $this->db->beginTransaction();

        try {
            // Check concurrency
            $currentVersion = $this->getCurrentVersion($aggregateId);
            
            if ($currentVersion !== $expectedVersion) {
                throw new ConcurrencyException("Expected version {$expectedVersion}, got {$currentVersion}");
            }

            // Store events
            foreach ($events as $event) {
                $this->db->table('event_store')->insert([
                    'aggregate_id' => $aggregateId,
                    'event_type' => get_class($event),
                    'event_data' => json_encode($event->serialize()),
                    'version' => ++$currentVersion,
                    'occurred_at' => $event->occurredAt()
                ]);
            }

            $this->db->commit();

        } catch (Exception $e) {
            $this->db->rollBack();
            throw $e;
        }
    }

    public function load($aggregateId) {
        $events = $this->db->table('event_store')
                          ->where('aggregate_id', $aggregateId)
                          ->orderBy('version')
                          ->get();

        return array_map(function ($event) {
            $eventClass = $event->event_type;
            return $eventClass::deserialize(json_decode($event->event_data, true));
        }, $events->toArray());
    }

    private function getCurrentVersion($aggregateId) {
        $result = $this->db->table('event_store')
                          ->where('aggregate_id', $aggregateId)
                          ->orderBy('version', 'desc')
                          ->first();

        return $result ? $result->version : 0;
    }
}

Part 3: Advanced Performance Optimization

3.1 Database Sharding & Read Replicas

php
<?php
// database/ShardManager.php
class ShardManager {
    private $shards;
    private $shardMap;

    public function __construct() {
        $this->shards = [
            'shard_1' => [
                'write' => env('DB_SHARD_1_WRITE'),
                'read' => [
                    env('DB_SHARD_1_READ_1'),
                    env('DB_SHARD_1_READ_2')
                ]
            ],
            'shard_2' => [
                'write' => env('DB_SHARD_2_WRITE'),
                'read' => [
                    env('DB_SHARD_2_READ_1'),
                    env('DB_SHARD_2_READ_2')
                ]
            ]
        ];

        $this->initializeShardMap();
    }

    public function getShardForUser($userId) {
        $shardId = $this->shardMap[$userId % count($this->shardMap)];
        return $this->shards[$shardId];
    }

    public function getReadConnection($shard) {
        $readConnections = $shard['read'];
        return $readConnections[array_rand($readConnections)];
    }

    public function getWriteConnection($shard) {
        return $shard['write'];
    }

    private function initializeShardMap() {
        // Simple round-robin shard mapping
        $this->shardMap = [];
        $shardKeys = array_keys($this->shards);
        
        for ($i = 0; $i < 1000; $i++) {
            $this->shardMap[$i] = $shardKeys[$i % count($shardKeys)];
        }
    }
}

// database/TenantAwareConnection.php
class TenantAwareConnection {
    private $shardManager;
    private $currentShard;

    public function __construct(ShardManager $shardManager) {
        $this->shardManager = $shardManager;
    }

    public function setTenant($tenantId) {
        $this->currentShard = $this->shardManager->getShardForUser($tenantId);
    }

    public function getReadPdo() {
        if (!$this->currentShard) {
            throw new RuntimeException('No tenant set for database connection');
        }

        $connection = $this->shardManager->getReadConnection($this->currentShard);
        return $this->createConnection($connection);
    }

    public function getWritePdo() {
        if (!$this->currentShard) {
            throw new RuntimeException('No tenant set for database connection');
        }

        $connection = $this->shardManager->getWriteConnection($this->currentShard);
        return $this->createConnection($connection);
    }

    private function createConnection($config) {
        return new PDO(
            "mysql:host={$config['host']};dbname={$config['database']}",
            $config['username'],
            $config['password'],
            [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
            ]
        );
    }
}

3.2 Advanced Caching Strategies

php
<?php
// cache/WriteBehindCache.php
class WriteBehindCache {
    private $cache;
    private $db;
    private $writeQueue;
    private $batchSize = 100;
    private $batchTimeout = 5; // seconds

    public function __construct(Cache $cache, Connection $db) {
        $this->cache = $cache;
        $this->db = $db;
        $this->writeQueue = new SplQueue();
    }

    public function get($key) {
        // Try cache first
        $value = $this->cache->get($key);
        
        if ($value !== null) {
            return $value;
        }

        // Cache miss - read from database
        $value = $this->readFromDatabase($key);
        
        if ($value !== null) {
            $this->cache->set($key, $value);
        }

        return $value;
    }

    public function set($key, $value) {
        // Write to cache immediately
        $this->cache->set($key, $value);
        
        // Queue database write
        $this->writeQueue->enqueue([
            'key' => $key,
            'value' => $value,
            'timestamp' => time()
        ]);

        // Process queue if batch size reached
        if ($this->writeQueue->count() >= $this->batchSize) {
            $this->processWriteQueue();
        }
    }

    public function delete($key) {
        $this->cache->delete($key);
        
        // Immediate delete from database for consistency
        $this->deleteFromDatabase($key);
    }

    private function processWriteQueue() {
        $batch = [];
        
        while (!$this->writeQueue->isEmpty()) {
            $batch[] = $this->writeQueue->dequeue();
            
            if (count($batch) >= $this->batchSize) {
                break;
            }
        }

        if (!empty($batch)) {
            $this->writeBatchToDatabase($batch);
        }
    }

    private function writeBatchToDatabase($batch) {
        $this->db->beginTransaction();

        try {
            foreach ($batch as $item) {
                $this->db->table('cache_data')
                        ->updateOrInsert(
                            ['key' => $item['key']],
                            [
                                'value' => json_encode($item['value']),
                                'updated_at' => now()
                            ]
                        );
            }

            $this->db->commit();
        } catch (Exception $e) {
            $this->db->rollBack();
            
            // Requeue failed items
            foreach ($batch as $item) {
                $this->writeQueue->enqueue($item);
            }
            
            Log::error('Write-behind cache batch failed: ' . $e->getMessage());
        }
    }

    public function __destruct() {
        // Process any remaining items on destruction
        if (!$this->writeQueue->isEmpty()) {
            $this->processWriteQueue();
        }
    }
}

// cache/LayeredCache.php
class LayeredCache {
    private $layers = [];

    public function __construct() {
        // L1: In-memory (APCu)
        $this->layers[] = new APCuCache();
        
        // L2: Redis
        $this->layers[] = new RedisCache();
        
        // L3: Database (persistent)
        $this->layers[] = new DatabaseCache();
    }

    public function get($key) {
        foreach ($this->layers as $layer) {
            $value = $layer->get($key);
            
            if ($value !== null) {
                // Populate higher layers
                $this->populateHigherLayers($key, $value, $layer);
                return $value;
            }
        }
        
        return null;
    }

    public function set($key, $value, $ttl = null) {
        foreach ($this->layers as $layer) {
            $layer->set($key, $value, $ttl);
        }
    }

    private function populateHigherLayers($key, $value, $sourceLayer) {
        foreach ($this->layers as $layer) {
            if ($layer === $sourceLayer) {
                break;
            }
            $layer->set($key, $value);
        }
    }
}

Part 4: Advanced Security Patterns

4.1 JWT Authentication with Refresh Tokens

php
<?php
// security/JWTAuthService.php
class JWTAuthService {
    private $secret;
    private $algorithm = 'HS256';
    private $accessTokenTtl = 3600; // 1 hour
    private $refreshTokenTtl = 2592000; // 30 days

    public function __construct() {
        $this->secret = env('JWT_SECRET');
    }

    public function generateTokenPair($userId, $payload = []) {
        $accessToken = $this->generateAccessToken($userId, $payload);
        $refreshToken = $this->generateRefreshToken($userId);

        return [
            'access_token' => $accessToken,
            'refresh_token' => $refreshToken,
            'expires_in' => $this->accessTokenTtl,
            'token_type' => 'Bearer'
        ];
    }

    public function generateAccessToken($userId, $payload = []) {
        $now = time();
        
        $tokenPayload = array_merge([
            'iss' => env('APP_URL'),
            'aud' => env('APP_URL'),
            'iat' => $now,
            'exp' => $now + $this->accessTokenTtl,
            'sub' => $userId,
            'type' => 'access'
        ], $payload);

        return JWT::encode($tokenPayload, $this->secret, $this->algorithm);
    }

    public function generateRefreshToken($userId) {
        $now = time();
        
        $tokenPayload = [
            'iss' => env('APP_URL'),
            'aud' => env('APP_URL'),
            'iat' => $now,
            'exp' => $now + $this->refreshTokenTtl,
            'sub' => $userId,
            'type' => 'refresh',
            'jti' => bin2hex(random_bytes(16)) // Unique token ID
        ];

        $refreshToken = JWT::encode($tokenPayload, $this->secret, $this->algorithm);

        // Store refresh token in database
        $this->storeRefreshToken($userId, $tokenPayload['jti'], $tokenPayload['exp']);

        return $refreshToken;
    }

    public function validateToken($token) {
        try {
            $payload = JWT::decode($token, $this->secret, [$this->algorithm]);
            
            // Check token type
            if ($payload->type !== 'access') {
                throw new Exception('Invalid token type');
            }

            // Check if token is blacklisted
            if ($this->isTokenBlacklisted($token)) {
                throw new Exception('Token has been revoked');
            }

            return $payload;

        } catch (Exception $e) {
            throw new Exception('Invalid token: ' . $e->getMessage());
        }
    }

    public function refreshToken($refreshToken) {
        try {
            $payload = JWT::decode($refreshToken, $this->secret, [$this->algorithm]);
            
            if ($payload->type !== 'refresh') {
                throw new Exception('Invalid refresh token');
            }

            // Verify refresh token exists in database
            if (!$this->validateRefreshToken($payload->sub, $payload->jti)) {
                throw new Exception('Refresh token not found or expired');
            }

            // Generate new token pair
            return $this->generateTokenPair($payload->sub);

        } catch (Exception $e) {
            throw new Exception('Token refresh failed: ' . $e->getMessage());
        }
    }

    public function revokeToken($token) {
        $payload = $this->validateToken($token);
        
        // Add to blacklist with TTL
        $ttl = $payload->exp - time();
        Redis::setex("token_blacklist:{$token}", $ttl, '1');
    }

    public function revokeRefreshToken($refreshToken) {
        try {
            $payload = JWT::decode($refreshToken, $this->secret, [$this->algorithm]);
            $this->deleteRefreshToken($payload->sub, $payload->jti);
        } catch (Exception $e) {
            // Token is invalid anyway
        }
    }

    private function storeRefreshToken($userId, $jti, $expiresAt) {
        DB::table('refresh_tokens')->insert([
            'user_id' => $userId,
            'token_id' => $jti,
            'expires_at' => date('Y-m-d H:i:s', $expiresAt),
            'created_at' => now()
        ]);
    }

    private function validateRefreshToken($userId, $jti) {
        return DB::table('refresh_tokens')
                ->where('user_id', $userId)
                ->where('token_id', $jti)
                ->where('expires_at', '>', now())
                ->exists();
    }

    private function deleteRefreshToken($userId, $jti) {
        DB::table('refresh_tokens')
          ->where('user_id', $userId)
          ->where('token_id', $jti)
          ->delete();
    }

    private function isTokenBlacklisted($token) {
        return Redis::exists("token_blacklist:{$token}");
    }
}

4.2 Rate Limiting with Redis

php
<?php
// security/AdvancedRateLimiter.php
class AdvancedRateLimiter {
    private $redis;
    private $limits;

    public function __construct() {
        $this->redis = new Redis();
        $this->limits = [
            'ip' => [
                'requests' => 1000, // 1000 requests
                'window' => 3600,   // per hour
                'block' => 3600     // block for 1 hour if exceeded
            ],
            'user' => [
                'requests' => 10000, // 10000 requests
                'window' => 3600,    // per hour
                'block' => 1800      // block for 30 minutes if exceeded
            ],
            'endpoint' => [
                'login' => [
                    'requests' => 5,   // 5 login attempts
                    'window' => 300,   // per 5 minutes
                    'block' => 900     // block for 15 minutes if exceeded
                ]
            ]
        ];
    }

    public function check($identifier, $type = 'ip', $endpoint = null) {
        $key = $this->buildKey($identifier, $type, $endpoint);
        $limit = $this->getLimit($type, $endpoint);

        // Check if blocked
        if ($this->isBlocked($key)) {
            throw new RateLimitException('Rate limit exceeded. Please try again later.');
        }

        // Check current usage
        $current = $this->getCurrentUsage($key, $limit['window']);

        if ($current >= $limit['requests']) {
            // Set block
            $this->setBlock($key, $limit['block']);
            throw new RateLimitException('Rate limit exceeded. Please try again later.');
        }

        // Increment counter
        $this->incrementUsage($key, $limit['window']);

        return [
            'limit' => $limit['requests'],
            'remaining' => $limit['requests'] - $current - 1,
            'reset' => $this->getResetTime($key, $limit['window'])
        ];
    }

    private function buildKey($identifier, $type, $endpoint) {
        $key = "rate_limit:{$type}:{$identifier}";
        
        if ($endpoint) {
            $key .= ":{$endpoint}";
        }
        
        return $key;
    }

    private function getLimit($type, $endpoint) {
        if ($endpoint && isset($this->limits['endpoint'][$endpoint])) {
            return $this->limits['endpoint'][$endpoint];
        }
        
        return $this->limits[$type] ?? $this->limits['ip'];
    }

    private function getCurrentUsage($key, $window) {
        $current = $this->redis->get($key);
        return $current ? (int)$current : 0;
    }

    private function incrementUsage($key, $window) {
        $pipe = $this->redis->pipeline();
        $pipe->incr($key);
        $pipe->expire($key, $window);
        $pipe->execute();
    }

    private function isBlocked($key) {
        return $this->redis->exists("{$key}:blocked");
    }

    private function setBlock($key, $blockDuration) {
        $this->redis->setex("{$key}:blocked", $blockDuration, '1');
    }

    private function getResetTime($key, $window) {
        $ttl = $this->redis->ttl($key);
        return $ttl > 0 ? time() + $ttl : time() + $window;
    }
}

Part 5: Production Monitoring & Alerting

5.1 Advanced Application Metrics

php
<?php
// monitoring/MetricsCollector.php
class MetricsCollector {
    private $prometheus;
    private $namespace = 'saas_app';

    public function __construct() {
        $this->prometheus = new Prometheus();
    }

    public function recordRequest($method, $route, $statusCode, $duration) {
        $this->prometheus->counter('http_requests_total', [
            'method' => $method,
            'route' => $route,
            'status' => $statusCode
        ])->inc();

        $this->prometheus->histogram('http_request_duration_seconds', [
            'method' => $method,
            'route' => $route
        ])->observe($duration);
    }

    public function recordDatabaseQuery($query, $duration, $success) {
        $this->prometheus->histogram('database_query_duration_seconds', [
            'query' => $this->normalizeQuery($query)
        ])->observe($duration);

        $this->prometheus->counter('database_queries_total', [
            'success' => $success ? 'true' : 'false'
        ])->inc();
    }

    public function recordCacheOperation($operation, $hit) {
        $this->prometheus->counter('cache_operations_total', [
            'operation' => $operation,
            'hit' => $hit ? 'true' : 'false'
        ])->inc();
    }

    public function recordBusinessEvent($event, $userId = null) {
        $labels = ['event' => $event];
        
        if ($userId) {
            $labels['user_id'] = $userId;
        }

        $this->prometheus->counter('business_events_total', $labels)->inc();
    }

    public function recordError($type, $message) {
        $this->prometheus->counter('errors_total', [
            'type' => $type
        ])->inc();
    }

    private function normalizeQuery($query) {
        // Normalize SQL queries for grouping
        $query = preg_replace('/\s+/', ' ', $query);
        $query = preg_replace('/= \?/', '= ?', $query);
        $query = preg_replace('/IN \(\?[^)]*\)/', 'IN (...)', $query);
        return substr($query, 0, 100); // Limit length
    }

    public function getMetrics() {
        return $this->prometheus->getMetrics();
    }
}

// monitoring/AlertManager.php
class AlertManager {
    private $alerts = [];
    private $notifiers = [];

    public function __construct() {
        $this->setupAlerts();
        $this->setupNotifiers();
    }

    private function setupAlerts() {
        $this->alerts = [
            'high_error_rate' => [
                'condition' => function($metrics) {
                    $totalRequests = $metrics['http_requests_total'] ?? 0;
                    $errorRequests = $metrics['http_requests_total{status=~"5.."}'] ?? 0;
                    
                    if ($totalRequests > 0) {
                        $errorRate = $errorRequests / $totalRequests;
                        return $errorRate > 0.05; // 5% error rate
                    }
                    return false;
                },
                'message' => 'High error rate detected: {error_rate}%',
                'severity' => 'critical'
            ],
            'high_response_time' => [
                'condition' => function($metrics) {
                    $p95 = $metrics['http_request_duration_seconds{quantile="0.95"}'] ?? 0;
                    return $p95 > 2.0; // P95 response time > 2 seconds
                },
                'message' => 'High response time detected: P95 at {p95}s',
                'severity' => 'warning'
            ],
            'database_slow_queries' => [
                'condition' => function($metrics) {
                    $slowQueries = $metrics['database_query_duration_seconds{quantile="0.95"}'] ?? 0;
                    return $slowQueries > 1.0; // Queries taking > 1 second
                },
                'message' => 'Slow database queries detected',
                'severity' => 'warning'
            ]
        ];
    }

    private function setupNotifiers() {
        $this->notifiers = [
            'slack' => new SlackNotifier(),
            'email' => new EmailNotifier(),
            'pagerduty' => new PagerDutyNotifier()
        ];
    }

    public function checkAlerts($metrics) {
        $activeAlerts = [];

        foreach ($this->alerts as $name => $alert) {
            if ($alert['condition']($metrics)) {
                $activeAlerts[] = [
                    'name' => $name,
                    'message' => $this->formatMessage($alert['message'], $metrics),
                    'severity' => $alert['severity'],
                    'timestamp' => time()
                ];
            }
        }

        if (!empty($activeAlerts)) {
            $this->notifyAlerts($activeAlerts);
        }

        return $activeAlerts;
    }

    private function formatMessage($template, $metrics) {
        return preg_replace_callback('/\{(\w+)\}/', function($matches) use ($metrics) {
            return $metrics[$matches[1]] ?? $matches[0];
        }, $template);
    }

    private function notifyAlerts($alerts) {
        foreach ($alerts as $alert) {
            foreach ($this->notifiers as $notifier) {
                try {
                    $notifier->send($alert);
                } catch (Exception $e) {
                    Log::error("Failed to send alert via " . get_class($notifier) . ": " . $e->getMessage());
                }
            }
        }
    }
}

Key Advanced Concepts Covered:

  1. Microservices Architecture: API Gateway, service discovery, circuit breakers

  2. Advanced Patterns: CQRS, Event Sourcing, Domain-Driven Design

  3. Performance Optimization: Database sharding, advanced caching strategies

  4. Security: JWT with refresh tokens, advanced rate limiting

  5. Monitoring: Metrics collection, alerting, observability

  6. Production Readiness: High availability, fault tolerance, scalability

Enterprise-Grade Features:

  1. Resilience: Circuit breakers, fallback strategies, retry mechanisms

  2. Scalability: Horizontal scaling, database sharding, read replicas

  3. Observability: Comprehensive metrics, distributed tracing, structured logging

  4. Security: Advanced authentication, rate limiting, security headers

  5. Maintainability: Clean architecture, separation of concerns, testing strategies

This lesson represents the pinnacle of PHP enterprise development, covering patterns and practices used by large-scale applications serving millions of users!

What's Your Reaction?

like

dislike

love

funny

angry

sad

wow

MA Hussain Amjad Hussain: Architect of Truth in the Realm of Fact In the intricate tapestry of human experience, where shadow often meets light, Amjad Hussain stands as a dedicated chronicler of reality. His is a world built on the unshakeable foundations of fact, a pursuit he undertakes with the dual tools of a journalist's discerning eye and a writer's compelling narrative skill.