Skip to content

Best Practices

Rumen Damyanov edited this page Jul 31, 2025 · 1 revision

Best Practices

Development, deployment, and maintenance best practices for php-chatbot.

Table of Contents

Best Practices

Development, deployment, and maintenance best practices for php-chatbot.

Table of Contents

Development Best Practices

Project Structure

Organize your chatbot implementation for maintainability and scalability:

app/
├── Chatbot/
│   ├── Controllers/
│   │   ├── ChatbotController.php
│   │   └── AdminChatbotController.php
│   ├── Services/
│   │   ├── ChatbotService.php
│   │   ├── ConversationManager.php
│   │   └── MessageProcessor.php
│   ├── Models/
│   │   ├── Conversation.php
│   │   ├── Message.php
│   │   └── ChatbotLog.php
│   ├── Middleware/
│   │   ├── RateLimitMiddleware.php
│   │   ├── ContentFilterMiddleware.php
│   │   └── AuthenticationMiddleware.php
│   ├── Jobs/
│   │   ├── ProcessChatMessage.php
│   │   └── AnalyzeChatInteraction.php
│   ├── Events/
│   │   ├── MessageSent.php
│   │   └── ConversationStarted.php
│   └── Providers/
│       └── ChatbotServiceProvider.php

Configuration Management

Use environment-specific configurations:

// config/chatbot.php
return [
    'default_model' => env('CHATBOT_DEFAULT_MODEL', 'openai'),
    
    'models' => [
        'openai' => [
            'api_key' => env('OPENAI_API_KEY'),
            'model' => env('OPENAI_MODEL', 'gpt-4'),
            'temperature' => env('OPENAI_TEMPERATURE', 0.7),
            'max_tokens' => env('OPENAI_MAX_TOKENS', 150),
            'timeout' => env('OPENAI_TIMEOUT', 30),
        ],
        
        'anthropic' => [
            'api_key' => env('ANTHROPIC_API_KEY'),
            'model' => env('ANTHROPIC_MODEL', 'claude-3-sonnet-20240229'),
            'temperature' => env('ANTHROPIC_TEMPERATURE', 0.7),
            'max_tokens' => env('ANTHROPIC_MAX_TOKENS', 150),
        ],
    ],
    
    'features' => [
        'conversation_memory' => env('CHATBOT_CONVERSATION_MEMORY', true),
        'content_filtering' => env('CHATBOT_CONTENT_FILTERING', true),
        'rate_limiting' => env('CHATBOT_RATE_LIMITING', true),
        'logging' => env('CHATBOT_LOGGING', true),
    ],
    
    'security' => [
        'max_message_length' => env('CHATBOT_MAX_MESSAGE_LENGTH', 2000),
        'rate_limit' => [
            'requests' => env('CHATBOT_RATE_LIMIT_REQUESTS', 20),
            'per_minutes' => env('CHATBOT_RATE_LIMIT_MINUTES', 1),
        ],
    ],
];

Environment file (.env):

# Chatbot Configuration
CHATBOT_DEFAULT_MODEL=openai
CHATBOT_CONVERSATION_MEMORY=true
CHATBOT_CONTENT_FILTERING=true
CHATBOT_RATE_LIMITING=true
CHATBOT_LOGGING=true

# OpenAI Settings
OPENAI_API_KEY=sk-your-openai-key
OPENAI_MODEL=gpt-4
OPENAI_TEMPERATURE=0.7
OPENAI_MAX_TOKENS=150
OPENAI_TIMEOUT=30

# Anthropic Settings
ANTHROPIC_API_KEY=your-anthropic-key
ANTHROPIC_MODEL=claude-3-sonnet-20240229

# Security Settings
CHATBOT_MAX_MESSAGE_LENGTH=2000
CHATBOT_RATE_LIMIT_REQUESTS=20
CHATBOT_RATE_LIMIT_MINUTES=1

Service-Oriented Architecture

Create dedicated services for different concerns:

<?php

namespace App\Chatbot\Services;

class ChatbotService
{
    public function __construct(
        private ConversationManager $conversationManager,
        private MessageProcessor $messageProcessor,
        private SecurityService $securityService,
        private LoggingService $loggingService
    ) {}
    
    public function processMessage(string $message, array $context = []): array
    {
        // 1. Security validation
        $this->securityService->validateMessage($message, $context);
        
        // 2. Process the message
        $response = $this->messageProcessor->process($message, $context);
        
        // 3. Manage conversation state
        $this->conversationManager->updateConversation($response, $context);
        
        // 4. Log the interaction
        $this->loggingService->logInteraction($message, $response, $context);
        
        return $response;
    }
}

class ConversationManager
{
    public function getConversationHistory(string $conversationId, int $limit = 10): array
    {
        return Conversation::where('id', $conversationId)
            ->with(['messages' => function ($query) use ($limit) {
                $query->latest()->take($limit);
            }])
            ->first()
            ?->messages
            ?->reverse()
            ?->values()
            ?->toArray() ?? [];
    }
    
