DEV Community

Cover image for PHP Engineering: Deep Dive into Core Concepts
Al Amin
Al Amin

Posted on

PHP Engineering: Deep Dive into Core Concepts

πŸš€ (Variable Scope, References, Closures & use)

These concepts separate "framework users" from real PHP engineers. Mastering them is key to writing efficient, bug-free, and complex code.


1. Variable Scope in PHP (Where variables live and die)

Scope defines the context in which a variable is defined and accessible.

βœ… Local Scope: The Function's Private Space

A variable declared inside a function (or method) is locally scoped. It is created when the function starts and completely destroyed when the function finishes execution.

function calculateArea($width, $height) {
    // $area, $width, and $height are all local variables.
    $area = $width * $height; 
    echo "Area is: " . $area;
}
// echo $area; // Fatal Error: Undefined variable $area
Enter fullscreen mode Exit fullscreen mode

Analogy: Like a private notebook inside a secured meeting room β€” the contents are relevant only for that meeting, and no one outside can read or modify it.

βœ… Global Scope: The Public Declaration

A variable declared outside any function or class belongs to the global scope. Functions cannot access global variables directly by default.

$site_name = "MyApp"; // Global variable

function showSite() {
    // Explicitly importing the global variable into the function's local scope
    global $site_name; 
    echo "Welcome to " . $site_name;
}
showSite(); // Output: Welcome to MyApp
Enter fullscreen mode Exit fullscreen mode

⚠️ Important: A better practice than using global is to pass the variable as a function argument.

Analogy: A notice board in the office foyer β€” everyone can see it, but you need to tell PHP explicitly to look outside the function's room to read it.

βœ… Static Scope: Function Memory with Persistence

Variables declared as static inside a function are local to that function but not destroyed when the function finishes. They retain their value between subsequent function calls.

function counter() {
    static $count = 0; // Initialized once
    $count++;
    return $count;
}

echo counter(); // 1
echo counter(); // 2
echo counter(); // 3
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Practical Example: Simple ID Generator

This ensures a unique, sequential ID is generated every time the function is called, without needing a global counter.

function generate_id() {
    static $current_id = 0; // Initialized ONLY once
    $current_id++;
    return $current_id;
}

echo "User ID: " . generate_id() . "\n"; // Output: User ID: 1
echo "Order ID: " . generate_id() . "\n"; // Output: Order ID: 2
Enter fullscreen mode Exit fullscreen mode

Practical Example: The Singleton Pattern (Database Connection)

The Singleton pattern ensures only one instance of a class (like a database connection) is created, often relying on a static property.

class Database
{
    private static $instance = null;

    // Use Static Scope to store and reuse the connection instance
    public static function getInstance(): Database
    {
        if (self::$instance === null) {
            // Only create the connection ONCE
            self::$instance = new self(); 
        }
        return self::$instance;
    }

    // Private constructor prevents direct object creation
    private function __construct() {}
}

$db1 = Database::getInstance();
$db2 = Database::getInstance(); 

// $db1 and $db2 are the same object, thanks to static scope.
Enter fullscreen mode Exit fullscreen mode

Why it matters: Essential for tracking state (like a call count or ID generator) without relying on global variables or class properties.

Analogy: Sticky notes that stay on your desk after you leave and come back.


2. References (&) β€” Working on the Original Data

In PHP, most variables are passed by value (a copy is made). References allow you to create a second alias or name for the same variable content, enabling pass-by-reference.

❌ Normal Copy (Pass-by-Value)

$a = 10;
$b = $a; // $b gets a copy of 10
$b = 20; // Only $b is changed

echo $a; // Output: 10
Enter fullscreen mode Exit fullscreen mode

βœ… Reference Version (Shared Memory)

The & operator means "share the memory address." Both $a and $b point to the same data.

$a = 10;
$b =& $a; // $b is now an alias for $a
$b = 20; // Changing $b also changes $a

echo $a; // Output: 20
Enter fullscreen mode Exit fullscreen mode

Analogy: Two remote controls for the same TV. Pressing a button on either control affects the single TV set.

Reference in Functions (Explicit Pass-by-Reference)

By putting & in the function signature, any changes made to the argument inside the function will affect the original variable passed in.

function addOne(&$num) { // & here is key
    $num++;
}

$x = 5;
addOne($x);
echo $x; // Output: 6
Enter fullscreen mode Exit fullscreen mode

Reference in foreach (The Danger Zone)

Using foreach ($arr as &$v) makes $v an alias for the array element. You must break this reference after the loop.

$arr = [1, 2, 3];

foreach ($arr as &$v) {
    $v *= 2;
}

