1. php
  2. /web development
  3. /ajax-json

PHP AJAX and JSON

Introduction to AJAX and JSON in PHP

AJAX (Asynchronous JavaScript and XML) combined with JSON (JavaScript Object Notation) enables the creation of dynamic, responsive web applications that can update content without requiring full page reloads. PHP serves as an excellent backend for AJAX requests, providing data processing and JSON responses for modern web applications.

Understanding AJAX and JSON integration with PHP is essential for building interactive user interfaces, real-time applications, and modern web APIs that provide seamless user experiences.

Why AJAX and JSON Matter

Enhanced User Experience: AJAX enables seamless interactions without page refreshes, creating fluid, desktop-like web applications.

Improved Performance: Only necessary data is transferred, reducing bandwidth usage and improving response times.

Real-time Updates: Applications can fetch and display new information without user intervention.

API Development: JSON responses make PHP applications ideal for serving mobile apps and single-page applications.

Progressive Enhancement: AJAX can enhance existing forms and interfaces while maintaining fallback functionality.

Key Concepts

Asynchronous Processing: JavaScript makes requests to PHP scripts without blocking the user interface.

JSON Data Exchange: Lightweight data format that's easy to parse in both JavaScript and PHP.

RESTful APIs: Standardized approach to creating web services using HTTP methods and JSON responses.

Error Handling: Proper handling of network failures, server errors, and validation issues.

Security Considerations: CSRF protection, input validation, and output sanitization for AJAX endpoints.

Basic AJAX Implementation

Simple AJAX with JSON Response

<?php
/**
 * Basic AJAX JSON Response Handler
 * 
 * Demonstrates fundamental AJAX request handling
 * with JSON responses and error management.
 */

// Set JSON content type
header('Content-Type: application/json');

// Enable CORS for development (configure properly for production)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');

/**
 * Simple API response helper
 */
function jsonResponse($data, $status = 200, $message = 'success') {
    http_response_code($status);
    
    $response = [
        'status' => $status < 400 ? 'success' : 'error',
        'message' => $message,
        'data' => $data,
        'timestamp' => date('c')
    ];
    
    echo json_encode($response, JSON_PRETTY_PRINT);
    exit;
}

/**
 * Error response helper
 */
function errorResponse($message, $status = 400, $errors = []) {
    http_response_code($status);
    
    $response = [
        'status' => 'error',
        'message' => $message,
        'errors' => $errors,
        'timestamp' => date('c')
    ];
    
    echo json_encode($response, JSON_PRETTY_PRINT);
    exit;
}

// Handle different request methods
$method = $_SERVER['REQUEST_METHOD'];
$requestUri = $_SERVER['REQUEST_URI'];

// Parse request data
$input = json_decode(file_get_contents('php://input'), true) ?? [];
$data = array_merge($_GET, $_POST, $input);

try {
    switch ($method) {
        case 'GET':
            handleGetRequest($data);
            break;
            
        case 'POST':
            handlePostRequest($data);
            break;
            
        case 'PUT':
            handlePutRequest($data);
            break;
            
        case 'DELETE':
            handleDeleteRequest($data);
            break;
            
        default:
            errorResponse('Method not allowed', 405);
    }
} catch (Exception $e) {
    errorResponse('Internal server error', 500, ['exception' => $e->getMessage()]);
}

/**
 * Handle GET requests
 */
function handleGetRequest($data) {
    // Example: Get user information
    if (isset($data['action']) && $data['action'] === 'get_user') {
        $userId = $data['user_id'] ?? null;
        
        if (!$userId) {
            errorResponse('User ID is required', 400);
        }
        
        // Simulate database lookup
        $user = [
            'id' => $userId,
            'name' => 'John Doe',
            'email' => '[email protected]',
            'created_at' => '2024-01-15T10:00:00Z'
        ];
        
        jsonResponse($user, 200, 'User retrieved successfully');
    }
    
    // Example: Search functionality
    if (isset($data['action']) && $data['action'] === 'search') {
        $query = $data['q'] ?? '';
        
        if (strlen($query) < 2) {
            errorResponse('Search query must be at least 2 characters', 400);
        }
        
        // Simulate search results
        $results = [
            ['id' => 1, 'title' => 'First Result', 'snippet' => 'This matches your search...'],
            ['id' => 2, 'title' => 'Second Result', 'snippet' => 'Another matching result...']
        ];
        
        jsonResponse($results, 200, 'Search completed');
    }
    
    // Default response
    jsonResponse(['message' => 'GET endpoint ready'], 200);
}

