Lesson 1.4: Understanding How PHP Works with Web Servers

The Magic Behind the Curtain

Ever wonder what happens between typing a URL and seeing a webpage? It's not magic - it's a fascinating dance between browsers, servers, and PHP. Understanding this flow transforms you from a code copier to a real developer who knows why things work.

Let's pull back the curtain and see the machinery that powers every PHP application.

The Request-Response Cycle

Think of visiting a website like ordering at a drive-through:

  1. You (browser) pull up to the speaker: Type URL, hit enter
  2. You place your order: Browser sends HTTP request
  3. Kitchen (PHP) prepares your food: Server processes PHP code
  4. Window hands you the bag: Server sends HTML response
  5. You drive away happy: Browser displays the page

This happens in milliseconds. Let's see it in action:

<?php
// request-demo.php
echo "<h1>Request Information</h1>";
echo "<pre>";
echo "Method: " . $_SERVER['REQUEST_METHOD'] . "\n";
echo "URL: " . $_SERVER['REQUEST_URI'] . "\n";
echo "User Agent: " . $_SERVER['HTTP_USER_AGENT'] . "\n";
echo "Your IP: " . $_SERVER['REMOTE_ADDR'] . "\n";
echo "Server Time: " . date('Y-m-d H:i:s') . "\n";
echo "</pre>";

// Show all request headers
echo "<h2>Request Headers:</h2>";
echo "<pre>";
foreach (getallheaders() as $name => $value) {
    echo "$name: $value\n";
}
echo "</pre>";
?>

Run this. See all that information? Your browser sent it all automatically. Every single request carries this metadata.

HTTP Methods Explained

HTTP isn't random. It follows strict rules. Different methods serve different purposes:

<?php
// method-demo.php
$method = $_SERVER['REQUEST_METHOD'];

switch ($method) {
    case 'GET':
        echo "<h2>GET Request</h2>";
        echo "<p>Used for retrieving data. Parameters visible in URL.</p>";
        if (!empty($_GET)) {
            echo "<h3>GET Parameters:</h3>";
            echo "<pre>" . print_r($_GET, true) . "</pre>";
        }
        break;

    case 'POST':
        echo "<h2>POST Request</h2>";
        echo "<p>Used for sending data. Parameters hidden in request body.</p>";
        if (!empty($_POST)) {
            echo "<h3>POST Data:</h3>";
            echo "<pre>" . print_r($_POST, true) . "</pre>";
        }
        break;

    default:
        echo "<h2>$method Request</h2>";
        echo "<p>Other methods like PUT, DELETE, PATCH exist too!</p>";
}
?>

<!-- Forms to test different methods -->
<hr>
<h3>Test GET Request:</h3>
<form method="GET">
    <input type="text" name="search" placeholder="Search term">
    <button type="submit">Search (GET)</button>
</form>

<h3>Test POST Request:</h3>
<form method="POST">
    <input type="text" name="username" placeholder="Username">
    <input type="password" name="password" placeholder="Password">
    <button type="submit">Login (POST)</button>
</form>

Try both forms. Notice how GET parameters appear in the URL while POST data stays hidden? That's why we use POST for sensitive data like passwords.

The Web Server's Role

Web servers (Apache, Nginx, IIS) act as middlemen. They're like restaurant hosts - they greet requests and direct them appropriately:

  1. Static files (.html, .css, .jpg): Served directly
  2. PHP files: Handed to PHP interpreter
  3. Missing files: Return 404 error

Here's a visual representation:

Browser Request: GET /profile.php?id=123
                          ↓
    Web Server (Apache/Nginx) receives request
                          ↓
            Is it a PHP file? → Yes
                          ↓
         Pass to PHP Interpreter
                          ↓
    PHP executes code, generates HTML
                          ↓
      Web Server sends HTML to browser
                          ↓
         Browser renders the page

Understanding URLs and Routing

URLs aren't just addresses - they're instructions:

<?php
// url-anatomy.php
$url = "https://example.com:8080/shop/products.php?category=electronics&sort=price#reviews";

$parsed = parse_url($url);
echo "<h2>URL Anatomy:</h2>";
echo "<pre>";
print_r($parsed);
echo "</pre>";

// In real usage:
echo "<h3>Current Page URL Components:</h3>";
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http";
$domain = $_SERVER['HTTP_HOST'];
$path = $_SERVER['REQUEST_URI'];
$fullUrl = $protocol . "://" . $domain . $path;

echo "Protocol: $protocol<br>";
echo "Domain: $domain<br>";
echo "Path: $path<br>";
echo "Full URL: $fullUrl<br>";

