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:
- You (browser) pull up to the speaker: Type URL, hit enter
- You place your order: Browser sends HTTP request
- Kitchen (PHP) prepares your food: Server processes PHP code
- Window hands you the bag: Server sends HTML response
- 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:
- Static files (.html, .css, .jpg): Served directly
- PHP files: Handed to PHP interpreter
- 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:
- Headers must come before output
- Include files execute when reached
- Functions can be called before declaration
- Variables must exist before use
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:
- 200: Success (default)
- 301/302: Redirects
- 400: Bad request
- 401: Unauthorized
- 403: Forbidden
- 404: Not found
- 500: Server error
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:
- ✅ The complete request-response cycle
- ✅ HTTP methods (GET, POST, etc.)
- ✅ Headers and their importance
- ✅ Cookies and state management
- ✅ Status codes and their meanings
- ✅ File upload mechanics
- ✅ Output buffering control
- ✅ Performance considerations
You now understand what happens "under the hood" of every PHP application!
Key Takeaways
- PHP is request-based: Each page load is independent
- Headers are powerful: They control caching, security, redirects
- State requires effort: HTTP is stateless; cookies/sessions add memory
- Order matters: Headers before output, always
- Everything has overhead: Minimize requests for better performance
Understanding these concepts separates beginners from developers. You're now in the second group!
Practice Challenges
- Build a redirect manager: Create a script that redirects based on URL parameters
- Custom 404 page: Design a helpful 404 page with search functionality
- Download counter: Track file downloads using headers
- Request analyzer: Expand the logger to detect bots vs humans
- Performance monitor: Time different operations and find bottlenecks
Next Up
You've completed Module 1! You understand:
- What PHP is and why it matters
- How to set up a development environment
- Basic PHP syntax and features
- How PHP interacts with web servers
Ready for your first project? Let's build something real that combines everything you've learned!
Quick Reference
Superglobals:
$_GET
- URL parameters$_POST
- Form data$_COOKIE
- Cookie values$_SESSION
- Session data$_SERVER
- Server/environment info$_FILES
- Upload file info
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:
- 2xx: Success
- 3xx: Redirection
- 4xx: Client error
- 5xx: Server error