Master function creation, parameters, return values, and understand variable scope to write modular and maintainable PHP code
Functions are the fundamental building blocks of modular programming. They allow you to encapsulate logic into reusable units, making your code more organized, maintainable, and testable. Understanding functions and variable scope is essential for writing professional PHP applications.
PHP provides comprehensive function capabilities including traditional named functions, anonymous functions (closures), arrow functions (PHP 7.4+), and modern features like named arguments and union types (PHP 8+). Variable scope determines where variables can be accessed, with PHP offering local, global, and static scopes.
In this lesson, you'll learn how to create powerful, flexible functions, understand how variables behave in different contexts, and apply modern PHP features to write clean, type-safe code. You'll also discover best practices for function documentation, error handling, and design patterns.
Functions in PHP are blocks of reusable code that can accept inputs (parameters), perform operations, and optionally return outputs. They help organize code and promote the DRY (Don't Repeat Yourself) principle.
Basic Function Syntax:
1function greet() {
2 echo "Hello, World!";
3}
4
5greet(); // Call the function
6// Output: Hello, World!Functions with Parameters:
Parameters allow you to pass data into functions:
1function greetUser(string $name) {
2 echo "Hello, $name!";
3}
4
5greetUser("John"); // Hello, John!
6greetUser("Alice"); // Hello, Alice!Multiple Parameters:
1function calculateTotal(float $price, float $tax_rate, int $quantity) {
2 $subtotal = $price * $quantity;
3 $tax = $subtotal * $tax_rate;
4 $total = $subtotal + $tax;
5
6 return $total;
7}
8
9$amount = calculateTotal(99.99, 0.08, 2);
10echo "Total: $" . number_format($amount, 2);
11// Total: $215.98Default Parameter Values:
Provide default values for optional parameters:
1function createUser(string $name, string $role = 'user', bool $active = true) {
2 return [
3 'name' => $name,
4 'role' => $role,
5 'active' => $active
6 ];
7}
8
9$user1 = createUser("John");
10// ['name' => 'John', 'role' => 'user', 'active' => true]
11
12$user2 = createUser("Admin", "admin", false);
13// ['name' => 'Admin', 'role' => 'admin', 'active' => false]Named Arguments (PHP 8+):
Pass arguments by name for better readability:
1function sendEmail(
2 string $to,
3 string $subject,
4 string $body,
5 string $from = '[email protected]',
6 bool $html = false
7) {
8 // Send email logic
9}
10
11// Traditional call
12sendEmail('[email protected]', 'Welcome', 'Welcome to our site', '[email protected]', true);
13
14// Named arguments (clearer)
15sendEmail(
16 to: '[email protected]',
17 subject: 'Welcome',
18 body: 'Welcome to our site',
19 html: true,
20 from: '[email protected]'
21);Variadic Functions:
Accept unlimited number of arguments using the ... operator:
1function sum(...$numbers) {
2 $total = 0;
3 foreach ($numbers as $number) {
4 $total += $number;
5 }
6 return $total;
7}
8
9echo sum(1, 2, 3); // 6
10echo sum(10, 20, 30, 40); // 100
11echo sum(1, 2, 3, 4, 5, 6); // 21Best Practices:
calculateTotal, validateEmail)Functions can return data to the caller using the return statement. PHP 7+ allows declaring return types for better type safety and code clarity.
Basic Return:
1function add(int $a, int $b): int {
2 return $a + $b;
3}
4
5$result = add(5, 3); // $result = 8Multiple Return Paths:
Functions can have multiple return statements based on conditions:
1function divide(float $a, float $b): ?float {
2 if ($b === 0.0) {
3 return null; // Can't divide by zero
4 }
5 return $a / $b;
6}
7
8$result = divide(10, 2); // 5.0
9$error = divide(10, 0); // nullNullable Return Types:
Use ? to indicate a function might return null:
1function findUser(int $id): ?array {
2 $users = [
3 1 => ['name' => 'John', 'email' => '[email protected]'],
4 2 => ['name' => 'Jane', 'email' => '[email protected]']
5 ];
6
7 return $users[$id] ?? null;
8}
9
10$user = findUser(1); // Returns user array
11$missing = findUser(99); // Returns nullUnion Types (PHP 8+):
Allow multiple return types:
1function processValue(mixed $input): int|float|string {
2 if (is_numeric($input)) {
3 return is_float($input) ? (float)$input : (int)$input;
4 }
5 return (string)$input;
6}
7
8$num = processValue(42); // int
9$float = processValue(3.14); // float
10$str = processValue("hello"); // stringVoid Return Type:
Indicates a function doesn't return a value:
1function logMessage(string $message): void {
2 file_put_contents('log.txt', $message . PHP_EOL, FILE_APPEND);
3 // No return statement needed
4}Never Return Type (PHP 8.1+):
Indicates a function never returns (exits or throws exception):
1function redirect(string $url): never {
2 header("Location: $url");
3 exit;
4}
5
6function fail(string $message): never {
7 throw new Exception($message);
8}Returning Multiple Values:
Use arrays or list destructuring:
1function getMinMax(array $numbers): array {
2 return [min($numbers), max($numbers)];
3}
4
5// Array destructuring
6[$min, $max] = getMinMax([3, 1, 4, 1, 5, 9]);
7echo "Min: $min, Max: $max"; // Min: 1, Max: 9Early Returns:
Return early for better readability:
1function validateAge(int $age): string {
2 // Early returns for error cases
3 if ($age < 0) {
4 return "Age cannot be negative";
5 }
6
7 if ($age > 150) {
8 return "Age seems unrealistic";
9 }
10
11 // Happy path
12 return "Valid age";
13}Best Practices:
?) when functions might failVariable scope determines where in your code a variable can be accessed. PHP has three primary scopes: local (function), global (script-wide), and static (persists across function calls).
Local Scope:
Variables declared inside functions are local and can't be accessed outside:
1function testLocal() {
2 $local_var = "I'm local";
3 echo $local_var; // Works
4}
5
6testLocal();
7// echo $local_var; // Error: Undefined variableGlobal Scope:
Variables declared outside functions are global but need the global keyword to access inside functions:
1$global_var = "I'm global";
2
3function testGlobal() {
4 global $global_var;
5 echo $global_var; // Now accessible
6}
7
8testGlobal(); // I'm global$GLOBALS Superglobal:
Alternative way to access global variables:
1$username = "John";
2$email = "[email protected]";
3
4function displayUser() {
5 echo "User: " . $GLOBALS['username'];
6 echo " (" . $GLOBALS['email'] . ")";
7}
8
9displayUser(); // User: John ([email protected])Static Variables:
Persist their value between function calls:
1function counter() {
2 static $count = 0;
3 $count++;
4 return $count;
5}
6
7echo counter(); // 1
8echo counter(); // 2
9echo counter(); // 3
10// $count retains its value between callsPractical Static Example - Request ID Generator:
1function generateRequestId(): string {
2 static $counter = 0;
3 $counter++;
4 return date('Ymd-His') . '-' . $counter;
5}
6
7echo generateRequestId(); // 20250115-143022-1
8echo generateRequestId(); // 20250115-143022-2
9echo generateRequestId(); // 20250115-143022-3Scope Best Practices:
1// ❌ BAD: Using global variables
2$db_connection = new PDO(...);
3
4function getUsers() {
5 global $db_connection;
6 return $db_connection->query("SELECT * FROM users");
7}
8
9// ✅ GOOD: Pass dependencies as parameters
10function getUsers(PDO $connection): array {
11 return $connection->query("SELECT * FROM users")->fetchAll();
12}
13
14$users = getUsers($db_connection);Closure Scope:
Anonymous functions can inherit variables from parent scope using use:
1$multiplier = 3;
2
3$triple = function($number) use ($multiplier) {
4 return $number * $multiplier;
5};
6
7echo $triple(5); // 15Modifying Closure Variables:
Use references to modify inherited variables:
1$counter = 0;
2
3$increment = function() use (&$counter) {
4 $counter++;
5};
6
7$increment();
8$increment();
9echo $counter; // 2Scope Guidelines:
global keyword** - use dependency injection insteadPHP supports multiple types of function expressions beyond traditional named functions, enabling functional programming patterns and more flexible code structures.
Anonymous Functions (Closures):
Functions without names, often used as callbacks:
1// Assign to variable
2$greet = function($name) {
3 return "Hello, $name!";
4};
5
6echo $greet("John"); // Hello, John!
7
8// Use as callback
9$numbers = [1, 2, 3, 4, 5];
10$doubled = array_map(function($n) {
11 return $n * 2;
12}, $numbers);
13
14print_r($doubled); // [2, 4, 6, 8, 10]Closures with Variable Inheritance:
Access parent scope variables using use:
1$discount = 0.10;
2
3$applyDiscount = function($price) use ($discount) {
4 return $price * (1 - $discount);
5};
6
7echo $applyDiscount(100); // 90Arrow Functions (PHP 7.4+):
Shorter syntax for simple anonymous functions with automatic variable capture:
1// Traditional anonymous function
2$numbers = [1, 2, 3, 4, 5];
3$squared = array_map(function($n) {
4 return $n * $n;
5}, $numbers);
6
7// Arrow function (shorter)
8$squared = array_map(fn($n) => $n * $n, $numbers);Arrow Functions with Captured Variables:
Automatically capture variables from parent scope:
1$multiplier = 10;
2
3// Traditional closure
4$multiply_traditional = function($n) use ($multiplier) {
5 return $n * $multiplier;
6};
7
8// Arrow function (cleaner)
9$multiply_arrow = fn($n) => $n * $multiplier;
10
11$numbers = [1, 2, 3];
12$result = array_map(fn($n) => $n * $multiplier, $numbers);
13// [10, 20, 30]Practical Example - Array Filtering:
1$products = [
2 ['name' => 'Laptop', 'price' => 999, 'in_stock' => true],
3 ['name' => 'Mouse', 'price' => 29, 'in_stock' => false],
4 ['name' => 'Keyboard', 'price' => 79, 'in_stock' => true],
5 ['name' => 'Monitor', 'price' => 299, 'in_stock' => true]
6];
7
8$min_price = 50;
9
10// Filter products
11$available_expensive = array_filter(
12 $products,
13 fn($p) => $p['in_stock'] && $p['price'] >= $min_price
14);
15
16// Extract names
17$names = array_map(fn($p) => $p['name'], $available_expensive);Callbacks in Sorting:
1$users = [
2 ['name' => 'Charlie', 'age' => 25],
3 ['name' => 'Alice', 'age' => 30],
4 ['name' => 'Bob', 'age' => 20]
5];
6
7// Sort by age (ascending)
8usort($users, fn($a, $b) => $a['age'] <=> $b['age']);
9
10// Sort by name (descending)
11usort($users, fn($a, $b) => $b['name'] <=> $a['name']);Higher-Order Functions:
Functions that accept or return other functions:
1function createMultiplier(int $factor): callable {
2 return fn($number) => $number * $factor;
3}
4
5$double = createMultiplier(2);
6$triple = createMultiplier(3);
7
8echo $double(5); // 10
9echo $triple(5); // 15Practical Use Cases:
1// Event handlers
2$button->onClick(fn() => echo "Clicked!");
3
4// Validation
5$validators = [
6 fn($email) => filter_var($email, FILTER_VALIDATE_EMAIL),
7 fn($email) => strlen($email) <= 255,
8 fn($email) => !str_contains($email, '..')
9];
10
11// Data transformation pipeline
12$result = collect($data)
13 ->filter(fn($item) => $item['active'])
14 ->map(fn($item) => $item['price'] * 1.08)
15 ->sum();When to Use Each:
Best Practices:
Building a Data Validation and Processing Library
1<?php
2declare(strict_types=1);
3
4/**
5 * Data Validation and Processing Library
6 *
7 * Demonstrates advanced function concepts:
8 * - Type-safe function signatures
9 * - Named arguments and default values
10 * - Anonymous functions and arrow functions
11 * - Variadic parameters
12 * - Variable scope and closures
13 * - Higher-order functions
14 */
15
16class DataValidator {
17 /**
18 * Validate email address
19 */
20 public static function email(string $email): bool {
21 return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
22 }
23
24 /**
25 * Validate string length
26 */
27 public static function length(
28 string $value,
29 int $min = 0,
30 int $max = PHP_INT_MAX
31 ): bool {
32 $length = strlen($value);
33 return $length >= $min && $length <= $max;
34 }
35
36 /**
37 * Validate number range
38 */
39 public static function range(
40 int|float $value,
41 int|float $min = PHP_INT_MIN,
42 int|float $max = PHP_INT_MAX
43 ): bool {
44 return $value >= $min && $value <= $max;
45 }
46
47 /**
48 * Validate required fields
49 */
50 public static function required(mixed $value): bool {
51 if ($value === null || $value === '') {
52 return false;
53 }
54
55 if (is_array($value) && empty($value)) {
56 return false;
57 }
58
59 return true;
60 }
61
62 /**
63 * Create custom validator
64 */
65 public static function custom(callable $validator): callable {
66 return $validator;
67 }
68}
69
70class DataProcessor {
71 /**
72 * Filter array by callback
73 */
74 public static function filter(array $data, callable $callback): array {
75 return array_filter($data, $callback);
76 }
77
78 /**
79 * Transform array elements
80 */
81 public static function map(array $data, callable $callback): array {
82 return array_map($callback, $data);
83 }
84
85 /**
86 * Reduce array to single value
87 */
88 public static function reduce(
89 array $data,
90 callable $callback,
91 mixed $initial = null
92 ): mixed {
93 return array_reduce($data, $callback, $initial);
94 }
95
96 /**
97 * Create processing pipeline
98 */
99 public static function pipeline(array $data, callable ...$operations): array {
100 foreach ($operations as $operation) {
101 $data = $operation($data);
102 }
103 return $data;
104 }
105}
106
107class FormValidator {
108 private array $errors = [];
109 private array $rules = [];
110
111 /**
112 * Add validation rule
113 */
114 public function rule(
115 string $field,
116 string $label,
117 callable ...$validators
118 ): self {
119 $this->rules[$field] = [
120 'label' => $label,
121 'validators' => $validators
122 ];
123 return $this;
124 }
125
126 /**
127 * Validate data against rules
128 */
129 public function validate(array $data): bool {
130 $this->errors = [];
131
132 foreach ($this->rules as $field => $rule) {
133 $value = $data[$field] ?? null;
134 $label = $rule['label'];
135
136 foreach ($rule['validators'] as $validator) {
137 if (!$validator($value)) {
138 $this->errors[$field][] = "$label validation failed";
139 }
140 }
141 }
142
143 return empty($this->errors);
144 }
145
146 /**
147 * Get validation errors
148 */
149 public function getErrors(): array {
150 return $this->errors;
151 }
152}
153
154// ==================== DEMONSTRATION ====================
155
156echo "📋 DATA VALIDATION & PROCESSING DEMO\n";
157echo str_repeat("=", 60) . "\n\n";
158
159// Example 1: User Registration Validation
160echo "Example 1: User Registration Validation\n";
161echo str_repeat("-", 60) . "\n";
162
163$validator = new FormValidator();
164
165$validator
166 ->rule('email', 'Email',
167 fn($v) => DataValidator::required($v),
168 fn($v) => DataValidator::email($v)
169 )
170 ->rule('username', 'Username',
171 fn($v) => DataValidator::required($v),
172 fn($v) => DataValidator::length($v, 3, 20)
173 )
174 ->rule('age', 'Age',
175 fn($v) => DataValidator::required($v),
176 fn($v) => DataValidator::range($v, 18, 120)
177 );
178
179$user_data = [
180 'email' => '[email protected]',
181 'username' => 'john_doe',
182 'age' => 25
183];
184
185if ($validator->validate($user_data)) {
186 echo "✅ Validation passed!\n";
187} else {
188 echo "❌ Validation failed:\n";
189 foreach ($validator->getErrors() as $field => $errors) {
190 echo " $field: " . implode(', ', $errors) . "\n";
191 }
192}
193
194// Example 2: Data Processing Pipeline
195echo "\nExample 2: Data Processing Pipeline\n";
196echo str_repeat("-", 60) . "\n";
197
198$products = [
199 ['name' => 'Laptop', 'price' => 999, 'category' => 'Electronics', 'in_stock' => true],
200 ['name' => 'Mouse', 'price' => 29, 'category' => 'Electronics', 'in_stock' => false],
201 ['name' => 'Desk', 'price' => 299, 'category' => 'Furniture', 'in_stock' => true],
202 ['name' => 'Chair', 'price' => 199, 'category' => 'Furniture', 'in_stock' => true],
203 ['name' => 'Monitor', 'price' => 399, 'category' => 'Electronics', 'in_stock' => true]
204];
205
206$expensive_electronics = DataProcessor::pipeline(
207 $products,
208 // Step 1: Filter in-stock items
209 fn($items) => DataProcessor::filter(
210 $items,
211 fn($item) => $item['in_stock']
212 ),
213 // Step 2: Filter electronics
214 fn($items) => DataProcessor::filter(
215 $items,
216 fn($item) => $item['category'] === 'Electronics'
217 ),
218 // Step 3: Filter expensive items (>= $300)
219 fn($items) => DataProcessor::filter(
220 $items,
221 fn($item) => $item['price'] >= 300
222 ),
223 // Step 4: Extract names
224 fn($items) => DataProcessor::map(
225 $items,
226 fn($item) => $item['name']
227 )
228);
229
230echo "Expensive Electronics in Stock:\n";
231foreach ($expensive_electronics as $product) {
232 echo " - $product\n";
233}
234
235// Example 3: Calculate Total with Tax
236echo "\nExample 3: Calculate Total with Tax\n";
237echo str_repeat("-", 60) . "\n";
238
239$cart_items = [
240 ['name' => 'Laptop', 'price' => 999],
241 ['name' => 'Mouse', 'price' => 29],
242 ['name' => 'Keyboard', 'price' => 79]
243];
244
245$subtotal = DataProcessor::reduce(
246 $cart_items,
247 fn($total, $item) => $total + $item['price'],
248 0
249);
250
251$tax_rate = 0.08;
252$tax = $subtotal * $tax_rate;
253$total = $subtotal + $tax;
254
255echo "Subtotal: $" . number_format($subtotal, 2) . "\n";
256echo "Tax (8%): $" . number_format($tax, 2) . "\n";
257echo "Total: $" . number_format($total, 2) . "\n";
258
259// Example 4: Higher-Order Functions
260echo "\nExample 4: Higher-Order Functions\n";
261echo str_repeat("-", 60) . "\n";
262
263function createPriceFormatter(string $currency = '$'): callable {
264 return fn($price) => $currency . number_format($price, 2);
265}
266
267$formatUSD = createPriceFormatter('$');
268$formatEUR = createPriceFormatter('€');
269
270$price = 1234.56;
271
272echo "USD: " . $formatUSD($price) . "\n";
273echo "EUR: " . $formatEUR($price) . "\n";
274
275echo "\n" . str_repeat("=", 60) . "\n";
276echo "✅ Demo Complete!\n";
277?>This validation library demonstrates professional function usage:
Type Safety:
int|float) for flexible inputsNamed Arguments:
DataValidator::length($value, min: 0, max: 100)FormValidator::rule($field, $label, ...$validators)Anonymous Functions:
fn($v) => DataValidator::email($v)Variadic Parameters:
callable ...$validators accepts unlimited validatorscallable ...$operations for pipeline operationsHigher-Order Functions:
createPriceFormatter() returns configured formatterspipeline() chains operationsClosures and Scope:
Practical Patterns:
Real-World Usage:
The design prioritizes:
Your progress is saved in this browser, on this device only.