// Query string parsing
if (!empty($_SERVER['QUERY_STRING'])) {
    echo "<h3>Query Parameters:</h3>";
    parse_str($_SERVER['QUERY_STRING'], $params);
    foreach ($params as $key => $value) {
        echo "$key = $value<br>";
    }
}
?>

Modern frameworks hide this complexity with pretty URLs, but understanding the basics helps debugging immensely.

Headers: The Invisible Messengers

Headers carry crucial information both ways. Like shipping labels, they tell servers and browsers how to handle content:

<?php
// headers-demo.php
// Headers must be sent before any output!

// Setting response headers
header('X-Powered-By: PHP/Awesome');
header('X-Developer: Learning PHP');

// Content type examples (uncomment one to test)
// header('Content-Type: application/json');
// header('Content-Type: text/plain');
// header('Content-Type: image/png');

// Cache control
header('Cache-Control: no-cache, must-revalidate');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');

// Security headers
header('X-Frame-Options: DENY');
header('X-Content-Type-Options: nosniff');
header('X-XSS-Protection: 1; mode=block');
?>
<!DOCTYPE html>
<html>
<head>
    <title>Headers Demo</title>
</head>
<body>
    <h1>Check Your Browser's Developer Tools</h1>
    <p>Open Network tab, refresh, and click this request to see headers!</p>

    <h2>Common Header Uses:</h2>
    <ul>
        <li>Redirects: <code>header('Location: /newpage.php');</code></li>
        <li>Downloads: <code>header('Content-Disposition: attachment; filename="file.pdf"');</code></li>
        <li>API Responses: <code>header('Content-Type: application/json');</code></li>
        <li>Authentication: <code>header('WWW-Authenticate: Basic realm="Secure Area"');</code></li>
    </ul>
</body>
</html>

Remember: headers must be sent before ANY output. Even a single space before <?php breaks header() calls!

Cookies: The Server's Memory

HTTP is stateless - each request stands alone. Cookies give servers memory:

<?php
// cookies-demo.php

// Set cookies (must be before any output!)
setcookie('first_visit', date('Y-m-d H:i:s'), time() + 3600 * 24 * 30); // 30 days
setcookie('visit_count', ($_COOKIE['visit_count'] ?? 0) + 1, time() + 3600 * 24 * 365);

// Theme preference cookie
if (isset($_POST['theme'])) {
    setcookie('theme', $_POST['theme'], time() + 3600 * 24 * 365);
    header('Location: ' . $_SERVER['PHP_SELF']);
    exit;
}

$theme = $_COOKIE['theme'] ?? 'light';
?>
<!DOCTYPE html>
<html>
<head>
    <title>Cookies Demo</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            padding: 20px;
            transition: all 0.3s ease;
        }
        .light {
            background: #fff;
            color: #333;
        }
        .dark {
            background: #333;
            color: #fff;
        }
        .cookie-info {
            background: rgba(0,0,0,0.1);
            padding: 15px;
            border-radius: 5px;
            margin: 10px 0;
        }
    </style>
</head>
<body class="<?php echo $theme; ?>">
    <h1>Cookie Demonstration</h1>

    <?php if (isset($_COOKIE['first_visit'])): ?>
        <div class="cookie-info">
            <p>Welcome back! Your first visit was: <?php echo $_COOKIE['first_visit']; ?></p>
            <p>You've visited this page <?php echo $_COOKIE['visit_count']; ?> times</p>
        </div>
    <?php else: ?>
        <div class="cookie-info">
            <p>Welcome, first-time visitor!</p>
        </div>
    <?php endif; ?>

    <h2>Theme Preference (Stored in Cookie)</h2>
    <form method="POST">
        <label>
            <input type="radio" name="theme" value="light" <?php echo $theme === 'light' ? 'checked' : ''; ?>>
            Light Theme
        </label>
        <label>
            <input type="radio" name="theme" value="dark" <?php echo $theme === 'dark' ? 'checked' : ''; ?>>
            Dark Theme
        </label>
        <button type="submit">Save Preference</button>
    </form>

    <h2>Your Current Cookies:</h2>
    <pre><?php print_r($_COOKIE); ?></pre>
</body>
</html>

Cookies enable shopping carts, login systems, and preferences. But remember - users control cookies. Never store sensitive data!

The PHP Execution Lifecycle

Understanding execution order prevents countless bugs:

<?php
// lifecycle-demo.php
echo "<h1>PHP Execution Lifecycle</h1>";

// 1. PHP reads entire file first
$lateDeclared = "I'm declared later but accessible here: " . $futureVar;