    public function updateConversation(array $response, array $context): void
    {
        if (!isset($context['conversation_id'])) {
            return;
        }
        
        $conversation = Conversation::firstOrCreate([
            'id' => $context['conversation_id'],
        ], [
            'user_id' => $context['user_id'] ?? null,
            'started_at' => now(),
        ]);
        
        $conversation->messages()->create([
            'content' => $response['reply'],
            'role' => 'assistant',
            'metadata' => [
                'model' => $response['model'] ?? null,
                'tokens' => $response['tokens'] ?? null,
            ],
        ]);
        
        $conversation->touch();
    }
}

Error Handling Strategy

Implement comprehensive error handling:

<?php

namespace App\Chatbot\Services;

use App\Chatbot\Exceptions\ChatbotException;
use App\Chatbot\Exceptions\RateLimitException;
use App\Chatbot\Exceptions\ContentFilterException;

class MessageProcessor
{
    public function process(string $message, array $context = []): array
    {
        try {
            return $this->performProcessing($message, $context);
            
        } catch (RateLimitException $e) {
            return $this->handleRateLimitError($e);
            
        } catch (ContentFilterException $e) {
            return $this->handleContentFilterError($e);
            
        } catch (ChatbotException $e) {
            return $this->handleChatbotError($e);
            
        } catch (\Exception $e) {
            \Log::error('Unexpected chatbot error', [
                'message' => $message,
                'context' => $context,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);
            
            return $this->handleGenericError($e);
        }
    }
    
    private function handleRateLimitError(RateLimitException $e): array
    {
        return [
            'reply' => 'You\'re sending messages too quickly. Please wait a moment and try again.',
            'error' => true,
            'error_type' => 'rate_limit',
            'retry_after' => $e->getRetryAfter(),
        ];
    }
    
    private function handleContentFilterError(ContentFilterException $e): array
    {
        return [
            'reply' => 'I cannot process that message due to content restrictions. Please rephrase your request.',
            'error' => true,
            'error_type' => 'content_filter',
        ];
    }
    
    private function handleGenericError(\Exception $e): array
    {
        return [
            'reply' => 'I apologize, but I encountered an error processing your message. Please try again later.',
            'error' => true,
            'error_type' => 'internal_error',
        ];
    }
}

Dependency Injection

Use dependency injection for testability:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use RumenX\PhpChatbot\PhpChatbot;
use App\Chatbot\Services\ChatbotService;

class ChatbotServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(PhpChatbot::class, function ($app) {
            return new PhpChatbot([
                'model' => config('chatbot.default_model'),
                'api_key' => config("chatbot.models.{config('chatbot.default_model')}.api_key"),
                'temperature' => config("chatbot.models.{config('chatbot.default_model')}.temperature"),
            ]);
        });
        
        $this->app->singleton(ChatbotService::class, function ($app) {
            return new ChatbotService(
                $app->make(ConversationManager::class),
                $app->make(MessageProcessor::class),
                $app->make(SecurityService::class),
                $app->make(LoggingService::class)
            );
        });
    }
}

Security Best Practices

Input Validation and Sanitization

Always validate and sanitize user input:

<?php

namespace App\Chatbot\Middleware;

class InputValidationMiddleware
{
    public function handle(string $message, \Closure $next): mixed
    {
        // Length validation
        if (strlen($message) > config('chatbot.security.max_message_length', 2000)) {
            throw new ContentFilterException('Message too long');
        }
        
        // Character validation
        if (!mb_check_encoding($message, 'UTF-8')) {
            throw new ContentFilterException('Invalid character encoding');
        }
        
        // HTML/Script tag detection
        if (preg_match('/<script|<iframe|javascript:/i', $message)) {
            throw new ContentFilterException('Potentially malicious content detected');
        }
        
        // Clean the message
        $cleanMessage = $this->sanitizeMessage($message);
        
        return $next($cleanMessage);
    }
    
    private function sanitizeMessage(string $message): string
    {
        // Remove potential XSS attempts
        $message = strip_tags($message);
        
        // Normalize whitespace
        $message = preg_replace('/\s+/', ' ', trim($message));
        
        // Remove null bytes
        $message = str_replace("\0", '', $message);
        
        return $message;
    }
}

Rate Limiting Implementation

<?php

namespace App\Chatbot\Middleware;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\RateLimiter;

class RateLimitMiddleware
{
    public function handle(string $message, \Closure $next, array $context = []): mixed
    {
        $identifier = $this->getIdentifier($context);
        $key = "chatbot_rate_limit:{$identifier}";
        
        if (RateLimiter::tooManyAttempts($key, $this->maxAttempts())) {
            $seconds = RateLimiter::availableIn($key);
            throw new RateLimitException("Rate limit exceeded. Try again in {$seconds} seconds.", $seconds);
        }
        
        RateLimiter::hit($key, $this->decayMinutes() * 60);
        
        $response = $next($message);
        
        // Clear rate limit on successful response
        if (!($response['error'] ?? false)) {
            RateLimiter::clear($key);
        }
        
        return $response;
    }
    
