PHP Lesson 6: Deployment, DevOps & Advanced Architecture
PHP Lesson 6: Deployment, DevOps & Advanced Architecture
PHP Lesson 6: Deployment, DevOps & Advanced Architecture
Welcome to Lesson 6! Now we're moving into professional deployment strategies, DevOps practices, and advanced architectural patterns used in enterprise PHP applications.
Part 1: Docker Containerization
1.1 Complete Docker Setup
# Dockerfile
FROM php:8.2-fpm
# Set working directory
WORKDIR /var/www
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip \
libzip-dev \
default-mysql-client \
nginx \
supervisor
# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Copy nginx configuration
COPY docker/nginx.conf /etc/nginx/sites-available/default
# Copy supervisor configuration
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Copy application files
COPY . .
# Set permissions
RUN chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache
# Expose port
EXPOSE 80
# Start supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
# docker/nginx.conf server { listen 80; server_name localhost; root /var/www/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; } location ~ /\.ht { deny all; } # Cache static files location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { expires 1y; add_header Cache-Control "public, immutable"; } # Security - hide PHP files location ~ /\.env$ { deny all; } # Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; gzip_proxied any; gzip_comp_level 6; gzip_types application/atom+xml application/geo+json application/javascript application/x-javascript application/json application/ld+json application/manifest+json application/rdf+xml application/rss+xml application/xhtml+xml application/xml font/eot font/otf font/ttf image/svg+xml text/css text/javascript text/plain text/xml; }
; docker/supervisord.conf [supervisord] nodaemon=true user=root [program:php-fpm] command=php-fpm autostart=true autorestart=true [program:nginx] command=nginx -g 'daemon off;' autostart=true autorestart=true [program:queue-worker] command=php /var/www/artisan queue:work --sleep=3 --tries=3 autostart=true autorestart=true user=www-data [program:scheduler] command=php /var/www/artisan schedule:work autostart=true autorestart=true user=www-data
# docker-compose.yml version: '3.8' services: app: build: context: . dockerfile: Dockerfile image: saas-app container_name: php_app restart: unless-stopped working_dir: /var/www volumes: - .:/var/www - ./docker/php.ini:/usr/local/etc/php/php.ini networks: - app-network webserver: image: nginx:alpine container_name: nginx_server restart: unless-stopped ports: - "80:80" - "443:443" volumes: - .:/var/www - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf depends_on: - app networks: - app-network database: image: mysql:8.0 container_name: mysql_db restart: unless-stopped environment: MYSQL_DATABASE: ${DB_DATABASE} MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} MYSQL_PASSWORD: ${DB_PASSWORD} MYSQL_USER: ${DB_USERNAME} volumes: - dbdata:/var/lib/mysql - ./docker/mysql.cnf:/etc/mysql/conf.d/custom.cnf ports: - "3306:3306" networks: - app-network redis: image: redis:alpine container_name: redis_cache restart: unless-stopped ports: - "6379:6379" volumes: - redisdata:/data networks: - app-network mailhog: image: mailhog/mailhog container_name: mailhog restart: unless-stopped ports: - "1025:1025" - "8025:8025" networks: - app-network volumes: dbdata: driver: local redisdata: driver: local networks: app-network: driver: bridge
#!/bin/bash # deploy.sh #!/bin/bash set -e echo "Starting deployment..." # Load environment variables source .env # Build and start containers docker-compose down docker-compose build --no-cache docker-compose up -d # Wait for MySQL to be ready echo "Waiting for MySQL to be ready..." while ! docker exec mysql_db mysqladmin ping -h"localhost" -u"root" -p"${DB_PASSWORD}" --silent; do sleep 1 done # Run database migrations docker exec -it php_app php artisan migrate --force # Clear and cache configuration docker exec -it php_app php artisan config:cache docker exec -it php_app php artisan route:cache docker exec -it php_app php artisan view:cache # Install node dependencies and build assets (if needed) # docker exec -it php_app npm install # docker exec -it php_app npm run build echo "Deployment completed successfully!"
Part 2: CI/CD Pipeline with GitHub Actions
2.1 Automated Testing & Deployment
# .github/workflows/deploy.yml name: Deploy to Production on: push: branches: [ main ] pull_request: branches: [ main ] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: tests: runs-on: ubuntu-latest services: mysql: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: testing ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 redis: image: redis:alpine ports: - 6379:6379 options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.2' extensions: mbstring, xml, ctype, iconv, intl, pdo_mysql, gd, zip coverage: xdebug - name: Copy .env run: php -r "file_exists('.env') || copy('.env.example', '.env');" - name: Install dependencies run: composer install --prefer-dist --no-progress --no-interaction - name: Generate key run: php artisan key:generate - name: Execute tests run: vendor/bin/phpunit --coverage-text - name: Run static analysis run: vendor/bin/phpstan analyse security-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run security check run: composer require --dev enlightn/security-checker && vendor/bin/security-checker security:check - name: Code quality uses: sonarsource/sonarcloud-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} build-and-push: needs: [tests, security-scan] runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout code uses: actions/checkout@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Log in to Container Registry uses: docker/login-action@v2 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v4 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha,prefix={{branch}}- - name: Build and push uses: docker/build-push-action@v4 with: context: . push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max deploy: needs: build-and-push runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - name: Deploy to production uses: appleboy/ssh-action@v0.1.7 with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USER }} key: ${{ secrets.SERVER_SSH_KEY }} script: | cd /var/www/saas-app git pull origin main docker-compose down docker-compose pull docker-compose up -d docker exec php_app php artisan migrate --force docker exec php_app php artisan config:cache docker exec php_app php artisan route:cache
Part 3: Advanced Architecture Patterns
3.1 Repository & Service Pattern
<?php
// app/Repositories/Contracts/UserRepositoryInterface.php
interface UserRepositoryInterface {
public function all();
public function find($id);
public function create(array $data);
public function update($id, array $data);
public function delete($id);
public function findByEmail($email);
public function paginate($perPage = 15);
public function with($relations);
}
// app/Repositories/UserRepository.php
class UserRepository implements UserRepositoryInterface {
protected $model;
public function __construct(User $user) {
$this->model = $user;
}
public function all() {
return $this->model->all();
}
public function find($id) {
return $this->model->findOrFail($id);
}
public function create(array $data) {
return $this->model->create($data);
}
public function update($id, array $data) {
$user = $this->find($id);
$user->update($data);
return $user;
}
public function delete($id) {
return $this->find($id)->delete();
}
public function findByEmail($email) {
return $this->model->where('email', $email)->first();
}
public function paginate($perPage = 15) {
return $this->model->paginate($perPage);
}
public function with($relations) {
return $this->model->with($relations);
}
}
// app/Services/UserService.php
class UserService {
protected $userRepository;
protected $emailService;
protected $cache;
public function __construct(
UserRepositoryInterface $userRepository,
EmailService $emailService,
CacheManager $cache
) {
$this->userRepository = $userRepository;
$this->emailService = $emailService;
$this->cache = $cache;
}
public function registerUser(array $userData) {
// Validate business logic
$this->validateUserData($userData);
// Check if user exists
if ($this->userRepository->findByEmail($userData['email'])) {
throw new \Exception('User already exists with this email.');
}
// Hash password
$userData['password'] = password_hash($userData['password'], PASSWORD_DEFAULT);
// Create user
$user = $this->userRepository->create($userData);
// Send welcome email
$this->emailService->sendWelcomeEmail($user);
// Clear cache
$this->cache->delete('users_list');
// Log registration
\Log::info("New user registered: {$user->email}");
return $user;
}
public function updateUserProfile($userId, array $profileData) {
$user = $this->userRepository->find($userId);
// Business logic validation
if (isset($profileData['email']) && $profileData['email'] !== $user->email) {
if ($this->userRepository->findByEmail($profileData['email'])) {
throw new \Exception('Email already taken.');
}
$profileData['email_verified_at'] = null;
}
$user = $this->userRepository->update($userId, $profileData);
// Clear relevant caches
$this->cache->delete("user_{$userId}");
$this->cache->delete('users_list');
return $user;
}
public function getUserWithStats($userId) {
$cacheKey = "user_stats_{$userId}";
return $this->cache->remember($cacheKey, 3600, function() use ($userId) {
$user = $this->userRepository->with(['projects', 'tasks'])->find($userId);
return [
'user' => $user,
'stats' => [
'project_count' => $user->projects->count(),
'task_count' => $user->tasks->count(),
'completed_tasks' => $user->tasks->where('status', 'completed')->count(),
'active_projects' => $user->projects->where('status', 'active')->count()
]
];
});
}
private function validateUserData(array $data) {
$validator = \Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed'
]);
if ($validator->fails()) {
throw new \Illuminate\Validation\ValidationException($validator);
}
}
}
// app/Http/Controllers/UserController.php
class UserController extends Controller {
protected $userService;
public function __construct(UserService $userService) {
$this->userService = $userService;
}
public function store(UserRequest $request) {
try {
$user = $this->userService->registerUser($request->validated());
return response()->json([
'message' => 'User registered successfully',
'user' => $user
], 201);
} catch (\Exception $e) {
return response()->json([
'message' => 'Registration failed',
'error' => $e->getMessage()
], 400);
}
}
public function updateProfile(ProfileUpdateRequest $request) {
try {
$user = $this->userService->updateUserProfile(
auth()->id(),
$request->validated()
);
return response()->json([
'message' => 'Profile updated successfully',
'user' => $user
]);
} catch (\Exception $e) {
return response()->json([
'message' => 'Profile update failed',
'error' => $e->getMessage()
], 400);
}
}
public function show($id) {
$userData = $this->userService->getUserWithStats($id);
return response()->json($userData);
}
}
3.2 Event-Driven Architecture
<?php
// app/Events/UserRegistered.php
class UserRegistered {
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public function __construct(User $user) {
$this->user = $user;
}
}
// app/Events/ProjectCreated.php
class ProjectCreated {
use Dispatchable, InteractsWithSockets, SerializesModels;
public $project;
public function __construct(Project $project) {
$this->project = $project;
}
}
// app/Listeners/SendWelcomeEmail.php
class SendWelcomeEmail {
protected $emailService;
public function __construct(EmailService $emailService) {
$this->emailService = $emailService;
}
public function handle(UserRegistered $event) {
$this->emailService->sendWelcomeEmail($event->user);
}
}
// app/Listeners/CreateDefaultProject.php
class CreateDefaultProject {
public function handle(UserRegistered $event) {
// Create a default project for new users
$project = Project::create([
'name' => 'My First Project',
'description' => 'Welcome to your first project!',
'user_id' => $event->user->id,
'status' => 'active'
]);
// Create some sample tasks
$tasks = [
['title' => 'Explore the dashboard', 'status' => 'todo'],
['title' => 'Create your first task', 'status' => 'todo'],
['title' => 'Invite team members', 'status' => 'todo']
];
foreach ($tasks as $taskData) {
$project->tasks()->create($taskData);
}
}
}
// app/Listeners/UpdateProjectStatistics.php
class UpdateProjectStatistics {
protected $cache;
public function __construct(CacheManager $cache) {
$this->cache = $cache;
}
public function handle(ProjectCreated $event) {
// Clear cached project statistics
$this->cache->delete("user_{$event->project->user_id}_project_stats");
$this->cache->delete("tenant_{$event->project->tenant_id}_projects_count");
}
}
// app/Providers/EventServiceProvider.php
class EventServiceProvider extends ServiceProvider {
protected $listen = [
UserRegistered::class => [
SendWelcomeEmail::class,
CreateDefaultProject::class,
NotifyAdmins::class,
],
ProjectCreated::class => [
UpdateProjectStatistics::class,
LogProjectActivity::class,
],
TaskCompleted::class => [
UpdateProjectProgress::class,
CheckProjectCompletion::class,
NotifyTaskAssignee::class,
]
];
public function boot() {
//
}
}
Part 4: Advanced Caching & Queue Systems
4.1 Redis Caching & Queue Implementation
<?php
// app/Services/CacheService.php
class CacheService {
protected $redis;
protected $prefix;
public function __construct() {
$this->redis = new Redis();
$this->redis->connect(env('REDIS_HOST', '127.0.0.1'), env('REDIS_PORT', 6379));
$this->prefix = env('REDIS_PREFIX', 'saas_app:');
}
public function get($key) {
$value = $this->redis->get($this->prefix . $key);
return $value ? unserialize($value) : null;
}
public function set($key, $value, $ttl = 3600) {
return $this->redis->setex(
$this->prefix . $key,
$ttl,
serialize($value)
);
}
public function remember($key, $ttl, $callback) {
$value = $this->get($key);
if (!is_null($value)) {
return $value;
}
$value = $callback();
$this->set($key, $value, $ttl);
return $value;
}
public function delete($key) {
return $this->redis->del($this->prefix . $key);
}
public function flushPattern($pattern) {
$keys = $this->redis->keys($this->prefix . $pattern);
if (!empty($keys)) {
return $this->redis->del($keys);
}
return 0;
}
public function increment($key, $value = 1) {
return $this->redis->incrby($this->prefix . $key, $value);
}
public function decrement($key, $value = 1) {
return $this->redis->decrby($this->prefix . $key, $value);
}
}
// app/Jobs/SendBulkEmails.php
class SendBulkEmails implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $users;
public $subject;
public $template;
public $data;
public function __construct($users, $subject, $template, $data = []) {
$this->users = $users;
$this->subject = $subject;
$this->template = $template;
$this->data = $data;
}
public function handle(EmailService $emailService) {
foreach ($this->users as $user) {
try {
$emailService->sendTemplateEmail(
$user->email,
$this->subject,
$this->template,
array_merge($this->data, ['user' => $user])
);
// Log success
\Log::info("Bulk email sent to: {$user->email}");
// Small delay to prevent overwhelming the email service
usleep(100000); // 0.1 second
} catch (\Exception $e) {
\Log::error("Failed to send email to {$user->email}: " . $e->getMessage());
// Retry logic
if ($this->attempts() < 3) {
$this->release(60); // Retry after 60 seconds
}
}
}
}
public function failed(\Exception $exception) {
\Log::error("Bulk email job failed: " . $exception->getMessage());
// Notify administrators
Mail::to('admin@example.com')->send(new JobFailedNotification(
'SendBulkEmails',
$exception->getMessage()
));
}
}
// app/Jobs/ProcessProjectExport.php
class ProcessProjectExport implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $projectId;
public $userId;
public $format;
public function __construct($projectId, $userId, $format = 'pdf') {
$this->projectId = $projectId;
$this->userId = $userId;
$this->format = $format;
}
public function handle() {
// Get project data
$project = Project::with(['tasks', 'users', 'files'])->find($this->projectId);
if (!$project) {
throw new \Exception("Project not found: {$this->projectId}");
}
// Generate export file
$exportData = $this->prepareExportData($project);
$filePath = $this->generateExportFile($exportData);
// Store file information
$file = File::create([
'name' => "project_export_{$project->id}_{$this->format}." . $this->format,
'path' => $filePath,
'user_id' => $this->userId,
'project_id' => $this->projectId
]);
// Notify user
Notification::send(
User::find($this->userId),
new ExportReadyNotification($file)
);
// Clean up old export files (older than 24 hours)
$this->cleanupOldExports();
}
private function prepareExportData($project) {
return [
'project' => $project->toArray(),
'tasks' => $project->tasks->toArray(),
'team_members' => $project->users->toArray(),
'statistics' => [
'total_tasks' => $project->tasks->count(),
'completed_tasks' => $project->tasks->where('status', 'completed')->count(),
'total_hours' => $project->tasks->sum('estimated_hours'),
'progress' => $project->tasks->count() > 0 ?
($project->tasks->where('status', 'completed')->count() / $project->tasks->count()) * 100 : 0
]
];
}
private function generateExportFile($data) {
$filename = "exports/project_export_" . time() . ".{$this->format}";
$fullPath = storage_path("app/{$filename}");
// Ensure directory exists
if (!file_exists(dirname($fullPath))) {
mkdir(dirname($fullPath), 0755, true);
}
switch ($this->format) {
case 'json':
file_put_contents($fullPath, json_encode($data, JSON_PRETTY_PRINT));
break;
case 'csv':
$this->generateCSV($data, $fullPath);
break;
case 'pdf':
$this->generatePDF($data, $fullPath);
break;
default:
throw new \Exception("Unsupported export format: {$this->format}");
}
return $filename;
}
private function cleanupOldExports() {
$cutoff = now()->subHours(24);
$oldFiles = File::where('created_at', '<', $cutoff)
->where('path', 'like', 'exports/%')
->get();
foreach ($oldFiles as $file) {
Storage::delete($file->path);
$file->delete();
}
}
}
Part 5: Monitoring & Logging
5.1 Advanced Application Monitoring
<?php
// app/Services/MonitoringService.php
class MonitoringService {
protected $redis;
protected $db;
public function __construct(Redis $redis, Connection $db) {
$this->redis = $redis;
$this->db = $db;
}
public function logRequest($request, $response, $executionTime) {
$logData = [
'method' => $request->method(),
'url' => $request->fullUrl(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'user_id' => auth()->id(),
'status_code' => $response->getStatusCode(),
'execution_time' => $executionTime,
'memory_usage' => memory_get_peak_usage(true),
'timestamp' => now()->toISOString()
];
// Store in Redis for real-time monitoring
$this->redis->lpush('request_logs', json_encode($logData));
// Keep only last 1000 requests
$this->redis->ltrim('request_logs', 0, 999);
// Log to database for long-term storage
if ($this->shouldLogToDatabase($logData)) {
DB::table('request_logs')->insert($logData);
}
}
public function getPerformanceMetrics() {
$recentLogs = $this->redis->lrange('request_logs', 0, 99);
$logs = array_map('json_decode', $recentLogs);
return [
'average_response_time' => collect($logs)->avg('execution_time'),
'requests_per_minute' => $this->calculateRPM(),
'error_rate' => $this->calculateErrorRate($logs),
'slow_requests' => collect($logs)->where('execution_time', '>', 1.0)->count(),
'memory_usage' => memory_get_peak_usage(true) / 1024 / 1024 // MB
];
}
public function getDatabaseMetrics() {
return [
'connections' => $this->getDatabaseConnections(),
'slow_queries' => $this->getSlowQueries(),
'table_sizes' => $this->getTableSizes()
];
}
public function getSystemMetrics() {
return [
'cpu_usage' => $this->getCPUUsage(),
'memory_usage' => $this->getMemoryUsage(),
'disk_usage' => $this->getDiskUsage(),
'uptime' => $this->getUptime()
];
}
private function calculateRPM() {
$minuteAgo = now()->subMinute()->timestamp;
$recentRequests = $this->redis->lrange('request_logs', 0, -1);
return collect($recentRequests)
->map('json_decode')
->filter(function($log) use ($minuteAgo) {
return strtotime($log->timestamp) > $minuteAgo;
})
->count();
}
private function shouldLogToDatabase($logData) {
// Only log slow requests or errors to database
return $logData['execution_time'] > 1.0 ||
$logData['status_code'] >= 400 ||
$logData['method'] !== 'GET';
}
}
// app/Http/Middleware/MonitoringMiddleware.php
class MonitoringMiddleware {
protected $monitoringService;
public function __construct(MonitoringService $monitoringService) {
$this->monitoringService = $monitoringService;
}
public function handle($request, Closure $next) {
$startTime = microtime(true);
$response = $next($request);
$executionTime = microtime(true) - $startTime;
// Log the request
$this->monitoringService->logRequest($request, $response, $executionTime);
// Add performance headers
$response->headers->set('X-Execution-Time', round($executionTime * 1000, 2) . 'ms');
$response->headers->set('X-Memory-Usage', round(memory_get_peak_usage(true) / 1024 / 1024, 2) . 'MB');
return $response;
}
}
5.2 Advanced Logging Configuration
<?php
// config/logging.php
return [
'default' => env('LOG_CHANNEL', 'stack'),
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'slack'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
'permission' => 0664,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'critical'),
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => SyslogUdpHandler::class,
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
// Custom application logs
'application' => [
'driver' => 'daily',
'path' => storage_path('logs/application.log'),
'level' => 'info',
'days' => 30,
],
'security' => [
'driver' => 'daily',
'path' => storage_path('logs/security.log'),
'level' => 'warning',
'days' => 90,
],
'performance' => [
'driver' => 'daily',
'path' => storage_path('logs/performance.log'),
'level' => 'debug',
'days' => 7,
],
],
];
Part 6: Deployment Scripts & Automation
6.1 Advanced Deployment Scripts
#!/bin/bash # scripts/deploy-production.sh set -e echo "???? Starting production deployment..." # Load environment source .env.production # Maintenance mode php artisan down --message="Deploying new version. Back soon!" --retry=60 # Get latest code git fetch origin git reset --hard origin/main # Install PHP dependencies composer install --no-dev --optimize-autoloader # Install node dependencies npm ci --production # Build assets npm run build # Run database migrations php artisan migrate --force # Clear caches php artisan config:cache php artisan route:cache php artisan view:cache # Restart queues php artisan queue:restart # Reload PHP-FPM sudo systemctl reload php8.2-fpm # Bring application back up php artisan up echo "✅ Production deployment completed successfully!" # Send deployment notification curl -X POST -H 'Content-type: application/json' \ --data "{\"text\":\"✅ Production deployment completed successfully!\"}" \ $SLACK_WEBHOOK_URL echo "???? Deployment notification sent"
#!/bin/bash # scripts/backup-database.sh #!/bin/bash set -e # Configuration BACKUP_DIR="/backups" DATE=$(date +%Y%m%d_%H%M%S) RETENTION_DAYS=7 echo "Starting database backup..." # Create backup directory mkdir -p $BACKUP_DIR # Backup database mysqldump -h $DB_HOST -u $DB_USERNAME -p$DB_PASSWORD $DB_DATABASE \ --single-transaction \ --quick \ --lock-tables=false \ > $BACKUP_DIR/backup_$DATE.sql # Compress backup gzip $BACKUP_DIR/backup_$DATE.sql echo "Database backup completed: $BACKUP_DIR/backup_$DATE.sql.gz" # Clean up old backups find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +$RETENTION_DAYS -delete echo "Old backups cleanup completed" # Upload to cloud storage (optional) # aws s3 cp $BACKUP_DIR/backup_$DATE.sql.gz s3://your-backup-bucket/
Key Enterprise DevOps Features Covered:
-
Containerization: Docker, multi-service architecture
-
CI/CD Pipelines: Automated testing, security scanning, deployment
-
Advanced Architecture: Repository pattern, service layers, event-driven
-
Queue Systems: Background job processing, error handling
-
Monitoring: Performance metrics, logging, alerting
-
Deployment Automation: Zero-downtime deployments, rollback strategies
Production-Ready Practices:
-
Security: Environment variables, secure headers, database encryption
-
Performance: Caching strategies, query optimization, asset compression
-
Reliability: Queue workers, database backups, monitoring
-
Scalability: Load balancing, database replication, cache clusters
-
Maintainability: Code quality tools, comprehensive logging, documentation
This lesson transforms your PHP application into a production-ready, enterprise-grade SaaS platform with professional DevOps practices!
What's Your Reaction?