/** * ResponseOptimizer.php * * @package care-book-block-ultimate.src.Performance * @author Descomplicar® Crescimento Digital * @link https://descomplicar.pt * @copyright 2025 Descomplicar® - Todos os direitos reservados */ cacheManager = $cacheManager; $this->initializeOptimizer(); } /** * Optimize AJAX response with compression and caching * * @param array $data Response data * @param array $options Response options * @return array Optimized response * @since 1.0.0 */ public function optimizeResponse(array $data, array $options = []): array { $startTime = microtime(true); // Generate response cache key $cacheKey = $this->generateResponseCacheKey($data, $options); // Try cached response first if ($options['use_cache'] ?? true) { $cachedResponse = $this->cacheManager->get( "ajax_response_{$cacheKey}", null, $options['cache_ttl'] ?? 300 // 5 minutes default ); if ($cachedResponse !== null) { $this->recordResponseMetric($startTime, true, strlen(json_encode($cachedResponse))); return $this->finalizeResponse($cachedResponse, $options); } } // Optimize response data $optimizedData = $this->optimizeResponseData($data, $options); // Apply compression if beneficial if ($this->shouldCompressResponse($optimizedData, $options)) { $optimizedData = $this->compressResponse($optimizedData, $options); } // Cache optimized response if ($options['use_cache'] ?? true) { $this->cacheManager->set( "ajax_response_{$cacheKey}", $optimizedData, $options['cache_ttl'] ?? 300 ); } $this->recordResponseMetric($startTime, false, strlen(json_encode($optimizedData))); return $this->finalizeResponse($optimizedData, $options); } /** * Batch multiple AJAX requests for efficiency * * @param array $requests Array of request configurations * @param array $options Batch options * @return array Batch response * @since 1.0.0 */ public function batchRequests(array $requests, array $options = []): array { $startTime = microtime(true); $batchId = uniqid('batch_', true); // Process requests in parallel simulation $responses = []; $errors = []; foreach ($requests as $index => $request) { try { $response = $this->processIndividualRequest($request, $options); $responses[$index] = $response; } catch (\Exception $e) { $errors[$index] = [ 'error' => $e->getMessage(), 'code' => $e->getCode() ]; } } $executionTime = (microtime(true) - $startTime) * 1000; $batchResponse = [ 'batch_id' => $batchId, 'responses' => $responses, 'errors' => $errors, 'execution_time' => $executionTime, 'requests_count' => count($requests), 'success_count' => count($responses), 'error_count' => count($errors) ]; $this->recordBatchMetric($batchResponse); return $batchResponse; } /** * Stream large responses for memory efficiency * * @param callable $dataProvider Data provider callback * @param array $options Streaming options * @return void * @since 1.0.0 */ public function streamResponse(callable $dataProvider, array $options = []): void { // Set appropriate headers for streaming $this->setStreamingHeaders($options); // Start output buffering with compression if ($this->compressionEnabled && ($options['compress'] ?? true)) { ob_start('gzhandler'); } else { ob_start(); } echo '{"data":['; $isFirst = true; $totalItems = 0; foreach ($dataProvider() as $item) { if (!$isFirst) { echo ','; } echo json_encode($item, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); // Flush every 100 items to prevent memory buildup if (++$totalItems % 100 === 0) { ob_flush(); flush(); } $isFirst = false; } echo '],"total":' . $totalItems . '}'; ob_end_flush(); } /** * Handle WebSocket-like real-time updates * * @param array $data Update data * @param array $options Update options * @return array Real-time response * @since 1.0.0 */ public function handleRealtimeUpdate(array $data, array $options = []): array { // For AJAX-based real-time updates (since WebSocket isn't available in this context) $updateKey = $options['update_key'] ?? 'default'; // Store update for polling clients $this->cacheManager->set( "realtime_update_{$updateKey}", [ 'data' => $data, 'timestamp' => time(), 'version' => $options['version'] ?? 1 ], 60 // 1 minute TTL for real-time data ); return [ 'status' => 'update_stored', 'update_key' => $updateKey, 'timestamp' => time(), 'data_size' => strlen(json_encode($data)) ]; } /** * Get performance metrics for AJAX responses * * @return array Performance metrics * @since 1.0.0 */ public function getPerformanceMetrics(): array { $recentMetrics = array_slice($this->responseMetrics, -100); if (empty($recentMetrics)) { return [ 'total_responses' => 0, 'average_response_time' => 0, 'cache_hit_rate' => 0, 'compression_ratio' => 0, 'target_achievement_rate' => 0 ]; } $responseTimes = array_column($recentMetrics, 'response_time'); $cachedResponses = array_filter($recentMetrics, fn($m) => $m['cached']); $targetAchieved = array_filter($recentMetrics, fn($m) => $m['response_time'] <= self::TARGET_RESPONSE_TIME); return [ 'total_responses' => count($recentMetrics), 'average_response_time' => array_sum($responseTimes) / count($responseTimes), 'median_response_time' => $this->calculateMedian($responseTimes), 'cache_hit_rate' => (count($cachedResponses) / count($recentMetrics)) * 100, 'compression_ratio' => $this->calculateCompressionRatio(), 'target_achievement_rate' => (count($targetAchieved) / count($recentMetrics)) * 100, 'batch_efficiency' => $this->calculateBatchEfficiency(), 'data_transfer_savings' => $this->calculateDataTransferSavings() ]; } /** * Optimize response data structure and content * * @param array $data Response data * @param array $options Optimization options * @return array Optimized data * @since 1.0.0 */ private function optimizeResponseData(array $data, array $options): array { $optimized = $data; // Remove null values to reduce payload size if ($options['remove_nulls'] ?? true) { $optimized = $this->removeNullValues($optimized); } // Compress repeated strings if ($options['compress_strings'] ?? true) { $optimized = $this->compressRepeatedStrings($optimized); } // Optimize numeric data if ($options['optimize_numbers'] ?? true) { $optimized = $this->optimizeNumbers($optimized); } // Remove unnecessary fields if (!empty($options['exclude_fields'])) { $optimized = $this->excludeFields($optimized, $options['exclude_fields']); } // Apply data transformation if (!empty($options['transform_callback'])) { $optimized = call_user_func($options['transform_callback'], $optimized); } return $optimized; } /** * Determine if response should be compressed * * @param array $data Response data * @param array $options Response options * @return bool True if should compress * @since 1.0.0 */ private function shouldCompressResponse(array $data, array $options): bool { if (!$this->compressionEnabled) { return false; } if ($options['force_compression'] ?? false) { return true; } if ($options['disable_compression'] ?? false) { return false; } $dataSize = strlen(json_encode($data)); return $dataSize >= self::COMPRESSION_THRESHOLD; } /** * Compress response data using various algorithms * * @param array $data Response data * @param array $options Compression options * @return array Compressed response * @since 1.0.0 */ private function compressResponse(array $data, array $options): array { $algorithm = $options['compression_algorithm'] ?? 'gzip'; $originalJson = json_encode($data); $originalSize = strlen($originalJson); switch ($algorithm) { case 'gzip': $compressed = gzcompress($originalJson, 6); break; case 'deflate': $compressed = gzdeflate($originalJson, 6); break; case 'brotli': if (function_exists('brotli_compress')) { $compressed = brotli_compress($originalJson); } else { $compressed = gzcompress($originalJson, 6); // Fallback } break; default: return $data; // No compression } $compressedSize = strlen($compressed); $compressionRatio = $originalSize > 0 ? ($compressedSize / $originalSize) : 1; // Only use compression if it provides significant benefit if ($compressionRatio < 0.8) { $this->recordCompressionStats($algorithm, $originalSize, $compressedSize); return [ 'compressed' => true, 'algorithm' => $algorithm, 'data' => base64_encode($compressed), 'original_size' => $originalSize, 'compressed_size' => $compressedSize, 'compression_ratio' => $compressionRatio ]; } return $data; // Return original if compression not beneficial } /** * Process individual request within batch * * @param array $request Request configuration * @param array $options Processing options * @return array Request response * @since 1.0.0 */ private function processIndividualRequest(array $request, array $options): array { $startTime = microtime(true); // Simulate request processing $action = $request['action'] ?? 'unknown'; $params = $request['params'] ?? []; // Process based on action type switch ($action) { case 'get_restrictions': $response = $this->processGetRestrictions($params); break; case 'update_restriction': $response = $this->processUpdateRestriction($params); break; case 'get_availability': $response = $this->processGetAvailability($params); break; default: throw new \InvalidArgumentException("Unknown action: {$action}"); } $executionTime = (microtime(true) - $startTime) * 1000; return [ 'action' => $action, 'data' => $response, 'execution_time' => $executionTime, 'success' => true ]; } /** * Generate response cache key * * @param array $data Response data * @param array $options Response options * @return string Cache key * @since 1.0.0 */ private function generateResponseCacheKey(array $data, array $options): string { $keyData = [ 'data_hash' => md5(json_encode($data)), 'options' => $options, 'user_id' => get_current_user_id(), 'version' => CARE_BOOK_ULTIMATE_VERSION ]; return md5(serialize($keyData)); } /** * Finalize response with headers and metadata * * @param array $data Response data * @param array $options Response options * @return array Final response * @since 1.0.0 */ private function finalizeResponse(array $data, array $options): array { $response = [ 'success' => true, 'data' => $data, 'timestamp' => time(), 'cache_info' => [ 'cached' => isset($data['_cached']), 'ttl' => $options['cache_ttl'] ?? 300 ] ]; // Add performance info in debug mode if (defined('WP_DEBUG') && WP_DEBUG) { $response['debug'] = [ 'memory_usage' => memory_get_usage(true), 'execution_time' => $options['_execution_time'] ?? 0, 'compression_enabled' => $this->compressionEnabled ]; } return $response; } /** * Initialize response optimizer * * @return void * @since 1.0.0 */ private function initializeOptimizer(): void { // Check compression support $this->compressionEnabled = extension_loaded('zlib'); // Register AJAX hooks add_action('wp_ajax_care_book_get_restrictions', [$this, 'handleGetRestrictions']); add_action('wp_ajax_care_book_update_restriction', [$this, 'handleUpdateRestriction']); add_action('wp_ajax_care_book_batch_request', [$this, 'handleBatchRequest']); // Set up response headers add_action('wp_ajax_care_book_*', [$this, 'setOptimalHeaders'], 1); } /** * Remove null values from array recursively * * @param array $data Input data * @return array Data without nulls * @since 1.0.0 */ private function removeNullValues(array $data): array { return array_filter($data, function($value) { if (is_array($value)) { return !empty($this->removeNullValues($value)); } return $value !== null; }); } /** * Compress repeated strings in data * * @param array $data Input data * @return array Data with compressed strings * @since 1.0.0 */ private function compressRepeatedStrings(array $data): array { // This would implement string deduplication // Simplified implementation for now return $data; } /** * Optimize numeric data representation * * @param array $data Input data * @return array Data with optimized numbers * @since 1.0.0 */ private function optimizeNumbers(array $data): array { array_walk_recursive($data, function(&$value) { if (is_numeric($value) && is_string($value)) { $value = is_int($value) ? (int) $value : (float) $value; } }); return $data; } /** * Exclude specified fields from data * * @param array $data Input data * @param array $fields Fields to exclude * @return array Data without excluded fields * @since 1.0.0 */ private function excludeFields(array $data, array $fields): array { return array_diff_key($data, array_flip($fields)); } /** * Set streaming headers for large responses * * @param array $options Streaming options * @return void * @since 1.0.0 */ private function setStreamingHeaders(array $options): void { header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); if ($options['enable_cors'] ?? true) { header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization'); } } /** * Record response performance metric * * @param float $startTime Start time * @param bool $cached Whether response was cached * @param int $responseSize Response size in bytes * @return void * @since 1.0.0 */ private function recordResponseMetric(float $startTime, bool $cached, int $responseSize): void { $responseTime = (microtime(true) - $startTime) * 1000; $this->responseMetrics[] = [ 'response_time' => $responseTime, 'cached' => $cached, 'response_size' => $responseSize, 'timestamp' => time() ]; // Keep only recent metrics if (count($this->responseMetrics) > 200) { $this->responseMetrics = array_slice($this->responseMetrics, -100); } } /** * Record batch processing metrics * * @param array $batchResponse Batch response data * @return void * @since 1.0.0 */ private function recordBatchMetric(array $batchResponse): void { // Store batch metrics for analysis $this->responseMetrics[] = [ 'type' => 'batch', 'execution_time' => $batchResponse['execution_time'], 'requests_count' => $batchResponse['requests_count'], 'success_rate' => $batchResponse['success_count'] / $batchResponse['requests_count'], 'timestamp' => time() ]; } /** * Record compression statistics * * @param string $algorithm Compression algorithm * @param int $originalSize Original size * @param int $compressedSize Compressed size * @return void * @since 1.0.0 */ private function recordCompressionStats(string $algorithm, int $originalSize, int $compressedSize): void { $this->compressionStats[] = [ 'algorithm' => $algorithm, 'original_size' => $originalSize, 'compressed_size' => $compressedSize, 'ratio' => $compressedSize / $originalSize, 'savings' => $originalSize - $compressedSize, 'timestamp' => time() ]; } /** * Calculate median value from array * * @param array $values Numeric values * @return float Median value * @since 1.0.0 */ private function calculateMedian(array $values): float { sort($values); $count = count($values); if ($count === 0) { return 0; } if ($count % 2 === 0) { return ($values[$count / 2 - 1] + $values[$count / 2]) / 2; } return $values[($count - 1) / 2]; } /** * Calculate compression ratio from stats * * @return float Average compression ratio * @since 1.0.0 */ private function calculateCompressionRatio(): float { if (empty($this->compressionStats)) { return 1.0; } $ratios = array_column($this->compressionStats, 'ratio'); return array_sum($ratios) / count($ratios); } /** * Calculate batch processing efficiency * * @return float Batch efficiency percentage * @since 1.0.0 */ private function calculateBatchEfficiency(): float { $batchMetrics = array_filter($this->responseMetrics, fn($m) => ($m['type'] ?? '') === 'batch'); if (empty($batchMetrics)) { return 0; } $successRates = array_column($batchMetrics, 'success_rate'); return (array_sum($successRates) / count($successRates)) * 100; } /** * Calculate data transfer savings from optimizations * * @return array Transfer savings data * @since 1.0.0 */ private function calculateDataTransferSavings(): array { if (empty($this->compressionStats)) { return ['savings_bytes' => 0, 'savings_percentage' => 0]; } $totalOriginal = array_sum(array_column($this->compressionStats, 'original_size')); $totalCompressed = array_sum(array_column($this->compressionStats, 'compressed_size')); $savingsBytes = $totalOriginal - $totalCompressed; $savingsPercentage = $totalOriginal > 0 ? ($savingsBytes / $totalOriginal) * 100 : 0; return [ 'savings_bytes' => $savingsBytes, 'savings_percentage' => $savingsPercentage ]; } /** * WordPress AJAX handlers */ public function handleGetRestrictions(): void { $data = $this->processGetRestrictions($_REQUEST); $response = $this->optimizeResponse($data); wp_send_json($response); } public function handleUpdateRestriction(): void { $data = $this->processUpdateRestriction($_REQUEST); $response = $this->optimizeResponse($data); wp_send_json($response); } public function handleBatchRequest(): void { $requests = json_decode(stripslashes($_POST['requests'] ?? '[]'), true); $response = $this->batchRequests($requests); wp_send_json($response); } public function setOptimalHeaders(): void { header('X-Response-Optimized: 1'); if ($this->compressionEnabled) { header('X-Compression-Available: 1'); } } /** * Process specific request types (simplified implementations) */ private function processGetRestrictions(array $params): array { return ['restrictions' => [], 'count' => 0]; } private function processUpdateRestriction(array $params): array { return ['success' => true, 'id' => $params['id'] ?? 0]; } private function processGetAvailability(array $params): array { return ['availability' => [], 'doctor_id' => $params['doctor_id'] ?? 0]; } }