    private function getIdentifier(array $context): string
    {
        return $context['user_id'] 
            ?? $context['ip_address'] 
            ?? $context['session_id'] 
            ?? 'anonymous';
    }
    
    private function maxAttempts(): int
    {
        return config('chatbot.security.rate_limit.requests', 20);
    }
    
    private function decayMinutes(): int
    {
        return config('chatbot.security.rate_limit.per_minutes', 1);
    }
}

API Key Management

Secure API key handling:

<?php

namespace App\Chatbot\Services;

use Illuminate\Encryption\Encrypter;

class ApiKeyManager
{
    private Encrypter $encrypter;
    
    public function __construct()
    {
        $this->encrypter = new Encrypter(config('app.key'), 'AES-256-CBC');
    }
    
    public function getApiKey(string $provider): string
    {
        $encryptedKey = config("chatbot.models.{$provider}.api_key");
        
        if (!$encryptedKey) {
            throw new \InvalidArgumentException("API key not configured for provider: {$provider}");
        }
        
        // In production, decrypt from secure storage
        return env("chatbot.api_keys.{$provider}") ?? $encryptedKey;
    }
    
    public function rotateApiKey(string $provider, string $newKey): void
    {
        // Encrypt and store new key
        $encryptedKey = $this->encrypter->encrypt($newKey);
        
        // Update configuration
        config()->set("chatbot.models.{$provider}.api_key", $encryptedKey);
        
        // Log key rotation
        \Log::info("API key rotated for provider: {$provider}");
    }
    
    public function validateApiKey(string $provider, string $key): bool
    {
        try {
            // Test the API key with a simple request
            $testChatbot = new PhpChatbot([
                'model' => $provider,
                'api_key' => $key,
            ]);
            
            $testChatbot->sendMessage('test', ['max_tokens' => 1]);
            return true;
            
        } catch (\Exception $e) {
            \Log::warning("API key validation failed for {$provider}: {$e->getMessage()}");
            return false;
        }
    }
}

Content Filtering

<?php

namespace App\Chatbot\Services;

class ContentFilterService
{
    private array $blockedWords;
    private array $patterns;
    
    public function __construct()
    {
        $this->loadFilterRules();
    }
    
    public function filter(string $message): string
    {
        // Check for blocked words
        if ($this->containsBlockedWords($message)) {
            throw new ContentFilterException('Message contains inappropriate content');
        }
        
        // Check for suspicious patterns
        if ($this->containsSuspiciousPatterns($message)) {
            throw new ContentFilterException('Message contains suspicious patterns');
        }
        
        // Check for PII
        $message = $this->filterPersonalInfo($message);
        
        return $message;
    }
    
    private function containsBlockedWords(string $message): bool
    {
        $messageLower = strtolower($message);
        
        foreach ($this->blockedWords as $word) {
            if (strpos($messageLower, strtolower($word)) !== false) {
                \Log::warning('Blocked word detected', ['word' => $word, 'message_hash' => md5($message)]);
                return true;
            }
        }
        
        return false;
    }
    
    private function containsSuspiciousPatterns(string $message): bool
    {
        foreach ($this->patterns as $pattern) {
            if (preg_match($pattern, $message)) {
                \Log::warning('Suspicious pattern detected', ['pattern' => $pattern, 'message_hash' => md5($message)]);
                return true;
            }
        }
        
        return false;
    }
    
    private function filterPersonalInfo(string $message): string
    {
        // Remove email addresses
        $message = preg_replace('/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/', '[EMAIL]', $message);
        
        // Remove phone numbers
        $message = preg_replace('/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/', '[PHONE]', $message);
        
        // Remove credit card numbers
        $message = preg_replace('/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/', '[CREDIT_CARD]', $message);
        
        // Remove SSN
        $message = preg_replace('/\b\d{3}[-]?\d{2}[-]?\d{4}\b/', '[SSN]', $message);
        
        return $message;
    }
    
    private function loadFilterRules(): void
    {
        $this->blockedWords = config('chatbot.content_filter.blocked_words', []);
        $this->patterns = config('chatbot.content_filter.patterns', []);
    }
}

Performance Best Practices

Caching Strategy

Implement multi-layer caching:

<?php

namespace App\Chatbot\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;

class CacheService
{
    private const CACHE_PREFIX = 'chatbot:';
    private const DEFAULT_TTL = 3600; // 1 hour
    
