Week 1: PHP Fundamentals & Local Dev Environment

This course assumes you can already write basic functions and use arrays or lists in some language — but nothing PHP-specific. This week covers PHP 8 syntax, variables and control flow, gets PHP 8.3 installed with a working local server and debugger, and ends with your first dynamic page running end to end.

Module 1 of 28 Week 1 of 32 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Write PHP 8 syntax: variables, types, control flow & functions
  • Run PHP 8.3 locally with the built-in server, and debug it with Xdebug
  • Explain what actually happens between a browser request and PHP's output

1. PHP Syntax & Variables

PHP is a server-side scripting language — code runs on the server, and only the output it produces (usually HTML) reaches the browser. Every PHP statement lives inside <?php ... ?> tags, and every statement ends with a semicolon:

hello.php
<?php

$name = "Ada";
$age = 30;
$is_admin = true;

echo "Hello, " . $name . "! You are " . $age . " years old.";

Variables start with $ and don't need a declared type — PHP is dynamically typed: a variable's type is whatever value it currently holds. The core scalar types are int, float, string and bool, plus array and object for compound data, and null for "no value."

types.php
<?php

$price = 19.99;          // float
$quantity = 3;           // int
$sku = "TSHIRT-BLU-M";   // string
$in_stock = true;        // bool
$discount = null;        // null

var_dump($price);        // float(19.99)
echo gettype($quantity); // "integer"

PHP also does implicit type conversion ("type juggling") in many operations — "5" + 3 evaluates to 8. This is convenient but a common source of bugs, which is why PHP 8 lets you opt into strict typing per file:

strict.php
<?php
declare(strict_types=1);

function addTax(float $price, float $rate): float {
    return $price + ($price * $rate);
}

addTax(100, 0.18);   // fine -- ints widen to float automatically
addTax("100", 0.18); // TypeError: must be of type float, string given
Use declare(strict_types=1) from day one

It must be the very first statement in a file. This course uses it in every example from here on — it turns silent, surprising coercions into loud TypeErrors at the exact call site that got it wrong, instead of a wrong value surfacing three functions later.

2. Control Flow & Functions

if/elseif/else, while, for and foreach all work close to how they do in most C-family languages. PHP 8's match expression is the one worth learning deliberately — it's a stricter, expression-based upgrade to switch:

match.php
<?php
declare(strict_types=1);

function shippingLabel(string $status): string {
    return match ($status) {
        'pending', 'processing' => 'Preparing your order',
        'shipped' => 'On its way',
        'delivered' => 'Delivered',
        default => 'Unknown status',
    };
}

echo shippingLabel('shipped'); // "On its way"

Unlike switch, match compares with strict equality (===, no type juggling), doesn't fall through between arms, and is itself an expression — it produces a value you can assign or return directly, as shown above.

Functions

Functions take typed parameters with optional default values, and can be called with named arguments — useful once a function has several optional parameters:

functions.php
<?php
declare(strict_types=1);

function formatPrice(float $amount, string $currency = "USD", bool $withSymbol = true): string {
    $symbol = $withSymbol ? ($currency === "USD" ? "$" : $currency . " ") : "";
    return $symbol . number_format($amount, 2);
}

echo formatPrice(1999.5);                              // "$1,999.50"
echo formatPrice(1999.5, withSymbol: false);            // "1,999.50"
echo formatPrice(amount: 42, currency: "EUR");           // "EUR 42.00"

// Arrow functions -- a short-lived closure that auto-captures outer variables
$taxRate = 0.18;
$withTax = fn(float $price) => $price + ($price * $taxRate);
echo $withTax(100); // 118

fn(...) => expr is an arrow function: a compact closure for a single expression that automatically captures variables from the surrounding scope by value — no explicit use (...) clause needed, unlike a full function () use (...) {} closure.

3. Installing PHP 8.3

This course uses PHP 8.3. Install it for your platform, then confirm it's active:

terminal
# macOS (Homebrew)
brew install php@8.3

# Ubuntu/Debian
sudo apt install php8.3-cli php8.3-mbstring php8.3-mysql php8.3-xml

# Windows -- install via https://windows.php.net/download or scoop:
scoop install php

php --version
# PHP 8.3.x (cli) ...

PHP ships with a built-in development server — no Apache or Nginx needed while you're learning. Point it at a folder and it serves any PHP file inside:

terminal
mkdir week-01 && cd week-01
echo '<?php echo "PHP is running: " . phpversion();' > index.php

php -S localhost:8000
# PHP 8.3.x Development Server (http://localhost:8000) started

# In another terminal:
curl http://localhost:8000/
# PHP is running: 8.3.x
Built-in server vs. production

The built-in server is single-threaded and explicitly documented as unsuitable for production — it's a development convenience only. From Week 6 onward, once MySQL enters the picture, you'll rely on it constantly; Week 28 covers the real Nginx + PHP-FPM setup you'd actually deploy.

