Performance Optimization - luckydeva03/barbershop_app GitHub Wiki

⚡ Performance Optimization

Panduan lengkap optimasi performa untuk barbershop management system agar berjalan cepat dan efisien.

🎯 Performance Overview

Aspek optimasi yang dicakup:

  • Database Optimization: Query optimization, indexing, caching
  • Frontend Performance: Asset optimization, lazy loading, compression
  • Server Optimization: Caching strategies, CDN integration
  • Code Optimization: PHP optimization, memory management
  • Monitoring: Performance tracking, profiling tools

🗄️ Database Optimization

1. Database Indexing Strategy

Index Configuration (database/migrations/add_performance_indexes.php)

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        // Users table indexes
        Schema::table('users', function (Blueprint $table) {
            $table->index(['email', 'is_active']);
            $table->index(['last_login_at']);
            $table->index(['created_at']);
            $table->index(['email_verified_at']);
        });

        // History Points table indexes
        Schema::table('history_points', function (Blueprint $table) {
            $table->index(['user_id', 'type', 'created_at']);
            $table->index(['expires_at']);
            $table->index(['reference_code']);
            $table->index(['created_at', 'type']);
        });

        // Reviews table indexes
        Schema::table('reviews', function (Blueprint $table) {
            $table->index(['store_id', 'created_at']);
            $table->index(['user_id', 'created_at']);
            $table->index(['rating']);
            $table->index(['created_at']);
        });

        // Stores table indexes
        Schema::table('stores', function (Blueprint $table) {
            $table->index(['is_active']);
            $table->index(['average_rating']);
            $table->index(['created_at']);
            $table->spatial(['latitude', 'longitude']);
        });

        // Redeem codes table indexes
        Schema::table('reedem_codes', function (Blueprint $table) {
            $table->index(['code', 'is_active']);
            $table->index(['expires_at', 'is_active']);
            $table->index(['created_at']);
        });

        // Security audit log indexes
        Schema::table('security_audit_log', function (Blueprint $table) {
            $table->index(['event', 'created_at']);
            $table->index(['severity', 'created_at']);
            $table->index(['user_id', 'created_at']);
            $table->index(['ip_address', 'created_at']);
        });
    }

    public function down()
    {
        // Drop indexes in reverse order
        Schema::table('security_audit_log', function (Blueprint $table) {
            $table->dropIndex(['event', 'created_at']);
            $table->dropIndex(['severity', 'created_at']);
            $table->dropIndex(['user_id', 'created_at']);
            $table->dropIndex(['ip_address', 'created_at']);
        });

        Schema::table('reedem_codes', function (Blueprint $table) {
            $table->dropIndex(['code', 'is_active']);
            $table->dropIndex(['expires_at', 'is_active']);
            $table->dropIndex(['created_at']);
        });

        Schema::table('stores', function (Blueprint $table) {
            $table->dropIndex(['is_active']);
            $table->dropIndex(['average_rating']);
            $table->dropIndex(['created_at']);
            $table->dropSpatialIndex(['latitude', 'longitude']);
        });

        Schema::table('reviews', function (Blueprint $table) {
            $table->dropIndex(['store_id', 'created_at']);
            $table->dropIndex(['user_id', 'created_at']);
            $table->dropIndex(['rating']);
            $table->dropIndex(['created_at']);
        });

        Schema::table('history_points', function (Blueprint $table) {
            $table->dropIndex(['user_id', 'type', 'created_at']);
            $table->dropIndex(['expires_at']);
            $table->dropIndex(['reference_code']);
            $table->dropIndex(['created_at', 'type']);
        });

        Schema::table('users', function (Blueprint $table) {
            $table->dropIndex(['email', 'is_active']);
            $table->dropIndex(['last_login_at']);
            $table->dropIndex(['created_at']);
            $table->dropIndex(['email_verified_at']);
        });
    }
};

2. Query Optimization

Optimized Repository Pattern (app/Repositories/OptimizedStoreRepository.php)

<?php

namespace App\Repositories;

use App\Models\Store;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

class OptimizedStoreRepository
{
    protected $cacheTime = 3600; // 1 hour

    /**
     * Get stores with optimized queries
     */
    public function getStoresWithStats(array $filters = [], int $perPage = 20): LengthAwarePaginator
    {
        $cacheKey = 'stores_with_stats_' . md5(serialize($filters) . $perPage);
        
        return Cache::remember($cacheKey, $this->cacheTime, function () use ($filters, $perPage) {
            $query = Store::select([
                'stores.*',
                DB::raw('COUNT(reviews.id) as reviews_count'),
                DB::raw('AVG(reviews.rating) as avg_rating')
            ])
            ->leftJoin('reviews', 'stores.id', '=', 'reviews.store_id')
            ->with(['reviews' => function ($query) {
                $query->select('id', 'store_id', 'rating', 'created_at')
                      ->latest()
                      ->limit(3);
            }])
            ->active()
            ->groupBy('stores.id');

            // Apply filters
            if (isset($filters['rating_min'])) {
                $query->having('avg_rating', '>=', $filters['rating_min']);
            }

            if (isset($filters['has_reviews'])) {
                $query->having('reviews_count', '>', 0);
            }

            if (isset($filters['location']) && isset($filters['radius'])) {
                $query->whereRaw(
                    "ST_Distance_Sphere(POINT(longitude, latitude), POINT(?, ?)) <= ?",
                    [$filters['location']['lng'], $filters['location']['lat'], $filters['radius'] * 1000]
                );
            }

            return $query->orderBy('avg_rating', 'desc')
                         ->orderBy('reviews_count', 'desc')
                         ->paginate($perPage);
        });
    }