    public function getCachedResponse(string $message, array $context = []): ?array
    {
        $cacheKey = $this->generateCacheKey($message, $context);
        
        // Try L1 cache (memory/Redis)
        if ($cached = $this->getFromL1Cache($cacheKey)) {
            return $cached;
        }
        
        // Try L2 cache (database)
        if ($cached = $this->getFromL2Cache($cacheKey)) {
            // Store in L1 cache for next time
            $this->storeInL1Cache($cacheKey, $cached, self::DEFAULT_TTL / 2);
            return $cached;
        }
        
        return null;
    }
    
    public function cacheResponse(string $message, array $response, array $context = []): void
    {
        $cacheKey = $this->generateCacheKey($message, $context);
        
        // Don't cache error responses
        if ($response['error'] ?? false) {
            return;
        }
        
        // Don't cache responses with user-specific data
        if ($this->containsUserSpecificData($response)) {
            return;
        }
        
        // Store in both cache layers
        $this->storeInL1Cache($cacheKey, $response, self::DEFAULT_TTL);
        $this->storeInL2Cache($cacheKey, $response, self::DEFAULT_TTL * 24); // 24 hours in DB
    }
    
    private function generateCacheKey(string $message, array $context): string
    {
        $keyData = [
            'message' => $this->normalizeMessage($message),
            'model' => $context['model'] ?? 'default',
            'temperature' => $context['temperature'] ?? 0.7,
            'language' => $context['language'] ?? 'en',
        ];
        
        return self::CACHE_PREFIX . md5(json_encode($keyData));
    }
    
    private function normalizeMessage(string $message): string
    {
        // Remove user-specific information for better cache hits
        $normalized = strtolower(trim($message));
        
        // Replace common variations
        $normalized = preg_replace('/\b(hi|hello|hey)\b/', 'greeting', $normalized);
        $normalized = preg_replace('/\b(thanks?|thank you)\b/', 'thanks', $normalized);
        
        return $normalized;
    }
    
    private function getFromL1Cache(string $key): ?array
    {
        return Cache::get($key);
    }
    
    private function storeInL1Cache(string $key, array $data, int $ttl): void
    {
        Cache::put($key, $data, $ttl);
    }
    
    private function getFromL2Cache(string $key): ?array
    {
        return \DB::table('chatbot_cache')
            ->where('cache_key', $key)
            ->where('expires_at', '>', now())
            ->value('data');
    }
    
    private function storeInL2Cache(string $key, array $data, int $ttl): void
    {
        \DB::table('chatbot_cache')->updateOrInsert(
            ['cache_key' => $key],
            [
                'data' => json_encode($data),
                'expires_at' => now()->addSeconds($ttl),
                'updated_at' => now(),
            ]
        );
    }
}

Database Optimization

Optimize database queries and structure:

-- Conversations table
CREATE TABLE conversations (
    id VARCHAR(36) PRIMARY KEY,
    user_id INT UNSIGNED NULL,
    started_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL,
    INDEX idx_user_updated (user_id, updated_at),
    INDEX idx_started_at (started_at)
);

-- Messages table  
CREATE TABLE messages (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    conversation_id VARCHAR(36) NOT NULL,
    role ENUM('user', 'assistant') NOT NULL,
    content TEXT NOT NULL,
    metadata JSON NULL,
    created_at TIMESTAMP NOT NULL,
    INDEX idx_conversation_created (conversation_id, created_at),
    INDEX idx_role_created (role, created_at),
    FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE
);

-- Chatbot cache table
CREATE TABLE chatbot_cache (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    cache_key VARCHAR(255) UNIQUE NOT NULL,
    data JSON NOT NULL,
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL,
    INDEX idx_cache_key_expires (cache_key, expires_at),
    INDEX idx_expires_at (expires_at)
);

-- Analytics table
CREATE TABLE chatbot_analytics (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    conversation_id VARCHAR(36) NOT NULL,
    model_used VARCHAR(50) NOT NULL,
    tokens_used INT UNSIGNED NULL,
    response_time_ms INT UNSIGNED NULL,
    user_satisfaction TINYINT NULL,
    created_at TIMESTAMP NOT NULL,
    INDEX idx_conversation_created (conversation_id, created_at),
    INDEX idx_model_created (model_used, created_at),
    INDEX idx_created_at (created_at)
);

Queue Configuration

Optimize queue processing:

<?php