/**
 * Handle POST requests
 */
function handlePostRequest($data) {
    // Example: Create new user
    if (isset($data['action']) && $data['action'] === 'create_user') {
        $name = $data['name'] ?? '';
        $email = $data['email'] ?? '';
        
        // Validation
        $errors = [];
        if (empty($name)) {
            $errors['name'] = 'Name is required';
        }
        if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $errors['email'] = 'Valid email is required';
        }
        
        if (!empty($errors)) {
            errorResponse('Validation failed', 422, $errors);
        }
        
        // Simulate user creation
        $newUser = [
            'id' => rand(1000, 9999),
            'name' => $name,
            'email' => $email,
            'created_at' => date('c')
        ];
        
        jsonResponse($newUser, 201, 'User created successfully');
    }
    
    // Example: Contact form submission
    if (isset($data['action']) && $data['action'] === 'contact_form') {
        $name = $data['name'] ?? '';
        $email = $data['email'] ?? '';
        $message = $data['message'] ?? '';
        
        // Validation
        $errors = [];
        if (empty($name)) $errors['name'] = 'Name is required';
        if (empty($email)) $errors['email'] = 'Email is required';
        if (empty($message)) $errors['message'] = 'Message is required';
        
        if (!empty($errors)) {
            errorResponse('Please fix the following errors', 422, $errors);
        }
        
        // Process form (send email, save to database, etc.)
        $success = true; // Simulate processing
        
        if ($success) {
            jsonResponse(['contact_id' => uniqid()], 200, 'Thank you! Your message has been sent.');
        } else {
            errorResponse('Failed to send message', 500);
        }
    }
    
    errorResponse('Invalid action', 400);
}

/**
 * Handle PUT requests
 */
function handlePutRequest($data) {
    if (isset($data['action']) && $data['action'] === 'update_user') {
        $userId = $data['user_id'] ?? null;
        $name = $data['name'] ?? '';
        $email = $data['email'] ?? '';
        
        if (!$userId) {
            errorResponse('User ID is required', 400);
        }
        
        // Simulate user update
        $updatedUser = [
            'id' => $userId,
            'name' => $name,
            'email' => $email,
            'updated_at' => date('c')
        ];
        
        jsonResponse($updatedUser, 200, 'User updated successfully');
    }
    
    errorResponse('Invalid action', 400);
}

/**
 * Handle DELETE requests
 */
