Functions
As programs grow, you find yourself writing the same steps again and again: work out GST, format a price, check a phone number. A function lets you write those steps once, give them a name and reuse them anywhere.
In this lesson you will learn to write your own functions with modern typed parameters, call them in flexible ways with default and named arguments, and use the shorter function forms that modern PHP code relies on: closures, arrow functions and first-class callables.
Defining and calling a function
You define a function with the function keyword, a name, a list of parameters in brackets and a body in curly braces. The return statement sends a value back to the caller:
<?php
declare(strict_types=1);
function priceWithGst(int $price, int $gstRate): float
{
return $price + $price * $gstRate / 100;
}
echo priceWithGst(1000, 18), "\n";
echo priceWithGst(2499, 5), "\n";1180 2623.95
The parts of the definition:
priceWithGstis the name. Function names follow the same rules as variable names, without the$. Use a verb or a clear description, in camelCase.int $price, int $gstRateare the parameters. Each has a type and a name. The values you pass when calling,1000and18, are called arguments.: floatafter the brackets is the return type: this function always returns a float.returnends the function and hands back the value.
With declare(strict_types=1); at the top of the file, calling priceWithGst('1000', 18) throws a TypeError instead of quietly converting the string. Types document what a function expects and catch mistakes early.
A function that returns nothing has the return type void. You may also meet never, for functions that always stop the script, for example by throwing an exception.
Return values, not echo
A common beginner habit is to echo inside every function. It is usually better to return the value and let the caller decide what to do with it: print it, store it in a database, or pass it to another function. A function that returns a value can be reused in many places; a function that prints can only print.
If a function reaches its end without a return, it returns null.
Default values and named arguments
A parameter can have a default value, which is used when the caller leaves that argument out. Parameters with defaults must come after the required ones.
Named arguments let you pass a value by parameter name instead of position. They make calls easier to read, and they let you skip optional parameters you do not need:
<?php
declare(strict_types=1);
function bookTicket(string $from, string $to, string $class = 'Sleeper', int $passengers = 1): string
{
return "$passengers x $class ticket(s) from $from to $to";
}
echo bookTicket('Rohtak', 'Jaipur'), "\n";
echo bookTicket('Rohtak', 'Jaipur', '3A'), "\n";
echo bookTicket('Rohtak', 'Jaipur', passengers: 4), "\n";
echo bookTicket(to: 'Pune', from: 'Lucknow', class: '2A'), "\n";1 x Sleeper ticket(s) from Rohtak to Jaipur 1 x 3A ticket(s) from Rohtak to Jaipur 4 x Sleeper ticket(s) from Rohtak to Jaipur 1 x 2A ticket(s) from Lucknow to Pune
The third call skips $class and sets only $passengers. The last call passes everything by name, in any order. Named arguments also work with PHP's own functions, for example number_format(1299.5, decimals: 2).
Nullable and union types
Sometimes a parameter can be one of several types:
- A nullable type such as
?stringaccepts a string ornull. It is often paired with a default ofnullfor optional values. - A union type such as
int|floataccepts any of the listed types.
<?php
declare(strict_types=1);
function applyCoupon(int|float $amount, ?string $coupon = null): int|float
{
if ($coupon === 'FEST10') {
return $amount * 0.9;
}
return $amount;
}
var_dump(applyCoupon(1000));
var_dump(applyCoupon(1000, 'FEST10'));
var_dump(applyCoupon(499.5));int(1000) float(900) float(499.5)
Without a coupon, the amount comes back unchanged with its original type. Use these types when a value truly can vary. If you find yourself writing a union of many types, the function is probably doing too much.
Variadic parameters
A variadic parameter, written with three dots, collects any number of arguments into an array. The same three dots used in a call do the opposite: they spread an array into separate arguments.
<?php
declare(strict_types=1);
function totalMarks(int ...$marks): int
{
return array_sum($marks);
}
echo totalMarks(78, 91, 66), "\n";
$termMarks = [88, 92, 79, 85];
echo totalMarks(...$termMarks), "\n";235 344
Inside the function, $marks is an ordinary array, and the type int applies to every item.
Variable scope
Variables created inside a function are local: they exist only while the function runs, and the function cannot see variables from outside. This keeps functions independent. If a function needs a value, pass it as an argument; if the caller needs a result, return it.
Closures and arrow functions
A function does not always need a name. An anonymous function (also called a closure) can be stored in a variable or passed to another function. To use an outside variable inside it, list that variable after use. The value is copied at the moment the closure is created.
An arrow function, written fn(...) => expression, is a shorter form for closures that return one expression. It captures outside variables automatically, again by value.
<?php
declare(strict_types=1);
$shop = 'Meera Sweets';
$greet = function (string $customer) use ($shop): string {
return "Welcome to $shop, $customer!";
};
$shop = 'Changed later';
echo $greet('Rohan'), "\n";
$gstRate = 18;
$withGst = fn(int $price): float => $price + $price * $gstRate / 100;
echo $withGst(500), "\n";Welcome to Meera Sweets, Rohan! 590
The greeting still says "Meera Sweets", because use copied the value before $shop changed. Notice the semicolon after the closing brace of the closure: the whole thing is an assignment statement. Closures and arrow functions are most useful as callbacks, functions you hand to another function such as array_map() or usort(), which you will use in Array Functions.
First-class callable syntax
Writing a function name followed by (...) creates a closure from that function without calling it. This is called the first-class callable syntax, added in PHP 8.1:
<?php
declare(strict_types=1);
$shout = strtoupper(...);
echo $shout('pune'), "\n";
$cities = [' Jaipur', 'Kochi ', ' Lucknow '];
$clean = array_map(trim(...), $cities);
echo implode(', ', $clean), "\n";PUNE Jaipur, Kochi, Lucknow
trim(...) is a real closure object, so your editor and PHP can check it. You will see this syntax with the PHP 8.5 pipe operator from Operators, and it also works with methods, as in $order->total(...).
Common mistakes
- Forgetting
return. The function then returnsnull, and the caller gets nothing useful. - Echoing instead of returning. Return values so the function can be reused.
- Using an outside variable without passing it. Inside a function it is undefined. Pass it as an argument, or use
usefor closures. - Putting optional parameters before required ones. PHP shows a deprecation message and treats the optional one as required. Put defaults last.
- Misspelling a named argument.
bookTicket(passenger: 2)fails with "Unknown named parameter $passenger".
Key points
- Define functions with typed parameters and a return type, and start files with
declare(strict_types=1);. - Return values rather than printing them; a function without
returngivesnull. - Default values make parameters optional; named arguments pass values by name in any order.
?stringaccepts a string ornull;int|floataccepts either type....$itemscollects many arguments into an array, and...$arrayspreads an array into arguments.- Closures capture outside variables with
use; arrow functionsfncapture them automatically. strlen(...)turns any function into a closure you can pass around.
Exercise
Write a function emi(int $principal, float $annualRate = 10.5, int $months = 12): float that returns the monthly instalment using the formula P * r * (1 + r) ** n / ((1 + r) ** n - 1), where r is the annual rate divided by 1,200 and n is the number of months. Round the result to 2 decimal places. Call it for a ₹60,000 loan with the default rate and months, and then for the same loan over 24 months using a named argument.
Show solution
<?php
declare(strict_types=1);
function emi(int $principal, float $annualRate = 10.5, int $months = 12): float
{
$r = $annualRate / 1200;
$growth = (1 + $r) ** $months;
return round($principal * $r * $growth / ($growth - 1), 2);
}
echo emi(60_000), "\n";
echo emi(60_000, months: 24), "\n";5288.92 2782.56
The second call skips $annualRate and sets only $months by name, so the default rate of 10.5% is still used. Storing (1 + $r) ** $months in a variable avoids working it out twice.
Quick Quiz
1. What does a function return if it has no return statement?
A function that ends without return gives back null.
2. Which call sets only $passengers and keeps the default for $class?
A named argument skips the parameters in between; the positional 4 would go into $class.
3. What does ?string $coupon = null mean?
The ? makes the type nullable, and = null makes it optional.
4. How does an arrow function fn get access to outside variables?
Arrow functions capture outside variables automatically; regular closures need use.
Learn PHP with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