    /**
     * Get popular stores (cached)
     */
    public function getPopularStores(int $limit = 10): Collection
    {
        return Cache::remember('popular_stores_' . $limit, $this->cacheTime, function () use ($limit) {
            return Store::select([
                'id', 'name', 'average_rating', 'reviews_count', 'image_url'
            ])
            ->active()
            ->where('reviews_count', '>', 0)
            ->orderBy('average_rating', 'desc')
            ->orderBy('reviews_count', 'desc')
            ->limit($limit)
            ->get();
        });
    }

    /**
     * Get nearby stores with spatial index
     */
    public function getNearbyStores(float $lat, float $lng, float $radius = 10, int $limit = 20): Collection
    {
        $cacheKey = "nearby_stores_{$lat}_{$lng}_{$radius}_{$limit}";
        
        return Cache::remember($cacheKey, 1800, function () use ($lat, $lng, $radius, $limit) {
            return Store::select([
                'stores.*',
                DB::raw("ST_Distance_Sphere(POINT(longitude, latitude), POINT(?, ?)) as distance")
            ])
            ->active()
            ->whereRaw(
                "ST_Distance_Sphere(POINT(longitude, latitude), POINT(?, ?)) <= ?",
                [$lng, $lat, $radius * 1000]
            )
            ->setBindings([$lng, $lat, $lng, $lat])
            ->orderBy('distance')
            ->limit($limit)
            ->get();
        });
    }

    /**
     * Get store statistics (heavily cached)
     */
    public function getStoreStatistics(): array
    {
        return Cache::remember('store_statistics', 7200, function () {
            return [
                'total_stores' => Store::count(),
                'active_stores' => Store::active()->count(),
                'avg_rating' => Store::active()->avg('average_rating'),
                'total_reviews' => Store::sum('reviews_count'),
                'top_rated' => Store::active()
                                  ->where('reviews_count', '>', 5)
                                  ->orderBy('average_rating', 'desc')
                                  ->first(['id', 'name', 'average_rating']),
                'newest_stores' => Store::active()
                                      ->latest()
                                      ->limit(5)
                                      ->get(['id', 'name', 'created_at']),
            ];
        });
    }

    /**
     * Clear related caches
     */
    public function clearStoreCache()
    {
        $patterns = [
            'stores_with_stats_*',
            'popular_stores_*',
            'nearby_stores_*',
            'store_statistics',
        ];

        foreach ($patterns as $pattern) {
            Cache::tags(['stores'])->flush();
        }
    }
}

3. Database Connection Optimization

Database Configuration (config/database.php)

<?php

return [
    'default' => env('DB_CONNECTION', 'mysql'),

    'connections' => [
        'mysql' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => 'InnoDB',
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
                PDO::ATTR_PERSISTENT => true,
                PDO::ATTR_TIMEOUT => 30,
                PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
                PDO::MYSQL_ATTR_LOCAL_INFILE => true,
            ]) : [],
        ],

        // Read replica for heavy read operations
        'mysql_read' => [
            'driver' => 'mysql',
            'read' => [
                'host' => [
                    env('DB_READ_HOST_1', env('DB_HOST', '127.0.0.1')),
                    env('DB_READ_HOST_2', env('DB_HOST', '127.0.0.1')),
                ],
            ],
            'write' => [
                'host' => [
                    env('DB_WRITE_HOST', env('DB_HOST', '127.0.0.1')),
                ],
            ],
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => 'InnoDB',
        ],
    ],

    'migrations' => 'migrations',
    'redis' => [
        'client' => env('REDIS_CLIENT', 'phpredis'),
        'options' => [
            'cluster' => env('REDIS_CLUSTER', 'redis'),
            'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
        ],
        'default' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD'),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_DB', '0'),
            'read_write_timeout' => 60,
            'persistent' => true,
        ],
        'cache' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD'),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_CACHE_DB', '1'),
            'read_write_timeout' => 60,
            'persistent' => true,
        ],
        'session' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD'),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_SESSION_DB', '2'),
            'read_write_timeout' => 60,
            'persistent' => true,
        ],
    ],
];

🚀 Caching Strategies