function handleDeleteRequest($data) {
    if (isset($data['action']) && $data['action'] === 'delete_user') {
        $userId = $data['user_id'] ?? null;
        
        if (!$userId) {
            errorResponse('User ID is required', 400);
        }
        
        // Simulate user deletion
        jsonResponse(['deleted_id' => $userId], 200, 'User deleted successfully');
    }
    
    errorResponse('Invalid action', 400);
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>AJAX JSON Example</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .section { margin-bottom: 30px; padding: 20px; border: 1px solid #ddd; }
        .form-group { margin-bottom: 15px; }
        .form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
        .form-group input, .form-group textarea { width: 100%; padding: 8px; border: 1px solid #ccc; }
        .button { background: #007cba; color: white; padding: 10px 20px; border: none; cursor: pointer; }
        .button:hover { background: #005a87; }
        .result { margin-top: 15px; padding: 10px; border-radius: 4px; }
        .success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
        .error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
        .loading { background: #fff3cd; color: #856404; border: 1px solid #ffeaa7; }
    </style>
</head>
<body>
    <h1>AJAX JSON Examples</h1>
    
    <!-- Search Example -->
    <div class="section">
        <h2>Search Example</h2>
        <div class="form-group">
            <label for="search-input">Search:</label>
            <input type="text" id="search-input" placeholder="Enter search term...">
        </div>
        <button class="button" onclick="performSearch()">Search</button>
        <div id="search-results" class="result" style="display: none;"></div>
    </div>
    
    <!-- Contact Form Example -->
    <div class="section">
        <h2>Contact Form</h2>
        <form id="contact-form">
            <div class="form-group">
                <label for="contact-name">Name:</label>
                <input type="text" id="contact-name" name="name" required>
            </div>
            <div class="form-group">
                <label for="contact-email">Email:</label>
                <input type="email" id="contact-email" name="email" required>
            </div>
            <div class="form-group">
                <label for="contact-message">Message:</label>
                <textarea id="contact-message" name="message" rows="4" required></textarea>
            </div>
            <button type="submit" class="button">Send Message</button>
        </form>
        <div id="contact-result" class="result" style="display: none;"></div>
    </div>
    
    <!-- User Management Example -->
    <div class="section">
        <h2>User Management</h2>
        <button class="button" onclick="loadUser(123)">Load User 123</button>
        <button class="button" onclick="createUser()">Create User</button>
        <div id="user-result" class="result" style="display: none;"></div>
    </div>

    <script>
        /**
         * Generic AJAX function
         */
        function makeAjaxRequest(method, data, callback) {
            const xhr = new XMLHttpRequest();
            
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    try {
                        const response = JSON.parse(xhr.responseText);
                        callback(response, xhr.status);
                    } catch (e) {
                        callback({
                            status: 'error',
                            message: 'Invalid JSON response',
                            data: xhr.responseText
                        }, xhr.status);
                    }
                }
            };
            
            xhr.open(method, window.location.href, true);
            xhr.setRequestHeader('Content-Type', 'application/json');
            
            if (method === 'GET') {
                xhr.send();
            } else {
                xhr.send(JSON.stringify(data));
            }
        }
        
        /**
         * Display result helper
         */
        function displayResult(elementId, response, status) {
            const element = document.getElementById(elementId);
            element.style.display = 'block';
            
            if (response.status === 'success') {
                element.className = 'result success';
                element.innerHTML = '<strong>Success:</strong> ' + response.message + 
                                  '<pre>' + JSON.stringify(response.data, null, 2) + '</pre>';
            } else {
                element.className = 'result error';
                element.innerHTML = '<strong>Error:</strong> ' + response.message;
                
                if (response.errors) {
                    element.innerHTML += '<ul>';
                    for (const field in response.errors) {
                        element.innerHTML += '<li>' + field + ': ' + response.errors[field] + '</li>';
                    }
                    element.innerHTML += '</ul>';
                }
            }
        }
        
        /**
         * Search functionality
         */
        function performSearch() {
            const query = document.getElementById('search-input').value;
            const resultDiv = document.getElementById('search-results');
            
            if (query.length < 2) {
                resultDiv.className = 'result error';
                resultDiv.innerHTML = 'Please enter at least 2 characters';
                resultDiv.style.display = 'block';
                return;
            }
            
            resultDiv.className = 'result loading';
            resultDiv.innerHTML = 'Searching...';
            resultDiv.style.display = 'block';
            
            makeAjaxRequest('GET', null, function(response, status) {
                displayResult('search-results', response, status);
            });
            
            // Note: In a real implementation, you'd pass the query parameter
        }
        
        /**
         * Contact form submission
         */
        document.getElementById('contact-form').addEventListener('submit', function(e) {
            e.preventDefault();
            
            const formData = {
                action: 'contact_form',
                name: document.getElementById('contact-name').value,
                email: document.getElementById('contact-email').value,
                message: document.getElementById('contact-message').value
            };
            
            const resultDiv = document.getElementById('contact-result');
            resultDiv.className = 'result loading';
            resultDiv.innerHTML = 'Sending message...';
            resultDiv.style.display = 'block';
            
            makeAjaxRequest('POST', formData, function(response, status) {
                displayResult('contact-result', response, status);
                
                if (response.status === 'success') {
                    document.getElementById('contact-form').reset();
                }
            });
        });
        
        /**
         * Load user
         */
        function loadUser(userId) {
            const resultDiv = document.getElementById('user-result');
            resultDiv.className = 'result loading';
            resultDiv.innerHTML = 'Loading user...';
            resultDiv.style.display = 'block';
            
            makeAjaxRequest('GET', null, function(response, status) {
                displayResult('user-result', response, status);
            });
            
            // Note: In a real implementation, you'd pass the user_id parameter
        }
        
        /**
         * Create user
         */
        function createUser() {
            const userData = {
                action: 'create_user',
                name: 'Jane Smith',
                email: '[email protected]'
            };
            
            const resultDiv = document.getElementById('user-result');
            resultDiv.className = 'result loading';
            resultDiv.innerHTML = 'Creating user...';
            resultDiv.style.display = 'block';
            
            makeAjaxRequest('POST', userData, function(response, status) {
                displayResult('user-result', response, status);
            });
        }
        
        /**
         * Search on input change (with debouncing)
         */
        let searchTimeout;
        document.getElementById('search-input').addEventListener('input', function() {
            clearTimeout(searchTimeout);
            searchTimeout = setTimeout(function() {
                const query = document.getElementById('search-input').value;
                if (query.length >= 2) {
                    performSearch();
                }
            }, 500);
        });
    </script>
</body>
</html>

Advanced AJAX Patterns

Real-time Updates and WebSocket-like Functionality

<?php
/**
 * Advanced AJAX Patterns
 * 
 * Demonstrates polling, long-polling, and real-time
 * update patterns using AJAX and PHP.
 */

session_start();

header('Content-Type: application/json');

/**
 * Server-Sent Events (SSE) endpoint
 */
if (isset($_GET['action']) && $_GET['action'] === 'events') {
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    header('Connection: keep-alive');
    
    // Prevent timeout
    ignore_user_abort(false);
    set_time_limit(0);
    
    $lastEventId = $_SERVER['HTTP_LAST_EVENT_ID'] ?? 0;
    
    while (true) {
        // Check if connection is still alive
        if (connection_aborted()) {
            break;
        }
        
        // Get new events (simulate with random data)
        $events = getNewEvents($lastEventId);
        
        foreach ($events as $event) {
            echo "id: {$event['id']}\n";
            echo "event: {$event['type']}\n";
            echo "data: " . json_encode($event['data']) . "\n\n";
            $lastEventId = $event['id'];
        }
        
        // Flush output
        if (ob_get_level()) {
            ob_end_flush();
        }
        flush();
        
        // Wait before next check
        sleep(2);
    }
    
    exit;
}

/**
 * Long polling endpoint
 */
if (isset($_GET['action']) && $_GET['action'] === 'poll') {
    $timeout = 30; // 30 seconds timeout
    $startTime = time();
    $lastUpdate = $_GET['last_update'] ?? 0;
    
    while ((time() - $startTime) < $timeout) {
        $data = checkForUpdates($lastUpdate);
        
        if ($data) {
            echo json_encode([
                'status' => 'success',
                'data' => $data,
                'timestamp' => time()
            ]);
            exit;
        }
        
        // Check if client disconnected
        if (connection_aborted()) {
            exit;
        }
        
        // Wait before checking again
        usleep(500000); // 0.5 seconds
    }
    
    // Timeout reached
    echo json_encode([
        'status' => 'timeout',
        'message' => 'No updates available',
        'timestamp' => time()
    ]);
    exit;
}

/**
 * File upload with progress tracking
 */
if (isset($_POST['action']) && $_POST['action'] === 'upload') {
    $uploadDir = 'uploads/';
    
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }
    
    if (isset($_FILES['file'])) {
        $file = $_FILES['file'];
        $filename = uniqid() . '_' . basename($file['name']);
        $filepath = $uploadDir . $filename;
        
        if (move_uploaded_file($file['tmp_name'], $filepath)) {
            echo json_encode([
                'status' => 'success',
                'message' => 'File uploaded successfully',
                'data' => [
                    'filename' => $filename,
                    'size' => $file['size'],
                    'url' => $filepath
                ]
            ]);
        } else {
            http_response_code(500);
            echo json_encode([
                'status' => 'error',
                'message' => 'Failed to upload file'
            ]);
        }
    } else {
        http_response_code(400);
        echo json_encode([
            'status' => 'error',
            'message' => 'No file provided'
        ]);
    }
    exit;
}

/**
 * Simulate getting new events
 */
function getNewEvents($lastEventId) {
    static $eventCounter = 0;
    $events = [];
    
    // Simulate random events
    if (rand(1, 10) <= 3) { // 30% chance of new event
        $eventCounter++;
        $eventId = $lastEventId + $eventCounter;
        
        $events[] = [
            'id' => $eventId,
            'type' => 'notification',
            'data' => [
                'title' => 'New Notification',
                'message' => 'You have a new message at ' . date('H:i:s'),
                'timestamp' => time()
            ]
        ];
    }
    
    return $events;
}

/**
 * Simulate checking for updates
 */
function checkForUpdates($lastUpdate) {
    // Simulate data changes
    if (rand(1, 10) <= 2) { // 20% chance of update
        return [
            'type' => 'data_update',
            'message' => 'New data available',
            'count' => rand(1, 5),
            'timestamp' => time()
        ];
    }
    
    return null;
}

/**
 * Batch processing endpoint
 */
if (isset($_POST['action']) && $_POST['action'] === 'batch_process') {
    $items = json_decode($_POST['items'], true) ?? [];
    $batchId = uniqid();
    
    // Store batch info in session for progress tracking
    $_SESSION['batch_' . $batchId] = [
        'total' => count($items),
        'processed' => 0,
        'status' => 'running',
        'results' => []
    ];
    
    echo json_encode([
        'status' => 'success',
        'batch_id' => $batchId,
        'total_items' => count($items)
    ]);
    
    // Process items in background (in real app, use queue)
    processBatchItems($batchId, $items);
    exit;
}

/**
 * Batch progress endpoint
 */
if (isset($_GET['action']) && $_GET['action'] === 'batch_progress') {
    $batchId = $_GET['batch_id'] ?? '';
    
    if (isset($_SESSION['batch_' . $batchId])) {
        echo json_encode([
            'status' => 'success',
            'data' => $_SESSION['batch_' . $batchId]
        ]);
    } else {
        http_response_code(404);
        echo json_encode([
            'status' => 'error',
            'message' => 'Batch not found'
        ]);
    }
    exit;
}

/**
 * Simulate batch processing
 */
function processBatchItems($batchId, $items) {
    // In a real application, this would be handled by a background job
    // For demo purposes, we'll simulate processing
    
    foreach ($items as $index => $item) {
        // Simulate processing time
        usleep(100000); // 0.1 seconds
        
        $_SESSION['batch_' . $batchId]['processed'] = $index + 1;
        $_SESSION['batch_' . $batchId]['results'][] = [
            'item' => $item,
            'status' => 'completed',
            'timestamp' => time()
        ];
    }
    
    $_SESSION['batch_' . $batchId]['status'] = 'completed';
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Advanced AJAX Patterns</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 1000px; margin: 0 auto; padding: 20px; }
        .section { margin-bottom: 30px; padding: 20px; border: 1px solid #ddd; }
        .progress { width: 100%; background: #f0f0f0; margin: 10px 0; }
        .progress-bar { height: 20px; background: #4caf50; width: 0%; transition: width 0.3s; }
        .log { background: #f5f5f5; padding: 10px; margin: 10px 0; max-height: 200px; overflow-y: auto; }
        .notification { background: #e7f3ff; padding: 10px; margin: 5px 0; border-left: 4px solid #007cba; }
        .button { background: #007cba; color: white; padding: 10px 20px; border: none; cursor: pointer; margin: 5px; }
    </style>
</head>
<body>
    <h1>Advanced AJAX Patterns</h1>
    
    <!-- Server-Sent Events -->
    <div class="section">
        <h2>Server-Sent Events (Real-time Notifications)</h2>
        <button class="button" onclick="startEventStream()">Start Event Stream</button>
        <button class="button" onclick="stopEventStream()">Stop Event Stream</button>
        <div id="notifications"></div>
    </div>
    
    <!-- Long Polling -->
    <div class="section">
        <h2>Long Polling</h2>
        <button class="button" onclick="startPolling()">Start Polling</button>
        <button class="button" onclick="stopPolling()">Stop Polling</button>
        <div id="polling-log" class="log"></div>
    </div>
    
    <!-- File Upload with Progress -->
    <div class="section">
        <h2>File Upload with Progress</h2>
        <input type="file" id="file-input" multiple>
        <button class="button" onclick="uploadFiles()">Upload Files</button>
        <div class="progress">
            <div id="upload-progress" class="progress-bar"></div>
        </div>
        <div id="upload-log" class="log"></div>
    </div>
    
    <!-- Batch Processing -->
    <div class="section">
        <h2>Batch Processing</h2>
        <button class="button" onclick="startBatchProcess()">Process 10 Items</button>
        <div class="progress">
            <div id="batch-progress" class="progress-bar"></div>
        </div>
        <div id="batch-status"></div>
    </div>

    <script>
        let eventSource = null;
        let pollingInterval = null;
        
        /**
         * Server-Sent Events
         */
        function startEventStream() {
            if (eventSource) {
                eventSource.close();
            }
            
            eventSource = new EventSource('?action=events');
            
            eventSource.onmessage = function(event) {
                const data = JSON.parse(event.data);
                addNotification(data.title, data.message);
            };
            
            eventSource.onerror = function(event) {
                console.error('EventSource error:', event);
                addNotification('Error', 'Connection error occurred');
            };
            
            addNotification('Info', 'Event stream started');
        }
        
        function stopEventStream() {
            if (eventSource) {
                eventSource.close();
                eventSource = null;
                addNotification('Info', 'Event stream stopped');
            }
        }
        
        function addNotification(title, message) {
            const container = document.getElementById('notifications');
            const notification = document.createElement('div');
            notification.className = 'notification';
            notification.innerHTML = '<strong>' + title + ':</strong> ' + message + 
                                   ' <small>(' + new Date().toLocaleTimeString() + ')</small>';
            container.insertBefore(notification, container.firstChild);
            
            // Remove old notifications
            while (container.children.length > 5) {
                container.removeChild(container.lastChild);
            }
        }
        
        /**
         * Long Polling
         */
        function startPolling() {
            if (pollingInterval) {
                clearInterval(pollingInterval);
            }
            
            let lastUpdate = Math.floor(Date.now() / 1000);
            
            function poll() {
                const xhr = new XMLHttpRequest();
                xhr.open('GET', '?action=poll&last_update=' + lastUpdate, true);
                
                xhr.onreadystatechange = function() {
                    if (xhr.readyState === 4) {
                        try {
                            const response = JSON.parse(xhr.responseText);
                            logPolling('Response: ' + JSON.stringify(response));
                            
                            if (response.status === 'success') {
                                lastUpdate = response.timestamp;
                            }
                            
                            // Start next poll
                            setTimeout(poll, 1000);
                        } catch (e) {
                            logPolling('Error: Invalid JSON response');
                            setTimeout(poll, 5000);
                        }
                    }
                };
                
                xhr.send();
                logPolling('Polling started...');
            }
            
            poll();
        }
        
        function stopPolling() {
            if (pollingInterval) {
                clearInterval(pollingInterval);
                pollingInterval = null;
                logPolling('Polling stopped');
            }
        }
        
        function logPolling(message) {
            const log = document.getElementById('polling-log');
            log.innerHTML = '[' + new Date().toLocaleTimeString() + '] ' + message + '\n' + log.innerHTML;
        }
        
        /**
         * File Upload with Progress
         */
        function uploadFiles() {
            const fileInput = document.getElementById('file-input');
            const files = fileInput.files;
            
            if (files.length === 0) {
                logUpload('Please select files to upload');
                return;
            }
            
            for (let i = 0; i < files.length; i++) {
                uploadFile(files[i]);
            }
        }
        
        function uploadFile(file) {
            const formData = new FormData();
            formData.append('action', 'upload');
            formData.append('file', file);
            
            const xhr = new XMLHttpRequest();
            
            xhr.upload.addEventListener('progress', function(e) {
                if (e.lengthComputable) {
                    const percentComplete = (e.loaded / e.total) * 100;
                    document.getElementById('upload-progress').style.width = percentComplete + '%';
                }
            });
            
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    try {
                        const response = JSON.parse(xhr.responseText);
                        if (response.status === 'success') {
                            logUpload('✓ ' + file.name + ' uploaded successfully');
                        } else {
                            logUpload('✗ ' + file.name + ' upload failed: ' + response.message);
                        }
                    } catch (e) {
                        logUpload('✗ ' + file.name + ' upload error');
                    }
                    
                    document.getElementById('upload-progress').style.width = '0%';
                }
            };
            
            xhr.open('POST', '', true);
            xhr.send(formData);
            
            logUpload('Uploading ' + file.name + '...');
        }
        
        function logUpload(message) {
            const log = document.getElementById('upload-log');
            log.innerHTML = '[' + new Date().toLocaleTimeString() + '] ' + message + '\n' + log.innerHTML;
        }
        
        /**
         * Batch Processing
         */
        function startBatchProcess() {
            const items = [];
            for (let i = 1; i <= 10; i++) {
                items.push('Item ' + i);
            }
            
            const formData = new FormData();
            formData.append('action', 'batch_process');
            formData.append('items', JSON.stringify(items));
            
            const xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    try {
                        const response = JSON.parse(xhr.responseText);
                        if (response.status === 'success') {
                            trackBatchProgress(response.batch_id);
                        }
                    } catch (e) {
                        document.getElementById('batch-status').innerHTML = 'Error starting batch process';
                    }
                }
            };
            
            xhr.open('POST', '', true);
            xhr.send(formData);
        }
        
        function trackBatchProgress(batchId) {
            const checkProgress = function() {
                const xhr = new XMLHttpRequest();
                xhr.onreadystatechange = function() {
                    if (xhr.readyState === 4) {
                        try {
                            const response = JSON.parse(xhr.responseText);
                            if (response.status === 'success') {
                                const data = response.data;
                                const percentage = (data.processed / data.total) * 100;
                                
                                document.getElementById('batch-progress').style.width = percentage + '%';
                                document.getElementById('batch-status').innerHTML = 
                                    'Processing: ' + data.processed + '/' + data.total + ' (' + Math.round(percentage) + '%)';
                                
                                if (data.status === 'completed') {
                                    document.getElementById('batch-status').innerHTML += ' - Completed!';
                                } else {
                                    setTimeout(checkProgress, 1000);
                                }
                            }
                        } catch (e) {
                            document.getElementById('batch-status').innerHTML = 'Error tracking progress';
                        }
                    }
                };
                
                xhr.open('GET', '?action=batch_progress&batch_id=' + batchId, true);
                xhr.send();
            };
            
            checkProgress();
        }
    </script>
</body>
</html>

For more PHP web development topics:

Summary

AJAX and JSON integration with PHP enables modern, interactive web applications:

Asynchronous Communication: AJAX allows seamless data exchange without page reloads, improving user experience and application responsiveness.

JSON Data Format: Lightweight, readable format for data exchange between client and server, compatible with JavaScript and PHP.

Real-time Features: Server-Sent Events and long polling enable real-time updates and notifications without constant page refreshes.

Progressive Enhancement: AJAX can enhance existing functionality while maintaining fallback compatibility for non-JavaScript users.

API Development: JSON responses make PHP applications suitable for serving mobile apps, SPAs, and third-party integrations.

Error Handling: Proper error management ensures graceful degradation and meaningful user feedback during network or server issues.

Security Considerations: CSRF protection, input validation, and output sanitization are essential for secure AJAX endpoints.

Mastering AJAX and JSON with PHP enables you to build responsive, interactive web applications that provide excellent user experiences while maintaining security and reliability.