namespace App\Chatbot\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessChatMessage implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
    // Job configuration
    public int $timeout = 60;      // 60 seconds timeout
    public int $tries = 3;         // Retry failed jobs 3 times
    public int $backoff = 10;      // Wait 10 seconds between retries
    
    public function __construct(
        private string $message,
        private array $context
    ) {
        // Use different queues based on priority
        $this->onQueue($this->determineQueue($context));
    }
    
    public function handle(ChatbotService $chatbotService): void
    {
        $startTime = microtime(true);
        
        try {
            $response = $chatbotService->processMessage($this->message, $this->context);
            
            // Broadcast response
            $this->broadcastResponse($response);
            
            // Record metrics
            $this->recordMetrics($response, microtime(true) - $startTime);
            
        } catch (\Exception $e) {
            // Log error and increment failure count
            \Log::error('Chat message processing failed', [
                'message' => $this->message,
                'context' => $this->context,
                'attempt' => $this->attempts(),
                'error' => $e->getMessage(),
            ]);
            
            // Broadcast error if final attempt
            if ($this->attempts() >= $this->tries) {
                $this->broadcastError($e->getMessage());
            }
            
            throw $e; // Re-throw to trigger retry
        }
    }
    
    private function determineQueue(array $context): string
    {
        // Premium users get priority queue
        if ($context['user_tier'] === 'premium') {
            return 'chatbot-priority';
        }
        
        // Support requests get dedicated queue
        if ($context['type'] === 'support') {
            return 'chatbot-support';
        }
        
        return 'chatbot-default';
    }
}

Deployment Guidelines

Environment Configuration

Production environment setup:

# .env.production
APP_ENV=production
APP_DEBUG=false

# Chatbot Configuration
CHATBOT_DEFAULT_MODEL=openai
CHATBOT_CACHE_DRIVER=redis
CHATBOT_QUEUE_CONNECTION=redis

# Rate Limiting
CHATBOT_RATE_LIMIT_REQUESTS=50
CHATBOT_RATE_LIMIT_MINUTES=1

# Security
CHATBOT_CONTENT_FILTERING=true
CHATBOT_MAX_MESSAGE_LENGTH=2000

# Performance
CHATBOT_RESPONSE_CACHE_TTL=3600
CHATBOT_CONVERSATION_MEMORY=true
CHATBOT_MAX_CONVERSATION_HISTORY=50

# Monitoring
CHATBOT_LOGGING=true
CHATBOT_ANALYTICS=true
CHATBOT_ERROR_REPORTING=true

Docker Configuration

# Dockerfile
FROM php:8.2-fpm-alpine

# Install dependencies
RUN apk add --no-cache \
    nginx \
    supervisor \
    redis \
    curl \
    && docker-php-ext-install \
    pdo_mysql \
    bcmath \
    opcache

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Configure PHP
COPY docker/php.ini /usr/local/etc/php/conf.d/99-app.ini
COPY docker/opcache.ini /usr/local/etc/php/conf.d/opcache.ini

# Configure Nginx
COPY docker/nginx.conf /etc/nginx/nginx.conf

# Configure Supervisor
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Copy application
WORKDIR /var/www/html
COPY . .

# Install dependencies
RUN composer install --no-dev --optimize-autoloader

# Set permissions
RUN chown -R www-data:www-data /var/www/html

EXPOSE 80

CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "80:80"
    environment:
      - APP_ENV=production
      - DB_HOST=database
      - REDIS_HOST=redis
    depends_on:
      - database
      - redis
    volumes:
      - ./storage:/var/www/html/storage
    
  database:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: chatbot
    volumes:
      - mysql_data:/var/lib/mysql
    
  redis:
    image: redis:alpine
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    
  queue:
    build: .
    command: php artisan queue:work --queue=chatbot-priority,chatbot-support,chatbot-default
    depends_on:
      - database
      - redis
    environment:
      - APP_ENV=production
      - DB_HOST=database
      - REDIS_HOST=redis

volumes:
  mysql_data:
  redis_data:

Load Balancing

Nginx load balancer configuration:

upstream chatbot_backend {
    least_conn;
    server app1:80 max_fails=3 fail_timeout=30s;
    server app2:80 max_fails=3 fail_timeout=30s;
    server app3:80 max_fails=3 fail_timeout=30s;
}

server {
    listen 80;
    server_name api.chatbot.com;
    
    location /api/chatbot {
        proxy_pass http://chatbot_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        
        # Timeout settings
        proxy_connect_timeout 5s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # Rate limiting
        limit_req zone=chatbot_api burst=20 nodelay;
    }
}

# Rate limiting configuration
http {
    limit_req_zone $binary_remote_addr zone=chatbot_api:10m rate=10r/s;
}

Monitoring and Maintenance

Logging Strategy

<?php

namespace App\Chatbot\Services;

use Illuminate\Support\Facades\Log;
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;

class LoggingService
{
    private Logger $chatbotLogger;
    
    public function __construct()
    {
        $this->chatbotLogger = new Logger('chatbot');
        $this->chatbotLogger->pushHandler(
            new RotatingFileHandler(storage_path('logs/chatbot.log'), 30)
        );
    }
    
    public function logInteraction(string $message, array $response, array $context): void
    {
        $this->chatbotLogger->info('Chat interaction', [
            'message_hash' => md5($message),
            'message_length' => strlen($message),
            'response_length' => strlen($response['reply'] ?? ''),
            'model_used' => $response['model'] ?? null,
            'tokens_used' => $response['tokens'] ?? null,
            'response_time' => $response['response_time'] ?? null,
            'conversation_id' => $context['conversation_id'] ?? null,
            'user_id' => $context['user_id'] ?? null,
            'user_agent' => $context['user_agent'] ?? null,
            'ip_address' => $context['ip_address'] ?? null,
        ]);
    }
    