1. Multi-Level Caching Service

Caching Service (app/Services/CachingService.php)

<?php

namespace App\Services;

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

class CachingService
{
    const SHORT_CACHE = 300;    // 5 minutes
    const MEDIUM_CACHE = 1800;  // 30 minutes
    const LONG_CACHE = 3600;    // 1 hour
    const DAILY_CACHE = 86400;  // 24 hours

    /**
     * Multi-level cache with fallback
     */
    public static function remember($key, $ttl, $callback, $tags = [])
    {
        // Try memory cache first (APCu)
        if (function_exists('apcu_fetch') && apcu_exists($key)) {
            return apcu_fetch($key);
        }

        // Try Redis cache
        $value = Cache::tags($tags)->remember($key, $ttl, $callback);

        // Store in memory cache for faster access
        if (function_exists('apcu_store')) {
            apcu_store($key, $value, min($ttl, 300)); // Max 5 minutes in memory
        }

        return $value;
    }

    /**
     * Cache user-specific data
     */
    public static function rememberForUser($userId, $key, $ttl, $callback)
    {
        $cacheKey = "user_{$userId}_{$key}";
        return self::remember($cacheKey, $ttl, $callback, ['users', "user_{$userId}"]);
    }

    /**
     * Cache store-specific data
     */
    public static function rememberForStore($storeId, $key, $ttl, $callback)
    {
        $cacheKey = "store_{$storeId}_{$key}";
        return self::remember($cacheKey, $ttl, $callback, ['stores', "store_{$storeId}"]);
    }

    /**
     * Invalidate cache with patterns
     */
    public static function forgetPattern($pattern)
    {
        // For Redis
        if (Cache::getStore() instanceof \Illuminate\Cache\RedisStore) {
            $keys = Redis::keys("*{$pattern}*");
            if (!empty($keys)) {
                Redis::del($keys);
            }
        }

        // For APCu
        if (function_exists('apcu_delete')) {
            $info = apcu_cache_info();
            foreach ($info['cache_list'] as $entry) {
                if (strpos($entry['info'], $pattern) !== false) {
                    apcu_delete($entry['info']);
                }
            }
        }
    }

    /**
     * Clear user-specific cache
     */
    public static function clearUserCache($userId)
    {
        Cache::tags(["user_{$userId}"])->flush();
        self::forgetPattern("user_{$userId}_");
    }

    /**
     * Clear store-specific cache
     */
    public static function clearStoreCache($storeId)
    {
        Cache::tags(["store_{$storeId}"])->flush();
        self::forgetPattern("store_{$storeId}_");
    }

    /**
     * Warm up common caches
     */
    public static function warmUpCache()
    {
        // Warm up popular stores
        \DB::table('stores')
           ->select('id')
           ->active()
           ->orderBy('average_rating', 'desc')
           ->limit(20)
           ->get()
           ->each(function ($store) {
               app(OptimizedStoreRepository::class)->getStoreDetails($store->id);
           });

        // Warm up statistics
        app(OptimizedStoreRepository::class)->getStoreStatistics();

        // Warm up popular locations
        $popularLocations = [
            ['lat' => -6.2088, 'lng' => 106.8456], // Jakarta
            ['lat' => -6.9175, 'lng' => 107.6191], // Bandung
            ['lat' => -7.2575, 'lng' => 112.7521], // Surabaya
        ];

        foreach ($popularLocations as $location) {
            app(OptimizedStoreRepository::class)->getNearbyStores(
                $location['lat'], 
                $location['lng']
            );
        }
    }

    /**
     * Cache with compression for large data
     */
    public static function rememberCompressed($key, $ttl, $callback, $tags = [])
    {
        return Cache::tags($tags)->remember($key, $ttl, function () use ($callback) {
            $data = $callback();
            return gzcompress(serialize($data), 9);
        });
    }

    /**
     * Retrieve compressed cache
     */
    public static function getCompressed($key)
    {
        $compressed = Cache::get($key);
        if ($compressed) {
            return unserialize(gzuncompress($compressed));
        }
        return null;
    }
}

2. Model Cache Integration

Cacheable Trait (app/Traits/Cacheable.php)

<?php

namespace App\Traits;

use App\Services\CachingService;

trait Cacheable
{
    /**
     * Boot the cacheable trait
     */
    public static function bootCacheable()
    {
        static::saved(function ($model) {
            $model->clearModelCache();
        });

        static::deleted(function ($model) {
            $model->clearModelCache();
        });
    }

    /**
     * Cache a model method result
     */
    public function cacheResult($method, $ttl = 3600, ...$args)
    {
        $key = $this->getCacheKey($method, $args);
        
        return CachingService::remember($key, $ttl, function () use ($method, $args) {
            return $this->$method(...$args);
        }, $this->getCacheTags());
    }