unset($v); // ALWAYS do this to break the reference tie
Enter fullscreen mode Exit fullscreen mode

3. Closures β€” Functions That Remember

A Closure is an anonymous function (a function without a name) that can be stored in a variable, passed as an argument, and, most importantly, access variables from the scope in which it was created.

// Storing an anonymous function in a variable
$greet = function ($name) {
    return "Hello " . $name;
};

echo $greet("Mamu");
Enter fullscreen mode Exit fullscreen mode

Analogy: A portable microchip containing a specific, pre-programmed action that you can carry around and activate anywhere.


4. use β€” Capturing Variables Inside Closures

An anonymous function cannot access variables from the outer scope by default. The use keyword allows you to explicitly import these external variables into the closure's definition.

βœ… Capturing by Value

The closure captures the value of $name at the moment the closure is defined.

$name = "Mamu";

$fn = function () use ($name) { // Capturing $name
    return $name;
};

$name = "Ali"; // This change happens after the capture

echo $fn(); // Output: Mamu (It captured the original "Mamu")
Enter fullscreen mode Exit fullscreen mode

Capture by Reference with use

Adding & to the captured variable makes the closure hold a reference to the external variable. Any change affects the original variable.

$count = 0;

$fn = function () use (&$count) { // Capturing $count by reference
    $count++;
};

$fn(); 
$fn(); 

echo $count; // Output: 2
Enter fullscreen mode Exit fullscreen mode

Analogy: The closure is holding a live wire connected to the original variable, not just a photograph of it.


5. Lexical Scoping (The Birthplace Rule)

Lexical Scoping means the structure of the code (where it is written) dictates which variables a closure can access.

$message = "Outside"; // Scope A

$fn = function () use ($message) {
    // $message is captured from Scope A: "Outside"
    echo $message;
};

function run($cb) {
    $message = "Inside"; // Scope B
    $cb();
}

run($fn); // Prints "Outside"
Enter fullscreen mode Exit fullscreen mode

Rule to Remember:

Closures use variables from where they were created (lexical scope), not where they are executed (dynamic scope).


6. πŸ’» Real-World Example: Creating a Dynamic Array Filter

Closures and use are crucial for functions like array_filter(), which require a dynamic callback.

Filtering Products by a Price Threshold

We define the products and the dynamic price threshold in the outer scope.

$products = [
    ['name' => 'Laptop', 'price' => 1200],
    ['name' => 'Mouse', 'price' => 25],
    ['name' => 'Monitor', 'price' => 350],
    ['name' => 'Keyboard', 'price' => 75],
];

// The variable we want to capture and use as a dynamic filter condition
$maxPrice = 100; 
Enter fullscreen mode Exit fullscreen mode

The Closure Implementation

The closure uses use ($maxPrice) to bring the external price threshold into the filtering logic.

$affordableProducts = array_filter($products, function ($product) use ($maxPrice) {
    // The closure compares the product's price against the captured $maxPrice
    return $product['price'] <= $maxPrice;
});
Enter fullscreen mode Exit fullscreen mode

Output

The filter uses the captured value of $maxPrice (100) successfully:

print_r($affordableProducts); 

/*
Output:
Array
(
    [1] => Array ( [name] => Mouse [price] => 25 )
    [3] => Array ( [name] => Keyboard [price] => 75 )
)
*/
Enter fullscreen mode Exit fullscreen mode

7. πŸ’‘ Summary of PHP Concepts

Concept Explanation Key Syntax
Local Scope Variables created inside a function; destroyed upon exit. $variable
Global Scope Variables created outside any function; must be explicitly imported. global $variable;
Static Scope Local variables that retain their value between function calls. static $count = 0;
Reference Creates an alias for a variable, pointing to the same memory location. &$variable
Closure An anonymous function that can be stored as a value. function () {}
use Keyword Imports external variables into a Closure's scope (lexical capture). function () use ($var)
Lexical Scoping Closures use variables from their creation scope. N/A

Top comments (1)

Collapse
 
xwero profile image
david duymelinck

Using static variables in a function that output the variable are problematic. A function call deeply hidden in the code could make expected output somewhere else unreliable.

A better option is to add the initial/previous value, and the logic creates a new value based on that input.

The reference variable in a loop is not more dangerous that the by value variable in a loop. Both live as long as the function it is in runs.
That is why it is a best practice to make the loop variables as specific to the context of the loop as possible. This makes it less likely a variable further in the function receives an unexpected value.

For the array_filter use an arrow function, that saves you from needing use.

$affordableProducts = array_filter($products, fn ($product) => $product['price'] <= $maxPrice);
Enter fullscreen mode Exit fullscreen mode