// 2. Functions are hoisted
echo sayHello("World"); // Works even though function is declared below

// 3. Execution happens top to bottom
echo "<h2>Step 1: Starting execution</h2>";

$futureVar = "I exist now!";
echo "<p>$lateDeclared</p>"; // Shows notice - variable didn't exist when used

// 4. Include/require happens when reached
echo "<h2>Step 2: About to include a file</h2>";
// include 'header.php';  // Would execute here

// 5. Output buffering can change things
ob_start();
echo "<p>This is buffered</p>";
$buffered = ob_get_clean();
echo "<h2>Step 3: Output buffering captured: </h2>";
echo htmlspecialchars($buffered);

// Function declaration (hoisted)
function sayHello($name) {
    return "<p>Hello, $name!</p>";
}

// 6. Script termination
echo "<h2>Step 4: Script ending</h2>";
// exit() or die() would stop here
// Uncaught exceptions would stop here
// Fatal errors would stop here

echo "<p>✓ Reached end successfully!</p>";
?>

This lifecycle knowledge helps you understand why:

Handling File Uploads

File uploads showcase the full request-response cycle:

<?php
// upload-demo.php
$uploadMessage = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upload'])) {
    $uploadedFile = $_FILES['upload'];

    // File upload information
    $fileName = $uploadedFile['name'];
    $fileTmpName = $uploadedFile['tmp_name'];
    $fileSize = $uploadedFile['size'];
    $fileError = $uploadedFile['error'];
    $fileType = $uploadedFile['type'];

    // Extract extension
    $fileExt = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));

    // Allowed extensions
    $allowed = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'txt'];

    if (in_array($fileExt, $allowed)) {
        if ($fileError === 0) {
            if ($fileSize < 5000000) { // 5MB limit
                // Generate unique name
                $newFileName = uniqid('', true) . '.' . $fileExt;
                $uploadPath = 'uploads/' . $newFileName;

                // Create uploads directory if it doesn't exist
                if (!file_exists('uploads')) {
                    mkdir('uploads', 0777, true);
                }

                // Move file from temp to permanent location
                if (move_uploaded_file($fileTmpName, $uploadPath)) {
                    $uploadMessage = "✓ File uploaded successfully!";
                } else {
                    $uploadMessage = "✗ Failed to move file";
                }
            } else {
                $uploadMessage = "✗ File too large (max 5MB)";
            }
        } else {
            $uploadMessage = "✗ Upload error code: $fileError";
        }
    } else {
        $uploadMessage = "✗ Invalid file type";
    }
}
?>
<!DOCTYPE html>
<html>
<head>
    <title>File Upload Demo</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 50px auto;
            padding: 20px;
        }
        .upload-area {
            border: 2px dashed #ccc;
            border-radius: 10px;
            padding: 30px;
            text-align: center;
            transition: border-color 0.3s;
        }
        .upload-area:hover {
            border-color: #999;
        }
        .message {
            margin: 20px 0;
            padding: 10px;
            border-radius: 5px;
        }
        .success { background: #d4edda; color: #155724; }
        .error { background: #f8d7da; color: #721c24; }
    </style>
</head>
<body>
    <h1>File Upload Demonstration</h1>

    <?php if ($uploadMessage): ?>
        <div class="message <?php echo strpos($uploadMessage, '✓') !== false ? 'success' : 'error'; ?>">
            <?php echo $uploadMessage; ?>
        </div>
    <?php endif; ?>

    <form method="POST" enctype="multipart/form-data">
        <div class="upload-area">
            <p>Choose a file to upload (jpg, png, gif, pdf, txt - max 5MB)</p>
            <input type="file" name="upload" required>
            <br><br>
            <button type="submit">Upload File</button>
        </div>
    </form>

    <h2>How File Uploads Work:</h2>
    <ol>
        <li><strong>Browser</strong> encodes file in multipart/form-data format</li>
        <li><strong>Server</strong> receives file in temporary directory</li>
        <li><strong>PHP</strong> provides file info via $_FILES superglobal</li>
        <li><strong>Your code</strong> validates and moves file to permanent location</li>
        <li><strong>Response</strong> confirms success or reports errors</li>
    </ol>

    <?php if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upload'])): ?>
    <h2>Upload Details:</h2>
    <pre><?php print_r($_FILES['upload']); ?></pre>
    <?php endif; ?>
</body>
</html>

Notice the enctype="multipart/form-data"? Without it, file uploads fail silently. Details matter!

Status Codes: HTTP's Response Language

Every response includes a status code. They're like traffic lights for browsers:

<?php
// status-codes.php
$page = $_GET['page'] ?? 'home';

switch ($page) {
    case 'home':
        // 200 OK - Default, no need to set
        echo "<h1>Welcome Home!</h1>";
        break;

    case 'moved':
        // 301 Moved Permanently
        header('HTTP/1.1 301 Moved Permanently');
        header('Location: /new-location.php');
        exit;

    case 'redirect':
        // 302 Found (Temporary Redirect)
        header('Location: /temporary-page.php');
        exit;

    case 'not-found':
        // 404 Not Found
        header('HTTP/1.1 404 Not Found');
        echo "<h1>404 - Page Not Found</h1>";
        echo "<p>The page you're looking for doesn't exist.</p>";
        break;

    case 'forbidden':
        // 403 Forbidden
        header('HTTP/1.1 403 Forbidden');
        echo "<h1>403 - Access Forbidden</h1>";
        echo "<p>You don't have permission to access this resource.</p>";
        break;

    case 'error':
        // 500 Internal Server Error
        header('HTTP/1.1 500 Internal Server Error');
        echo "<h1>500 - Internal Server Error</h1>";
        echo "<p>Something went wrong on our end.</p>";
        break;

    case 'api':
        // 201 Created (Common for APIs)
        header('HTTP/1.1 201 Created');
        header('Content-Type: application/json');
        echo json_encode(['status' => 'success', 'id' => 12345]);
        exit;
}
?>

<h2>Test Different Status Codes:</h2>
<ul>
    <li><a href="?page=home">200 OK</a></li>
    <li><a href="?page=moved">301 Moved Permanently</a></li>
    <li><a href="?page=redirect">302 Temporary Redirect</a></li>
    <li><a href="?page=forbidden">403 Forbidden</a></li>
    <li><a href="?page=not-found">404 Not Found</a></li>
    <li><a href="?page=error">500 Server Error</a></li>
    <li><a href="?page=api">201 Created (API)</a></li>
</ul>

Common codes you'll use:

Output Buffering: Controlling the Flow

Output buffering gives you control over when content is sent:

<?php
// buffer-demo.php
// Start output buffering
ob_start();

echo "<h1>Output Buffering Demo</h1>";

// Normally this would cause an error (output already sent)
// But with buffering, we can still send headers!
$userLoggedIn = false;

if (!$userLoggedIn) {
    // Clear the buffer
    ob_clean();

    // Now we can redirect even though we already echoed
    header('Location: login.php');
    // exit; // Uncomment to actually redirect
}

// Continue with page
echo "<p>You're logged in!</p>";

// Capture buffer content
$content = ob_get_contents();

// Add wrapper
echo "<div style='border: 2px solid #333; padding: 20px;'>";
echo $content;
echo "</div>";

// End buffering and send output
ob_end_flush();
?>

<h2>Output Buffering Benefits:</h2>
<ul>
    <li>Send headers after output starts</li>
    <li>Capture output for processing</li>
    <li>Implement template systems</li>
    <li>Compress output before sending</li>
    <li>Handle errors gracefully</li>
</ul>

Output buffering enables powerful techniques like gzip compression, template engines, and error handling.

Building a Request Logger

Let's combine everything into a practical tool:

<?php
// request-logger.php
session_start();

// Initialize log in session
if (!isset($_SESSION['request_log'])) {
    $_SESSION['request_log'] = [];
}

// Log current request
$logEntry = [
    'time' => date('Y-m-d H:i:s'),
    'method' => $_SERVER['REQUEST_METHOD'],
    'url' => $_SERVER['REQUEST_URI'],
    'ip' => $_SERVER['REMOTE_ADDR'],
    'user_agent' => $_SERVER['HTTP_USER_AGENT'],
    'referrer' => $_SERVER['HTTP_REFERER'] ?? 'Direct',
    'data' => [
        'GET' => $_GET,
        'POST' => $_POST,
        'COOKIES' => $_COOKIE
    ]
];

// Add to log (keep last 10 entries)
array_unshift($_SESSION['request_log'], $logEntry);
$_SESSION['request_log'] = array_slice($_SESSION['request_log'], 0, 10);

// Clear log if requested
if (isset($_GET['clear'])) {
    $_SESSION['request_log'] = [];
    header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
    exit;
}
?>
<!DOCTYPE html>
<html>
<head>
    <title>Request Logger</title>
    <style>
        body {
            font-family: monospace;
            background: #1a1a1a;
            color: #0f0;
            padding: 20px;
        }
        .log-entry {
            background: #000;
            border: 1px solid #0f0;
            padding: 10px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .method {
            display: inline-block;
            padding: 2px 8px;
            border-radius: 3px;
            font-weight: bold;
        }
        .GET { background: #28a745; color: white; }
        .POST { background: #007bff; color: white; }
        a { color: #0f0; }
        pre { overflow-x: auto; }
    </style>
</head>
<body>
    <h1>🔍 Request Logger</h1>
    <p>This page logs all incoming requests. Test it out!</p>

    <form method="GET" style="display: inline;">
        <input type="text" name="search" placeholder="GET parameter">
        <button type="submit">Send GET</button>
    </form>

    <form method="POST" style="display: inline;">
        <input type="text" name="message" placeholder="POST parameter">
        <button type="submit">Send POST</button>
    </form>

    <a href="?clear=1">[Clear Log]</a>

    <h2>Recent Requests:</h2>
    <?php foreach ($_SESSION['request_log'] as $entry): ?>
        <div class="log-entry">
            <span class="method <?php echo $entry['method']; ?>">
                <?php echo $entry['method']; ?>
            </span>
            <?php echo $entry['url']; ?>
            <small>(<?php echo $entry['time']; ?>)</small>
            <br>
            <small>IP: <?php echo $entry['ip']; ?> |
            Referrer: <?php echo $entry['referrer']; ?></small>

            <?php if (!empty($entry['data']['GET']) || !empty($entry['data']['POST'])): ?>
                <pre><?php
                    $data = array_filter($entry['data'], function($v) {
                        return !empty($v);
                    });
                    echo htmlspecialchars(json_encode($data, JSON_PRETTY_PRINT));
                ?></pre>
            <?php endif; ?>
        </div>
    <?php endforeach; ?>

    <?php if (empty($_SESSION['request_log'])): ?>
        <p>No requests logged yet. Try the forms above!</p>
    <?php endif; ?>
</body>
</html>

This logger helps visualize the request-response cycle. Watch how different actions create different log entries!

Performance Considerations

Understanding the cycle helps optimize performance:

<?php
// performance-demo.php
$startTime = microtime(true);

// Demonstrate performance impact
echo "<h1>Performance Demonstration</h1>";

// Slow operation example
echo "<h2>Calculating...</h2>";
$result = 0;
for ($i = 0; $i < 1000000; $i++) {
    $result += $i;
}

$endTime = microtime(true);
$executionTime = round(($endTime - $startTime) * 1000, 2);

echo "<p>Sum of 1 to 1,000,000: " . number_format($result) . "</p>";
echo "<p>Execution time: {$executionTime}ms</p>";

// Show memory usage
echo "<h2>Memory Usage:</h2>";
echo "<p>Current: " . round(memory_get_usage() / 1024 / 1024, 2) . " MB</p>";
echo "<p>Peak: " . round(memory_get_peak_usage() / 1024 / 1024, 2) . " MB</p>";

// Performance tips
?>
<h2>Performance Tips:</h2>
<ul>
    <li><strong>Minimize Database Queries:</strong> Each query adds latency</li>
    <li><strong>Cache Static Content:</strong> Don't regenerate unchanged data</li>
    <li><strong>Optimize Images:</strong> Large images slow page loads</li>
    <li><strong>Use Compression:</strong> Enable gzip for text content</li>
    <li><strong>Minimize HTTP Requests:</strong> Combine CSS/JS files</li>
</ul>

Fast sites keep users happy. Understanding the request cycle helps identify bottlenecks.

What You've Learned

This lesson covered crucial concepts:

You now understand what happens "under the hood" of every PHP application!

Key Takeaways

  1. PHP is request-based: Each page load is independent
  2. Headers are powerful: They control caching, security, redirects
  3. State requires effort: HTTP is stateless; cookies/sessions add memory
  4. Order matters: Headers before output, always
  5. Everything has overhead: Minimize requests for better performance

Understanding these concepts separates beginners from developers. You're now in the second group!

Practice Challenges

  1. Build a redirect manager: Create a script that redirects based on URL parameters
  2. Custom 404 page: Design a helpful 404 page with search functionality
  3. Download counter: Track file downloads using headers
  4. Request analyzer: Expand the logger to detect bots vs humans
  5. Performance monitor: Time different operations and find bottlenecks

Next Up

You've completed Module 1! You understand:

Ready for your first project? Let's build something real that combines everything you've learned!


Quick Reference

Superglobals:

Common Headers:

header('Location: /path/');           // Redirect
header('Content-Type: text/json');    // JSON response
header('Cache-Control: no-cache');    // Prevent caching
header('X-Powered-By: PHP');          // Custom header

Status Codes: