PHP Lesson 7: Microservices, Advanced Patterns & Production Excellence
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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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:
-
Microservices Architecture: API Gateway, service discovery, circuit breakers
-
Advanced Patterns: CQRS, Event Sourcing, Domain-Driven Design
-
Performance Optimization: Database sharding, advanced caching strategies
-
Security: JWT with refresh tokens, advanced rate limiting
-
Monitoring: Metrics collection, alerting, observability
-
Production Readiness: High availability, fault tolerance, scalability
Enterprise-Grade Features:
-
Resilience: Circuit breakers, fallback strategies, retry mechanisms
-
Scalability: Horizontal scaling, database sharding, read replicas
-
Observability: Comprehensive metrics, distributed tracing, structured logging
-
Security: Advanced authentication, rate limiting, security headers
-
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?