    public function logError(string $message, \Exception $exception, array $context): void
    {
        $this->chatbotLogger->error('Chat error', [
            'message_hash' => md5($message),
            'error_message' => $exception->getMessage(),
            'error_class' => get_class($exception),
            'error_file' => $exception->getFile(),
            'error_line' => $exception->getLine(),
            'context' => $context,
            'trace' => $exception->getTraceAsString(),
        ]);
    }
    
    public function logPerformance(array $metrics): void
    {
        $this->chatbotLogger->info('Performance metrics', $metrics);
    }
}

Health Checks

<?php

namespace App\Http\Controllers;

use App\Chatbot\Services\ChatbotService;
use Illuminate\Http\JsonResponse;

class HealthController extends Controller
{
    public function chatbotHealth(ChatbotService $chatbotService): JsonResponse
    {
        $checks = [
            'database' => $this->checkDatabase(),
            'redis' => $this->checkRedis(),
            'api_keys' => $this->checkApiKeys(),
            'queue' => $this->checkQueue(),
            'ai_providers' => $this->checkAiProviders($chatbotService),
        ];
        
        $healthy = !in_array(false, array_values($checks));
        
        return response()->json([
            'status' => $healthy ? 'healthy' : 'unhealthy',
            'checks' => $checks,
            'timestamp' => now()->toISOString(),
        ], $healthy ? 200 : 503);
    }
    
    private function checkDatabase(): bool
    {
        try {
            \DB::connection()->getPdo();
            return true;
        } catch (\Exception $e) {
            return false;
        }
    }
    
    private function checkRedis(): bool
    {
        try {
            \Redis::ping();
            return true;
        } catch (\Exception $e) {
            return false;
        }
    }
    
    private function checkApiKeys(): bool
    {
        $providers = config('chatbot.models');
        
        foreach ($providers as $provider => $config) {
            if (empty($config['api_key'])) {
                return false;
            }
        }
        
        return true;
    }
    
    private function checkQueue(): bool
    {
        try {
            $size = \Queue::size('chatbot-default');
            return $size !== false;
        } catch (\Exception $e) {
            return false;
        }
    }
    
    private function checkAiProviders(ChatbotService $chatbotService): array
    {
        $providers = ['openai', 'anthropic'];
        $results = [];
        
        foreach ($providers as $provider) {
            try {
                // Test with a simple request
                $startTime = microtime(true);
                $response = $chatbotService->testProvider($provider);
                $responseTime = (microtime(true) - $startTime) * 1000;
                
                $results[$provider] = [
                    'status' => 'healthy',
                    'response_time_ms' => round($responseTime, 2),
                ];
            } catch (\Exception $e) {
                $results[$provider] = [
                    'status' => 'unhealthy',
                    'error' => $e->getMessage(),
                ];
            }
        }
        
        return $results;
    }
}

Metrics Collection

<?php

namespace App\Chatbot\Services;

use Illuminate\Support\Facades\DB;

class MetricsService
{
    public function recordInteraction(array $data): void
    {
        DB::table('chatbot_analytics')->insert([
            'conversation_id' => $data['conversation_id'],
            'model_used' => $data['model'],
            'tokens_used' => $data['tokens'] ?? null,
            'response_time_ms' => $data['response_time_ms'] ?? null,
            'user_satisfaction' => $data['satisfaction'] ?? null,
            'created_at' => now(),
        ]);
    }
    
    public function getDailyMetrics(\DateTime $date): array
    {
        $startOfDay = $date->format('Y-m-d 00:00:00');
        $endOfDay = $date->format('Y-m-d 23:59:59');
        
        return [
            'total_interactions' => $this->getTotalInteractions($startOfDay, $endOfDay),
            'unique_conversations' => $this->getUniqueConversations($startOfDay, $endOfDay),
            'average_response_time' => $this->getAverageResponseTime($startOfDay, $endOfDay),
            'model_usage' => $this->getModelUsage($startOfDay, $endOfDay),
            'error_rate' => $this->getErrorRate($startOfDay, $endOfDay),
            'user_satisfaction' => $this->getAverageSatisfaction($startOfDay, $endOfDay),
        ];
    }
    
    private function getTotalInteractions(string $start, string $end): int
    {
        return DB::table('chatbot_analytics')
            ->whereBetween('created_at', [$start, $end])
            ->count();
    }
    
    private function getUniqueConversations(string $start, string $end): int
    {
        return DB::table('chatbot_analytics')
            ->whereBetween('created_at', [$start, $end])
            ->distinct('conversation_id')
            ->count();
    }
    
