php language basics

Scope, Globals, and Closures

Scope means "where a variable can be used." In PHP, a variable created inside a function belongs to that function. Code outside the function cannot see it, and the function cannot automatically see variables from outside.

This matters because hidden data makes code hard to debug. A function should usually get what it needs through parameters and return a result. That makes the dependency visible to the next developer.

Local Scope

PHP example
<?php

function buildSkuLabel(string $sku): string
{
    $prefix = 'SKU';

    return "{$prefix}: {$sku}";
}

echo buildSkuLabel('ABC-123') . "\n";

// Prints:
// SKU: ABC-123

$prefix exists inside buildSkuLabel(). It is local to the function. This is useful because the function can use temporary variables without accidentally changing variables elsewhere in the file.

Pass Values In Explicitly

If a function needs a value, prefer a parameter.

PHP example
<?php

$currency = 'GBP';

function formatMoney(int $cents, string $currency): string
{
    return $currency . ' ' . number_format($cents / 100, 2);
}

echo formatMoney(1299, $currency) . "\n";

// Prints:
// GBP 12.99

The function does not secretly reach out to find $currency. The caller passes it in. That makes the function easier to test and easier to reuse with a different currency.

Why Hidden Globals Hurt

PHP has a global keyword, but relying on it in everyday application code usually makes functions harder to understand.

PHP example
<?php

$taxRate = 0.2;

function calculateTotalCents(int $subtotalCents, float $taxRate): int
{
    return $subtotalCents + (int) round($subtotalCents * $taxRate);
}

echo calculateTotalCents(1000, $taxRate) . "\n";

// Prints:
// 1200

Passing $taxRate as a parameter is clearer than reading it from global state. A reviewer can see the input in the function signature.

Superglobals such as $_GET, $_POST, and $_SERVER are different: PHP provides them everywhere. They are useful at request boundaries, but you should still read from them near the boundary, validate the value, and pass cleaned values deeper into your code.

Static Variables Inside Functions

A static variable inside a function keeps its value between calls.

PHP example
<?php

function nextReceiptNumber(): int
{
    static $number = 1000;

    $number++;

    return $number;
}

echo nextReceiptNumber() . "\n";
echo nextReceiptNumber() . "\n";

// Prints:
// 1001
// 1002

This can be useful for small counters, but use it carefully. Static state can surprise tests and long-running processes because the function remembers earlier calls.

Closures And use

A closure is an anonymous function you can store in a variable or pass to another function. Closures can capture outside values with use.

PHP example
<?php

$prefix = 'Order';

$formatOrderId = function (int $id) use ($prefix): string {
    return "{$prefix} #{$id}";
};

echo $formatOrderId(42) . "\n";

// Prints:
// Order #42

The use ($prefix) part copies the outside $prefix value into the closure. Without it, $prefix would not be available inside the closure.

Closures show up often with array functions, callbacks, routing, middleware, tests, and small formatting rules.

Common Mistakes

Do not assume a function can see a variable just because the variable appears earlier in the file. Pass it as a parameter.

Do not hide important dependencies in global. A function that reads hidden state is harder to test and harder to move.

Do not use static variables as a general storage mechanism. If the value matters beyond a tiny helper, make the state explicit.

Do not forget use when a closure needs a value from the surrounding scope.

What You Should Be Able To Do

After this lesson, you should be able to:

  • explain local function scope;
  • pass outside values into a function with parameters;
  • avoid hidden global dependencies;
  • recognize when a superglobal is a request boundary;
  • use a simple static variable and explain why it remembers state;
  • write a closure that captures a value with use.

Practice

Task: Prefix Formatter

Task

Write a function named formatTicketId() that accepts an integer ticket ID and a string prefix.

The function should:

  • create any temporary variables inside the function;
  • return a string such as SUPPORT-1042;
  • not depend on a global $prefix variable.

Call it with 1042 and SUPPORT, then print the result.

Hints

  • Pass the prefix as a parameter.
  • Return the formatted value.
  • Keep the echo outside the function.
Show solution

Solution

PHP example
<?php

function formatTicketId(int $ticketId, string $prefix): string
{
    $normalisedPrefix = strtoupper($prefix);

    return "{$normalisedPrefix}-{$ticketId}";
}

echo formatTicketId(1042, 'support') . "\n";

// Prints:
// SUPPORT-1042

Explanation

The function receives both pieces of data through parameters. $normalisedPrefix is local to the function, so it cannot accidentally affect code outside the function.

Task: Fix Hidden Global

Task

Refactor this function so it does not use global.

PHP example
<?php

$taxRate = 0.2;

function totalWithTax(int $subtotalCents): int
{
    global $taxRate;

    return $subtotalCents + (int) round($subtotalCents * $taxRate);
}

echo totalWithTax(1000) . "\n";

The refactored function should accept the tax rate as a parameter and still print 1200.

Hints

  • Add float $taxRate to the function parameters.
  • Pass $taxRate when you call the function.
  • Remove the global line.
Show solution

Solution

PHP example
<?php

$taxRate = 0.2;

function totalWithTax(int $subtotalCents, float $taxRate): int
{
    return $subtotalCents + (int) round($subtotalCents * $taxRate);
}

echo totalWithTax(1000, $taxRate) . "\n";

// Prints:
// 1200

Explanation

The function now declares the tax rate as an input. That makes the dependency visible in the function signature and removes the hidden dependency on global state.

Task: Build Prefix Closure

Task

Build a closure that formats order IDs with a prefix from the surrounding scope.

Start with:

PHP example
<?php

$prefix = 'Order';

Create a closure named $formatOrderId that:

  • accepts an integer ID;
  • captures $prefix with use;
  • returns a string like Order #42.

Call the closure with 42 and print the result.

Hints

  • Store the anonymous function in $formatOrderId.
  • Use use ($prefix) after the parameter list.
  • Call the closure like a normal function variable.
Show solution

Solution

PHP example
<?php

$prefix = 'Order';

$formatOrderId = function (int $id) use ($prefix): string {
    return "{$prefix} #{$id}";
};

echo $formatOrderId(42) . "\n";

// Prints:
// Order #42

Explanation

The closure captures $prefix with use ($prefix). Without that use clause, the $prefix variable from the surrounding scope would not be available inside the closure.