4. php.ini Essentials & Xdebug

php.ini is PHP's main configuration file — it controls error reporting, upload limits, timezone and dozens of other runtime settings. Find your active one with:

terminal
php --ini
# Loaded Configuration File: /usr/local/etc/php/8.3/php.ini

While learning, turn on full error visibility — silent failures are the hardest bugs to chase:

php.ini
error_reporting = E_ALL
display_errors = On
date.timezone = "UTC"

Xdebug is PHP's step debugger and profiler — it lets your editor set breakpoints and inspect variables mid-request instead of scattering var_dump() calls everywhere:

terminal
# Install via PECL (works across platforms)
pecl install xdebug

# Then add to php.ini:
zend_extension=xdebug
xdebug.mode=debug
xdebug.start_with_request=yes

Install the Xdebug extension for your editor (PHP Debug for VS Code, or the built-in support in PhpStorm) and you'll have working breakpoints by Week 2, once there's request-handling logic worth stepping through.

5. Your First Dynamic Page & the Request Lifecycle

PHP files can mix HTML and PHP freely — you drop in and out of <?php ?> tags anywhere in the file:

index.php
<?php
declare(strict_types=1);

$products = ["Keyboard", "Mouse", "Monitor"];
$storeName = "CodeVerse Shop";
?>
<!DOCTYPE html>
<html>
<head><title><?= $storeName ?></title></head>
<body>
  <h1><?= $storeName ?></h1>
  <ul>
    <?php foreach ($products as $product): ?>
      <li><?= htmlspecialchars($product) ?></li>
    <?php endforeach; ?>
  </ul>
</body>
</html>

<?= $x ?> is shorthand for <?php echo $x; ?>. The foreach ... endforeach alternate syntax reads more naturally when PHP is interleaved with HTML like this, versus curly braces. htmlspecialchars() escapes special characters before they hit the page — always wrap any value that might contain user input before printing it into HTML, to prevent cross-site scripting (covered properly in Week 8).

Here's what actually happens between the request and that output:

  1. The browser sends an HTTP request for /index.php.
  2. The server (right now, PHP's built-in one) locates the file and hands it to the PHP interpreter.
  3. PHP executes top to bottom — every <?php ?> block runs; everything outside it is passed through as literal output.
  4. The interpreter's combined output (HTML + evaluated PHP) becomes the HTTP response body.
  5. The browser receives plain HTML — it never sees your PHP source.
Try it now

Save the file above, run php -S localhost:8000 in that folder, and load http://localhost:8000/. View the page source in your browser — you'll see clean HTML with no trace of the PHP that generated it.

6. Hands-on Exercise

Hands-on

Build a dynamic product listing page

Get the local server running, then apply this week's syntax to render real data.

Requirements:

  1. Create index.php and start it with php -S localhost:8000, confirming it loads in a browser.
  2. Add declare(strict_types=1); as the first line.
  3. Define an array of at least 4 associative arrays, each with name (string), price (float) and inStock (bool) keys.
  4. Write a typed function formatPrice(float $amount): string that returns a string like "$19.99" using number_format().
  5. Render the products as an HTML list using foreach, calling formatPrice() for each price and using match to print "In Stock" or "Out of Stock" based on inStock.
  6. Escape every printed string with htmlspecialchars(), even though this data isn't user-supplied yet — build the habit now.
Hint

match (true) { $inStock => 'In Stock', default => 'Out of Stock' } is a common pattern for turning a boolean into one of two strings with match.

7. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

What does declare(strict_types=1); actually change?

It disables implicit type coercion for scalar type declarations in that file's function calls — passing a string where a float parameter is declared throws a TypeError instead of silently converting. It does not affect files that call into this one without their own declare; the setting is per-file, and only governs calls made from within that file.

Q2

How is PHP's match expression different from switch?

match compares with strict equality (===) instead of switch's loose ==, never falls through between arms (no break needed), throws an UnhandledMatchError if nothing matches and there's no default, and is an expression — it returns a value you can assign directly, rather than a statement you branch inside.

Q3

Why call htmlspecialchars() on values before printing them into HTML, even on data you generated yourself?

It escapes characters like <, > and & so they render as text instead of being interpreted as HTML/script. Building the habit on trusted data now means it's already there once real user input flows through the same code paths from Week 2 onward — that's exactly the gap that causes cross-site scripting (XSS) vulnerabilities, covered fully in Week 8.

Q4

Does the browser ever see your PHP source code?

No. PHP executes entirely on the server; the browser only ever receives the interpreter's final output (typically HTML, JSON, or similar). This is what "server-side" means in practice — anything sensitive (database credentials, business logic, unvalidated secrets) is safe from direct exposure precisely because it never leaves the server process.