    private function getAverageResponseTime(string $start, string $end): float
    {
        return DB::table('chatbot_analytics')
            ->whereBetween('created_at', [$start, $end])
            ->whereNotNull('response_time_ms')
            ->avg('response_time_ms') ?: 0;
    }
    
    private function getModelUsage(string $start, string $end): array
    {
        return DB::table('chatbot_analytics')
            ->whereBetween('created_at', [$start, $end])
            ->groupBy('model_used')
            ->selectRaw('model_used, count(*) as usage_count')
            ->pluck('usage_count', 'model_used')
            ->toArray();
    }
}

Code Quality

Static Analysis Configuration

PHPStan configuration (phpstan.neon):

parameters:
    level: 8
    paths:
        - app/Chatbot
    excludePaths:
        - app/Chatbot/vendor/*
    checkMissingIterableValueType: false
    checkGenericClassInNonGenericObjectType: false
    ignoreErrors:
        - '#Call to an undefined method Illuminate\\Database\\Query\\Builder::#'

PHP CS Fixer configuration (.php-cs-fixer.php):

<?php

$finder = PhpCsFixer\Finder::create()
    ->in(__DIR__ . '/app/Chatbot')
    ->name('*.php')
    ->notName('*.blade.php');

return (new PhpCsFixer\Config())
    ->setRules([
        '@PSR12' => true,
        'array_syntax' => ['syntax' => 'short'],
        'ordered_imports' => ['sort_algorithm' => 'alpha'],
        'no_unused_imports' => true,
        'not_operator_with_successor_space' => true,
        'trailing_comma_in_multiline' => true,
        'phpdoc_scalar' => true,
        'unary_operator_spaces' => true,
        'binary_operator_spaces' => true,
        'blank_line_before_statement' => [
            'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
        ],
        'phpdoc_single_line_var_spacing' => true,
        'phpdoc_var_without_name' => true,
    ])
    ->setFinder($finder);

Code Documentation

<?php

namespace App\Chatbot\Services;

/**
 * ChatbotService handles the core chatbot functionality including
 * message processing, conversation management, and response generation.
 *
 * @package App\Chatbot\Services
 */
class ChatbotService
{
    /**
     * Process a user message and generate an appropriate response.
     *
     * This method handles the complete flow of message processing including:
     * - Security validation and content filtering
     * - Message preprocessing and context building
     * - AI model interaction
     * - Response post-processing
     * - Conversation state management
     * - Logging and analytics
     *
     * @param string $message The user's input message
     * @param array<string, mixed> $context Additional context data including:
     *   - user_id: ID of the user sending the message
     *   - conversation_id: Unique conversation identifier
     *   - ip_address: User's IP address for rate limiting
     *   - user_agent: Browser/client information
     *   - language: Preferred language code
     *   - model: AI model to use (optional)
     *
     * @return array{
     *   reply: string,
     *   conversation_id: string,
     *   model: string,
     *   tokens?: int,
     *   response_time_ms?: float,
     *   cached?: bool,
     *   error?: bool,
     *   error_type?: string
     * } The processed response with metadata
     *
     * @throws RateLimitException When rate limits are exceeded
     * @throws ContentFilterException When message violates content policy
     * @throws ChatbotException When processing fails
     *
     * @example
     * ```php
     * $response = $chatbotService->processMessage(
     *     'Hello, how can you help me?',
     *     [
     *         'user_id' => 123,
     *         'conversation_id' => 'conv_abc123',
     *         'language' => 'en'
     *     ]
     * );
     * echo $response['reply']; // "Hello! I'm here to help..."
     * ```
     */
    public function processMessage(string $message, array $context = []): array
    {
        // Implementation...
    }
}

Testing Strategies

Unit Testing

<?php

namespace Tests\Unit\Chatbot;

use Tests\TestCase;
use App\Chatbot\Services\ChatbotService;
use App\Chatbot\Services\MessageProcessor;
use App\Chatbot\Exceptions\RateLimitException;
use Mockery\MockInterface;

class ChatbotServiceTest extends TestCase
{
    private ChatbotService $chatbotService;
    private MockInterface $messageProcessor;
    
    protected function setUp(): void
    {
        parent::setUp();
        
        $this->messageProcessor = $this->mock(MessageProcessor::class);
        $this->chatbotService = new ChatbotService(
            $this->messageProcessor,
            $this->mock(ConversationManager::class),
            $this->mock(SecurityService::class),
            $this->mock(LoggingService::class)
        );
    }
    
    /** @test */
    public function it_processes_a_simple_message(): void
    {
        $message = 'Hello, world!';
        $expectedResponse = [
            'reply' => 'Hello! How can I help you?',
            'conversation_id' => 'conv_123',
        ];
        
        $this->messageProcessor
            ->shouldReceive('process')
            ->once()
            ->with($message, [])
            ->andReturn($expectedResponse);
        
        $response = $this->chatbotService->processMessage($message);
        
        $this->assertEquals($expectedResponse, $response);
    }
    