    /**
     * Get cache key for model
     */
    protected function getCacheKey($method, $args = [])
    {
        $class = class_basename($this);
        $id = $this->getKey();
        $argsHash = md5(serialize($args));
        
        return strtolower("{$class}_{$id}_{$method}_{$argsHash}");
    }

    /**
     * Get cache tags for model
     */
    protected function getCacheTags()
    {
        $class = class_basename($this);
        return [strtolower($class . 's'), strtolower($class . '_' . $this->getKey())];
    }

    /**
     * Clear model-specific cache
     */
    public function clearModelCache()
    {
        $tags = $this->getCacheTags();
        \Cache::tags($tags)->flush();
    }

    /**
     * Clear all model caches
     */
    public static function clearAllModelCache()
    {
        $class = class_basename(static::class);
        \Cache::tags([strtolower($class . 's')])->flush();
    }
}

🎨 Frontend Optimization

1. Asset Optimization (Vite Configuration)

Optimized Vite Config (vite.config.js)

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import { resolve } from 'path';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js',
                'resources/js/admin.js',
                'resources/js/charts.js',
            ],
            refresh: true,
        }),
    ],
    
    build: {
        rollupOptions: {
            output: {
                manualChunks: {
                    // Vendor chunk for third-party libraries
                    vendor: [
                        'bootstrap',
                        'axios',
                        'lodash',
                    ],
                    // Charts chunk for analytics
                    charts: [
                        'chart.js',
                    ],
                    // Utils chunk for common utilities
                    utils: [
                        './resources/js/utils.js',
                    ],
                },
            },
        },
        
        // Compression and optimization
        minify: 'terser',
        terserOptions: {
            compress: {
                drop_console: true,
                drop_debugger: true,
            },
        },
        
        // CSS optimization
        cssCodeSplit: true,
        cssMinify: true,
        
        // Asset optimization
        assetsInlineLimit: 4096, // 4KB
        
        // Source maps for production debugging
        sourcemap: process.env.NODE_ENV === 'development',
    },
    
    server: {
        hmr: {
            host: 'localhost',
        },
        watch: {
            usePolling: true,
        },
    },
    
    resolve: {
        alias: {
            '@': resolve(__dirname, 'resources/js'),
            '@css': resolve(__dirname, 'resources/css'),
        },
    },
    
    // CSS preprocessing
    css: {
        preprocessorOptions: {
            scss: {
                additionalData: `@import "@css/variables.scss";`,
            },
        },
        postcss: {
            plugins: [
                require('autoprefixer'),
                require('cssnano')({
                    preset: 'default',
                }),
            ],
        },
    },
});

2. Lazy Loading Implementation

Lazy Loading Service (resources/js/services/LazyLoadService.js)

class LazyLoadService {
    constructor() {
        this.imageObserver = null;
        this.contentObserver = null;
        this.init();
    }

    init() {
        if ('IntersectionObserver' in window) {
            this.setupImageLazyLoading();
            this.setupContentLazyLoading();
        } else {
            // Fallback for older browsers
            this.loadAllImages();
        }
    }

    setupImageLazyLoading() {
        this.imageObserver = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    this.loadImage(entry.target);
                    this.imageObserver.unobserve(entry.target);
                }
            });
        }, {
            root: null,
            rootMargin: '50px',
            threshold: 0.1
        });

        this.observeImages();
    }

    setupContentLazyLoading() {
        this.contentObserver = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    this.loadContent(entry.target);
                    this.contentObserver.unobserve(entry.target);
                }
            });
        }, {
            root: null,
            rootMargin: '100px',
            threshold: 0.1
        });

        this.observeContent();
    }

    observeImages() {
        const lazyImages = document.querySelectorAll('img[data-src]');
        lazyImages.forEach(img => {
            this.imageObserver.observe(img);
        });
    }

    observeContent() {
        const lazyContent = document.querySelectorAll('[data-lazy-load]');
        lazyContent.forEach(element => {
            this.contentObserver.observe(element);
        });
    }

    loadImage(img) {
        // Show loading placeholder
        img.classList.add('loading');
        
        const image = new Image();
        image.onload = () => {
            img.src = img.dataset.src;
            img.classList.remove('loading');
            img.classList.add('loaded');
            
            // Remove data-src to prevent reloading
            delete img.dataset.src;
        };
        
        image.onerror = () => {
            img.src = '/images/placeholder-error.png';
            img.classList.remove('loading');
            img.classList.add('error');
        };
        
        image.src = img.dataset.src;
    }

    loadContent(element) {
        const url = element.dataset.lazyLoad;
        const method = element.dataset.method || 'GET';
        
        // Show loading state
        element.innerHTML = '<div class="text-center p-4"><div class="spinner-border" role="status"></div></div>';
        
        fetch(url, { method })
            .then(response => response.text())
            .then(html => {
                element.innerHTML = html;
                element.classList.add('lazy-loaded');
                
                // Initialize any JavaScript components in the loaded content
                this.initializeComponents(element);
            })
            .catch(error => {
                element.innerHTML = '<div class="alert alert-warning">Failed to load content</div>';
                console.error('Lazy load error:', error);
            });
    }

    initializeComponents(container) {
        // Reinitialize tooltips
        const tooltips = container.querySelectorAll('[data-bs-toggle="tooltip"]');
        tooltips.forEach(el => new bootstrap.Tooltip(el));
        
        // Reinitialize any other components
        if (window.App && window.App.reinitialize) {
            window.App.reinitialize(container);
        }
    }

    loadAllImages() {
        // Fallback for browsers without IntersectionObserver
        const lazyImages = document.querySelectorAll('img[data-src]');
        lazyImages.forEach(img => {
            img.src = img.dataset.src;
            delete img.dataset.src;
        });
    }

    // Public methods for dynamic content
    observeNewImages(container = document) {
        if (this.imageObserver) {
            const newImages = container.querySelectorAll('img[data-src]');
            newImages.forEach(img => this.imageObserver.observe(img));
        }
    }

    observeNewContent(container = document) {
        if (this.contentObserver) {
            const newContent = container.querySelectorAll('[data-lazy-load]');
            newContent.forEach(element => this.contentObserver.observe(element));
        }
    }
}

// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
    window.LazyLoadService = new LazyLoadService();
});

export default LazyLoadService;

3. Image Optimization Service

Image Optimization (app/Services/ImageOptimizationService.php)

<?php

namespace App\Services;

use Intervention\Image\Facades\Image;
use Illuminate\Support\Facades\Storage;

class ImageOptimizationService
{
    const SIZES = [
        'thumbnail' => [150, 150],
        'small' => [300, 300],
        'medium' => [600, 600],
        'large' => [1200, 1200],
    ];

    const QUALITY = [
        'thumbnail' => 70,
        'small' => 75,
        'medium' => 80,
        'large' => 85,
    ];

    /**
     * Optimize and create multiple sizes of an image
     */
    public static function optimizeAndResize($imagePath, $outputDir = 'optimized')
    {
        $results = [];
        $originalImage = Image::make(Storage::path($imagePath));
        
        foreach (self::SIZES as $sizeName => [$width, $height]) {
            $optimizedImage = clone $originalImage;
            
            // Resize with aspect ratio
            $optimizedImage->resize($width, $height, function ($constraint) {
                $constraint->aspectRatio();
                $constraint->upsize();
            });
            
            // Add padding if needed to exact dimensions
            $optimizedImage->resizeCanvas($width, $height, 'center', false, 'ffffff');
            
            // Set quality
            $quality = self::QUALITY[$sizeName] ?? 80;
            
            // Generate filename
            $pathInfo = pathinfo($imagePath);
            $filename = $pathInfo['filename'] . "_{$sizeName}." . $pathInfo['extension'];
            $outputPath = $outputDir . '/' . $filename;
            
            // Save optimized image
            $optimizedImage->encode($pathInfo['extension'], $quality);
            Storage::put($outputPath, $optimizedImage->getEncoded());
            
            $results[$sizeName] = $outputPath;
        }
        
        // Create WebP versions for modern browsers
        foreach ($results as $sizeName => $path) {
            $webpPath = str_replace('.jpg', '.webp', str_replace('.png', '.webp', $path));
            $image = Image::make(Storage::path($path));
            $image->encode('webp', self::QUALITY[$sizeName] ?? 80);
            Storage::put($webpPath, $image->getEncoded());
            $results[$sizeName . '_webp'] = $webpPath;
        }
        
        return $results;
    }

    /**
     * Generate responsive image HTML
     */
    public static function generateResponsiveImageHtml($basePath, $alt = '', $class = '', $lazy = true)
    {
        $baseUrl = Storage::url('');
        $pathInfo = pathinfo($basePath);
        $baseFilename = $pathInfo['filename'];
        $extension = $pathInfo['extension'];
        
        // Build srcset for different sizes
        $srcset = [];
        $webpSrcset = [];
        
        foreach (self::SIZES as $sizeName => [$width, $height]) {
            $filename = "{$baseFilename}_{$sizeName}.{$extension}";
            $webpFilename = "{$baseFilename}_{$sizeName}.webp";
            
            $srcset[] = "{$baseUrl}{$filename} {$width}w";
            $webpSrcset[] = "{$baseUrl}{$webpFilename} {$width}w";
        }
        
        $srcsetStr = implode(', ', $srcset);
        $webpSrcsetStr = implode(', ', $webpSrcset);
        
        // Default image (medium size)
        $defaultImage = "{$baseUrl}{$baseFilename}_medium.{$extension}";
        
        $lazyAttr = $lazy ? 'loading="lazy"' : '';
        $dataAttr = $lazy ? 'data-src' : 'src';
        
        return <<<HTML
        <picture>
            <source type="image/webp" {$dataAttr}set="{$webpSrcsetStr}" sizes="(max-width: 300px) 300px, (max-width: 600px) 600px, 1200px">
            <img {$dataAttr}="{$defaultImage}" 
                 srcset="{$srcsetStr}" 
                 sizes="(max-width: 300px) 300px, (max-width: 600px) 600px, 1200px"
                 alt="{$alt}" 
                 class="{$class}"
                 {$lazyAttr}>
        </picture>
        HTML;
    }

