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
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
$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
$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
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
$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
$prefixvariable.
Call it with 1042 and SUPPORT, then print the result.
Hints
- Pass the prefix as a parameter.
- Return the formatted value.
- Keep the
echooutside the function.
Show solution
Solution
<?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: Build Prefix Closure
Task
Build a closure that formats order IDs with a prefix from the surrounding scope.
Start with:
<?php
$prefix = 'Order';
Create a closure named $formatOrderId that:
- accepts an integer ID;
- captures
$prefixwithuse; - 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
$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.