    /** @test */
    public function it_handles_rate_limit_exceptions(): void
    {
        $message = 'Test message';
        
        $this->messageProcessor
            ->shouldReceive('process')
            ->once()
            ->andThrow(new RateLimitException('Rate limit exceeded', 60));
        
        $response = $this->chatbotService->processMessage($message);
        
        $this->assertTrue($response['error']);
        $this->assertEquals('rate_limit', $response['error_type']);
        $this->assertStringContains('too quickly', $response['reply']);
    }
    
    /** @test */
    public function it_validates_message_length(): void
    {
        $longMessage = str_repeat('a', 3000); // Exceeds max length
        
        $this->expectException(ContentFilterException::class);
        
        $this->chatbotService->processMessage($longMessage);
    }
}

Integration Testing

<?php

namespace Tests\Integration\Chatbot;

use Tests\TestCase;
use App\Chatbot\Services\ChatbotService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;

class ChatbotIntegrationTest extends TestCase
{
    use RefreshDatabase;
    
    /** @test */
    public function it_processes_a_complete_conversation_flow(): void
    {
        // Mock external API calls
        Http::fake([
            'api.openai.com/*' => Http::response([
                'choices' => [
                    ['message' => ['content' => 'Hello! How can I help you today?']]
                ],
                'usage' => ['total_tokens' => 25]
            ])
        ]);
        
        $chatbotService = app(ChatbotService::class);
        
        // First message
        $response1 = $chatbotService->processMessage('Hello', [
            'user_id' => 1,
            'conversation_id' => 'conv_test_123'
        ]);
        
        $this->assertArrayHasKey('reply', $response1);
        $this->assertArrayHasKey('conversation_id', $response1);
        $this->assertEquals('conv_test_123', $response1['conversation_id']);
        
        // Second message in same conversation
        $response2 = $chatbotService->processMessage('How are you?', [
            'user_id' => 1,
            'conversation_id' => 'conv_test_123'
        ]);
        
        $this->assertArrayHasKey('reply', $response2);
        $this->assertEquals('conv_test_123', $response2['conversation_id']);
        
        // Verify conversation is stored in database
        $this->assertDatabaseHas('conversations', [
            'id' => 'conv_test_123',
            'user_id' => 1
        ]);
        
        $this->assertDatabaseCount('messages', 4); // 2 user + 2 assistant messages
    }
    
    /** @test */
    public function it_respects_rate_limits(): void
    {
        $chatbotService = app(ChatbotService::class);
        $userId = 1;
        $context = ['user_id' => $userId];
        
        // Send messages up to rate limit
        for ($i = 0; $i < config('chatbot.security.rate_limit.requests'); $i++) {
            $response = $chatbotService->processMessage("Message {$i}", $context);
            $this->assertFalse($response['error'] ?? false);
        }
        
        // Next message should trigger rate limit
        $response = $chatbotService->processMessage('Rate limited message', $context);
        
        $this->assertTrue($response['error']);
        $this->assertEquals('rate_limit', $response['error_type']);
    }
}

Feature Testing

<?php

namespace Tests\Feature\Chatbot;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;

class ChatbotApiTest extends TestCase
{
    use RefreshDatabase;
    
    /** @test */
    public function it_accepts_chat_messages_via_api(): void
    {
        Queue::fake();
        
        $response = $this->postJson('/api/chatbot/message', [
            'message' => 'Hello, chatbot!',
            'conversation_id' => 'conv_api_test'
        ]);
        
        $response->assertStatus(200)
            ->assertJsonStructure([
                'reply',
                'conversation_id',
                'timestamp'
            ]);
        
        Queue::assertPushed(ProcessChatMessage::class);
    }
    
    /** @test */
    public function it_validates_api_input(): void
    {
        $response = $this->postJson('/api/chatbot/message', [
            'message' => '', // Empty message
        ]);
        
        $response->assertStatus(422)
            ->assertJsonValidationErrors(['message']);
    }
    
    /** @test */
    public function it_handles_authentication_when_required(): void
    {
        config(['chatbot.require_auth' => true]);
        
        $response = $this->postJson('/api/chatbot/message', [
            'message' => 'Hello'
        ]);
        
        $response->assertStatus(401);
        
        // Test with authenticated user
        $user = \App\Models\User::factory()->create();
        
        $response = $this->actingAs($user)
            ->postJson('/api/chatbot/message', [
                'message' => 'Hello'
            ]);
        
        $response->assertStatus(200);
    }
}

These best practices provide a comprehensive guide for building, deploying, and maintaining production-ready chatbot applications with php-chatbot. Following these guidelines will help ensure your implementation is secure, performant, and maintainable.

Next recommended reading: Contributing to learn how to contribute to the project.

Clone this wiki locally