    /**
     * Clean up old optimized images
     */
    public static function cleanupOldImages($directory = 'optimized', $olderThanDays = 30)
    {
        $files = Storage::allFiles($directory);
        $cutoffDate = now()->subDays($olderThanDays);
        
        $deletedCount = 0;
        
        foreach ($files as $file) {
            $lastModified = Storage::lastModified($file);
            
            if ($lastModified < $cutoffDate->timestamp) {
                Storage::delete($file);
                $deletedCount++;
            }
        }
        
        return $deletedCount;
    }
}

⚡ Server-Side Optimization

1. PHP Performance Configuration

PHP Optimization (config/app.php additions)

<?php

// Add to existing config/app.php

/*
|--------------------------------------------------------------------------
| Performance Configuration
|--------------------------------------------------------------------------
*/

'performance' => [
    'opcache' => [
        'enabled' => env('OPCACHE_ENABLED', true),
        'memory_consumption' => env('OPCACHE_MEMORY', 256),
        'max_accelerated_files' => env('OPCACHE_MAX_FILES', 20000),
    ],
    
    'session' => [
        'gc_probability' => env('SESSION_GC_PROBABILITY', 1),
        'gc_divisor' => env('SESSION_GC_DIVISOR', 1000),
        'gc_maxlifetime' => env('SESSION_GC_MAXLIFETIME', 7200),
    ],
    
    'memory' => [
        'limit' => env('MEMORY_LIMIT', '256M'),
        'max_execution_time' => env('MAX_EXECUTION_TIME', 60),
    ],
],

2. Response Compression Middleware

Compression Middleware (app/Http/Middleware/CompressResponse.php)

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class CompressResponse
{
    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);
        
        // Only compress if client accepts gzip
        if (!$this->shouldCompress($request, $response)) {
            return $response;
        }
        
        $content = $response->getContent();
        
        if (strlen($content) > 1024) { // Only compress if content > 1KB
            $compressed = gzencode($content, 6); // Compression level 6 (good balance)
            
            if ($compressed !== false && strlen($compressed) < strlen($content)) {
                $response->setContent($compressed);
                $response->headers->set('Content-Encoding', 'gzip');
                $response->headers->set('Content-Length', strlen($compressed));
                $response->headers->set('Vary', 'Accept-Encoding');
            }
        }
        
        return $response;
    }
    
    protected function shouldCompress(Request $request, $response)
    {
        // Check if client accepts gzip
        if (!str_contains($request->header('Accept-Encoding', ''), 'gzip')) {
            return false;
        }
        
        // Don't compress if already compressed
        if ($response->headers->has('Content-Encoding')) {
            return false;
        }
        
        // Only compress certain content types
        $contentType = $response->headers->get('Content-Type', '');
        $compressibleTypes = [
            'text/html',
            'text/css',
            'text/javascript',
            'application/javascript',
            'application/json',
            'text/xml',
            'application/xml',
        ];
        
        foreach ($compressibleTypes as $type) {
            if (str_contains($contentType, $type)) {
                return true;
            }
        }
        
        return false;
    }
}

3. Static Asset Optimization

Asset Optimization Command (app/Console/Commands/OptimizeAssets.php)

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;

class OptimizeAssets extends Command
{
    protected $signature = 'assets:optimize {--force : Force regeneration of optimized assets}';
    protected $description = 'Optimize static assets for production';

    public function handle()
    {
        $this->info('🚀 Starting asset optimization...');
        
        // Optimize images
        $this->optimizeImages();
        
        // Minify CSS/JS (if not using Vite)
        $this->minifyAssets();
        
        // Generate service worker
        $this->generateServiceWorker();
        
        // Generate manifest
        $this->generateManifest();
        
        $this->info('✅ Asset optimization completed!');
        return 0;
    }

    protected function optimizeImages()
    {
        $this->info('📷 Optimizing images...');
        
        $imagePaths = [
            'public/images',
            'storage/app/public/images',
        ];
        
        foreach ($imagePaths as $path) {
            if (File::isDirectory($path)) {
                $files = File::allFiles($path);
                
                foreach ($files as $file) {
                    if (in_array(strtolower($file->getExtension()), ['jpg', 'jpeg', 'png'])) {
                        $this->optimizeImage($file->getPathname());
                    }
                }
            }
        }
    }

    protected function optimizeImage($path)
    {
        // Use ImageMagick or similar tool for optimization
        $command = "jpegoptim --max=85 --strip-all {$path}";
        
        if (strtolower(pathinfo($path, PATHINFO_EXTENSION)) === 'png') {
            $command = "optipng -o7 {$path}";
        }
        
        exec($command);
    }

    protected function minifyAssets()
    {
        $this->info('📦 Minifying assets...');
        
        // This is handled by Vite in modern setups
        // But you can add custom minification here if needed
    }

    protected function generateServiceWorker()
    {
        $this->info('⚙️ Generating service worker...');
        
        $serviceWorker = <<<JS
const CACHE_NAME = 'barbershop-v' + Date.now();
const STATIC_CACHE = 'barbershop-static-v1';

// Cache static assets
const STATIC_ASSETS = [
    '/',
    '/css/app.css',
    '/js/app.js',
    '/images/logo.png',
    '/manifest.json'
];

// Install event
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(STATIC_CACHE)
            .then(cache => cache.addAll(STATIC_ASSETS))
    );
});

// Activate event
self.addEventListener('activate', event => {
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.map(cacheName => {
                    if (cacheName !== CACHE_NAME && cacheName !== STATIC_CACHE) {
                        return caches.delete(cacheName);
                    }
                })
            );
        })
    );
});

// Fetch event
self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request)
            .then(response => {
                // Return cached version or fetch from network
                return response || fetch(event.request);
            })
    );
});
JS;

        File::put(public_path('sw.js'), $serviceWorker);
    }

    protected function generateManifest()
    {
        $this->info('📱 Generating manifest...');
        
        $manifest = [
            'name' => config('app.name'),
            'short_name' => 'Barbershop',
            'description' => 'Barbershop Management System',
            'start_url' => '/',
            'display' => 'standalone',
            'background_color' => '#ffffff',
            'theme_color' => '#007bff',
            'icons' => [
                [
                    'src' => '/images/icon-192.png',
                    'sizes' => '192x192',
                    'type' => 'image/png'
                ],
                [
                    'src' => '/images/icon-512.png',
                    'sizes' => '512x512',
                    'type' => 'image/png'
                ]
            ]
        ];

        File::put(public_path('manifest.json'), json_encode($manifest, JSON_PRETTY_PRINT));
    }
}

📊 Performance Monitoring

1. Performance Monitoring Service

Performance Monitor (app/Services/PerformanceMonitorService.php)

<?php

namespace App\Services;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;

class PerformanceMonitorService
{
    /**
     * Record performance metrics
     */
    public static function recordMetric($metric, $value, $tags = [])
    {
        DB::table('performance_metrics')->insert([
            'metric' => $metric,
            'value' => $value,
            'tags' => json_encode($tags),
            'created_at' => now(),
        ]);
    }

    /**
     * Record page load time
     */
    public static function recordPageLoad($route, $loadTime, $memoryUsage = null)
    {
        self::recordMetric('page_load_time', $loadTime, [
            'route' => $route,
            'memory_usage' => $memoryUsage ?: memory_get_peak_usage(true),
        ]);
    }

    /**
     * Record database query performance
     */
    public static function recordQueryPerformance($query, $time, $bindings = [])
    {
        self::recordMetric('query_time', $time, [
            'query' => $query,
            'bindings_count' => count($bindings),
        ]);
    }

    /**
     * Get performance statistics
     */
    public static function getPerformanceStats($hours = 24)
    {
        $startTime = now()->subHours($hours);
        
        return [
            'avg_page_load' => DB::table('performance_metrics')
                                ->where('metric', 'page_load_time')
                                ->where('created_at', '>=', $startTime)
                                ->avg('value'),
                                
            'avg_query_time' => DB::table('performance_metrics')
                               ->where('metric', 'query_time')
                               ->where('created_at', '>=', $startTime)
                               ->avg('value'),
                               
            'slow_queries' => DB::table('performance_metrics')
                             ->where('metric', 'query_time')
                             ->where('value', '>', 1000) // > 1 second
                             ->where('created_at', '>=', $startTime)
                             ->count(),
                             
            'memory_usage' => DB::table('performance_metrics')
                             ->where('metric', 'page_load_time')
                             ->where('created_at', '>=', $startTime)
                             ->selectRaw('AVG(JSON_EXTRACT(tags, "$.memory_usage")) as avg_memory')
                             ->first(),
        ];
    }

    /**
     * Get slow routes
     */
    public static function getSlowRoutes($limit = 10, $hours = 24)
    {
        $startTime = now()->subHours($hours);
        
        return DB::table('performance_metrics')
                 ->select('tags->route as route', DB::raw('AVG(value) as avg_time'))
                 ->where('metric', 'page_load_time')
                 ->where('created_at', '>=', $startTime)
                 ->groupBy('tags->route')
                 ->orderByDesc('avg_time')
                 ->limit($limit)
                 ->get();
    }

    /**
     * Clean old metrics
     */
    public static function cleanOldMetrics($days = 30)
    {
        return DB::table('performance_metrics')
                 ->where('created_at', '<', now()->subDays($days))
                 ->delete();
    }
}

2. Performance Middleware

Performance Tracking Middleware (app/Http/Middleware/PerformanceTracker.php)

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use App\Services\PerformanceMonitorService;

class PerformanceTracker
{
    public function handle(Request $request, Closure $next)
    {
        $startTime = microtime(true);
        $startMemory = memory_get_usage(true);
        
        $response = $next($request);
        
        $endTime = microtime(true);
        $endMemory = memory_get_usage(true);
        
        $loadTime = ($endTime - $startTime) * 1000; // Convert to milliseconds
        $memoryUsed = $endMemory - $startMemory;
        
        // Record metrics
        PerformanceMonitorService::recordPageLoad(
            $request->route()?->getName() ?: $request->path(),
            $loadTime,
            $memoryUsed
        );
        
        // Add performance headers
        $response->headers->set('X-Response-Time', round($loadTime, 2) . 'ms');
        $response->headers->set('X-Memory-Usage', round($memoryUsed / 1024 / 1024, 2) . 'MB');
        
        return $response;
    }
}

3. Performance Dashboard

Performance Command (app/Console/Commands/PerformanceReport.php)

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\PerformanceMonitorService;

class PerformanceReport extends Command
{
    protected $signature = 'performance:report {--hours=24 : Hours to analyze}';
    protected $description = 'Generate performance report';

    public function handle()
    {
        $hours = $this->option('hours');
        
        $this->info("📊 Performance Report (Last {$hours} hours)");
        $this->newLine();
        
        $stats = PerformanceMonitorService::getPerformanceStats($hours);
        
        $this->table(
            ['Metric', 'Value'],
            [
                ['Average Page Load Time', round($stats['avg_page_load'], 2) . ' ms'],
                ['Average Query Time', round($stats['avg_query_time'], 2) . ' ms'],
                ['Slow Queries Count', $stats['slow_queries']],
                ['Average Memory Usage', round($stats['memory_usage']->avg_memory / 1024 / 1024, 2) . ' MB'],
            ]
        );
        
        $this->newLine();
        $this->info('🐌 Slowest Routes:');
        
        $slowRoutes = PerformanceMonitorService::getSlowRoutes(10, $hours);
        
        if ($slowRoutes->isNotEmpty()) {
            $this->table(
                ['Route', 'Average Time (ms)'],
                $slowRoutes->map(function($route) {
                    return [$route->route, round($route->avg_time, 2)];
                })->toArray()
            );
        } else {
            $this->line('  No slow routes detected');
        }
        
        return 0;
    }
}

⚙️ Configuration Optimization

1. Production Environment Variables

Optimized Environment (.env.production)

# Application
APP_ENV=production
APP_DEBUG=false
APP_LOG_LEVEL=error

# Performance
OPCACHE_ENABLED=true
OPCACHE_MEMORY=512
OPCACHE_MAX_FILES=50000

SESSION_GC_PROBABILITY=1
SESSION_GC_DIVISOR=10000
SESSION_GC_MAXLIFETIME=7200

MEMORY_LIMIT=512M
MAX_EXECUTION_TIME=60

# Cache
CACHE_DRIVER=redis
REDIS_CLIENT=phpredis
REDIS_PREFIX=barbershop_prod_

# Queue
QUEUE_CONNECTION=redis
QUEUE_FAILED_DRIVER=database

# Database
DB_CONNECTION=mysql
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
DB_STRICT=true
DB_ENGINE=InnoDB

# Session
SESSION_DRIVER=redis
SESSION_LIFETIME=120
SESSION_ENCRYPT=true
SESSION_SECURE_COOKIE=true

# CDN and Assets
ASSET_URL=https://cdn.yourdomain.com
CDN_URL=https://cdn.yourdomain.com

# Image optimization
IMAGE_OPTIMIZATION=true
IMAGE_COMPRESSION_QUALITY=85
WEBP_SUPPORT=true

2. Laravel Configuration Optimizations

Cache Configuration (config/cache.php)

<?php

return [
    'default' => env('CACHE_DRIVER', 'redis'),
    
    'stores' => [
        'redis' => [
            'driver' => 'redis',
            'connection' => 'cache',
            'lock_connection' => 'default',
            'serializer' => 'igbinary', // Faster serialization
        ],
        
        'file' => [
            'driver' => 'file',
            'path' => storage_path('framework/cache/data'),
            'lock_path' => storage_path('framework/cache/locks'),
        ],
        
        // APCu for in-memory caching
        'apcu' => [
            'driver' => 'apcu',
        ],
        
        // Multi-tier cache
        'multi' => [
            'driver' => 'array', // Custom driver
            'stores' => ['apcu', 'redis', 'file'],
        ],
    ],
    
    'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
];

Next: Backup & Restore untuk strategi backup dan disaster recovery.

⚠️ **GitHub.com Fallback** ⚠️